diff --git a/gprMax/cmds_geometry/add_grass.py b/gprMax/cmds_geometry/add_grass.py
index 9277bfe7..e17493f4 100644
--- a/gprMax/cmds_geometry/add_grass.py
+++ b/gprMax/cmds_geometry/add_grass.py
@@ -1,164 +1,164 @@
-"""Class for add_grass command."""
-from .cmds_geometry import UserObjectGeometry
-from ..exceptions import CmdInputError
-from ..utilities import round_value
-from ..materials import Material
-from ..fractals import FractalSurface
-from ..fractals import Grass
-
-from tqdm import tqdm
-import numpy as np
-
-
-class AddGrass(UserObjectGeometry):
- """User class for Grass command."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 12
- self.hash = '#add_grass'
-
- def create(self, grid, uip):
- """Add Grass to fractal box."""
- try:
- p1 = self.kwargs['p1']
- p2 = self.kwargs['p2']
- fractal_box_id = self.kwargs['fractal_box_id']
- frac_dim = self.kwargs['frac_dim']
- limits = self.kwargs['limits']
- n_blades = self.kwargs['n_blades']
- except KeyError:
- raise CmdInputError(self.__str__() + ' requires at least eleven parameters')
-
- try:
- seed = self.kwargs['seed']
- except KeyError:
- seed = None
-
- # grab the correct fractal volume
- volumes = [volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id]
- if volumes:
- volume = volumes[0]
- else:
- raise CmdInputError(self.__str__() + ' Cant find FractalBox {}'.format(fractal_box_id))
-
- p1, p2 = uip.check_box_points(p1, p2, self.__str__())
- xs, ys, zs = p1
- xf, yf, zf = p2
-
- if frac_dim < 0:
- raise CmdInputError(self.__str__() + ' requires a positive value for the fractal dimension')
- if limits[0] < 0 or limits[1] < 0:
- raise CmdInputError(self.__str__() + ' requires a positive value for the minimum and maximum heights for grass blades')
-
- # Check for valid orientations
- if xs == xf:
- if ys == yf or zs == zf:
- raise CmdInputError(self.__str__() + ' dimensions are not specified correctly')
- if xs != volume.xs and xs != volume.xf:
- raise CmdInputError(self.__str__() + ' must specify external surfaces on a fractal box')
- fractalrange = (round_value(limits[0] / grid.dx), round_value(limits[1] / grid.dx))
- # xminus surface
- if xs == volume.xs:
- raise CmdInputError(self.__str__() + ' grass can only be specified on surfaces in the positive axis direction')
- # xplus surface
- elif xf == volume.xf:
- if fractalrange[1] > grid.nx:
- raise CmdInputError(self.__str__() + ' cannot apply grass to fractal box as it would exceed the domain size in the x direction')
- requestedsurface = 'xplus'
-
- elif ys == yf:
- if xs == xf or zs == zf:
- raise CmdInputError(self.__str__() + ' dimensions are not specified correctly')
- if ys != volume.ys and ys != volume.yf:
- raise CmdInputError(self.__str__() + ' must specify external surfaces on a fractal box')
- fractalrange = (round_value(limits[0] / grid.dy), round_value(limits[1] / grid.dy))
- # yminus surface
- if ys == volume.ys:
- raise CmdInputError(self.__str__() + ' grass can only be specified on surfaces in the positive axis direction')
- # yplus surface
- elif yf == volume.yf:
- if fractalrange[1] > grid.ny:
- raise CmdInputError(self.__str__() + ' cannot apply grass to fractal box as it would exceed the domain size in the y direction')
- requestedsurface = 'yplus'
-
- elif zs == zf:
- if xs == xf or ys == yf:
- raise CmdInputError(self.__str__() + ' dimensions are not specified correctly')
- if zs != volume.zs and zs != volume.zf:
- raise CmdInputError(self.__str__() + ' must specify external surfaces on a fractal box')
- fractalrange = (round_value(limits[0] / grid.dz), round_value(limits[1] / grid.dz))
- # zminus surface
- if zs == volume.zs:
- raise CmdInputError(self.__str__() + ' grass can only be specified on surfaces in the positive axis direction')
- # zplus surface
- elif zf == volume.zf:
- if fractalrange[1] > grid.nz:
- raise CmdInputError(self.__str__() + ' cannot apply grass to fractal box as it would exceed the domain size in the z direction')
- requestedsurface = 'zplus'
-
- else:
- raise CmdInputError(self.__str__() + ' dimensions are not specified correctly')
-
- surface = FractalSurface(xs, xf, ys, yf, zs, zf, frac_dim)
- surface.ID = 'grass'
- surface.surfaceID = requestedsurface
- surface.seed = seed
-
- # Set the fractal range to scale the fractal distribution between zero and one
- surface.fractalrange = (0, 1)
- surface.operatingonID = volume.ID
- surface.generate_fractal_surface(grid)
- if n_blades > surface.fractalsurface.shape[0] * surface.fractalsurface.shape[1]:
- raise CmdInputError(self.__str__() + ' the specified surface is not large enough for the number of grass blades/roots specified')
-
- # Scale the distribution so that the summation is equal to one, i.e. a probability distribution
- surface.fractalsurface = surface.fractalsurface / np.sum(surface.fractalsurface)
-
- # Set location of grass blades using probability distribution
- # Create 1D vector of probability values from the 2D surface
- probability1D = np.cumsum(np.ravel(surface.fractalsurface))
-
- # Create random numbers between zero and one for the number of blades of grass
- R = np.random.RandomState(surface.seed)
- A = R.random_sample(n_blades)
-
- # Locate the random numbers in the bins created by the 1D vector of 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]))
-
- # Set the fractal range to minimum and maximum heights of the grass blades
- surface.fractalrange = fractalrange
-
- # 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]))
- 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)
-
- # Create grass geometry parameters
- g = Grass(n_blades)
- g.seed = surface.seed
- surface.grass.append(g)
-
- # Check to see if grass has been already defined as a material
- if not any(x.ID == 'grass' for x in grid.materials):
- m = Material(len(grid.materials), 'grass')
- m.averagable = False
- m.type = 'builtin, debye'
- m.er = Material.grasseri
- m.deltaer.append(Material.grassdeltaer)
- m.tau.append(Material.grasstau)
- grid.materials.append(m)
- if Material.maxpoles == 0:
- Material.maxpoles = 1
-
- # Check if time step for model is suitable for using grass
- grass = next((x for x in grid.materials if x.ID == 'grass'))
- testgrass = next((x for x in grass.tau if x < grid.dt), None)
- if testgrass:
- raise CmdInputError(self.__str__() + ' requires the time step for the model to be less than the relaxation time required to model grass.')
-
- volume.fractalsurfaces.append(surface)
-
- if config.is_messages():
- tqdm.write('{} blades of grass on surface from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m with fractal dimension {:g}, fractal seeding {}, and range {:g}m to {:g}m, added to {}.'.format(n_blades, xs * grid.dx, ys * grid.dy, zs * grid.dz, xf * grid.dx, yf * grid.dy, zf * grid.dz, surface.dimension, surface.seed, limits[0], limits[1], surface.operatingonID))
+"""Class for add_grass command."""
+from .cmds_geometry import UserObjectGeometry
+from ..exceptions import CmdInputError
+from ..utilities import round_value
+from ..materials import Material
+from ..fractals import FractalSurface
+from ..fractals import Grass
+
+from tqdm import tqdm
+import numpy as np
+
+
+class AddGrass(UserObjectGeometry):
+ """User class for Grass command."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 12
+ self.hash = '#add_grass'
+
+ def create(self, grid, uip):
+ """Add Grass to fractal box."""
+ try:
+ p1 = self.kwargs['p1']
+ p2 = self.kwargs['p2']
+ fractal_box_id = self.kwargs['fractal_box_id']
+ frac_dim = self.kwargs['frac_dim']
+ limits = self.kwargs['limits']
+ n_blades = self.kwargs['n_blades']
+ except KeyError:
+ raise CmdInputError(self.__str__() + ' requires at least eleven parameters')
+
+ try:
+ seed = self.kwargs['seed']
+ except KeyError:
+ seed = None
+
+ # grab the correct fractal volume
+ volumes = [volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id]
+ if volumes:
+ volume = volumes[0]
+ else:
+ raise CmdInputError(self.__str__() + ' Cant find FractalBox {}'.format(fractal_box_id))
+
+ p1, p2 = uip.check_box_points(p1, p2, self.__str__())
+ xs, ys, zs = p1
+ xf, yf, zf = p2
+
+ if frac_dim < 0:
+ raise CmdInputError(self.__str__() + ' requires a positive value for the fractal dimension')
+ if limits[0] < 0 or limits[1] < 0:
+ raise CmdInputError(self.__str__() + ' requires a positive value for the minimum and maximum heights for grass blades')
+
+ # Check for valid orientations
+ if xs == xf:
+ if ys == yf or zs == zf:
+ raise CmdInputError(self.__str__() + ' dimensions are not specified correctly')
+ if xs != volume.xs and xs != volume.xf:
+ raise CmdInputError(self.__str__() + ' must specify external surfaces on a fractal box')
+ fractalrange = (round_value(limits[0] / grid.dx), round_value(limits[1] / grid.dx))
+ # xminus surface
+ if xs == volume.xs:
+ raise CmdInputError(self.__str__() + ' grass can only be specified on surfaces in the positive axis direction')
+ # xplus surface
+ elif xf == volume.xf:
+ if fractalrange[1] > grid.nx:
+ raise CmdInputError(self.__str__() + ' cannot apply grass to fractal box as it would exceed the domain size in the x direction')
+ requestedsurface = 'xplus'
+
+ elif ys == yf:
+ if xs == xf or zs == zf:
+ raise CmdInputError(self.__str__() + ' dimensions are not specified correctly')
+ if ys != volume.ys and ys != volume.yf:
+ raise CmdInputError(self.__str__() + ' must specify external surfaces on a fractal box')
+ fractalrange = (round_value(limits[0] / grid.dy), round_value(limits[1] / grid.dy))
+ # yminus surface
+ if ys == volume.ys:
+ raise CmdInputError(self.__str__() + ' grass can only be specified on surfaces in the positive axis direction')
+ # yplus surface
+ elif yf == volume.yf:
+ if fractalrange[1] > grid.ny:
+ raise CmdInputError(self.__str__() + ' cannot apply grass to fractal box as it would exceed the domain size in the y direction')
+ requestedsurface = 'yplus'
+
+ elif zs == zf:
+ if xs == xf or ys == yf:
+ raise CmdInputError(self.__str__() + ' dimensions are not specified correctly')
+ if zs != volume.zs and zs != volume.zf:
+ raise CmdInputError(self.__str__() + ' must specify external surfaces on a fractal box')
+ fractalrange = (round_value(limits[0] / grid.dz), round_value(limits[1] / grid.dz))
+ # zminus surface
+ if zs == volume.zs:
+ raise CmdInputError(self.__str__() + ' grass can only be specified on surfaces in the positive axis direction')
+ # zplus surface
+ elif zf == volume.zf:
+ if fractalrange[1] > grid.nz:
+ raise CmdInputError(self.__str__() + ' cannot apply grass to fractal box as it would exceed the domain size in the z direction')
+ requestedsurface = 'zplus'
+
+ else:
+ raise CmdInputError(self.__str__() + ' dimensions are not specified correctly')
+
+ surface = FractalSurface(xs, xf, ys, yf, zs, zf, frac_dim)
+ surface.ID = 'grass'
+ surface.surfaceID = requestedsurface
+ surface.seed = seed
+
+ # Set the fractal range to scale the fractal distribution between zero and one
+ surface.fractalrange = (0, 1)
+ surface.operatingonID = volume.ID
+ surface.generate_fractal_surface(grid)
+ if n_blades > surface.fractalsurface.shape[0] * surface.fractalsurface.shape[1]:
+ raise CmdInputError(self.__str__() + ' the specified surface is not large enough for the number of grass blades/roots specified')
+
+ # Scale the distribution so that the summation is equal to one, i.e. a probability distribution
+ surface.fractalsurface = surface.fractalsurface / np.sum(surface.fractalsurface)
+
+ # Set location of grass blades using probability distribution
+ # Create 1D vector of probability values from the 2D surface
+ probability1D = np.cumsum(np.ravel(surface.fractalsurface))
+
+ # Create random numbers between zero and one for the number of blades of grass
+ R = np.random.RandomState(surface.seed)
+ A = R.random_sample(n_blades)
+
+ # Locate the random numbers in the bins created by the 1D vector of 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]))
+
+ # Set the fractal range to minimum and maximum heights of the grass blades
+ surface.fractalrange = fractalrange
+
+ # 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]))
+ 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)
+
+ # Create grass geometry parameters
+ g = Grass(n_blades)
+ g.seed = surface.seed
+ surface.grass.append(g)
+
+ # Check to see if grass has been already defined as a material
+ if not any(x.ID == 'grass' for x in grid.materials):
+ m = Material(len(grid.materials), 'grass')
+ m.averagable = False
+ m.type = 'builtin, debye'
+ m.er = Material.grasseri
+ m.deltaer.append(Material.grassdeltaer)
+ m.tau.append(Material.grasstau)
+ grid.materials.append(m)
+ if Material.maxpoles == 0:
+ Material.maxpoles = 1
+
+ # Check if time step for model is suitable for using grass
+ grass = next((x for x in grid.materials if x.ID == 'grass'))
+ testgrass = next((x for x in grass.tau if x < grid.dt), None)
+ if testgrass:
+ raise CmdInputError(self.__str__() + ' requires the time step for the model to be less than the relaxation time required to model grass.')
+
+ volume.fractalsurfaces.append(surface)
+
+ if config.is_messages():
+ tqdm.write('{} blades of grass on surface from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m with fractal dimension {:g}, fractal seeding {}, and range {:g}m to {:g}m, added to {}.'.format(n_blades, xs * grid.dx, ys * grid.dy, zs * grid.dz, xf * grid.dx, yf * grid.dy, zf * grid.dz, surface.dimension, surface.seed, limits[0], limits[1], surface.operatingonID))
diff --git a/gprMax/cmds_geometry/add_surface_roughness.py b/gprMax/cmds_geometry/add_surface_roughness.py
index bf35549e..90cd10b5 100644
--- a/gprMax/cmds_geometry/add_surface_roughness.py
+++ b/gprMax/cmds_geometry/add_surface_roughness.py
@@ -1,127 +1,127 @@
-"""Class for surface roughness command."""
-from .cmds_geometry import UserObjectGeometry
-from ..exceptions import CmdInputError
-from ..fractals import FractalSurface
-from ..utilities import round_value
-import gprMax.config as config
-
-from tqdm import tqdm
-import numpy as np
-
-
-class AddSurfaceRoughness(UserObjectGeometry):
- """User class for edge command."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 10
- self.hash = '#add_surface_roughness'
-
- def create(self, grid, uip):
-
- try:
- p1 = self.kwargs['p1']
- p2 = self.kwargs['p2']
- frac_dim = self.kwargs['frac_dim']
- weighting = np.array(self.kwargs['weighting'], dtype=np.float64)
- limits = np.array(self.kwargs['limits'])
- fractal_box_id = self.kwargs['fractal_box_id']
- except KeyError:
- raise CmdInputError(self.__str__() + ' Incorrect parameters')
-
- try:
- seed = self.kwargs['seed']
- except KeyError:
- seed = None
-
- # grab the correct fractal volume
- volumes = [volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id]
- if volumes:
- volume = volumes[0]
- else:
- raise CmdInputError(self.__str__() + ' Cant find FractalBox {}'.format(fractal_box_id))
-
- p1, p2 = uip.check_box_points(p1, p2, self.__str__())
- xs, ys, zs = p1
- xf, yf, zf = p2
-
- if frac_dim < 0:
- raise CmdInputError(self.__str__() + ' requires a positive value for the fractal dimension')
- if weighting[0] < 0:
- raise CmdInputError(self.__str__() + ' requires a positive value for the fractal weighting in the first direction of the surface')
- if weighting[1] < 0:
- raise CmdInputError(self.__str__() + ' requires a positive value for the fractal weighting in the second direction of the surface')
-
- # Check for valid orientations
- if xs == xf:
- if ys == yf or zs == zf:
- raise CmdInputError(self.__str__() + ' dimensions are not specified correctly')
- if xs != volume.xs and xs != volume.xf:
- raise CmdInputError(self.__str__() + ' can only be used on the external surfaces of a fractal box')
- 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:
- raise CmdInputError(self.__str__() + ' cannot apply fractal surface to fractal box as it would exceed either the upper coordinates of the fractal box or the domain in the x direction')
- requestedsurface = 'xminus'
- # xplus surface
- elif xf == volume.xf:
- if fractalrange[0] < volume.xs or fractalrange[1] > grid.nx:
- raise CmdInputError(self.__str__() + ' cannot apply fractal surface to fractal box as it would exceed either the lower coordinates of the fractal box or the domain in the x direction')
- requestedsurface = 'xplus'
-
- elif ys == yf:
- if xs == xf or zs == zf:
- raise CmdInputError(self.__str__() + ' dimensions are not specified correctly')
- if ys != volume.ys and ys != volume.yf:
- raise CmdInputError(self.__str__() + ' can only be used on the external surfaces of a fractal box')
- 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:
- raise CmdInputError(self.__str__() + ' cannot apply fractal surface to fractal box as it would exceed either the upper coordinates of the fractal box or the domain in the y direction')
- requestedsurface = 'yminus'
- # yplus surface
- elif yf == volume.yf:
- if fractalrange[0] < volume.ys or fractalrange[1] > grid.ny:
- raise CmdInputError(self.__str__() + ' cannot apply fractal surface to fractal box as it would exceed either the lower coordinates of the fractal box or the domain in the y direction')
- requestedsurface = 'yplus'
-
- elif zs == zf:
- if xs == xf or ys == yf:
- raise CmdInputError(self.__str__() + ' dimensions are not specified correctly')
- if zs != volume.zs and zs != volume.zf:
- raise CmdInputError(self.__str__() + ' can only be used on the external surfaces of a fractal box')
- 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:
- raise CmdInputError(self.__str__() + ' cannot apply fractal surface to fractal box as it would exceed either the upper coordinates of the fractal box or the domain in the x direction')
- requestedsurface = 'zminus'
- # zplus surface
- elif zf == volume.zf:
- if fractalrange[0] < volume.zs or fractalrange[1] > grid.nz:
- raise CmdInputError(self.__str__() + ' cannot apply fractal surface to fractal box as it would exceed either the lower coordinates of the fractal box or the domain in the z direction')
- requestedsurface = 'zplus'
-
- else:
- raise CmdInputError(self.__str__() + ' dimensions are not specified correctly')
-
- surface = FractalSurface(xs, xf, ys, yf, zs, zf, frac_dim)
- surface.surfaceID = requestedsurface
- surface.fractalrange = fractalrange
- surface.operatingonID = volume.ID
- surface.seed = seed
- surface.weighting = weighting
-
- # List of existing surfaces IDs
- existingsurfaceIDs = [x.surfaceID for x in volume.fractalsurfaces]
- if surface.surfaceID in existingsurfaceIDs:
- raise CmdInputError(self.__str__() + ' has already been used on the {} surface'.format(surface.surfaceID))
-
- surface.generate_fractal_surface(grid)
- volume.fractalsurfaces.append(surface)
-
- if config.is_messages():
- tqdm.write('Fractal surface from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m with fractal dimension {:g}, fractal weightings {:g}, {:g}, fractal seeding {}, and range {:g}m to {:g}m, added to {}.'.format(xs * grid.dx, ys * grid.dy, zs * grid.dz, xf * grid.dx, yf * grid.dy, zf * grid.dz, surface.dimension, surface.weighting[0], surface.weighting[1], surface.seed, limits[0], limits[1], surface.operatingonID))
+"""Class for surface roughness command."""
+from .cmds_geometry import UserObjectGeometry
+from ..exceptions import CmdInputError
+from ..fractals import FractalSurface
+from ..utilities import round_value
+import gprMax.config as config
+
+from tqdm import tqdm
+import numpy as np
+
+
+class AddSurfaceRoughness(UserObjectGeometry):
+ """User class for edge command."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 10
+ self.hash = '#add_surface_roughness'
+
+ def create(self, grid, uip):
+
+ try:
+ p1 = self.kwargs['p1']
+ p2 = self.kwargs['p2']
+ frac_dim = self.kwargs['frac_dim']
+ weighting = np.array(self.kwargs['weighting'], dtype=np.float64)
+ limits = np.array(self.kwargs['limits'])
+ fractal_box_id = self.kwargs['fractal_box_id']
+ except KeyError:
+ raise CmdInputError(self.__str__() + ' Incorrect parameters')
+
+ try:
+ seed = self.kwargs['seed']
+ except KeyError:
+ seed = None
+
+ # grab the correct fractal volume
+ volumes = [volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id]
+ if volumes:
+ volume = volumes[0]
+ else:
+ raise CmdInputError(self.__str__() + ' Cant find FractalBox {}'.format(fractal_box_id))
+
+ p1, p2 = uip.check_box_points(p1, p2, self.__str__())
+ xs, ys, zs = p1
+ xf, yf, zf = p2
+
+ if frac_dim < 0:
+ raise CmdInputError(self.__str__() + ' requires a positive value for the fractal dimension')
+ if weighting[0] < 0:
+ raise CmdInputError(self.__str__() + ' requires a positive value for the fractal weighting in the first direction of the surface')
+ if weighting[1] < 0:
+ raise CmdInputError(self.__str__() + ' requires a positive value for the fractal weighting in the second direction of the surface')
+
+ # Check for valid orientations
+ if xs == xf:
+ if ys == yf or zs == zf:
+ raise CmdInputError(self.__str__() + ' dimensions are not specified correctly')
+ if xs != volume.xs and xs != volume.xf:
+ raise CmdInputError(self.__str__() + ' can only be used on the external surfaces of a fractal box')
+ 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:
+ raise CmdInputError(self.__str__() + ' cannot apply fractal surface to fractal box as it would exceed either the upper coordinates of the fractal box or the domain in the x direction')
+ requestedsurface = 'xminus'
+ # xplus surface
+ elif xf == volume.xf:
+ if fractalrange[0] < volume.xs or fractalrange[1] > grid.nx:
+ raise CmdInputError(self.__str__() + ' cannot apply fractal surface to fractal box as it would exceed either the lower coordinates of the fractal box or the domain in the x direction')
+ requestedsurface = 'xplus'
+
+ elif ys == yf:
+ if xs == xf or zs == zf:
+ raise CmdInputError(self.__str__() + ' dimensions are not specified correctly')
+ if ys != volume.ys and ys != volume.yf:
+ raise CmdInputError(self.__str__() + ' can only be used on the external surfaces of a fractal box')
+ 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:
+ raise CmdInputError(self.__str__() + ' cannot apply fractal surface to fractal box as it would exceed either the upper coordinates of the fractal box or the domain in the y direction')
+ requestedsurface = 'yminus'
+ # yplus surface
+ elif yf == volume.yf:
+ if fractalrange[0] < volume.ys or fractalrange[1] > grid.ny:
+ raise CmdInputError(self.__str__() + ' cannot apply fractal surface to fractal box as it would exceed either the lower coordinates of the fractal box or the domain in the y direction')
+ requestedsurface = 'yplus'
+
+ elif zs == zf:
+ if xs == xf or ys == yf:
+ raise CmdInputError(self.__str__() + ' dimensions are not specified correctly')
+ if zs != volume.zs and zs != volume.zf:
+ raise CmdInputError(self.__str__() + ' can only be used on the external surfaces of a fractal box')
+ 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:
+ raise CmdInputError(self.__str__() + ' cannot apply fractal surface to fractal box as it would exceed either the upper coordinates of the fractal box or the domain in the x direction')
+ requestedsurface = 'zminus'
+ # zplus surface
+ elif zf == volume.zf:
+ if fractalrange[0] < volume.zs or fractalrange[1] > grid.nz:
+ raise CmdInputError(self.__str__() + ' cannot apply fractal surface to fractal box as it would exceed either the lower coordinates of the fractal box or the domain in the z direction')
+ requestedsurface = 'zplus'
+
+ else:
+ raise CmdInputError(self.__str__() + ' dimensions are not specified correctly')
+
+ surface = FractalSurface(xs, xf, ys, yf, zs, zf, frac_dim)
+ surface.surfaceID = requestedsurface
+ surface.fractalrange = fractalrange
+ surface.operatingonID = volume.ID
+ surface.seed = seed
+ surface.weighting = weighting
+
+ # List of existing surfaces IDs
+ existingsurfaceIDs = [x.surfaceID for x in volume.fractalsurfaces]
+ if surface.surfaceID in existingsurfaceIDs:
+ raise CmdInputError(self.__str__() + ' has already been used on the {} surface'.format(surface.surfaceID))
+
+ surface.generate_fractal_surface(grid)
+ volume.fractalsurfaces.append(surface)
+
+ if config.is_messages():
+ tqdm.write('Fractal surface from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m with fractal dimension {:g}, fractal weightings {:g}, {:g}, fractal seeding {}, and range {:g}m to {:g}m, added to {}.'.format(xs * grid.dx, ys * grid.dy, zs * grid.dz, xf * grid.dx, yf * grid.dy, zf * grid.dz, surface.dimension, surface.weighting[0], surface.weighting[1], surface.seed, limits[0], limits[1], surface.operatingonID))
diff --git a/gprMax/cmds_geometry/add_surface_water.py b/gprMax/cmds_geometry/add_surface_water.py
index b954e2bd..57b8de00 100644
--- a/gprMax/cmds_geometry/add_surface_water.py
+++ b/gprMax/cmds_geometry/add_surface_water.py
@@ -1,118 +1,118 @@
-"""Class for surface water command."""
-from .cmds_geometry import UserObjectGeometry
-from ..exceptions import CmdInputError
-from ..utilities import round_value
-from ..materials import Material
-
-from tqdm import tqdm
-
-
-class AddSurfaceWater(UserObjectGeometry):
- """User class for edge command."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 11
- self.hash = '#add_surface_water'
-
- def create(self, grid, uip):
- """"Create surface water on fractal box."""
- try:
- p1 = self.kwargs['p1']
- p2 = self.kwargs['p2']
- fractal_box_id = self.kwargs['fractal_box_id']
- depth = self.kwargs['depth']
- except KeyError:
- raise CmdInputError(self.__str__() + ' requires exactly eight parameters')
-
- # grab the correct fractal volume
- volumes = [volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id]
- if volumes:
- volume = volumes[0]
- else:
- raise CmdInputError(self.__str__() + ' Cant find FractalBox {}'.format(fractal_box_id))
-
- p1, p2 = uip.check_box_points(p1, p2, self.__str__())
- xs, ys, zs = p1
- xf, yf, zf = p2
-
- if depth <= 0:
- raise CmdInputError(self.__str__() + ' requires a positive value for the depth of water')
-
- # Check for valid orientations
- if xs == xf:
- if ys == yf or zs == zf:
- raise CmdInputError(self.__str__() + ' dimensions are not specified correctly')
- if xs != volume.xs and xs != volume.xf:
- raise CmdInputError(self.__str__() + ' can only be used on the external surfaces of a fractal box')
- # xminus surface
- if xs == volume.xs:
- requestedsurface = 'xminus'
- # xplus surface
- elif xf == volume.xf:
- requestedsurface = 'xplus'
- filldepthcells = round_value(depth / grid.dx)
- filldepth = filldepthcells * grid.dx
-
- elif ys == yf:
- if xs == xf or zs == zf:
- raise CmdInputError(self.__str__() + ' dimensions are not specified correctly')
- if ys != volume.ys and ys != volume.yf:
- raise CmdInputError(self.__str__() + ' can only be used on the external surfaces of a fractal box')
- # yminus surface
- if ys == volume.ys:
- requestedsurface = 'yminus'
- # yplus surface
- elif yf == volume.yf:
- requestedsurface = 'yplus'
- filldepthcells = round_value(depth / grid.dy)
- filldepth = filldepthcells * grid.dy
-
- elif zs == zf:
- if xs == xf or ys == yf:
- raise CmdInputError(self.__str__() + ' dimensions are not specified correctly')
- if zs != volume.zs and zs != volume.zf:
- raise CmdInputError(self.__str__() + ' can only be used on the external surfaces of a fractal box')
- # zminus surface
- if zs == volume.zs:
- requestedsurface = 'zminus'
- # zplus surface
- elif zf == volume.zf:
- requestedsurface = 'zplus'
- filldepthcells = round_value(depth / grid.dz)
- filldepth = filldepthcells * grid.dz
-
- else:
- raise CmdInputError(self.__str__() + ' dimensions are not specified correctly')
-
- surface = next((x for x in volume.fractalsurfaces if x.surfaceID == requestedsurface), None)
- if not surface:
- raise CmdInputError(self.__str__() + ' specified surface {} does not have a rough surface applied'.format(requestedsurface))
-
- 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]:
- raise CmdInputError(self.__str__() + ' requires a value for the depth of water that lies with the range of the requested surface roughness')
-
- # Check to see if water has been already defined as a material
- if not any(x.ID == 'water' for x in grid.materials):
- m = Material(len(grid.materials), 'water')
- m.averagable = False
- m.type = 'builtin, debye'
- m.er = Material.watereri
- m.deltaer.append(Material.waterdeltaer)
- m.tau.append(Material.watertau)
- grid.materials.append(m)
- if Material.maxpoles == 0:
- Material.maxpoles = 1
-
- # Check if time step for model is suitable for using water
- water = next((x for x in grid.materials if x.ID == 'water'))
- testwater = next((x for x in water.tau if x < grid.dt), None)
- if testwater:
- raise CmdInputError(self.__str__() + ' requires the time step for the model to be less than the relaxation time required to model water.')
-
- if config.is_messages():
- tqdm.write('Water on surface from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m with depth {:g}m, added to {}.'.format(xs * grid.dx, ys * grid.dy, zs * grid.dz, xf * grid.dx, yf * grid.dy, zf * grid.dz, filldepth, surface.operatingonID))
+"""Class for surface water command."""
+from .cmds_geometry import UserObjectGeometry
+from ..exceptions import CmdInputError
+from ..utilities import round_value
+from ..materials import Material
+
+from tqdm import tqdm
+
+
+class AddSurfaceWater(UserObjectGeometry):
+ """User class for edge command."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 11
+ self.hash = '#add_surface_water'
+
+ def create(self, grid, uip):
+ """"Create surface water on fractal box."""
+ try:
+ p1 = self.kwargs['p1']
+ p2 = self.kwargs['p2']
+ fractal_box_id = self.kwargs['fractal_box_id']
+ depth = self.kwargs['depth']
+ except KeyError:
+ raise CmdInputError(self.__str__() + ' requires exactly eight parameters')
+
+ # grab the correct fractal volume
+ volumes = [volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id]
+ if volumes:
+ volume = volumes[0]
+ else:
+ raise CmdInputError(self.__str__() + ' Cant find FractalBox {}'.format(fractal_box_id))
+
+ p1, p2 = uip.check_box_points(p1, p2, self.__str__())
+ xs, ys, zs = p1
+ xf, yf, zf = p2
+
+ if depth <= 0:
+ raise CmdInputError(self.__str__() + ' requires a positive value for the depth of water')
+
+ # Check for valid orientations
+ if xs == xf:
+ if ys == yf or zs == zf:
+ raise CmdInputError(self.__str__() + ' dimensions are not specified correctly')
+ if xs != volume.xs and xs != volume.xf:
+ raise CmdInputError(self.__str__() + ' can only be used on the external surfaces of a fractal box')
+ # xminus surface
+ if xs == volume.xs:
+ requestedsurface = 'xminus'
+ # xplus surface
+ elif xf == volume.xf:
+ requestedsurface = 'xplus'
+ filldepthcells = round_value(depth / grid.dx)
+ filldepth = filldepthcells * grid.dx
+
+ elif ys == yf:
+ if xs == xf or zs == zf:
+ raise CmdInputError(self.__str__() + ' dimensions are not specified correctly')
+ if ys != volume.ys and ys != volume.yf:
+ raise CmdInputError(self.__str__() + ' can only be used on the external surfaces of a fractal box')
+ # yminus surface
+ if ys == volume.ys:
+ requestedsurface = 'yminus'
+ # yplus surface
+ elif yf == volume.yf:
+ requestedsurface = 'yplus'
+ filldepthcells = round_value(depth / grid.dy)
+ filldepth = filldepthcells * grid.dy
+
+ elif zs == zf:
+ if xs == xf or ys == yf:
+ raise CmdInputError(self.__str__() + ' dimensions are not specified correctly')
+ if zs != volume.zs and zs != volume.zf:
+ raise CmdInputError(self.__str__() + ' can only be used on the external surfaces of a fractal box')
+ # zminus surface
+ if zs == volume.zs:
+ requestedsurface = 'zminus'
+ # zplus surface
+ elif zf == volume.zf:
+ requestedsurface = 'zplus'
+ filldepthcells = round_value(depth / grid.dz)
+ filldepth = filldepthcells * grid.dz
+
+ else:
+ raise CmdInputError(self.__str__() + ' dimensions are not specified correctly')
+
+ surface = next((x for x in volume.fractalsurfaces if x.surfaceID == requestedsurface), None)
+ if not surface:
+ raise CmdInputError(self.__str__() + ' specified surface {} does not have a rough surface applied'.format(requestedsurface))
+
+ 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]:
+ raise CmdInputError(self.__str__() + ' requires a value for the depth of water that lies with the range of the requested surface roughness')
+
+ # Check to see if water has been already defined as a material
+ if not any(x.ID == 'water' for x in grid.materials):
+ m = Material(len(grid.materials), 'water')
+ m.averagable = False
+ m.type = 'builtin, debye'
+ m.er = Material.watereri
+ m.deltaer.append(Material.waterdeltaer)
+ m.tau.append(Material.watertau)
+ grid.materials.append(m)
+ if Material.maxpoles == 0:
+ Material.maxpoles = 1
+
+ # Check if time step for model is suitable for using water
+ water = next((x for x in grid.materials if x.ID == 'water'))
+ testwater = next((x for x in water.tau if x < grid.dt), None)
+ if testwater:
+ raise CmdInputError(self.__str__() + ' requires the time step for the model to be less than the relaxation time required to model water.')
+
+ if config.is_messages():
+ tqdm.write('Water on surface from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m with depth {:g}m, added to {}.'.format(xs * grid.dx, ys * grid.dy, zs * grid.dz, xf * grid.dx, yf * grid.dy, zf * grid.dz, filldepth, surface.operatingonID))
diff --git a/gprMax/cmds_geometry/box.py b/gprMax/cmds_geometry/box.py
index 9fd674ed..c519cc8b 100644
--- a/gprMax/cmds_geometry/box.py
+++ b/gprMax/cmds_geometry/box.py
@@ -1,93 +1,93 @@
-"""Class for triangle command."""
-from .cmds_geometry import UserObjectGeometry
-from ..exceptions import CmdInputError
-from ..materials import Material
-from ..cython.geometry_primitives import build_box
-import gprMax.config as config
-
-from tqdm import tqdm
-import numpy as np
-
-
-class Box(UserObjectGeometry):
- """User class for edge command."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 5
- self.hash = '#box'
-
- def create(self, grid, uip):
- try:
- p1 = self.kwargs['p1']
- p2 = self.kwargs['p2']
-
- except KeyError:
- raise CmdInputError(self.__str__() + ' Please specify two points.')
- # check materials have been specified
- # isotropic case
- try:
- materialsrequested = [self.kwargs['material_id']]
- except KeyError:
- # Anisotropic case
- try:
- materialsrequested = self.kwargs['material_ids']
- except KeyError:
- raise CmdInputError(self.__str__() + ' No materials have been specified')
-
- # check averaging
- try:
- # go with user specified averaging
- averagebox = self.kwargs['averaging']
- except KeyError:
- # if they havent specfied - go with the grid default
- averagebox = grid.averagevolumeobjects
-
- p1, p2 = uip.check_box_points(p1, p2, self.__str__())
- xs, ys, zs = p1
- xf, yf, zf = p2
-
- # Look up requested materials in existing list of material instances
- materials = [y for x in materialsrequested for y in grid.materials if y.ID == x]
-
- if len(materials) != len(materialsrequested):
- notfound = [x for x in materialsrequested if x not in materials]
- raise CmdInputError(self.__str__() + ' material(s) {} do not exist'.format(notfound))
-
- # Isotropic case
- if len(materials) == 1:
- averaging = materials[0].averagable and averagebox
- numID = numIDx = numIDy = numIDz = materials[0].numID
-
- # Uniaxial anisotropic case
- elif len(materials) == 3:
- averaging = False
- numIDx = materials[0].numID
- numIDy = materials[1].numID
- numIDz = materials[2].numID
- 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
- else:
- numID = len(grid.materials)
- 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].mr, materials[1].mr, materials[2].mr), axis=0)
-
- # Append the new material object to the materials list
- grid.materials.append(m)
-
- build_box(xs, xf, ys, yf, zs, zf, numID, numIDx, numIDy, numIDz, averaging, grid.solid, grid.rigidE, grid.rigidH, grid.ID)
-
- if config.is_messages():
- if averaging:
- dielectricsmoothing = 'on'
- else:
- dielectricsmoothing = 'off'
- tqdm.write('Box from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m of material(s) {} created, dielectric smoothing is {}.'.format(xs * grid.dx, ys * grid.dy, zs * grid.dz, xf * grid.dx, yf * grid.dy, zf * grid.dz, ', '.join(materialsrequested), dielectricsmoothing))
+"""Class for triangle command."""
+from .cmds_geometry import UserObjectGeometry
+from ..exceptions import CmdInputError
+from ..materials import Material
+from ..cython.geometry_primitives import build_box
+import gprMax.config as config
+
+from tqdm import tqdm
+import numpy as np
+
+
+class Box(UserObjectGeometry):
+ """User class for edge command."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 5
+ self.hash = '#box'
+
+ def create(self, grid, uip):
+ try:
+ p1 = self.kwargs['p1']
+ p2 = self.kwargs['p2']
+
+ except KeyError:
+ raise CmdInputError(self.__str__() + ' Please specify two points.')
+ # check materials have been specified
+ # isotropic case
+ try:
+ materialsrequested = [self.kwargs['material_id']]
+ except KeyError:
+ # Anisotropic case
+ try:
+ materialsrequested = self.kwargs['material_ids']
+ except KeyError:
+ raise CmdInputError(self.__str__() + ' No materials have been specified')
+
+ # check averaging
+ try:
+ # go with user specified averaging
+ averagebox = self.kwargs['averaging']
+ except KeyError:
+ # if they havent specfied - go with the grid default
+ averagebox = grid.averagevolumeobjects
+
+ p1, p2 = uip.check_box_points(p1, p2, self.__str__())
+ xs, ys, zs = p1
+ xf, yf, zf = p2
+
+ # Look up requested materials in existing list of material instances
+ materials = [y for x in materialsrequested for y in grid.materials if y.ID == x]
+
+ if len(materials) != len(materialsrequested):
+ notfound = [x for x in materialsrequested if x not in materials]
+ raise CmdInputError(self.__str__() + ' material(s) {} do not exist'.format(notfound))
+
+ # Isotropic case
+ if len(materials) == 1:
+ averaging = materials[0].averagable and averagebox
+ numID = numIDx = numIDy = numIDz = materials[0].numID
+
+ # Uniaxial anisotropic case
+ elif len(materials) == 3:
+ averaging = False
+ numIDx = materials[0].numID
+ numIDy = materials[1].numID
+ numIDz = materials[2].numID
+ 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
+ else:
+ numID = len(grid.materials)
+ 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].mr, materials[1].mr, materials[2].mr), axis=0)
+
+ # Append the new material object to the materials list
+ grid.materials.append(m)
+
+ build_box(xs, xf, ys, yf, zs, zf, numID, numIDx, numIDy, numIDz, averaging, grid.solid, grid.rigidE, grid.rigidH, grid.ID)
+
+ if config.is_messages():
+ if averaging:
+ dielectricsmoothing = 'on'
+ else:
+ dielectricsmoothing = 'off'
+ tqdm.write('Box from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m of material(s) {} created, dielectric smoothing is {}.'.format(xs * grid.dx, ys * grid.dy, zs * grid.dz, xf * grid.dx, yf * grid.dy, zf * grid.dz, ', '.join(materialsrequested), dielectricsmoothing))
diff --git a/gprMax/cmds_geometry/build_templates.py b/gprMax/cmds_geometry/build_templates.py
index 214d7f00..54ea0547 100644
--- a/gprMax/cmds_geometry/build_templates.py
+++ b/gprMax/cmds_geometry/build_templates.py
@@ -1,52 +1,52 @@
-from jinja2 import Environment, PackageLoader, select_autoescape
-env = Environment(
- loader=PackageLoader(__name__, 'templates'),
-)
-
-template = env.get_template('fields_updates_dispersive_template')
-
-r = template.render(
- functions=[
- # name, double, real
- {
- 'name_a': 'update_electric_dispersive_multipole_A_double_real',
- 'name_b': 'update_electric_dispersive_multipole_B_double_real',
- 'name_a_1': 'update_electric_dispersive_1pole_A_double_real',
- 'name_b_1': 'update_electric_dispersive_1pole_B_double_real',
- 'field_type': 'double',
- 'dispersive_type': 'double'
- },
- # name, float, real
- {
- 'name_a': 'update_electric_dispersive_multipole_A_float_real',
- 'name_b': 'update_electric_dispersive_multipole_B_float_real',
- 'name_a_1': 'update_electric_dispersive_1pole_A_float_real',
- 'name_b_1': 'update_electric_dispersive_1pole_B_float_real',
- 'field_type': 'float',
- 'dispersive_type': 'float'
- },
- # name, double, complex
- {
- 'name_a': 'update_electric_dispersive_multipole_A_double_complex',
- 'name_b': 'update_electric_dispersive_multipole_B_double_complex',
- 'name_a_1': 'update_electric_dispersive_1pole_A_double_complex',
- 'name_b_1': 'update_electric_dispersive_1pole_B_double_complex',
- 'field_type': 'double',
- 'dispersive_type': 'double complex',
- 'real_part': 'creal'
- },
- # name, float, complex
- {
- 'name_a': 'update_electric_dispersive_multipole_A_float_complex',
- 'name_b': 'update_electric_dispersive_multipole_B_float_complex',
- 'name_a_1': 'update_electric_dispersive_1pole_A_float_complex',
- 'name_b_1': 'update_electric_dispersive_1pole_B_float_complex',
- 'field_type': 'float',
- 'dispersive_type': 'float complex',
- 'real_part': 'crealf'
- }]
-)
-
-f = open('cython/dispersive_updates_test.pyx', 'w')
-f.write(r)
-f.close()
+from jinja2 import Environment, PackageLoader, select_autoescape
+env = Environment(
+ loader=PackageLoader(__name__, 'templates'),
+)
+
+template = env.get_template('fields_updates_dispersive_template')
+
+r = template.render(
+ functions=[
+ # name, double, real
+ {
+ 'name_a': 'update_electric_dispersive_multipole_A_double_real',
+ 'name_b': 'update_electric_dispersive_multipole_B_double_real',
+ 'name_a_1': 'update_electric_dispersive_1pole_A_double_real',
+ 'name_b_1': 'update_electric_dispersive_1pole_B_double_real',
+ 'field_type': 'double',
+ 'dispersive_type': 'double'
+ },
+ # name, float, real
+ {
+ 'name_a': 'update_electric_dispersive_multipole_A_float_real',
+ 'name_b': 'update_electric_dispersive_multipole_B_float_real',
+ 'name_a_1': 'update_electric_dispersive_1pole_A_float_real',
+ 'name_b_1': 'update_electric_dispersive_1pole_B_float_real',
+ 'field_type': 'float',
+ 'dispersive_type': 'float'
+ },
+ # name, double, complex
+ {
+ 'name_a': 'update_electric_dispersive_multipole_A_double_complex',
+ 'name_b': 'update_electric_dispersive_multipole_B_double_complex',
+ 'name_a_1': 'update_electric_dispersive_1pole_A_double_complex',
+ 'name_b_1': 'update_electric_dispersive_1pole_B_double_complex',
+ 'field_type': 'double',
+ 'dispersive_type': 'double complex',
+ 'real_part': 'creal'
+ },
+ # name, float, complex
+ {
+ 'name_a': 'update_electric_dispersive_multipole_A_float_complex',
+ 'name_b': 'update_electric_dispersive_multipole_B_float_complex',
+ 'name_a_1': 'update_electric_dispersive_1pole_A_float_complex',
+ 'name_b_1': 'update_electric_dispersive_1pole_B_float_complex',
+ 'field_type': 'float',
+ 'dispersive_type': 'float complex',
+ 'real_part': 'crealf'
+ }]
+)
+
+f = open('cython/dispersive_updates_test.pyx', 'w')
+f.write(r)
+f.close()
diff --git a/gprMax/cmds_geometry/cylinder.py b/gprMax/cmds_geometry/cylinder.py
index ec1ea061..18936210 100644
--- a/gprMax/cmds_geometry/cylinder.py
+++ b/gprMax/cmds_geometry/cylinder.py
@@ -1,98 +1,98 @@
-"""Class for cylinder command."""
-from .cmds_geometry import UserObjectGeometry
-from ..exceptions import CmdInputError
-from ..materials import Material
-from ..cython.geometry_primitives import build_cylinder
-
-from tqdm import tqdm
-import numpy as np
-import gprMax.config as config
-
-
-class Cylinder(UserObjectGeometry):
- """User class for edge command."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 6
- self.hash = '#cylinder'
-
- def create(self, grid, uip):
-
- try:
- p1 = self.kwargs['p1']
- p2 = self.kwargs['p2']
- r = self.kwargs['r']
-
- except KeyError:
- raise CmdInputError(self.__str__() + ' Please specify 2 points and a radius')
-
- # check averaging
- try:
- # go with user specified averaging
- averagecylinder = self.kwargs['averaging']
- except KeyError:
- # if they havent specfied - go with the grid default
- averagecylinder = grid.averagevolumeobjects
-
- # check materials have been specified
- # isotropic case
- try:
- materialsrequested = [self.kwargs['material_id']]
- except KeyError:
- # Anisotropic case
- try:
- materialsrequested = self.kwargs['material_ids']
- except KeyError:
- raise CmdInputError(self.__str__() + ' No materials have been specified')
-
- x1, y1, z1 = uip.round_to_grid(p1)
- x2, y2, z2 = uip.round_to_grid(p2)
-
- if r <= 0:
- raise CmdInputError(self.__str__() + ' the radius {:g} should be a positive value.'.format(r))
-
- # Look up requested materials in existing list of material instances
- materials = [y for x in materialsrequested for y in grid.materials if y.ID == x]
-
- if len(materials) != len(materialsrequested):
- notfound = [x for x in materialsrequested if x not in materials]
- raise CmdInputError(self.__str__() + ' material(s) {} do not exist'.format(notfound))
-
- # Isotropic case
- if len(materials) == 1:
- averaging = materials[0].averagable and averagecylinder
- numID = numIDx = numIDy = numIDz = materials[0].numID
-
- # Uniaxial anisotropic case
- elif len(materials) == 3:
- averaging = False
- numIDx = materials[0].numID
- numIDy = materials[1].numID
- numIDz = materials[2].numID
- 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
- else:
- numID = len(grid.materials)
- 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].mr, materials[1].mr, materials[2].mr), axis=0)
-
- # Append the new material object to the materials list
- grid.materials.append(m)
-
- build_cylinder(x1, y1, z1, x2, y2, z2, r, grid.dx, grid.dy, grid.dz, numID, numIDx, numIDy, numIDz, averaging, grid.solid, grid.rigidE, grid.rigidH, grid.ID)
-
- if config.is_messages():
- if averaging:
- dielectricsmoothing = 'on'
- else:
- dielectricsmoothing = 'off'
- tqdm.write('Cylinder with face centres {:g}m, {:g}m, {:g}m and {:g}m, {:g}m, {:g}m, with radius {:g}m, of material(s) {} created, dielectric smoothing is {}.'.format(x1, y1, z1, x2, y2, z2, r, ', '.join(materialsrequested), dielectricsmoothing))
+"""Class for cylinder command."""
+from .cmds_geometry import UserObjectGeometry
+from ..exceptions import CmdInputError
+from ..materials import Material
+from ..cython.geometry_primitives import build_cylinder
+
+from tqdm import tqdm
+import numpy as np
+import gprMax.config as config
+
+
+class Cylinder(UserObjectGeometry):
+ """User class for edge command."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 6
+ self.hash = '#cylinder'
+
+ def create(self, grid, uip):
+
+ try:
+ p1 = self.kwargs['p1']
+ p2 = self.kwargs['p2']
+ r = self.kwargs['r']
+
+ except KeyError:
+ raise CmdInputError(self.__str__() + ' Please specify 2 points and a radius')
+
+ # check averaging
+ try:
+ # go with user specified averaging
+ averagecylinder = self.kwargs['averaging']
+ except KeyError:
+ # if they havent specfied - go with the grid default
+ averagecylinder = grid.averagevolumeobjects
+
+ # check materials have been specified
+ # isotropic case
+ try:
+ materialsrequested = [self.kwargs['material_id']]
+ except KeyError:
+ # Anisotropic case
+ try:
+ materialsrequested = self.kwargs['material_ids']
+ except KeyError:
+ raise CmdInputError(self.__str__() + ' No materials have been specified')
+
+ x1, y1, z1 = uip.round_to_grid(p1)
+ x2, y2, z2 = uip.round_to_grid(p2)
+
+ if r <= 0:
+ raise CmdInputError(self.__str__() + ' the radius {:g} should be a positive value.'.format(r))
+
+ # Look up requested materials in existing list of material instances
+ materials = [y for x in materialsrequested for y in grid.materials if y.ID == x]
+
+ if len(materials) != len(materialsrequested):
+ notfound = [x for x in materialsrequested if x not in materials]
+ raise CmdInputError(self.__str__() + ' material(s) {} do not exist'.format(notfound))
+
+ # Isotropic case
+ if len(materials) == 1:
+ averaging = materials[0].averagable and averagecylinder
+ numID = numIDx = numIDy = numIDz = materials[0].numID
+
+ # Uniaxial anisotropic case
+ elif len(materials) == 3:
+ averaging = False
+ numIDx = materials[0].numID
+ numIDy = materials[1].numID
+ numIDz = materials[2].numID
+ 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
+ else:
+ numID = len(grid.materials)
+ 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].mr, materials[1].mr, materials[2].mr), axis=0)
+
+ # Append the new material object to the materials list
+ grid.materials.append(m)
+
+ build_cylinder(x1, y1, z1, x2, y2, z2, r, grid.dx, grid.dy, grid.dz, numID, numIDx, numIDy, numIDz, averaging, grid.solid, grid.rigidE, grid.rigidH, grid.ID)
+
+ if config.is_messages():
+ if averaging:
+ dielectricsmoothing = 'on'
+ else:
+ dielectricsmoothing = 'off'
+ tqdm.write('Cylinder with face centres {:g}m, {:g}m, {:g}m and {:g}m, {:g}m, {:g}m, with radius {:g}m, of material(s) {} created, dielectric smoothing is {}.'.format(x1, y1, z1, x2, y2, z2, r, ', '.join(materialsrequested), dielectricsmoothing))
diff --git a/gprMax/cmds_geometry/cylindrical_sector.py b/gprMax/cmds_geometry/cylindrical_sector.py
index c572417c..082607cc 100644
--- a/gprMax/cmds_geometry/cylindrical_sector.py
+++ b/gprMax/cmds_geometry/cylindrical_sector.py
@@ -1,137 +1,137 @@
-"""Class for cylinder command."""
-from .cmds_geometry import UserObjectGeometry
-from ..exceptions import CmdInputError
-from ..materials import Material
-from ..cython.geometry_primitives import build_cylindrical_sector
-
-from tqdm import tqdm
-import numpy as np
-
-
-class CylindricalSector(UserObjectGeometry):
- """User class for edge command."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 7
- self.hash = '#cylindrical_sector'
-
- def create(self, grid, uip):
-
- try:
- normal = self.kwargs['normal'].lower()
- ctr1 = self.kwargs['ctr1']
- ctr2 = self.kwargs['ctr2']
- extent1 = self.kwargs['extent1']
- extent2 = self.kwargs['extent2']
- start = self.kwargs['start']
- end = self.kwargs['end']
- r = self.kwargs['r']
- thickness = extent2 - extent1
- except KeyError:
- raise CmdInputError(self.__str__())
-
- # check averaging
- try:
- # go with user specified averaging
- averagecylindricalsector = self.kwargs['averaging']
- except KeyError:
- # if they havent specfied - go with the grid default
- averagecylindricalsector = grid.averagevolumeobjects
-
- # check materials have been specified
- # isotropic case
- try:
- materialsrequested = [self.kwargs['material_id']]
- except KeyError:
- # Anisotropic case
- try:
- materialsrequested = self.kwargs['material_ids']
- except KeyError:
- raise CmdInputError(self.__str__() + ' No materials have been specified')
-
- sectorstartangle = 2 * np.pi * (start / 360)
- sectorangle = 2 * np.pi * (end / 360)
-
- if normal != 'x' and normal != 'y' and normal != 'z':
- raise CmdInputError(self.__str__() + ' the normal direction must be either x, y or z.')
- if r <= 0:
- raise CmdInputError(self.__str__() + ' the radius {:g} should be a positive value.'.format(r))
- if sectorstartangle < 0 or sectorangle <= 0:
- raise CmdInputError(self.__str__() + ' the starting angle and sector angle should be a positive values.')
- if sectorstartangle >= 2 * np.pi or sectorangle >= 2 * np.pi:
- raise CmdInputError(self.__str__() + ' the starting angle and sector angle must be less than 360 degrees.')
-
- # Look up requested materials in existing list of material instances
- materials = [y for x in materialsrequested for y in grid.materials if y.ID == x]
-
- if len(materials) != len(materialsrequested):
- notfound = [x for x in materialsrequested if x not in materials]
- raise CmdInputError(self.__str__() + ' material(s) {} do not exist'.format(notfound))
-
- if thickness > 0:
- # Isotropic case
- if len(materials) == 1:
- averaging = materials[0].averagable and averagecylindricalsector
- numID = numIDx = numIDy = numIDz = materials[0].numID
-
- # Uniaxial anisotropic case
- elif len(materials) == 3:
- averaging = False
- numIDx = materials[0].numID
- numIDy = materials[1].numID
- numIDz = materials[2].numID
- 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
- else:
- numID = len(grid.materials)
- 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].mr, materials[1].mr, materials[2].mr), axis=0)
-
- # Append the new material object to the materials list
- grid.materials.append(m)
- else:
- averaging = False
- # Isotropic case
- if len(materials) == 1:
- numID = numIDx = numIDy = numIDz = materials[0].numID
-
- # Uniaxial anisotropic case
- elif len(materials) == 3:
- # numID requires a value but it will not be used
- numID = None
- numIDx = materials[0].numID
- numIDy = materials[1].numID
- numIDz = materials[2].numID
-
- # yz-plane cylindrical sector
- if normal == 'x':
- level, ctr1, ctr2 = uip.round_to_grid((extent1, ctr1, ctr2))
-
- # xz-plane cylindrical sector
- elif normal == 'y':
- ctr1, level, ctr2 = uip.round_to_grid((ctr1, extent1, ctr2))
-
- # xy-plane cylindrical sector
- elif normal == 'z':
- ctr1, ctr2, level = uip.round_to_grid((ctr1, ctr2, extent1))
-
- build_cylindrical_sector(ctr1, ctr2, level, sectorstartangle, sectorangle, r, normal, thickness, grid.dx, grid.dy, grid.dz, numID, numIDx, numIDy, numIDz, averaging, grid.solid, grid.rigidE, grid.rigidH, grid.ID)
-
- if config.is_messages():
- if thickness > 0:
- if averaging:
- dielectricsmoothing = 'on'
- else:
- dielectricsmoothing = 'off'
- tqdm.write('Cylindrical sector with centre {:g}m, {:g}m, radius {:g}m, starting angle {:.1f} degrees, sector angle {:.1f} degrees, thickness {:g}m, of material(s) {} created, dielectric smoothing is {}.'.format(ctr1, ctr2, r, (sectorstartangle / (2 * np.pi)) * 360, (sectorangle / (2 * np.pi)) * 360, thickness, ', '.join(materialsrequested), dielectricsmoothing))
- else:
- tqdm.write('Cylindrical sector with centre {:g}m, {:g}m, radius {:g}m, starting angle {:.1f} degrees, sector angle {:.1f} degrees, of material(s) {} created.'.format(ctr1, ctr2, r, (sectorstartangle / (2 * np.pi)) * 360, (sectorangle / (2 * np.pi)) * 360, ', '.join(materialsrequested)))
+"""Class for cylinder command."""
+from .cmds_geometry import UserObjectGeometry
+from ..exceptions import CmdInputError
+from ..materials import Material
+from ..cython.geometry_primitives import build_cylindrical_sector
+
+from tqdm import tqdm
+import numpy as np
+
+
+class CylindricalSector(UserObjectGeometry):
+ """User class for edge command."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 7
+ self.hash = '#cylindrical_sector'
+
+ def create(self, grid, uip):
+
+ try:
+ normal = self.kwargs['normal'].lower()
+ ctr1 = self.kwargs['ctr1']
+ ctr2 = self.kwargs['ctr2']
+ extent1 = self.kwargs['extent1']
+ extent2 = self.kwargs['extent2']
+ start = self.kwargs['start']
+ end = self.kwargs['end']
+ r = self.kwargs['r']
+ thickness = extent2 - extent1
+ except KeyError:
+ raise CmdInputError(self.__str__())
+
+ # check averaging
+ try:
+ # go with user specified averaging
+ averagecylindricalsector = self.kwargs['averaging']
+ except KeyError:
+ # if they havent specfied - go with the grid default
+ averagecylindricalsector = grid.averagevolumeobjects
+
+ # check materials have been specified
+ # isotropic case
+ try:
+ materialsrequested = [self.kwargs['material_id']]
+ except KeyError:
+ # Anisotropic case
+ try:
+ materialsrequested = self.kwargs['material_ids']
+ except KeyError:
+ raise CmdInputError(self.__str__() + ' No materials have been specified')
+
+ sectorstartangle = 2 * np.pi * (start / 360)
+ sectorangle = 2 * np.pi * (end / 360)
+
+ if normal != 'x' and normal != 'y' and normal != 'z':
+ raise CmdInputError(self.__str__() + ' the normal direction must be either x, y or z.')
+ if r <= 0:
+ raise CmdInputError(self.__str__() + ' the radius {:g} should be a positive value.'.format(r))
+ if sectorstartangle < 0 or sectorangle <= 0:
+ raise CmdInputError(self.__str__() + ' the starting angle and sector angle should be a positive values.')
+ if sectorstartangle >= 2 * np.pi or sectorangle >= 2 * np.pi:
+ raise CmdInputError(self.__str__() + ' the starting angle and sector angle must be less than 360 degrees.')
+
+ # Look up requested materials in existing list of material instances
+ materials = [y for x in materialsrequested for y in grid.materials if y.ID == x]
+
+ if len(materials) != len(materialsrequested):
+ notfound = [x for x in materialsrequested if x not in materials]
+ raise CmdInputError(self.__str__() + ' material(s) {} do not exist'.format(notfound))
+
+ if thickness > 0:
+ # Isotropic case
+ if len(materials) == 1:
+ averaging = materials[0].averagable and averagecylindricalsector
+ numID = numIDx = numIDy = numIDz = materials[0].numID
+
+ # Uniaxial anisotropic case
+ elif len(materials) == 3:
+ averaging = False
+ numIDx = materials[0].numID
+ numIDy = materials[1].numID
+ numIDz = materials[2].numID
+ 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
+ else:
+ numID = len(grid.materials)
+ 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].mr, materials[1].mr, materials[2].mr), axis=0)
+
+ # Append the new material object to the materials list
+ grid.materials.append(m)
+ else:
+ averaging = False
+ # Isotropic case
+ if len(materials) == 1:
+ numID = numIDx = numIDy = numIDz = materials[0].numID
+
+ # Uniaxial anisotropic case
+ elif len(materials) == 3:
+ # numID requires a value but it will not be used
+ numID = None
+ numIDx = materials[0].numID
+ numIDy = materials[1].numID
+ numIDz = materials[2].numID
+
+ # yz-plane cylindrical sector
+ if normal == 'x':
+ level, ctr1, ctr2 = uip.round_to_grid((extent1, ctr1, ctr2))
+
+ # xz-plane cylindrical sector
+ elif normal == 'y':
+ ctr1, level, ctr2 = uip.round_to_grid((ctr1, extent1, ctr2))
+
+ # xy-plane cylindrical sector
+ elif normal == 'z':
+ ctr1, ctr2, level = uip.round_to_grid((ctr1, ctr2, extent1))
+
+ build_cylindrical_sector(ctr1, ctr2, level, sectorstartangle, sectorangle, r, normal, thickness, grid.dx, grid.dy, grid.dz, numID, numIDx, numIDy, numIDz, averaging, grid.solid, grid.rigidE, grid.rigidH, grid.ID)
+
+ if config.is_messages():
+ if thickness > 0:
+ if averaging:
+ dielectricsmoothing = 'on'
+ else:
+ dielectricsmoothing = 'off'
+ tqdm.write('Cylindrical sector with centre {:g}m, {:g}m, radius {:g}m, starting angle {:.1f} degrees, sector angle {:.1f} degrees, thickness {:g}m, of material(s) {} created, dielectric smoothing is {}.'.format(ctr1, ctr2, r, (sectorstartangle / (2 * np.pi)) * 360, (sectorangle / (2 * np.pi)) * 360, thickness, ', '.join(materialsrequested), dielectricsmoothing))
+ else:
+ tqdm.write('Cylindrical sector with centre {:g}m, {:g}m, radius {:g}m, starting angle {:.1f} degrees, sector angle {:.1f} degrees, of material(s) {} created.'.format(ctr1, ctr2, r, (sectorstartangle / (2 * np.pi)) * 360, (sectorangle / (2 * np.pi)) * 360, ', '.join(materialsrequested)))
diff --git a/gprMax/cmds_geometry/edge.py b/gprMax/cmds_geometry/edge.py
index 1e4ff282..691be9cf 100644
--- a/gprMax/cmds_geometry/edge.py
+++ b/gprMax/cmds_geometry/edge.py
@@ -1,64 +1,64 @@
-"""Class for edge command."""
-from .cmds_geometry import UserObjectGeometry
-from ..exceptions import CmdInputError
-from ..cython.geometry_primitives import build_edge_x
-from ..cython.geometry_primitives import build_edge_y
-from ..cython.geometry_primitives import build_edge_z
-
-from tqdm import tqdm
-
-
-class Edge(UserObjectGeometry):
- """User class for edge command."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 2
- self.hash = '#edge'
-
- def create(self, grid, uip):
- """Create edge and add it to the grid."""
- try:
- p1 = self.kwargs['p1']
- p2 = self.kwargs['p2']
- material_id = self.kwargs['material_id']
- except KeyError:
- raise CmdInputError(self.__str__() + ' requires exactly 3 parameters')
-
- p1, p2 = uip.check_box_points(p1, p2, self.__str__())
- xs, ys, zs = p1
- xf, yf, zf = p2
-
- material = next((x for x in grid.materials if x.ID == material_id), None)
-
- if not material:
- raise CmdInputError('Material with ID {} does not exist'.format(material_id))
-
- # Check for valid orientations
- # x-orientated wire
- if xs != xf:
- if ys != yf or zs != zf:
- raise CmdInputError(self.__str__() + ' the edge is not specified correctly')
- else:
- for i in range(xs, xf):
- build_edge_x(i, ys, zs, material.numID, grid.rigidE, grid.rigidH, grid.ID)
-
- # y-orientated wire
- elif ys != yf:
- if xs != xf or zs != zf:
- raise CmdInputError(self.__str__() + ' the edge is not specified correctly')
- else:
- for j in range(ys, yf):
- build_edge_y(xs, j, zs, material.numID, grid.rigidE, grid.rigidH, grid.ID)
-
- # z-orientated wire
- elif zs != zf:
- if xs != xf or ys != yf:
- raise CmdInputError(self.__str__() + ' the edge is not specified correctly')
- else:
- for k in range(zs, zf):
- build_edge_z(xs, ys, k, material.numID, grid.rigidE, grid.rigidH, grid.ID)
-
- if config.is_messages():
- tqdm.write('Edge from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m of material {} created.'.format(xs * grid.dx, ys * grid.dy, zs * grid.dz, xf * grid.dx, yf * grid.dy, zf * grid.dz, material_id))
+"""Class for edge command."""
+from .cmds_geometry import UserObjectGeometry
+from ..exceptions import CmdInputError
+from ..cython.geometry_primitives import build_edge_x
+from ..cython.geometry_primitives import build_edge_y
+from ..cython.geometry_primitives import build_edge_z
+
+from tqdm import tqdm
+
+
+class Edge(UserObjectGeometry):
+ """User class for edge command."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 2
+ self.hash = '#edge'
+
+ def create(self, grid, uip):
+ """Create edge and add it to the grid."""
+ try:
+ p1 = self.kwargs['p1']
+ p2 = self.kwargs['p2']
+ material_id = self.kwargs['material_id']
+ except KeyError:
+ raise CmdInputError(self.__str__() + ' requires exactly 3 parameters')
+
+ p1, p2 = uip.check_box_points(p1, p2, self.__str__())
+ xs, ys, zs = p1
+ xf, yf, zf = p2
+
+ material = next((x for x in grid.materials if x.ID == material_id), None)
+
+ if not material:
+ raise CmdInputError('Material with ID {} does not exist'.format(material_id))
+
+ # Check for valid orientations
+ # x-orientated wire
+ if xs != xf:
+ if ys != yf or zs != zf:
+ raise CmdInputError(self.__str__() + ' the edge is not specified correctly')
+ else:
+ for i in range(xs, xf):
+ build_edge_x(i, ys, zs, material.numID, grid.rigidE, grid.rigidH, grid.ID)
+
+ # y-orientated wire
+ elif ys != yf:
+ if xs != xf or zs != zf:
+ raise CmdInputError(self.__str__() + ' the edge is not specified correctly')
+ else:
+ for j in range(ys, yf):
+ build_edge_y(xs, j, zs, material.numID, grid.rigidE, grid.rigidH, grid.ID)
+
+ # z-orientated wire
+ elif zs != zf:
+ if xs != xf or ys != yf:
+ raise CmdInputError(self.__str__() + ' the edge is not specified correctly')
+ else:
+ for k in range(zs, zf):
+ build_edge_z(xs, ys, k, material.numID, grid.rigidE, grid.rigidH, grid.ID)
+
+ if config.is_messages():
+ tqdm.write('Edge from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m of material {} created.'.format(xs * grid.dx, ys * grid.dy, zs * grid.dz, xf * grid.dx, yf * grid.dy, zf * grid.dz, material_id))
diff --git a/gprMax/cmds_geometry/fractal_box.py b/gprMax/cmds_geometry/fractal_box.py
index 50ec3b63..c792bb51 100644
--- a/gprMax/cmds_geometry/fractal_box.py
+++ b/gprMax/cmds_geometry/fractal_box.py
@@ -1,93 +1,93 @@
-"""Class for surface roughness command."""
-from .cmds_geometry import UserObjectGeometry
-from ..exceptions import CmdInputError
-from ..fractals import FractalVolume
-import gprMax.config as config
-
-from tqdm import tqdm
-import numpy as np
-
-
-class FractalBox(UserObjectGeometry):
- """User class for edge command."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 9
- self.hash = '#fractal_box'
-
- def create(self, grid, uip):
- try:
- p1 = self.kwargs['p1']
- p2 = self.kwargs['p2']
- frac_dim = self.kwargs['frac_dim']
- weighting = np.array(self.kwargs['weighting'])
- n_materials = self.kwargs['n_materials']
- mixing_model_id = self.kwargs['mixing_model_id']
- ID = self.kwargs['id']
-
- except KeyError:
- raise CmdInputError(self.__str__() + ' Incorrect parameters')
-
- try:
- seed = self.kwargs['seed']
- except KeyError:
- seed = None
-
- # Default is no dielectric smoothing for a fractal box
- averagefractalbox = False
-
- # check averaging
- try:
- # go with user specified averaging
- averagefractalbox = self.kwargs['averaging']
- except KeyError:
- # if they havent specfied - go with the grid default
- averagefractalbox = False
-
- p1, p2 = uip.check_box_points(p1, p2, self.__str__())
- xs, ys, zs = p1
- xf, yf, zf = p2
-
- if frac_dim < 0:
- raise CmdInputError(self.__str__() + ' requires a positive value for the fractal dimension')
- if weighting[0] < 0:
- raise CmdInputError(self.__str__() + ' requires a positive value for the fractal weighting in the x direction')
- if weighting[1] < 0:
- raise CmdInputError(self.__str__() + ' requires a positive value for the fractal weighting in the y direction')
- if weighting[2] < 0:
- raise CmdInputError(self.__str__() + ' requires a positive value for the fractal weighting in the z direction')
- if n_materials < 0:
- raise CmdInputError(self.__str__() + ' requires a positive value for the number of bins')
-
- # 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)
- material = next((x for x in grid.materials if x.ID == mixing_model_id), None)
- nbins = n_materials
-
- if mixingmodel:
- if nbins == 1:
- raise CmdInputError(self.__str__() + ' must be used with more than one material from the mixing model.')
- # Create materials from mixing model as number of bins now known from fractal_box command
- mixingmodel.calculate_debye_properties(nbins, grid)
- elif not material:
- raise CmdInputError(self.__str__() + ' mixing model or material with ID {} does not exist'.format(mixing_model_id))
-
- volume = FractalVolume(xs, xf, ys, yf, zs, zf, frac_dim)
- volume.ID = ID
- volume.operatingonID = mixing_model_id
- volume.nbins = nbins
- volume.seed = seed
- volume.weighting = weighting
- volume.averaging = averagefractalbox
- volume.mixingmodel = mixingmodel
-
- if config.is_messages():
- if volume.averaging:
- dielectricsmoothing = 'on'
- else:
- dielectricsmoothing = 'off'
- tqdm.write('Fractal box {} from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m with {}, fractal dimension {:g}, fractal weightings {:g}, {:g}, {:g}, fractal seeding {}, with {} material(s) created, dielectric smoothing is {}.'.format(volume.ID, xs * grid.dx, ys * grid.dy, zs * grid.dz, xf * grid.dx, yf * grid.dy, zf * grid.dz, volume.operatingonID, volume.dimension, volume.weighting[0], volume.weighting[1], volume.weighting[2], volume.seed, volume.nbins, dielectricsmoothing))
-
- grid.fractalvolumes.append(volume)
+"""Class for surface roughness command."""
+from .cmds_geometry import UserObjectGeometry
+from ..exceptions import CmdInputError
+from ..fractals import FractalVolume
+import gprMax.config as config
+
+from tqdm import tqdm
+import numpy as np
+
+
+class FractalBox(UserObjectGeometry):
+ """User class for edge command."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 9
+ self.hash = '#fractal_box'
+
+ def create(self, grid, uip):
+ try:
+ p1 = self.kwargs['p1']
+ p2 = self.kwargs['p2']
+ frac_dim = self.kwargs['frac_dim']
+ weighting = np.array(self.kwargs['weighting'])
+ n_materials = self.kwargs['n_materials']
+ mixing_model_id = self.kwargs['mixing_model_id']
+ ID = self.kwargs['id']
+
+ except KeyError:
+ raise CmdInputError(self.__str__() + ' Incorrect parameters')
+
+ try:
+ seed = self.kwargs['seed']
+ except KeyError:
+ seed = None
+
+ # Default is no dielectric smoothing for a fractal box
+ averagefractalbox = False
+
+ # check averaging
+ try:
+ # go with user specified averaging
+ averagefractalbox = self.kwargs['averaging']
+ except KeyError:
+ # if they havent specfied - go with the grid default
+ averagefractalbox = False
+
+ p1, p2 = uip.check_box_points(p1, p2, self.__str__())
+ xs, ys, zs = p1
+ xf, yf, zf = p2
+
+ if frac_dim < 0:
+ raise CmdInputError(self.__str__() + ' requires a positive value for the fractal dimension')
+ if weighting[0] < 0:
+ raise CmdInputError(self.__str__() + ' requires a positive value for the fractal weighting in the x direction')
+ if weighting[1] < 0:
+ raise CmdInputError(self.__str__() + ' requires a positive value for the fractal weighting in the y direction')
+ if weighting[2] < 0:
+ raise CmdInputError(self.__str__() + ' requires a positive value for the fractal weighting in the z direction')
+ if n_materials < 0:
+ raise CmdInputError(self.__str__() + ' requires a positive value for the number of bins')
+
+ # 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)
+ material = next((x for x in grid.materials if x.ID == mixing_model_id), None)
+ nbins = n_materials
+
+ if mixingmodel:
+ if nbins == 1:
+ raise CmdInputError(self.__str__() + ' must be used with more than one material from the mixing model.')
+ # Create materials from mixing model as number of bins now known from fractal_box command
+ mixingmodel.calculate_debye_properties(nbins, grid)
+ elif not material:
+ raise CmdInputError(self.__str__() + ' mixing model or material with ID {} does not exist'.format(mixing_model_id))
+
+ volume = FractalVolume(xs, xf, ys, yf, zs, zf, frac_dim)
+ volume.ID = ID
+ volume.operatingonID = mixing_model_id
+ volume.nbins = nbins
+ volume.seed = seed
+ volume.weighting = weighting
+ volume.averaging = averagefractalbox
+ volume.mixingmodel = mixingmodel
+
+ if config.is_messages():
+ if volume.averaging:
+ dielectricsmoothing = 'on'
+ else:
+ dielectricsmoothing = 'off'
+ tqdm.write('Fractal box {} from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m with {}, fractal dimension {:g}, fractal weightings {:g}, {:g}, {:g}, fractal seeding {}, with {} material(s) created, dielectric smoothing is {}.'.format(volume.ID, xs * grid.dx, ys * grid.dy, zs * grid.dz, xf * grid.dx, yf * grid.dy, zf * grid.dz, volume.operatingonID, volume.dimension, volume.weighting[0], volume.weighting[1], volume.weighting[2], volume.seed, volume.nbins, dielectricsmoothing))
+
+ grid.fractalvolumes.append(volume)
diff --git a/gprMax/cmds_geometry/plate.py b/gprMax/cmds_geometry/plate.py
index 74ebaed0..68a9b270 100644
--- a/gprMax/cmds_geometry/plate.py
+++ b/gprMax/cmds_geometry/plate.py
@@ -1,112 +1,112 @@
-"""Class for edge command."""
-from .cmds_geometry import UserObjectGeometry
-from ..exceptions import CmdInputError
-from ..cython.geometry_primitives import build_face_yz
-from ..cython.geometry_primitives import build_face_xz
-from ..cython.geometry_primitives import build_face_xy
-
-from tqdm import tqdm
-
-
-class Plate(UserObjectGeometry):
- """User class for edge command."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 3
- self.hash = '#plate'
-
- def create(self, grid, uip):
-
- try:
- p1 = self.kwargs['p1']
- p2 = self.kwargs['p2']
-
- except KeyError:
- raise CmdInputError(self.__str__() + ' 2 points must be specified')
-
- # isotropic
- try:
- materialsrequested = [self.kwargs['material_id']]
- except KeyError:
- # Anisotropic case
- try:
- materialsrequested = self.kwargs['material_ids']
- except KeyError:
- raise CmdInputError(self.__str__() + ' No materials have been specified')
-
- p1, p2 = uip.check_box_points(p1, p2, self.__str__())
- xs, ys, zs = p1
- xf, yf, zf = p2
-
- # Check for valid orientations
- if xs == xf:
- if ys == yf or zs == zf:
- raise CmdInputError(self.__str__() + ' the plate is not specified correctly')
-
- elif ys == yf:
- if xs == xf or zs == zf:
- raise CmdInputError(self.__str__() + ' the plate is not specified correctly')
-
- elif zs == zf:
- if xs == xf or ys == yf:
- raise CmdInputError(self.__str__() + ' the plate is not specified correctly')
-
- else:
- raise CmdInputError(self.__str__() + ' the plate is not specified correctly')
-
- # Look up requested materials in existing list of material instances
- materials = [y for x in materialsrequested for y in grid.materials if y.ID == x]
-
- if len(materials) != len(materialsrequested):
- notfound = [x for x in materialsrequested if x not in materials]
- raise CmdInputError(self.__str__() + ' material(s) {} do not exist'.format(notfound))
-
- # yz-plane plate
- if xs == xf:
- # Isotropic case
- if len(materials) == 1:
- numIDx = numIDy = numIDz = materials[0].numID
-
- # Uniaxial anisotropic case
- elif len(materials) == 2:
- numIDy = materials[0].numID
- numIDz = materials[1].numID
-
- 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)
-
- # xz-plane plate
- elif ys == yf:
- # Isotropic case
- if len(materials) == 1:
- numIDx = numIDy = numIDz = materials[0].numID
-
- # Uniaxial anisotropic case
- elif len(materials) == 2:
- numIDx = materials[0].numID
- numIDz = materials[1].numID
-
- 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)
-
- # xy-plane plate
- elif zs == zf:
- # Isotropic case
- if len(materials) == 1:
- numIDx = numIDy = numIDz = materials[0].numID
-
- # Uniaxial anisotropic case
- elif len(materials) == 2:
- numIDx = materials[0].numID
- numIDy = materials[1].numID
-
- 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)
-
- if config.is_messages():
- tqdm.write('Plate from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m of material(s) {} created.'.format(xs * grid.dx, ys * grid.dy, zs * grid.dz, xf * grid.dx, yf * grid.dy, zf * grid.dz, ', '.join(materialsrequested)))
+"""Class for edge command."""
+from .cmds_geometry import UserObjectGeometry
+from ..exceptions import CmdInputError
+from ..cython.geometry_primitives import build_face_yz
+from ..cython.geometry_primitives import build_face_xz
+from ..cython.geometry_primitives import build_face_xy
+
+from tqdm import tqdm
+
+
+class Plate(UserObjectGeometry):
+ """User class for edge command."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 3
+ self.hash = '#plate'
+
+ def create(self, grid, uip):
+
+ try:
+ p1 = self.kwargs['p1']
+ p2 = self.kwargs['p2']
+
+ except KeyError:
+ raise CmdInputError(self.__str__() + ' 2 points must be specified')
+
+ # isotropic
+ try:
+ materialsrequested = [self.kwargs['material_id']]
+ except KeyError:
+ # Anisotropic case
+ try:
+ materialsrequested = self.kwargs['material_ids']
+ except KeyError:
+ raise CmdInputError(self.__str__() + ' No materials have been specified')
+
+ p1, p2 = uip.check_box_points(p1, p2, self.__str__())
+ xs, ys, zs = p1
+ xf, yf, zf = p2
+
+ # Check for valid orientations
+ if xs == xf:
+ if ys == yf or zs == zf:
+ raise CmdInputError(self.__str__() + ' the plate is not specified correctly')
+
+ elif ys == yf:
+ if xs == xf or zs == zf:
+ raise CmdInputError(self.__str__() + ' the plate is not specified correctly')
+
+ elif zs == zf:
+ if xs == xf or ys == yf:
+ raise CmdInputError(self.__str__() + ' the plate is not specified correctly')
+
+ else:
+ raise CmdInputError(self.__str__() + ' the plate is not specified correctly')
+
+ # Look up requested materials in existing list of material instances
+ materials = [y for x in materialsrequested for y in grid.materials if y.ID == x]
+
+ if len(materials) != len(materialsrequested):
+ notfound = [x for x in materialsrequested if x not in materials]
+ raise CmdInputError(self.__str__() + ' material(s) {} do not exist'.format(notfound))
+
+ # yz-plane plate
+ if xs == xf:
+ # Isotropic case
+ if len(materials) == 1:
+ numIDx = numIDy = numIDz = materials[0].numID
+
+ # Uniaxial anisotropic case
+ elif len(materials) == 2:
+ numIDy = materials[0].numID
+ numIDz = materials[1].numID
+
+ 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)
+
+ # xz-plane plate
+ elif ys == yf:
+ # Isotropic case
+ if len(materials) == 1:
+ numIDx = numIDy = numIDz = materials[0].numID
+
+ # Uniaxial anisotropic case
+ elif len(materials) == 2:
+ numIDx = materials[0].numID
+ numIDz = materials[1].numID
+
+ 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)
+
+ # xy-plane plate
+ elif zs == zf:
+ # Isotropic case
+ if len(materials) == 1:
+ numIDx = numIDy = numIDz = materials[0].numID
+
+ # Uniaxial anisotropic case
+ elif len(materials) == 2:
+ numIDx = materials[0].numID
+ numIDy = materials[1].numID
+
+ 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)
+
+ if config.is_messages():
+ tqdm.write('Plate from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m of material(s) {} created.'.format(xs * grid.dx, ys * grid.dy, zs * grid.dz, xf * grid.dx, yf * grid.dy, zf * grid.dz, ', '.join(materialsrequested)))
diff --git a/gprMax/cmds_geometry/sphere.py b/gprMax/cmds_geometry/sphere.py
index e38ea409..bea45f99 100644
--- a/gprMax/cmds_geometry/sphere.py
+++ b/gprMax/cmds_geometry/sphere.py
@@ -1,91 +1,91 @@
-"""Class for sphere command."""
-from .cmds_geometry import UserObjectGeometry
-from ..exceptions import CmdInputError
-from ..materials import Material
-from ..cython.geometry_primitives import build_sphere
-
-from tqdm import tqdm
-import numpy as np
-
-
-class Sphere(UserObjectGeometry):
- """User class for edge command."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 8
- self.hash = '#sphere'
-
- def create(self, grid, uip):
- try:
- p1 = self.kwargs['p1']
- r = self.kwargs['r']
- except KeyError:
- raise CmdInputError(self.__str__() + ' Please specify a point and a radius.')
-
- # check averaging
- try:
- # go with user specified averaging
- averagesphere = self.kwargs['averaging']
- except KeyError:
- # if they havent specfied - go with the grid default
- averagesphere = grid.averagevolumeobjects
-
- # check materials have been specified
- # isotropic case
- try:
- materialsrequested = [self.kwargs['material_id']]
- except KeyError:
- # Anisotropic case
- try:
- materialsrequested = self.kwargs['material_ids']
- except KeyError:
- raise CmdInputError(self.__str__() + ' No materials have been specified')
-
- # Centre of sphere
- xc, yc, zc = uip.round_to_grid(p1)
-
- # Look up requested materials in existing list of material instances
- materials = [y for x in materialsrequested for y in grid.materials if y.ID == x]
-
- if len(materials) != len(materialsrequested):
- notfound = [x for x in materialsrequested if x not in materials]
- raise CmdInputError(self.__str__() + ' material(s) {} do not exist'.format(notfound))
-
- # Isotropic case
- if len(materials) == 1:
- averaging = materials[0].averagable and averagesphere
- numID = numIDx = numIDy = numIDz = materials[0].numID
-
- # Uniaxial anisotropic case
- elif len(materials) == 3:
- averaging = False
- numIDx = materials[0].numID
- numIDy = materials[1].numID
- numIDz = materials[2].numID
- 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
- else:
- numID = len(grid.materials)
- 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].mr, materials[1].mr, materials[2].mr), axis=0)
-
- # Append the new material object to the materials list
- grid.materials.append(m)
-
- build_sphere(xc, yc, zc, r, grid.dx, grid.dy, grid.dz, numID, numIDx, numIDy, numIDz, averaging, grid.solid, grid.rigidE, grid.rigidH, grid.ID)
-
- if config.is_messages():
- if averaging:
- dielectricsmoothing = 'on'
- else:
- dielectricsmoothing = 'off'
- tqdm.write('Sphere with centre {:g}m, {:g}m, {:g}m, radius {:g}m, of material(s) {} created, dielectric smoothing is {}.'.format(xc * grid.dx, yc * grid.dy, zc * grid.dz, r, ', '.join(materialsrequested), dielectricsmoothing))
+"""Class for sphere command."""
+from .cmds_geometry import UserObjectGeometry
+from ..exceptions import CmdInputError
+from ..materials import Material
+from ..cython.geometry_primitives import build_sphere
+
+from tqdm import tqdm
+import numpy as np
+
+
+class Sphere(UserObjectGeometry):
+ """User class for edge command."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 8
+ self.hash = '#sphere'
+
+ def create(self, grid, uip):
+ try:
+ p1 = self.kwargs['p1']
+ r = self.kwargs['r']
+ except KeyError:
+ raise CmdInputError(self.__str__() + ' Please specify a point and a radius.')
+
+ # check averaging
+ try:
+ # go with user specified averaging
+ averagesphere = self.kwargs['averaging']
+ except KeyError:
+ # if they havent specfied - go with the grid default
+ averagesphere = grid.averagevolumeobjects
+
+ # check materials have been specified
+ # isotropic case
+ try:
+ materialsrequested = [self.kwargs['material_id']]
+ except KeyError:
+ # Anisotropic case
+ try:
+ materialsrequested = self.kwargs['material_ids']
+ except KeyError:
+ raise CmdInputError(self.__str__() + ' No materials have been specified')
+
+ # Centre of sphere
+ xc, yc, zc = uip.round_to_grid(p1)
+
+ # Look up requested materials in existing list of material instances
+ materials = [y for x in materialsrequested for y in grid.materials if y.ID == x]
+
+ if len(materials) != len(materialsrequested):
+ notfound = [x for x in materialsrequested if x not in materials]
+ raise CmdInputError(self.__str__() + ' material(s) {} do not exist'.format(notfound))
+
+ # Isotropic case
+ if len(materials) == 1:
+ averaging = materials[0].averagable and averagesphere
+ numID = numIDx = numIDy = numIDz = materials[0].numID
+
+ # Uniaxial anisotropic case
+ elif len(materials) == 3:
+ averaging = False
+ numIDx = materials[0].numID
+ numIDy = materials[1].numID
+ numIDz = materials[2].numID
+ 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
+ else:
+ numID = len(grid.materials)
+ 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].mr, materials[1].mr, materials[2].mr), axis=0)
+
+ # Append the new material object to the materials list
+ grid.materials.append(m)
+
+ build_sphere(xc, yc, zc, r, grid.dx, grid.dy, grid.dz, numID, numIDx, numIDy, numIDz, averaging, grid.solid, grid.rigidE, grid.rigidH, grid.ID)
+
+ if config.is_messages():
+ if averaging:
+ dielectricsmoothing = 'on'
+ else:
+ dielectricsmoothing = 'off'
+ tqdm.write('Sphere with centre {:g}m, {:g}m, {:g}m, radius {:g}m, of material(s) {} created, dielectric smoothing is {}.'.format(xc * grid.dx, yc * grid.dy, zc * grid.dz, r, ', '.join(materialsrequested), dielectricsmoothing))
diff --git a/gprMax/cmds_geometry/triangle.py b/gprMax/cmds_geometry/triangle.py
index be2eadd9..89671aa1 100644
--- a/gprMax/cmds_geometry/triangle.py
+++ b/gprMax/cmds_geometry/triangle.py
@@ -1,132 +1,132 @@
-"""Class for triangle command."""
-from .cmds_geometry import UserObjectGeometry
-from ..exceptions import CmdInputError
-from ..materials import Material
-from ..cython.geometry_primitives import build_triangle
-
-from tqdm import tqdm
-import numpy as np
-
-
-class Triangle(UserObjectGeometry):
- """User class for edge command."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 4
- self.hash = '#triangle'
-
- def create(self, grid, uip):
-
- try:
- up1 = self.kwargs['p1']
- up2 = self.kwargs['p2']
- up3 = self.kwargs['p3']
- thickness = self.kwargs['thickness']
-
- except KeyError:
- raise CmdInputError(self.params_str() + ' Specify 3 points and a thickness')
-
- # check averaging
- try:
- # go with user specified averaging
- averagetriangularprism = self.kwargs['averaging']
- except KeyError:
- # if they havent specfied - go with the grid default
- averagetriangularprism = grid.averagevolumeobjects
-
- # check materials have been specified
- # isotropic case
- try:
- materialsrequested = [self.kwargs['material_id']]
- except KeyError:
- # Anisotropic case
- try:
- materialsrequested = self.kwargs['material_ids']
- except KeyError:
- raise CmdInputError(self.__str__() + ' No materials have been specified')
-
- # Check whether points are valid against grid
- uip.check_tri_points(up1, up2, up3, object)
- # Convert points to metres
- x1, y1, z1 = uip.round_to_grid(up1)
- x2, y2, z2 = uip.round_to_grid(up2)
- x3, y3, z3 = uip.round_to_grid(up3)
-
- if thickness < 0:
- raise CmdInputError(self.__str__() + ' requires a positive value for thickness')
-
- # Check for valid orientations
- # yz-plane triangle
- if x1 == x2 and x2 == x3:
- normal = 'x'
- # xz-plane triangle
- elif y1 == y2 and y2 == y3:
- normal = 'y'
- # xy-plane triangle
- elif z1 == z2 and z2 == z3:
- normal = 'z'
- else:
- raise CmdInputError(self.__str__() + ' the triangle is not specified correctly')
-
- # Look up requested materials in existing list of material instances
- materials = [y for x in materialsrequested for y in grid.materials if y.ID == x]
-
- if len(materials) != len(materialsrequested):
- notfound = [x for x in materialsrequested if x not in materials]
- raise CmdInputError(self.__str__() + ' material(s) {} do not exist'.format(notfound))
-
- if thickness > 0:
- # Isotropic case
- if len(materials) == 1:
- averaging = materials[0].averagable and averagetriangularprism
- numID = numIDx = numIDy = numIDz = materials[0].numID
-
- # Uniaxial anisotropic case
- elif len(materials) == 3:
- averaging = False
- numIDx = materials[0].numID
- numIDy = materials[1].numID
- numIDz = materials[2].numID
- 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
- else:
- numID = len(grid.materials)
- 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].mr, materials[1].mr, materials[2].mr), axis=0)
-
- # Append the new material object to the materials list
- grid.materials.append(m)
- else:
- averaging = False
- # Isotropic case
- if len(materials) == 1:
- numID = numIDx = numIDy = numIDz = materials[0].numID
-
- # Uniaxial anisotropic case
- elif len(materials) == 3:
- # numID requires a value but it will not be used
- numID = None
- numIDx = materials[0].numID
- numIDy = materials[1].numID
- numIDz = materials[2].numID
-
- build_triangle(x1, y1, z1, x2, y2, z2, x3, y3, z3, normal, thickness, grid.dx, grid.dy, grid.dz, numID, numIDx, numIDy, numIDz, averaging, grid.solid, grid.rigidE, grid.rigidH, grid.ID)
-
- if config.is_messages():
- if thickness > 0:
- if averaging:
- dielectricsmoothing = 'on'
- else:
- dielectricsmoothing = 'off'
- tqdm.write('Triangle with coordinates {:g}m {:g}m {:g}m, {:g}m {:g}m {:g}m, {:g}m {:g}m {:g}m and thickness {:g}m of material(s) {} created, dielectric smoothing is {}.'.format(x1, y1, z1, x2, y2, z2, x3, y3, z3, thickness, ', '.join(materialsrequested), dielectricsmoothing))
- else:
- tqdm.write('Triangle with coordinates {:g}m {:g}m {:g}m, {:g}m {:g}m {:g}m, {:g}m {:g}m {:g}m of material(s) {} created.'.format(x1, y1, z1, x2, y2, z2, x3, y3, z3, ', '.join(materialsrequested)))
+"""Class for triangle command."""
+from .cmds_geometry import UserObjectGeometry
+from ..exceptions import CmdInputError
+from ..materials import Material
+from ..cython.geometry_primitives import build_triangle
+
+from tqdm import tqdm
+import numpy as np
+
+
+class Triangle(UserObjectGeometry):
+ """User class for edge command."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 4
+ self.hash = '#triangle'
+
+ def create(self, grid, uip):
+
+ try:
+ up1 = self.kwargs['p1']
+ up2 = self.kwargs['p2']
+ up3 = self.kwargs['p3']
+ thickness = self.kwargs['thickness']
+
+ except KeyError:
+ raise CmdInputError(self.params_str() + ' Specify 3 points and a thickness')
+
+ # check averaging
+ try:
+ # go with user specified averaging
+ averagetriangularprism = self.kwargs['averaging']
+ except KeyError:
+ # if they havent specfied - go with the grid default
+ averagetriangularprism = grid.averagevolumeobjects
+
+ # check materials have been specified
+ # isotropic case
+ try:
+ materialsrequested = [self.kwargs['material_id']]
+ except KeyError:
+ # Anisotropic case
+ try:
+ materialsrequested = self.kwargs['material_ids']
+ except KeyError:
+ raise CmdInputError(self.__str__() + ' No materials have been specified')
+
+ # Check whether points are valid against grid
+ uip.check_tri_points(up1, up2, up3, object)
+ # Convert points to metres
+ x1, y1, z1 = uip.round_to_grid(up1)
+ x2, y2, z2 = uip.round_to_grid(up2)
+ x3, y3, z3 = uip.round_to_grid(up3)
+
+ if thickness < 0:
+ raise CmdInputError(self.__str__() + ' requires a positive value for thickness')
+
+ # Check for valid orientations
+ # yz-plane triangle
+ if x1 == x2 and x2 == x3:
+ normal = 'x'
+ # xz-plane triangle
+ elif y1 == y2 and y2 == y3:
+ normal = 'y'
+ # xy-plane triangle
+ elif z1 == z2 and z2 == z3:
+ normal = 'z'
+ else:
+ raise CmdInputError(self.__str__() + ' the triangle is not specified correctly')
+
+ # Look up requested materials in existing list of material instances
+ materials = [y for x in materialsrequested for y in grid.materials if y.ID == x]
+
+ if len(materials) != len(materialsrequested):
+ notfound = [x for x in materialsrequested if x not in materials]
+ raise CmdInputError(self.__str__() + ' material(s) {} do not exist'.format(notfound))
+
+ if thickness > 0:
+ # Isotropic case
+ if len(materials) == 1:
+ averaging = materials[0].averagable and averagetriangularprism
+ numID = numIDx = numIDy = numIDz = materials[0].numID
+
+ # Uniaxial anisotropic case
+ elif len(materials) == 3:
+ averaging = False
+ numIDx = materials[0].numID
+ numIDy = materials[1].numID
+ numIDz = materials[2].numID
+ 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
+ else:
+ numID = len(grid.materials)
+ 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].mr, materials[1].mr, materials[2].mr), axis=0)
+
+ # Append the new material object to the materials list
+ grid.materials.append(m)
+ else:
+ averaging = False
+ # Isotropic case
+ if len(materials) == 1:
+ numID = numIDx = numIDy = numIDz = materials[0].numID
+
+ # Uniaxial anisotropic case
+ elif len(materials) == 3:
+ # numID requires a value but it will not be used
+ numID = None
+ numIDx = materials[0].numID
+ numIDy = materials[1].numID
+ numIDz = materials[2].numID
+
+ build_triangle(x1, y1, z1, x2, y2, z2, x3, y3, z3, normal, thickness, grid.dx, grid.dy, grid.dz, numID, numIDx, numIDy, numIDz, averaging, grid.solid, grid.rigidE, grid.rigidH, grid.ID)
+
+ if config.is_messages():
+ if thickness > 0:
+ if averaging:
+ dielectricsmoothing = 'on'
+ else:
+ dielectricsmoothing = 'off'
+ tqdm.write('Triangle with coordinates {:g}m {:g}m {:g}m, {:g}m {:g}m {:g}m, {:g}m {:g}m {:g}m and thickness {:g}m of material(s) {} created, dielectric smoothing is {}.'.format(x1, y1, z1, x2, y2, z2, x3, y3, z3, thickness, ', '.join(materialsrequested), dielectricsmoothing))
+ else:
+ tqdm.write('Triangle with coordinates {:g}m {:g}m {:g}m, {:g}m {:g}m {:g}m, {:g}m {:g}m {:g}m of material(s) {} created.'.format(x1, y1, z1, x2, y2, z2, x3, y3, z3, ', '.join(materialsrequested)))
diff --git a/gprMax/cmds_multiple.py b/gprMax/cmds_multiple.py
index 8316c143..15572223 100644
--- a/gprMax/cmds_multiple.py
+++ b/gprMax/cmds_multiple.py
@@ -1,1042 +1,1042 @@
-# Copyright (C) 2015-2019: The University of Edinburgh
-# Authors: Craig Warren and Antonis Giannopoulos
-#
-# This file is part of gprMax.
-#
-# gprMax is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# gprMax is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with gprMax. If not, see .
-
-"""Object which can be created multiple times."""
-import gprMax.config as config
-from .config import z0
-from .config import dtypes
-from .utilities import round_value
-from .cmds_geometry.cmds_geometry import UserObjectGeometry
-from .waveforms import Waveform as WaveformUser
-from .sources import VoltageSource as VoltageSourceUser
-from .sources import HertzianDipole as HertzianDipoleUser
-from .sources import MagneticDipole as MagneticDipoleUser
-from .sources import TransmissionLine as TransmissionLineUser
-from .snapshots import Snapshot as SnapshotUser
-from .receivers import Rx as RxUser
-from .materials import Material as MaterialUser
-from .materials import PeplinskiSoil as PeplinskiSoilUser
-from .geometry_outputs import GeometryObjects as GeometryObjectsUser
-from .pml import CFSParameter
-from .pml import CFS
-from .subgrids.base import SubGridBase
-
-from .exceptions import CmdInputError
-
-
-import numpy as np
-from tqdm import tqdm
-
-floattype = dtypes['float_or_double']
-
-
-class UserObjectMulti:
- """Specific multiobject object."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- self.kwargs = kwargs
- self.order = None
- self.hash = '#example'
-
- def __str__(self):
- """Readble user string as per hash commands."""
- s = ''
- for k, v in self.kwargs.items():
- if isinstance(v, tuple) or isinstance(v, list):
- v = ' '.join([str(el) for el in v])
- s += str(v) + ' '
-
- return '{}: {}'.format(self.hash, s[:-1])
-
- def create(self, grid, uip):
- """Create the object and add it to the grid."""
- pass
-
- def params_str(self):
- """Readble string of parameters given to object."""
- return self.hash + ': ' + str(self.kwargs)
-
-
-class Waveform(UserObjectMulti):
- """Create Waveform different excitation types."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 0
- self.hash = '#waveform'
-
- def create(self, grid, uip):
- """Create the waveform and add it to the grid."""
- try:
- wavetype = self.kwargs['wave_type'].lower()
- amp = self.kwargs['amp']
- freq = self.kwargs['freq']
- ID = self.kwargs['id']
-
- except KeyError:
- raise CmdInputError(self.params_str() + ' requires exactly four parameters')
-
- if wavetype not in WaveformUser.types:
- raise CmdInputError(self.__str__() + ' must have one of the following types {}'.format(','.join(WaveformUser.types)))
- if freq <= 0:
- raise CmdInputError(self.__str__() + ' requires an excitation frequency value of greater than zero')
- if any(x.ID == ID for x in grid.waveforms):
- raise CmdInputError(self.__str__() + ' with ID {} already exists'.format(ID))
-
- w = WaveformUser()
- w.ID = ID
- w.type = wavetype
- w.amp = amp
- w.freq = freq
-
- if config.is_messages():
- print('Waveform {} of type {} with maximum amplitude scaling {:g}, frequency {:g}Hz created.'.format(w.ID, w.type, w.amp, w.freq))
-
- grid.waveforms.append(w)
-
-
-class VoltageSource(UserObjectMulti):
- """User Object for a voltage source."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 1
- self.hash = '#voltage_source'
-
- def create(self, grid, uip):
- """Create voltage source and add it to the grid."""
- try:
- polarisation = self.kwargs['polarisation'].lower()
- p1 = self.kwargs['p1']
- resistance = self.kwargs['resistance']
- waveform_id = self.kwargs['waveform_id']
-
- except KeyError:
- raise CmdInputError(self.__str__() + " requires at least six parameters")
-
- # Check polarity & position parameters
- if polarisation not in ('x', 'y', 'z'):
- raise CmdInputError("'{}' polarisation must be x, y, or z".format(self.__str__()))
- if '2D TMx' in grid.mode and (polarisation == 'y' or polarisation == 'z'):
- raise CmdInputError("'{}' polarisation must be x in 2D TMx mode".format(self.__str__()))
- elif '2D TMy' in grid.mode and (polarisation == 'x' or polarisation == 'z'):
- raise CmdInputError("'{}' polarisation must be y in 2D TMy mode".format(self.__str__()))
- elif '2D TMz' in grid.mode and (polarisation == 'x' or polarisation == 'y'):
- raise CmdInputError("'{}' polarisation must be z in 2D TMz mode".format(self.__str__()))
-
- xcoord, ycoord, zcoord = uip.check_src_rx_point(p1, self.__str__())
-
- if resistance < 0:
- raise CmdInputError("'{}' requires a source resistance of zero or greater".format(self.__str__()))
-
- # Check if there is a waveformID in the waveforms list
- if not any(x.ID == waveform_id for x in grid.waveforms):
- raise CmdInputError("'{}' there is no waveform with the identifier {}'.format(tmp[5]".format(self.__str__()))
-
- v = VoltageSourceUser()
- v.polarisation = polarisation
- v.xcoord = xcoord
- v.ycoord = ycoord
- v.zcoord = zcoord
- v.ID = v.__class__.__name__ + '(' + str(v.xcoord) + ',' + str(v.ycoord) + ',' + str(v.zcoord) + ')'
- v.resistance = resistance
- v.waveformID = waveform_id
-
- try:
- start = self.kwargs['start']
- stop = self.kwargs['stop']
- # Check source start & source remove time parameters
- if start < 0:
- raise CmdInputError("'{}' delay of the initiation of the source should not be less than zero".format(self.__str__()))
- if stop < 0:
- raise CmdInputError("'{}' time to remove the source should not be less than zero".format(self.__str__()))
- if stop - start <= 0:
- raise CmdInputError("'{}' duration of the source should not be zero or less".format(self.__str__()))
- v.start = start
- if stop > grid.timewindow:
- v.stop = grid.timewindow
- else:
- v.stop = stop
- startstop = ' start time {:g} secs, finish time {:g} secs '.format(v.start, v.stop)
- except KeyError:
- v.start = 0
- v.stop = grid.timewindow
- startstop = ' '
-
- v.calculate_waveform_values(grid)
-
- if config.is_messages():
- print('Voltage source with polarity {} at {:g}m, {:g}m, {:g}m, resistance {:.1f} Ohms,'.format(v.polarisation, v.xcoord * grid.dx, v.ycoord * grid.dy, v.zcoord * grid.dz, v.resistance) + startstop + 'using waveform {} created.'.format(v.waveformID))
-
- grid.voltagesources.append(v)
-
-
-class HertzianDipole(UserObjectMulti):
- """User Object for HertzianDipole."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 2
- self.hash = '#hertzian_dipole'
-
- def create(self, grid, uip):
- """Create HertzianDipole and add it to the grid."""
- try:
- polarisation = self.kwargs['polarisation'].lower()
- p1 = self.kwargs['p1']
- waveform_id = self.kwargs['waveform_id']
-
- except KeyError:
- raise CmdInputError("'{}' requires at least 3 parameters".format(self.params_str()))
-
- # Check polarity & position parameters
- if polarisation not in ('x', 'y', 'z'):
- raise CmdInputError("'{}' polarisation must be x, y, or z".format(self.__str__()))
- if '2D TMx' in grid.mode and (polarisation == 'y' or polarisation == 'z'):
- raise CmdInputError("'{}' polarisation must be x in 2D TMx mode".format(self.__str__()))
- elif '2D TMy' in grid.mode and (polarisation == 'x' or polarisation == 'z'):
- raise CmdInputError("'{}' polarisation must be y in 2D TMy mode".format(self.__str__()))
- elif '2D TMz' in grid.mode and (polarisation == 'x' or polarisation == 'y'):
- raise CmdInputError("'{}' polarisation must be z in 2D TMz mode".format(self.__str__()))
-
- xcoord, ycoord, zcoord = uip.check_src_rx_point(p1, self.__str__())
-
- # Check if there is a waveformID in the waveforms list
- if not any(x.ID == waveform_id for x in grid.waveforms):
- raise CmdInputError("'{}' there is no waveform with the identifier {}'.format(tmp[4]".format(self.__str__()))
-
- h = HertzianDipoleUser()
- h.polarisation = polarisation
-
- # Set length of dipole to grid size in polarisation direction
- if h.polarisation == 'x':
- h.dl = grid.dx
- elif h.polarisation == 'y':
- h.dl = grid.dy
- elif h.polarisation == 'z':
- h.dl = grid.dz
-
- h.xcoord = xcoord
- h.ycoord = ycoord
- h.zcoord = zcoord
- h.xcoordorigin = xcoord
- h.ycoordorigin = ycoord
- h.zcoordorigin = zcoord
- h.ID = h.__class__.__name__ + '(' + str(h.xcoord) + ',' + str(h.ycoord) + ',' + str(h.zcoord) + ')'
- h.waveformID = waveform_id
-
- try:
- # Check source start & source remove time parameters
- start = self.kwargs['start']
- stop = self.kwargs['stop']
- if start < 0:
- raise CmdInputError("'{}' delay of the initiation of the source should not be less than zero".format(self.__str__()))
- if stop < 0:
- raise CmdInputError("'{}' time to remove the source should not be less than zero".format(self.__str__()))
- if stop - start <= 0:
- raise CmdInputError("'{}' duration of the source should not be zero or less".format(self.__str__()))
- h.start = start
- if stop > grid.timewindow:
- h.stop = grid.timewindow
- else:
- h.stop = stop
- startstop = ' start time {:g} secs, finish time {:g} secs '.format(h.start, h.stop)
- except KeyError:
- h.start = 0
- h.stop = grid.timewindow
- print(grid.timewindow)
- startstop = ' '
-
- h.calculate_waveform_values(grid)
-
- if config.is_messages():
- if grid.mode == '2D':
- print('Hertzian dipole is a line source in 2D with polarity {} at {:g}m, {:g}m, {:g}m,'.format(h.polarisation, h.xcoord * grid.dx, h.ycoord * grid.dy, h.zcoord * grid.dz) + startstop + 'using waveform {} created.'.format(h.waveformID))
- else:
- print('Hertzian dipole with polarity {} at {:g}m, {:g}m, {:g}m,'.format(h.polarisation, h.xcoord * grid.dx, h.ycoord * grid.dy, h.zcoord * grid.dz) + startstop + 'using waveform {} created.'.format(h.waveformID))
-
- grid.hertziandipoles.append(h)
-
-
-class MagneticDipole(UserObjectMulti):
- """Magnetic Dipole User Object."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 3
- self.hash = '#magnetic_dipole'
-
- def create(self, grid, uip):
- """Create Magnetic Dipole and add it the grid."""
- try:
- polarisation = self.kwargs['polarisation'].lower()
- p1 = self.kwargs['p1']
- waveform_id = self.kwargs['waveform_id']
- except KeyError:
- raise CmdInputError("'{}' requires at least five parameters".format(self.__str__()))
-
- # Check polarity & position parameters
- if polarisation not in ('x', 'y', 'z'):
- raise CmdInputError("'{}' polarisation must be x, y, or z".format(self.__str__()))
- if '2D TMx' in grid.mode and (polarisation == 'y' or polarisation == 'z'):
- raise CmdInputError("'{}' polarisation must be x in 2D TMx mode".format(self.__str__()))
- elif '2D TMy' in grid.mode and (polarisation == 'x' or polarisation == 'z'):
- raise CmdInputError("'{}' polarisation must be y in 2D TMy mode".format(self.__str__()))
- elif '2D TMz' in grid.mode and (polarisation == 'x' or polarisation == 'y'):
- raise CmdInputError("'{}' polarisation must be z in 2D TMz mode".format(self.__str__()))
-
- xcoord, ycoord, zcoord = uip.check_src_rx_point(p1, self.__str__())
-
- # Check if there is a waveformID in the waveforms list
- if not any(x.ID == waveform_id for x in grid.waveforms):
- raise CmdInputError("'{}' there is no waveform with the identifier {}".format(self.__str__(), waveform_id))
-
- m = MagneticDipoleUser()
- m.polarisation = polarisation
- m.xcoord = xcoord
- m.ycoord = ycoord
- m.zcoord = zcoord
- m.xcoordorigin = xcoord
- m.ycoordorigin = ycoord
- m.zcoordorigin = zcoord
- m.ID = m.__class__.__name__ + '(' + str(m.xcoord) + ',' + str(m.ycoord) + ',' + str(m.zcoord) + ')'
- m.waveformID = waveform_id
-
- try:
- # Check source start & source remove time parameters
- start = self.kwargs['start']
- stop = self.kwargs['stop']
- if start < 0:
- raise CmdInputError("'{}' delay of the initiation of the source should not be less than zero".format(self.__str__()))
- if stop < 0:
- raise CmdInputError("'{}' time to remove the source should not be less than zero".format(self.__str__()))
- if stop - start <= 0:
- raise CmdInputError("'{}' duration of the source should not be zero or less".format(self.__str__()))
- m.start = start
- if stop > grid.timewindow:
- m.stop = grid.timewindow
- else:
- m.stop = stop
- startstop = ' start time {:g} secs, finish time {:g} secs '.format(m.start, m.stop)
- except KeyError:
- m.start = 0
- m.stop = grid.timewindow
- startstop = ' '
-
- m.calculate_waveform_values(grid)
-
- if config.is_messages():
- print('Magnetic dipole with polarity {} at {:g}m, {:g}m, {:g}m,'.format(m.polarisation, m.xcoord * grid.dx, m.ycoord * grid.dy, m.zcoord * grid.dz) + startstop + 'using waveform {} created.'.format(m.waveformID))
-
- grid.magneticdipoles.append(m)
-
-
-class TransmissionLine(UserObjectMulti):
- """Magnetic Dipole User Object."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 4
- self.hash = '#transmission_line'
-
- def create(self, grid, uip):
-
- try:
- polarisation = self.kwargs['polarisation'].lower()
- p1 = self.kwargs['p1']
- waveform_id = self.kwargs['waveform_id']
- resistance = self.kwargs['resistance']
-
- except KeyError:
- raise CmdInputError("'{}' requires at least six parameters".format(self.params_str()))
-
- # Warn about using a transmission line on GPU
- if grid.gpu is not None:
- raise CmdInputError("'{}' A #transmission_line cannot currently be used with GPU solving. Consider using a #voltage_source instead.".format(self.__str__()))
-
- # Check polarity & position parameters
- if polarisation not in ('x', 'y', 'z'):
- raise CmdInputError("'{}' polarisation must be x, y, or z".format(self.__str__()))
- if '2D TMx' in grid.mode and (polarisation == 'y' or polarisation == 'z'):
- raise CmdInputError("'{}' polarisation must be x in 2D TMx mode".format(self.__str__()))
- elif '2D TMy' in grid.mode and (polarisation == 'x' or polarisation == 'z'):
- raise CmdInputError("'{}' polarisation must be y in 2D TMy mode".format(self.__str__()))
- elif '2D TMz' in grid.mode and (polarisation == 'x' or polarisation == 'y'):
- raise CmdInputError("'{}' polarisation must be z in 2D TMz mode".format(self.__str__()))
-
- xcoord, ycoord, zcoord = uip.check_src_rx_point(p1, self.__str__())
-
- if resistance <= 0 or resistance >= z0:
- raise CmdInputError("'{}' requires a resistance greater than zero and less than the impedance of free space, i.e. 376.73 Ohms".format(self.__str__()))
-
- # Check if there is a waveformID in the waveforms list
- if not any(x.ID == waveform_id for x in grid.waveforms):
- raise CmdInputError("'{}' there is no waveform with the identifier {}'.format(tmp[5]".format(self.__str__()))
-
- t = TransmissionLineUser(grid)
- t.polarisation = polarisation
- t.xcoord = xcoord
- t.ycoord = ycoord
- t.zcoord = zcoord
- t.ID = t.__class__.__name__ + '(' + str(t.xcoord) + ',' + str(t.ycoord) + ',' + str(t.zcoord) + ')'
- t.resistance = resistance
- t.waveformID = waveform_id
-
- try:
- # Check source start & source remove time parameters
- start = self.kwargs['start']
- stop = self.kwargs['stop']
- if start < 0:
- raise CmdInputError("'{}' delay of the initiation of the source should not be less than zero".format(self.__str__()))
- if stop < 0:
- raise CmdInputError("'{}' time to remove the source should not be less than zero".format(self.__str__()))
- if stop - start <= 0:
- raise CmdInputError("'{}' duration of the source should not be zero or less".format(self.__str__()))
- t.start = start
- if stop > grid.timewindow:
- t.stop = grid.timewindow
- else:
- t.stop = stop
- startstop = ' start time {:g} secs, finish time {:g} secs '.format(t.start, t.stop)
- except KeyError:
- t.start = 0
- t.stop = grid.timewindow
- startstop = ' '
-
- t.calculate_waveform_values(grid)
- t.calculate_incident_V_I(grid)
-
- if config.is_messages():
- print('Transmission line with polarity {} at {:g}m, {:g}m, {:g}m, resistance {:.1f} Ohms,'.format(t.polarisation, t.xcoord * grid.dx, t.ycoord * grid.dy, t.zcoord * grid.dz, t.resistance) + startstop + 'using waveform {} created.'.format(t.waveformID))
-
- grid.transmissionlines.append(t)
-
-
-class Rx(UserObjectMulti):
- """Magnetic Dipole User Object."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 5
- self.hash = '#rx'
- self.constructor = RxUser
-
- def create(self, grid, uip):
- try:
- p1 = self.kwargs['p1']
- except KeyError:
- raise CmdInputError("'{}' has an incorrect number of parameters".format(self.__str__()))
-
- p = uip.check_src_rx_point(p1, self.__str__())
-
- r = self.constructor()
- r.xcoord, r.ycoord, r.zcoord = p
- r.xcoordorigin, r.ycoordorigin, r.zcoordorigin = p
-
- try:
- r.ID = self.kwargs['id']
- outputs = self.kwargs['outputs']
- # Get allowable outputs
- if grid.gpu is not None:
- allowableoutputs = RxUser.gpu_allowableoutputs
- else:
- allowableoutputs = RxUser.allowableoutputs
- # Check and add field output names
- for field in outputs:
- if field in allowableoutputs:
- r.outputs[field] = np.zeros(grid.iterations, dtype=floattype)
- else:
- raise CmdInputError("{} contains an output type that is not allowable. Allowable outputs in current context are {}".format(self.__str__(), allowableoutputs))
-
- # If no ID or outputs are specified, use default
- except KeyError:
- r.ID = 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=floattype)
- if config.is_messages():
- print('Receiver at {:g}m, {:g}m, {:g}m with output component(s) {} created.'.format(r.xcoord * grid.dx, r.ycoord * grid.dy, r.zcoord * grid.dz, ', '.join(r.outputs)))
-
- grid.rxs.append(r)
-
- return r
-
-class RxArray(UserObjectMulti):
- """Receiver Array User Object."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 6
- self.hash = '#rx_array'
-
- def create(self, grid, uip):
-
- try:
- p1 = self.kwargs['p1']
- p2 = self.kwargs['p2']
- dl = self.kwargs['dl']
-
- except KeyError:
- raise CmdInputError("'{}' requires exactly 9 parameters".format(self.__str__()))
-
- xs, ys, zs = uip.check_src_rx_point(p1, self.__str__(), 'lower')
- xf, yf, zf = uip.check_src_rx_point(p2, self.__str__(), 'upper')
- dx, dy, dz = uip.discretise_point(dl)
-
- if xs > xf or ys > yf or zs > zf:
- raise CmdInputError("'{}' the lower coordinates should be less than the upper coordinates".format(self.__str__()))
- if dx < 0 or dy < 0 or dz < 0:
- raise CmdInputError("'{}' the step size should not be less than zero".format(self.__str__()))
- if dx < 1:
- if dx == 0:
- dx = 1
- else:
- raise CmdInputError("'{}' the step size should not be less than the spatial discretisation".format(self.__str__()))
- if dy < 1:
- if dy == 0:
- dy = 1
- else:
- raise CmdInputError("'{}' the step size should not be less than the spatial discretisation".format(self.__str__()))
- if dz < 1:
- if dz == 0:
- dz = 1
- else:
- raise CmdInputError("'{}' the step size should not be less than the spatial discretisation".format(self.__str__()))
-
- if config.is_messages():
- print('Receiver array {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m with steps {:g}m, {:g}m, {:g}m'.format(xs * grid.dx, ys * grid.dy, zs * grid.dz, xf * grid.dx, yf * grid.dy, zf * grid.dz, dx * grid.dx, dy * grid.dy, dz * grid.dz))
-
- for x in range(xs, xf + 1, dx):
- for y in range(ys, yf + 1, dy):
- for z in range(zs, zf + 1, dz):
- r = RxUser()
- r.xcoord = x
- r.ycoord = y
- r.zcoord = z
- r.xcoordorigin = x
- r.ycoordorigin = y
- r.zcoordorigin = z
- r.ID = r.__class__.__name__ + '(' + str(x) + ',' + str(y) + ',' + str(z) + ')'
- for key in RxUser.defaultoutputs:
- r.outputs[key] = np.zeros(grid.iterations, dtype=floattype)
- if config.is_messages():
- print(' Receiver at {:g}m, {:g}m, {:g}m with output component(s) {} created.'.format(r.xcoord * grid.dx, r.ycoord * grid.dy, r.zcoord * grid.dz, ', '.join(r.outputs)))
- grid.rxs.append(r)
-
-
-class Snapshot(UserObjectMulti):
- """Snapshot User Object."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 19
- self.hash = '#snapshot'
-
- def create(self, grid, uip):
-
- if isinstance(grid, SubGridBase):
- raise CmdInputError("'{}' Do not add Snapshots to Subgrids.".format(self.__str__()))
- try:
- p1 = self.kwargs['p1']
- p2 = self.kwargs['p2']
- dl = self.kwargs['dl']
- filename = self.kwargs['filename']
- except KeyError:
- raise CmdInputError("'{}' requires exactly 11 parameters".format(self.__str__()))
-
- p1, p2 = uip.check_box_points(p1, p2, self.__str__())
- xs, ys, zs = p1
- xf, yf, zf = p2
- dx, dy, dz = uip.discretise_point(dl)
-
- # If number of iterations given
- try:
- iterations = self.kwargs['iterations']
- # If real floating point value given
- except KeyError:
- try:
- time = self.kwargs['time']
- except KeyError:
- raise CmdInputError("'{}' requires exactly 5 parameters".format(self.__str__()))
- if time > 0:
- iterations = round_value((time / grid.dt)) + 1
- else:
- raise CmdInputError("'{}' time value must be greater than zero".format(self.__str__()))
-
- if dx < 0 or dy < 0 or dz < 0:
- raise CmdInputError("'{}' the step size should not be less than zero".format(self.__str__()))
- if dx < 1 or dy < 1 or dz < 1:
- raise CmdInputError("'{}' the step size should not be less than the spatial discretisation".format(self.__str__()))
- if iterations <= 0 or iterations > grid.iterations:
- raise CmdInputError("'{}' time value is not valid".format(self.__str__()))
-
- # Replace with old style snapshots if there are subgrids
- if grid.subgrids:
- from .snapshot_subgrid import Snapshot as SnapshotSub
- s = SnapshotSub(xs, ys, zs, xf, yf, zf, dx, dy, dz, iterations, filename)
- else:
- s = SnapshotUser(xs, ys, zs, xf, yf, zf, dx, dy, dz, iterations, filename)
-
- if config.is_messages():
- print('Snapshot from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m, discretisation {:g}m, {:g}m, {:g}m, at {:g} secs with filename {} created.'.format(xs * grid.dx, ys * grid.dy, zs * grid.dz, xf * grid.dx, yf * grid.dy, zf * grid.dz, dx * grid.dx, dy * grid.dy, dz * grid.dz, s.time * grid.dt, s.basefilename))
-
- grid.snapshots.append(s)
-
-class Material(UserObjectMulti):
- """Material User Object."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 8
- self.hash = '#material'
-
- def create(self, grid, uip):
- try:
- er = self.kwargs['er']
- se = self.kwargs['se']
- mr = self.kwargs['mr']
- sm = self.kwargs['sm']
- material_id = self.kwargs['id']
- except KeyError:
- raise CmdInputError('{} requires exactly five parameters'.format(self.params_str()))
-
- if er < 1:
- raise CmdInputError('{} requires a positive value of one or greater for static (DC) permittivity'.format(self.__str__()))
- if se != 'inf':
- se = float(se)
- if se < 0:
- raise CmdInputError('{} requires a positive value for conductivity'.format(self.__str__()))
- else:
- se = float('inf')
- if mr < 1:
- raise CmdInputError('{} requires a positive value of one or greater for permeability'.format(self.__str__()))
- if sm < 0:
- raise CmdInputError('{} requires a positive value for magnetic conductivity'.format(self.__str__()))
- if any(x.ID == material_id for x in grid.materials):
- raise CmdInputError('{} with ID {} already exists'.format(material_id).format(self.__str__()))
-
- # Create a new instance of the Material class material (start index after pec & free_space)
- m = MaterialUser(len(grid.materials), material_id)
- m.er = er
- m.se = se
- m.mr = mr
- m.sm = sm
-
- # Set material averaging to False if infinite conductivity, i.e. pec
- if m.se == float('inf'):
- m.averagable = False
-
- if config.is_messages():
- tqdm.write('Material {} with eps_r={:g}, sigma={:g} S/m; mu_r={:g}, sigma*={:g} Ohm/m created.'.format(m.ID, m.er, m.se, m.mr, m.sm))
-
- # Append the new material object to the materials list
- grid.materials.append(m)
-
-
-class AddDebyeDispersion(UserObjectMulti):
- """Material User Object."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 9
- self.hash = '#add_dispersion_debye'
-
- def create(self, grid, uip):
-
- try:
- poles = self.kwargs['n_poles']
- er_delta = self.kwargs['er_delta']
- tau = self.kwargs['tau']
- material_ids = self.kwargs['material_ids']
-
- except KeyError:
- raise CmdInputError(self.__str__() + ' requires at least four parameters')
-
- if poles < 0:
- raise CmdInputError(self.__str__() + ' requires a positive value for number of poles')
-
- # Look up requested materials in existing list of material instances
- materials = [y for x in material_ids for y in grid.materials if y.ID == x]
-
- if len(materials) != len(material_ids):
- notfound = [x for x in material_ids if x not in materials]
- raise CmdInputError(self.__str__() + ' material(s) {} do not exist'.format(notfound))
-
- for material in materials:
- material.type = 'debye'
- material.poles = poles
- material.averagable = False
- for i in range(0, poles):
- # N.B Not checking if relaxation times are greater than time-step
- if tau[i] > 0:
- material.deltaer.append(er_delta[i])
- material.tau.append(tau[i])
- else:
- raise CmdInputError(self.__str__() + ' requires positive values for the permittivity difference.')
- if material.poles > MaterialUser.maxpoles:
- MaterialUser.maxpoles = material.poles
-
- if config.is_messages():
- tqdm.write('Debye disperion added to {} with delta_eps_r={}, and tau={} secs created.'.format(material.ID, ', '.join('%4.2f' % deltaer for deltaer in material.deltaer), ', '.join('%4.3e' % tau for tau in material.tau)))
-
-
-class AddLorentzDispersion(UserObjectMulti):
- """Material User Object."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 10
- self.hash = '#add_dispersion_lorentz'
-
- def create(self, grid, uip):
-
- try:
- poles = self.kwargs['n_poles']
- er_delta = self.kwargs['er_delta']
- tau = self.kwargs['omega']
- alpha = self.kwargs['delta']
- material_ids = self.kwargs['material_ids']
- except KeyError:
- raise CmdInputError(self.__str__() + ' requires at least five parameters')
-
- if poles < 0:
- raise CmdInputError(self.__str__() + ' requires a positive value for number of poles')
-
- # Look up requested materials in existing list of material instances
- materials = [y for x in material_ids for y in grid.materials if y.ID == x]
-
- if len(materials) != len(material_ids):
- notfound = [x for x in material_ids if x not in materials]
- raise CmdInputError(self.__str__() + ' material(s) {} do not exist'.format(notfound))
-
- for material in materials:
- material.type = 'lorentz'
- material.poles = poles
- material.averagable = False
- for i in range(0, poles):
- if er_delta[i] > 0 and tau[i] > grid.dt and alpha[i] > grid.dt:
- material.deltaer.append(er_delta[i])
- material.tau.append(tau[i])
- material.alpha.append(alpha[i])
- else:
- raise CmdInputError(self.__str__() + ' requires positive values for the permittivity difference and frequencies, and associated times that are greater than the time step for the model.')
- if material.poles > MaterialUser.maxpoles:
- MaterialUser.maxpoles = material.poles
-
- if config.is_messages():
- tqdm.write('Lorentz disperion added to {} with delta_eps_r={}, omega={} secs, and gamma={} created.'.format(material.ID, ', '.join('%4.2f' % deltaer for deltaer in material.deltaer), ', '.join('%4.3e' % tau for tau in material.tau), ', '.join('%4.3e' % alpha for alpha in material.alpha)))
-
-
-class AddDrudeDispersion(UserObjectMulti):
- """Material User Object."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 11
- self.hash = '#add_dispersion_Drude'
-
- def create(self, grid, uip):
-
- try:
- poles = self.kwargs['n_poles']
- tau = self.kwargs['tau']
- alpha = self.kwargs['alpha']
- material_ids = self.kwargs['material_ids']
- except KeyError:
- raise CmdInputError(self.__str__() + ' requires at least four parameters')
- if poles < 0:
- raise CmdInputError(self.__str__() + ' requires a positive value for number of poles')
-
- # Look up requested materials in existing list of material instances
- materials = [y for x in material_ids for y in grid.materials if y.ID == x]
-
- if len(materials) != len(material_ids):
- notfound = [x for x in material_ids if x not in materials]
- raise CmdInputError(self.__str__() + ' material(s) {} do not exist'.format(notfound))
-
- for material in materials:
- material.type = 'drude'
- material.poles = poles
- material.averagable = False
- for i in range(0, poles):
- if tau[i] > 0 and alpha[i] > grid.dt:
- material.tau.append(tau[i])
- material.alpha.append(alpha[i])
- else:
- raise CmdInputError(self.__str__() + ' requires positive values for the frequencies, and associated times that are greater than the time step for the model.')
- if material.poles > MaterialUser.maxpoles:
- MaterialUser.maxpoles = material.poles
-
- if config.is_messages():
- tqdm.write('Drude disperion added to {} with omega={} secs, and gamma={} secs created.'.format(material.ID, ', '.join('%4.3e' % tau for tau in material.tau), ', '.join('%4.3e' % alpha for alpha in material.alpha)))
-
-
-class SoilPeplinski(UserObjectMulti):
- """Material User Object."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 12
- self.hash = '#soil_peplinski'
-
- def create(self, grid, uip):
-
- try:
- sand_fraction = self.kwargs['sand_fraction']
- clay_fraction = self.kwargs['clay_fraction']
- bulk_density = self.kwargs['bulk_density']
- sand_density = self.kwargs['sand_density']
- water_fraction_lower = self.kwargs['water_fraction_lower']
- water_fraction_upper = self.kwargs['water_fraction_upper']
- ID = self.kwargs['id']
-
- except KeyError:
- raise CmdInputError(self.__str__() + ' requires at exactly seven parameters')
-
- if sand_fraction < 0:
- raise CmdInputError(self.__str__() + ' requires a positive value for the sand fraction')
- if clay_fraction < 0:
- raise CmdInputError(self.__str__() + ' requires a positive value for the clay fraction')
- if bulk_density < 0:
- raise CmdInputError(self.__str__() + ' requires a positive value for the bulk density')
- if sand_density < 0:
- raise CmdInputError(self.__str__() + ' requires a positive value for the sand particle density')
- if water_fraction_lower < 0:
- raise CmdInputError(self.__str__() + ' requires a positive value for the lower limit of the water volumetric fraction')
- if water_fraction_upper < 0:
- raise CmdInputError(self.__str__() + ' requires a positive value for the upper limit of the water volumetric fraction')
- if any(x.ID == ID for x in grid.mixingmodels):
- raise CmdInputError(self.__str__() + ' with ID {} already exists'.format(ID))
-
- # 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))
-
- if config.is_messages():
- print('Mixing model (Peplinski) used to create {} with sand fraction {:g}, clay fraction {:g}, bulk density {:g}g/cm3, sand particle density {:g}g/cm3, and water volumetric fraction {:g} to {:g} created.'.format(s.ID, s.S, s.C, s.rb, s.rs, s.mu[0], s.mu[1]))
-
- # Append the new material object to the materials list
- grid.mixingmodels.append(s)
-
-
-class GeometryView(UserObjectMulti):
- """Material User Object."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 18
- self.hash = '#geometry_view'
- self.multi_grid = False
-
- def geometry_view_constructor(self, grid, output_type):
- # Check if user want geometry output for all grids
- try:
- self.kwargs['multi_grid']
- # there is no voxel output for multi grid output
- if isinstance(grid, SubGridBase):
- raise CmdInputError("'{}' Do not add multi_grid output to subgrid user object. Please add to Scene".format(self.__str__()))
- if output_type == 'n':
- raise CmdInputError("'{}' Voxel output type (n) is not supported for multigrid output :(".format(self.__str__()))
- # Change constructor to the multi grid output
- from .geometry_outputs import GeometryViewFineMultiGrid as GeometryViewUser
- self.multi_grid = True
- except KeyError:
- self.multi_grid = False
- from .geometry_outputs import GeometryView as GeometryViewUser
-
- return GeometryViewUser
-
- def create(self, grid, uip):
- try:
- p1 = self.kwargs['p1']
- p2 = self.kwargs['p2']
- dl = self.kwargs['dl']
- output_type = self.kwargs['output_type'].lower()
- filename = self.kwargs['filename']
- except KeyError:
- raise CmdInputError("'{}' requires exactly eleven parameters".format(self.__str__()))
-
- GeometryViewUser = self.geometry_view_constructor(grid, output_type)
-
- p1, p2 = uip.check_box_points(p1, p2, self.__str__())
- xs, ys, zs = p1
- xf, yf, zf = p2
-
- dx, dy, dz = uip.discretise_point(dl)
-
- if dx < 0 or dy < 0 or dz < 0:
- raise CmdInputError("'{}' the step size should not be less than zero".format(self.__str__()))
- if dx > grid.nx or dy > grid.ny or dz > grid.nz:
- raise CmdInputError("'{}' the step size should be less than the domain size".format(self.__str__()))
- if dx < 1 or dy < 1 or dz < 1:
- raise CmdInputError("'{}' the step size should not be less than the spatial discretisation".format(self.__str__()))
- if output_type != 'n' and output_type != 'f':
- raise CmdInputError("'{}' requires type to be either n (normal) or f (fine)".format(self.__str__()))
- if output_type == 'f' and (dx * grid.dx != grid.dx or dy * grid.dy != grid.dy or dz * grid.dz != grid.dz):
- raise CmdInputError("'{}' requires the spatial discretisation for the geometry view to be the same as the model for geometry view of type f (fine)".format(self.__str__()))
-
- # Set type of geometry file
- if output_type == 'n':
- fileext = '.vti'
- else:
- fileext = '.vtp'
-
- g = GeometryViewUser(xs, ys, zs, xf, yf, zf, dx, dy, dz, filename, fileext, grid)
-
- if config.is_messages():
- print('Geometry view from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m, discretisation {:g}m, {:g}m, {:g}m, multi_grid {}, grid={}, with filename base {} created.'.format(xs * grid.dx, ys * grid.dy, zs * grid.dz, xf * grid.dx, yf * grid.dy, zf * grid.dz, dx * grid.dx, dy * grid.dy, dz * grid.dz, self.multi_grid, grid.name, g.basefilename))
-
- # Append the new GeometryView object to the geometry views list
- grid.geometryviews.append(g)
-
-
-class GeometryObjectsWrite(UserObjectMulti):
- """Material User Object."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 14
- self.hash = '#geometry_objects_write'
-
- def create(self, grid, uip):
- try:
- p1 = self.kwargs['p1']
- p2 = self.kwargs['p2']
- filename = self.kwargs['filename']
- except KeyError:
- raise CmdInputError("'{}' requires exactly seven parameters".format(self.__str__()))
-
- p1, p2 = uip.check_box_points(p1, p2, self.__str__())
- x0, y0, z0 = p1
- x1, y1, z1 = p2
-
- g = GeometryObjectsUser(x0, y0, z0, x1, y1, z1, filename)
-
- if config.is_messages():
- print('Geometry objects in the volume from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m, will be written to {}, with materials written to {}'.format(p1[0] * grid.dx, p1[1] * grid.dy, p1[2] * grid.dz, p2[0] * grid.dx, p2[1] * grid.dy, p2[2] * grid.dz, g.filename, g.materialsfilename))
-
- # Append the new GeometryView object to the geometry objects to write list
- grid.geometryobjectswrite.append(g)
-
-
-class PMLCFS(UserObjectMulti):
- """Material User Object."""
-
- count = 0
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.order = 15
- self.hash = '#pml_cfs'
- PMLCFS.count += 1
- if PMLCFS.count == 2:
- raise CmdInputError(self.__str__() + ' can only be used up to two times, for up to a 2nd order PML')
-
- def create(self, grid, uip):
-
- try:
- alphascalingprofile = self.kwargs['alphascalingprofile']
- alphascalingdirection = self.kwargs['alphascalingdirection']
- alphamin = self.kwargs['alphamin']
- alphamax = self.kwargs['alphamax']
- kappascalingprofile = self.kwargs['kappascalingprofile']
- kappascalingdirection = self.kwargs['kappascalingdirection']
- kappamin = self.kwargs['kappamin']
- kappamax = self.kwargs['kappamax']
- sigmascalingprofile = self.kwargs['sigmascalingprofile']
- sigmascalingdirection = self.kwargs['sigmascalingdirection']
- sigmamin = self.kwargs['sigmamin']
- sigmamax = self.kwargs['sigmamax']
-
- except KeyError:
- raise CmdInputError(self.__str__() + ' requires exactly twelve parameters')
-
- if alphascalingprofile not in CFSParameter.scalingprofiles.keys() or kappascalingprofile not in CFSParameter.scalingprofiles.keys() or sigmascalingprofile not in CFSParameter.scalingprofiles.keys():
- raise CmdInputError(self.__str__() + ' must have scaling type {}'.format(','.join(CFSParameter.scalingprofiles.keys())))
- if alphascalingdirection not in CFSParameter.scalingdirections or kappascalingdirection not in CFSParameter.scalingdirections or sigmascalingdirection not in CFSParameter.scalingdirections:
- raise CmdInputError(self.__str__() + ' must have scaling type {}'.format(','.join(CFSParameter.scalingdirections)))
- if float(alphamin) < 0 or float(alphamax) < 0 or float(kappamin) < 0 or float(kappamax) < 0 or float(sigmamin) < 0:
- raise CmdInputError(self.__str__() + ' minimum and maximum scaling values must be greater than zero')
- if float(kappamin) < 1:
- raise CmdInputError(self.__str__() + ' minimum scaling value for kappa must be greater than or equal to one')
-
- cfsalpha = CFSParameter()
- cfsalpha.ID = 'alpha'
- cfsalpha.scalingprofile = alphascalingprofile
- cfsalpha.scalingdirection = alphascalingdirection
- cfsalpha.min = float(alphamin)
- cfsalpha.max = float(alphamax)
- cfskappa = CFSParameter()
- cfskappa.ID = 'kappa'
- cfskappa.scalingprofile = kappascalingprofile
- cfskappa.scalingdirection = kappascalingdirection
- cfskappa.min = float(kappamin)
- cfskappa.max = float(kappamax)
- cfssigma = CFSParameter()
- cfssigma.ID = 'sigma'
- cfssigma.scalingprofile = sigmascalingprofile
- cfssigma.scalingdirection = sigmascalingdirection
- cfssigma.min = float(sigmamin)
- if sigmamax == 'None':
- cfssigma.max = None
- else:
- cfssigma.max = float(sigmamax)
- cfs = CFS()
- cfs.alpha = cfsalpha
- cfs.kappa = cfskappa
- cfs.sigma = cfssigma
-
- if config.is_messages():
- print('PML CFS parameters: alpha (scaling: {}, scaling direction: {}, min: {:g}, max: {:g}), kappa (scaling: {}, scaling direction: {}, min: {:g}, max: {:g}), sigma (scaling: {}, scaling direction: {}, min: {:g}, max: {}) created.'.format(cfsalpha.scalingprofile, cfsalpha.scalingdirection, cfsalpha.min, cfsalpha.max, cfskappa.scalingprofile, cfskappa.scalingdirection, cfskappa.min, cfskappa.max, cfssigma.scalingprofile, cfssigma.scalingdirection, cfssigma.min, cfssigma.max))
-
- grid.cfs.append(cfs)
-
-class Subgrid(UserObjectMulti):
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- self.children_multiple = []
- self.children_geometry = []
-
- def add(self, node):
- if isinstance(node, UserObjectMulti):
- self.children_multiple.append(node)
- elif isinstance(node, UserObjectGeometry):
- self.children_geometry.append(node)
- else:
- raise Exception('This Object is Unknown to gprMax')
-
-class SubgridHSG(UserObjectMulti):
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
+# Copyright (C) 2015-2019: The University of Edinburgh
+# Authors: Craig Warren and Antonis Giannopoulos
+#
+# This file is part of gprMax.
+#
+# gprMax is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# gprMax is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with gprMax. If not, see .
+
+"""Object which can be created multiple times."""
+import gprMax.config as config
+from .config import z0
+from .config import dtypes
+from .utilities import round_value
+from .cmds_geometry.cmds_geometry import UserObjectGeometry
+from .waveforms import Waveform as WaveformUser
+from .sources import VoltageSource as VoltageSourceUser
+from .sources import HertzianDipole as HertzianDipoleUser
+from .sources import MagneticDipole as MagneticDipoleUser
+from .sources import TransmissionLine as TransmissionLineUser
+from .snapshots import Snapshot as SnapshotUser
+from .receivers import Rx as RxUser
+from .materials import Material as MaterialUser
+from .materials import PeplinskiSoil as PeplinskiSoilUser
+from .geometry_outputs import GeometryObjects as GeometryObjectsUser
+from .pml import CFSParameter
+from .pml import CFS
+from .subgrids.base import SubGridBase
+
+from .exceptions import CmdInputError
+
+
+import numpy as np
+from tqdm import tqdm
+
+floattype = dtypes['float_or_double']
+
+
+class UserObjectMulti:
+ """Specific multiobject object."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ self.kwargs = kwargs
+ self.order = None
+ self.hash = '#example'
+
+ def __str__(self):
+ """Readble user string as per hash commands."""
+ s = ''
+ for k, v in self.kwargs.items():
+ if isinstance(v, tuple) or isinstance(v, list):
+ v = ' '.join([str(el) for el in v])
+ s += str(v) + ' '
+
+ return '{}: {}'.format(self.hash, s[:-1])
+
+ def create(self, grid, uip):
+ """Create the object and add it to the grid."""
+ pass
+
+ def params_str(self):
+ """Readble string of parameters given to object."""
+ return self.hash + ': ' + str(self.kwargs)
+
+
+class Waveform(UserObjectMulti):
+ """Create Waveform different excitation types."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 0
+ self.hash = '#waveform'
+
+ def create(self, grid, uip):
+ """Create the waveform and add it to the grid."""
+ try:
+ wavetype = self.kwargs['wave_type'].lower()
+ amp = self.kwargs['amp']
+ freq = self.kwargs['freq']
+ ID = self.kwargs['id']
+
+ except KeyError:
+ raise CmdInputError(self.params_str() + ' requires exactly four parameters')
+
+ if wavetype not in WaveformUser.types:
+ raise CmdInputError(self.__str__() + ' must have one of the following types {}'.format(','.join(WaveformUser.types)))
+ if freq <= 0:
+ raise CmdInputError(self.__str__() + ' requires an excitation frequency value of greater than zero')
+ if any(x.ID == ID for x in grid.waveforms):
+ raise CmdInputError(self.__str__() + ' with ID {} already exists'.format(ID))
+
+ w = WaveformUser()
+ w.ID = ID
+ w.type = wavetype
+ w.amp = amp
+ w.freq = freq
+
+ if config.is_messages():
+ print('Waveform {} of type {} with maximum amplitude scaling {:g}, frequency {:g}Hz created.'.format(w.ID, w.type, w.amp, w.freq))
+
+ grid.waveforms.append(w)
+
+
+class VoltageSource(UserObjectMulti):
+ """User Object for a voltage source."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 1
+ self.hash = '#voltage_source'
+
+ def create(self, grid, uip):
+ """Create voltage source and add it to the grid."""
+ try:
+ polarisation = self.kwargs['polarisation'].lower()
+ p1 = self.kwargs['p1']
+ resistance = self.kwargs['resistance']
+ waveform_id = self.kwargs['waveform_id']
+
+ except KeyError:
+ raise CmdInputError(self.__str__() + " requires at least six parameters")
+
+ # Check polarity & position parameters
+ if polarisation not in ('x', 'y', 'z'):
+ raise CmdInputError("'{}' polarisation must be x, y, or z".format(self.__str__()))
+ if '2D TMx' in grid.mode and (polarisation == 'y' or polarisation == 'z'):
+ raise CmdInputError("'{}' polarisation must be x in 2D TMx mode".format(self.__str__()))
+ elif '2D TMy' in grid.mode and (polarisation == 'x' or polarisation == 'z'):
+ raise CmdInputError("'{}' polarisation must be y in 2D TMy mode".format(self.__str__()))
+ elif '2D TMz' in grid.mode and (polarisation == 'x' or polarisation == 'y'):
+ raise CmdInputError("'{}' polarisation must be z in 2D TMz mode".format(self.__str__()))
+
+ xcoord, ycoord, zcoord = uip.check_src_rx_point(p1, self.__str__())
+
+ if resistance < 0:
+ raise CmdInputError("'{}' requires a source resistance of zero or greater".format(self.__str__()))
+
+ # Check if there is a waveformID in the waveforms list
+ if not any(x.ID == waveform_id for x in grid.waveforms):
+ raise CmdInputError("'{}' there is no waveform with the identifier {}'.format(tmp[5]".format(self.__str__()))
+
+ v = VoltageSourceUser()
+ v.polarisation = polarisation
+ v.xcoord = xcoord
+ v.ycoord = ycoord
+ v.zcoord = zcoord
+ v.ID = v.__class__.__name__ + '(' + str(v.xcoord) + ',' + str(v.ycoord) + ',' + str(v.zcoord) + ')'
+ v.resistance = resistance
+ v.waveformID = waveform_id
+
+ try:
+ start = self.kwargs['start']
+ stop = self.kwargs['stop']
+ # Check source start & source remove time parameters
+ if start < 0:
+ raise CmdInputError("'{}' delay of the initiation of the source should not be less than zero".format(self.__str__()))
+ if stop < 0:
+ raise CmdInputError("'{}' time to remove the source should not be less than zero".format(self.__str__()))
+ if stop - start <= 0:
+ raise CmdInputError("'{}' duration of the source should not be zero or less".format(self.__str__()))
+ v.start = start
+ if stop > grid.timewindow:
+ v.stop = grid.timewindow
+ else:
+ v.stop = stop
+ startstop = ' start time {:g} secs, finish time {:g} secs '.format(v.start, v.stop)
+ except KeyError:
+ v.start = 0
+ v.stop = grid.timewindow
+ startstop = ' '
+
+ v.calculate_waveform_values(grid)
+
+ if config.is_messages():
+ print('Voltage source with polarity {} at {:g}m, {:g}m, {:g}m, resistance {:.1f} Ohms,'.format(v.polarisation, v.xcoord * grid.dx, v.ycoord * grid.dy, v.zcoord * grid.dz, v.resistance) + startstop + 'using waveform {} created.'.format(v.waveformID))
+
+ grid.voltagesources.append(v)
+
+
+class HertzianDipole(UserObjectMulti):
+ """User Object for HertzianDipole."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 2
+ self.hash = '#hertzian_dipole'
+
+ def create(self, grid, uip):
+ """Create HertzianDipole and add it to the grid."""
+ try:
+ polarisation = self.kwargs['polarisation'].lower()
+ p1 = self.kwargs['p1']
+ waveform_id = self.kwargs['waveform_id']
+
+ except KeyError:
+ raise CmdInputError("'{}' requires at least 3 parameters".format(self.params_str()))
+
+ # Check polarity & position parameters
+ if polarisation not in ('x', 'y', 'z'):
+ raise CmdInputError("'{}' polarisation must be x, y, or z".format(self.__str__()))
+ if '2D TMx' in grid.mode and (polarisation == 'y' or polarisation == 'z'):
+ raise CmdInputError("'{}' polarisation must be x in 2D TMx mode".format(self.__str__()))
+ elif '2D TMy' in grid.mode and (polarisation == 'x' or polarisation == 'z'):
+ raise CmdInputError("'{}' polarisation must be y in 2D TMy mode".format(self.__str__()))
+ elif '2D TMz' in grid.mode and (polarisation == 'x' or polarisation == 'y'):
+ raise CmdInputError("'{}' polarisation must be z in 2D TMz mode".format(self.__str__()))
+
+ xcoord, ycoord, zcoord = uip.check_src_rx_point(p1, self.__str__())
+
+ # Check if there is a waveformID in the waveforms list
+ if not any(x.ID == waveform_id for x in grid.waveforms):
+ raise CmdInputError("'{}' there is no waveform with the identifier {}'.format(tmp[4]".format(self.__str__()))
+
+ h = HertzianDipoleUser()
+ h.polarisation = polarisation
+
+ # Set length of dipole to grid size in polarisation direction
+ if h.polarisation == 'x':
+ h.dl = grid.dx
+ elif h.polarisation == 'y':
+ h.dl = grid.dy
+ elif h.polarisation == 'z':
+ h.dl = grid.dz
+
+ h.xcoord = xcoord
+ h.ycoord = ycoord
+ h.zcoord = zcoord
+ h.xcoordorigin = xcoord
+ h.ycoordorigin = ycoord
+ h.zcoordorigin = zcoord
+ h.ID = h.__class__.__name__ + '(' + str(h.xcoord) + ',' + str(h.ycoord) + ',' + str(h.zcoord) + ')'
+ h.waveformID = waveform_id
+
+ try:
+ # Check source start & source remove time parameters
+ start = self.kwargs['start']
+ stop = self.kwargs['stop']
+ if start < 0:
+ raise CmdInputError("'{}' delay of the initiation of the source should not be less than zero".format(self.__str__()))
+ if stop < 0:
+ raise CmdInputError("'{}' time to remove the source should not be less than zero".format(self.__str__()))
+ if stop - start <= 0:
+ raise CmdInputError("'{}' duration of the source should not be zero or less".format(self.__str__()))
+ h.start = start
+ if stop > grid.timewindow:
+ h.stop = grid.timewindow
+ else:
+ h.stop = stop
+ startstop = ' start time {:g} secs, finish time {:g} secs '.format(h.start, h.stop)
+ except KeyError:
+ h.start = 0
+ h.stop = grid.timewindow
+ print(grid.timewindow)
+ startstop = ' '
+
+ h.calculate_waveform_values(grid)
+
+ if config.is_messages():
+ if grid.mode == '2D':
+ print('Hertzian dipole is a line source in 2D with polarity {} at {:g}m, {:g}m, {:g}m,'.format(h.polarisation, h.xcoord * grid.dx, h.ycoord * grid.dy, h.zcoord * grid.dz) + startstop + 'using waveform {} created.'.format(h.waveformID))
+ else:
+ print('Hertzian dipole with polarity {} at {:g}m, {:g}m, {:g}m,'.format(h.polarisation, h.xcoord * grid.dx, h.ycoord * grid.dy, h.zcoord * grid.dz) + startstop + 'using waveform {} created.'.format(h.waveformID))
+
+ grid.hertziandipoles.append(h)
+
+
+class MagneticDipole(UserObjectMulti):
+ """Magnetic Dipole User Object."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 3
+ self.hash = '#magnetic_dipole'
+
+ def create(self, grid, uip):
+ """Create Magnetic Dipole and add it the grid."""
+ try:
+ polarisation = self.kwargs['polarisation'].lower()
+ p1 = self.kwargs['p1']
+ waveform_id = self.kwargs['waveform_id']
+ except KeyError:
+ raise CmdInputError("'{}' requires at least five parameters".format(self.__str__()))
+
+ # Check polarity & position parameters
+ if polarisation not in ('x', 'y', 'z'):
+ raise CmdInputError("'{}' polarisation must be x, y, or z".format(self.__str__()))
+ if '2D TMx' in grid.mode and (polarisation == 'y' or polarisation == 'z'):
+ raise CmdInputError("'{}' polarisation must be x in 2D TMx mode".format(self.__str__()))
+ elif '2D TMy' in grid.mode and (polarisation == 'x' or polarisation == 'z'):
+ raise CmdInputError("'{}' polarisation must be y in 2D TMy mode".format(self.__str__()))
+ elif '2D TMz' in grid.mode and (polarisation == 'x' or polarisation == 'y'):
+ raise CmdInputError("'{}' polarisation must be z in 2D TMz mode".format(self.__str__()))
+
+ xcoord, ycoord, zcoord = uip.check_src_rx_point(p1, self.__str__())
+
+ # Check if there is a waveformID in the waveforms list
+ if not any(x.ID == waveform_id for x in grid.waveforms):
+ raise CmdInputError("'{}' there is no waveform with the identifier {}".format(self.__str__(), waveform_id))
+
+ m = MagneticDipoleUser()
+ m.polarisation = polarisation
+ m.xcoord = xcoord
+ m.ycoord = ycoord
+ m.zcoord = zcoord
+ m.xcoordorigin = xcoord
+ m.ycoordorigin = ycoord
+ m.zcoordorigin = zcoord
+ m.ID = m.__class__.__name__ + '(' + str(m.xcoord) + ',' + str(m.ycoord) + ',' + str(m.zcoord) + ')'
+ m.waveformID = waveform_id
+
+ try:
+ # Check source start & source remove time parameters
+ start = self.kwargs['start']
+ stop = self.kwargs['stop']
+ if start < 0:
+ raise CmdInputError("'{}' delay of the initiation of the source should not be less than zero".format(self.__str__()))
+ if stop < 0:
+ raise CmdInputError("'{}' time to remove the source should not be less than zero".format(self.__str__()))
+ if stop - start <= 0:
+ raise CmdInputError("'{}' duration of the source should not be zero or less".format(self.__str__()))
+ m.start = start
+ if stop > grid.timewindow:
+ m.stop = grid.timewindow
+ else:
+ m.stop = stop
+ startstop = ' start time {:g} secs, finish time {:g} secs '.format(m.start, m.stop)
+ except KeyError:
+ m.start = 0
+ m.stop = grid.timewindow
+ startstop = ' '
+
+ m.calculate_waveform_values(grid)
+
+ if config.is_messages():
+ print('Magnetic dipole with polarity {} at {:g}m, {:g}m, {:g}m,'.format(m.polarisation, m.xcoord * grid.dx, m.ycoord * grid.dy, m.zcoord * grid.dz) + startstop + 'using waveform {} created.'.format(m.waveformID))
+
+ grid.magneticdipoles.append(m)
+
+
+class TransmissionLine(UserObjectMulti):
+ """Magnetic Dipole User Object."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 4
+ self.hash = '#transmission_line'
+
+ def create(self, grid, uip):
+
+ try:
+ polarisation = self.kwargs['polarisation'].lower()
+ p1 = self.kwargs['p1']
+ waveform_id = self.kwargs['waveform_id']
+ resistance = self.kwargs['resistance']
+
+ except KeyError:
+ raise CmdInputError("'{}' requires at least six parameters".format(self.params_str()))
+
+ # Warn about using a transmission line on GPU
+ if grid.gpu is not None:
+ raise CmdInputError("'{}' A #transmission_line cannot currently be used with GPU solving. Consider using a #voltage_source instead.".format(self.__str__()))
+
+ # Check polarity & position parameters
+ if polarisation not in ('x', 'y', 'z'):
+ raise CmdInputError("'{}' polarisation must be x, y, or z".format(self.__str__()))
+ if '2D TMx' in grid.mode and (polarisation == 'y' or polarisation == 'z'):
+ raise CmdInputError("'{}' polarisation must be x in 2D TMx mode".format(self.__str__()))
+ elif '2D TMy' in grid.mode and (polarisation == 'x' or polarisation == 'z'):
+ raise CmdInputError("'{}' polarisation must be y in 2D TMy mode".format(self.__str__()))
+ elif '2D TMz' in grid.mode and (polarisation == 'x' or polarisation == 'y'):
+ raise CmdInputError("'{}' polarisation must be z in 2D TMz mode".format(self.__str__()))
+
+ xcoord, ycoord, zcoord = uip.check_src_rx_point(p1, self.__str__())
+
+ if resistance <= 0 or resistance >= z0:
+ raise CmdInputError("'{}' requires a resistance greater than zero and less than the impedance of free space, i.e. 376.73 Ohms".format(self.__str__()))
+
+ # Check if there is a waveformID in the waveforms list
+ if not any(x.ID == waveform_id for x in grid.waveforms):
+ raise CmdInputError("'{}' there is no waveform with the identifier {}'.format(tmp[5]".format(self.__str__()))
+
+ t = TransmissionLineUser(grid)
+ t.polarisation = polarisation
+ t.xcoord = xcoord
+ t.ycoord = ycoord
+ t.zcoord = zcoord
+ t.ID = t.__class__.__name__ + '(' + str(t.xcoord) + ',' + str(t.ycoord) + ',' + str(t.zcoord) + ')'
+ t.resistance = resistance
+ t.waveformID = waveform_id
+
+ try:
+ # Check source start & source remove time parameters
+ start = self.kwargs['start']
+ stop = self.kwargs['stop']
+ if start < 0:
+ raise CmdInputError("'{}' delay of the initiation of the source should not be less than zero".format(self.__str__()))
+ if stop < 0:
+ raise CmdInputError("'{}' time to remove the source should not be less than zero".format(self.__str__()))
+ if stop - start <= 0:
+ raise CmdInputError("'{}' duration of the source should not be zero or less".format(self.__str__()))
+ t.start = start
+ if stop > grid.timewindow:
+ t.stop = grid.timewindow
+ else:
+ t.stop = stop
+ startstop = ' start time {:g} secs, finish time {:g} secs '.format(t.start, t.stop)
+ except KeyError:
+ t.start = 0
+ t.stop = grid.timewindow
+ startstop = ' '
+
+ t.calculate_waveform_values(grid)
+ t.calculate_incident_V_I(grid)
+
+ if config.is_messages():
+ print('Transmission line with polarity {} at {:g}m, {:g}m, {:g}m, resistance {:.1f} Ohms,'.format(t.polarisation, t.xcoord * grid.dx, t.ycoord * grid.dy, t.zcoord * grid.dz, t.resistance) + startstop + 'using waveform {} created.'.format(t.waveformID))
+
+ grid.transmissionlines.append(t)
+
+
+class Rx(UserObjectMulti):
+ """Magnetic Dipole User Object."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 5
+ self.hash = '#rx'
+ self.constructor = RxUser
+
+ def create(self, grid, uip):
+ try:
+ p1 = self.kwargs['p1']
+ except KeyError:
+ raise CmdInputError("'{}' has an incorrect number of parameters".format(self.__str__()))
+
+ p = uip.check_src_rx_point(p1, self.__str__())
+
+ r = self.constructor()
+ r.xcoord, r.ycoord, r.zcoord = p
+ r.xcoordorigin, r.ycoordorigin, r.zcoordorigin = p
+
+ try:
+ r.ID = self.kwargs['id']
+ outputs = self.kwargs['outputs']
+ # Get allowable outputs
+ if grid.gpu is not None:
+ allowableoutputs = RxUser.gpu_allowableoutputs
+ else:
+ allowableoutputs = RxUser.allowableoutputs
+ # Check and add field output names
+ for field in outputs:
+ if field in allowableoutputs:
+ r.outputs[field] = np.zeros(grid.iterations, dtype=floattype)
+ else:
+ raise CmdInputError("{} contains an output type that is not allowable. Allowable outputs in current context are {}".format(self.__str__(), allowableoutputs))
+
+ # If no ID or outputs are specified, use default
+ except KeyError:
+ r.ID = 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=floattype)
+ if config.is_messages():
+ print('Receiver at {:g}m, {:g}m, {:g}m with output component(s) {} created.'.format(r.xcoord * grid.dx, r.ycoord * grid.dy, r.zcoord * grid.dz, ', '.join(r.outputs)))
+
+ grid.rxs.append(r)
+
+ return r
+
+class RxArray(UserObjectMulti):
+ """Receiver Array User Object."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 6
+ self.hash = '#rx_array'
+
+ def create(self, grid, uip):
+
+ try:
+ p1 = self.kwargs['p1']
+ p2 = self.kwargs['p2']
+ dl = self.kwargs['dl']
+
+ except KeyError:
+ raise CmdInputError("'{}' requires exactly 9 parameters".format(self.__str__()))
+
+ xs, ys, zs = uip.check_src_rx_point(p1, self.__str__(), 'lower')
+ xf, yf, zf = uip.check_src_rx_point(p2, self.__str__(), 'upper')
+ dx, dy, dz = uip.discretise_point(dl)
+
+ if xs > xf or ys > yf or zs > zf:
+ raise CmdInputError("'{}' the lower coordinates should be less than the upper coordinates".format(self.__str__()))
+ if dx < 0 or dy < 0 or dz < 0:
+ raise CmdInputError("'{}' the step size should not be less than zero".format(self.__str__()))
+ if dx < 1:
+ if dx == 0:
+ dx = 1
+ else:
+ raise CmdInputError("'{}' the step size should not be less than the spatial discretisation".format(self.__str__()))
+ if dy < 1:
+ if dy == 0:
+ dy = 1
+ else:
+ raise CmdInputError("'{}' the step size should not be less than the spatial discretisation".format(self.__str__()))
+ if dz < 1:
+ if dz == 0:
+ dz = 1
+ else:
+ raise CmdInputError("'{}' the step size should not be less than the spatial discretisation".format(self.__str__()))
+
+ if config.is_messages():
+ print('Receiver array {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m with steps {:g}m, {:g}m, {:g}m'.format(xs * grid.dx, ys * grid.dy, zs * grid.dz, xf * grid.dx, yf * grid.dy, zf * grid.dz, dx * grid.dx, dy * grid.dy, dz * grid.dz))
+
+ for x in range(xs, xf + 1, dx):
+ for y in range(ys, yf + 1, dy):
+ for z in range(zs, zf + 1, dz):
+ r = RxUser()
+ r.xcoord = x
+ r.ycoord = y
+ r.zcoord = z
+ r.xcoordorigin = x
+ r.ycoordorigin = y
+ r.zcoordorigin = z
+ r.ID = r.__class__.__name__ + '(' + str(x) + ',' + str(y) + ',' + str(z) + ')'
+ for key in RxUser.defaultoutputs:
+ r.outputs[key] = np.zeros(grid.iterations, dtype=floattype)
+ if config.is_messages():
+ print(' Receiver at {:g}m, {:g}m, {:g}m with output component(s) {} created.'.format(r.xcoord * grid.dx, r.ycoord * grid.dy, r.zcoord * grid.dz, ', '.join(r.outputs)))
+ grid.rxs.append(r)
+
+
+class Snapshot(UserObjectMulti):
+ """Snapshot User Object."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 19
+ self.hash = '#snapshot'
+
+ def create(self, grid, uip):
+
+ if isinstance(grid, SubGridBase):
+ raise CmdInputError("'{}' Do not add Snapshots to Subgrids.".format(self.__str__()))
+ try:
+ p1 = self.kwargs['p1']
+ p2 = self.kwargs['p2']
+ dl = self.kwargs['dl']
+ filename = self.kwargs['filename']
+ except KeyError:
+ raise CmdInputError("'{}' requires exactly 11 parameters".format(self.__str__()))
+
+ p1, p2 = uip.check_box_points(p1, p2, self.__str__())
+ xs, ys, zs = p1
+ xf, yf, zf = p2
+ dx, dy, dz = uip.discretise_point(dl)
+
+ # If number of iterations given
+ try:
+ iterations = self.kwargs['iterations']
+ # If real floating point value given
+ except KeyError:
+ try:
+ time = self.kwargs['time']
+ except KeyError:
+ raise CmdInputError("'{}' requires exactly 5 parameters".format(self.__str__()))
+ if time > 0:
+ iterations = round_value((time / grid.dt)) + 1
+ else:
+ raise CmdInputError("'{}' time value must be greater than zero".format(self.__str__()))
+
+ if dx < 0 or dy < 0 or dz < 0:
+ raise CmdInputError("'{}' the step size should not be less than zero".format(self.__str__()))
+ if dx < 1 or dy < 1 or dz < 1:
+ raise CmdInputError("'{}' the step size should not be less than the spatial discretisation".format(self.__str__()))
+ if iterations <= 0 or iterations > grid.iterations:
+ raise CmdInputError("'{}' time value is not valid".format(self.__str__()))
+
+ # Replace with old style snapshots if there are subgrids
+ if grid.subgrids:
+ from .snapshot_subgrid import Snapshot as SnapshotSub
+ s = SnapshotSub(xs, ys, zs, xf, yf, zf, dx, dy, dz, iterations, filename)
+ else:
+ s = SnapshotUser(xs, ys, zs, xf, yf, zf, dx, dy, dz, iterations, filename)
+
+ if config.is_messages():
+ print('Snapshot from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m, discretisation {:g}m, {:g}m, {:g}m, at {:g} secs with filename {} created.'.format(xs * grid.dx, ys * grid.dy, zs * grid.dz, xf * grid.dx, yf * grid.dy, zf * grid.dz, dx * grid.dx, dy * grid.dy, dz * grid.dz, s.time * grid.dt, s.basefilename))
+
+ grid.snapshots.append(s)
+
+class Material(UserObjectMulti):
+ """Material User Object."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 8
+ self.hash = '#material'
+
+ def create(self, grid, uip):
+ try:
+ er = self.kwargs['er']
+ se = self.kwargs['se']
+ mr = self.kwargs['mr']
+ sm = self.kwargs['sm']
+ material_id = self.kwargs['id']
+ except KeyError:
+ raise CmdInputError('{} requires exactly five parameters'.format(self.params_str()))
+
+ if er < 1:
+ raise CmdInputError('{} requires a positive value of one or greater for static (DC) permittivity'.format(self.__str__()))
+ if se != 'inf':
+ se = float(se)
+ if se < 0:
+ raise CmdInputError('{} requires a positive value for conductivity'.format(self.__str__()))
+ else:
+ se = float('inf')
+ if mr < 1:
+ raise CmdInputError('{} requires a positive value of one or greater for permeability'.format(self.__str__()))
+ if sm < 0:
+ raise CmdInputError('{} requires a positive value for magnetic conductivity'.format(self.__str__()))
+ if any(x.ID == material_id for x in grid.materials):
+ raise CmdInputError('{} with ID {} already exists'.format(material_id).format(self.__str__()))
+
+ # Create a new instance of the Material class material (start index after pec & free_space)
+ m = MaterialUser(len(grid.materials), material_id)
+ m.er = er
+ m.se = se
+ m.mr = mr
+ m.sm = sm
+
+ # Set material averaging to False if infinite conductivity, i.e. pec
+ if m.se == float('inf'):
+ m.averagable = False
+
+ if config.is_messages():
+ tqdm.write('Material {} with eps_r={:g}, sigma={:g} S/m; mu_r={:g}, sigma*={:g} Ohm/m created.'.format(m.ID, m.er, m.se, m.mr, m.sm))
+
+ # Append the new material object to the materials list
+ grid.materials.append(m)
+
+
+class AddDebyeDispersion(UserObjectMulti):
+ """Material User Object."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 9
+ self.hash = '#add_dispersion_debye'
+
+ def create(self, grid, uip):
+
+ try:
+ poles = self.kwargs['n_poles']
+ er_delta = self.kwargs['er_delta']
+ tau = self.kwargs['tau']
+ material_ids = self.kwargs['material_ids']
+
+ except KeyError:
+ raise CmdInputError(self.__str__() + ' requires at least four parameters')
+
+ if poles < 0:
+ raise CmdInputError(self.__str__() + ' requires a positive value for number of poles')
+
+ # Look up requested materials in existing list of material instances
+ materials = [y for x in material_ids for y in grid.materials if y.ID == x]
+
+ if len(materials) != len(material_ids):
+ notfound = [x for x in material_ids if x not in materials]
+ raise CmdInputError(self.__str__() + ' material(s) {} do not exist'.format(notfound))
+
+ for material in materials:
+ material.type = 'debye'
+ material.poles = poles
+ material.averagable = False
+ for i in range(0, poles):
+ # N.B Not checking if relaxation times are greater than time-step
+ if tau[i] > 0:
+ material.deltaer.append(er_delta[i])
+ material.tau.append(tau[i])
+ else:
+ raise CmdInputError(self.__str__() + ' requires positive values for the permittivity difference.')
+ if material.poles > MaterialUser.maxpoles:
+ MaterialUser.maxpoles = material.poles
+
+ if config.is_messages():
+ tqdm.write('Debye disperion added to {} with delta_eps_r={}, and tau={} secs created.'.format(material.ID, ', '.join('%4.2f' % deltaer for deltaer in material.deltaer), ', '.join('%4.3e' % tau for tau in material.tau)))
+
+
+class AddLorentzDispersion(UserObjectMulti):
+ """Material User Object."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 10
+ self.hash = '#add_dispersion_lorentz'
+
+ def create(self, grid, uip):
+
+ try:
+ poles = self.kwargs['n_poles']
+ er_delta = self.kwargs['er_delta']
+ tau = self.kwargs['omega']
+ alpha = self.kwargs['delta']
+ material_ids = self.kwargs['material_ids']
+ except KeyError:
+ raise CmdInputError(self.__str__() + ' requires at least five parameters')
+
+ if poles < 0:
+ raise CmdInputError(self.__str__() + ' requires a positive value for number of poles')
+
+ # Look up requested materials in existing list of material instances
+ materials = [y for x in material_ids for y in grid.materials if y.ID == x]
+
+ if len(materials) != len(material_ids):
+ notfound = [x for x in material_ids if x not in materials]
+ raise CmdInputError(self.__str__() + ' material(s) {} do not exist'.format(notfound))
+
+ for material in materials:
+ material.type = 'lorentz'
+ material.poles = poles
+ material.averagable = False
+ for i in range(0, poles):
+ if er_delta[i] > 0 and tau[i] > grid.dt and alpha[i] > grid.dt:
+ material.deltaer.append(er_delta[i])
+ material.tau.append(tau[i])
+ material.alpha.append(alpha[i])
+ else:
+ raise CmdInputError(self.__str__() + ' requires positive values for the permittivity difference and frequencies, and associated times that are greater than the time step for the model.')
+ if material.poles > MaterialUser.maxpoles:
+ MaterialUser.maxpoles = material.poles
+
+ if config.is_messages():
+ tqdm.write('Lorentz disperion added to {} with delta_eps_r={}, omega={} secs, and gamma={} created.'.format(material.ID, ', '.join('%4.2f' % deltaer for deltaer in material.deltaer), ', '.join('%4.3e' % tau for tau in material.tau), ', '.join('%4.3e' % alpha for alpha in material.alpha)))
+
+
+class AddDrudeDispersion(UserObjectMulti):
+ """Material User Object."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 11
+ self.hash = '#add_dispersion_Drude'
+
+ def create(self, grid, uip):
+
+ try:
+ poles = self.kwargs['n_poles']
+ tau = self.kwargs['tau']
+ alpha = self.kwargs['alpha']
+ material_ids = self.kwargs['material_ids']
+ except KeyError:
+ raise CmdInputError(self.__str__() + ' requires at least four parameters')
+ if poles < 0:
+ raise CmdInputError(self.__str__() + ' requires a positive value for number of poles')
+
+ # Look up requested materials in existing list of material instances
+ materials = [y for x in material_ids for y in grid.materials if y.ID == x]
+
+ if len(materials) != len(material_ids):
+ notfound = [x for x in material_ids if x not in materials]
+ raise CmdInputError(self.__str__() + ' material(s) {} do not exist'.format(notfound))
+
+ for material in materials:
+ material.type = 'drude'
+ material.poles = poles
+ material.averagable = False
+ for i in range(0, poles):
+ if tau[i] > 0 and alpha[i] > grid.dt:
+ material.tau.append(tau[i])
+ material.alpha.append(alpha[i])
+ else:
+ raise CmdInputError(self.__str__() + ' requires positive values for the frequencies, and associated times that are greater than the time step for the model.')
+ if material.poles > MaterialUser.maxpoles:
+ MaterialUser.maxpoles = material.poles
+
+ if config.is_messages():
+ tqdm.write('Drude disperion added to {} with omega={} secs, and gamma={} secs created.'.format(material.ID, ', '.join('%4.3e' % tau for tau in material.tau), ', '.join('%4.3e' % alpha for alpha in material.alpha)))
+
+
+class SoilPeplinski(UserObjectMulti):
+ """Material User Object."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 12
+ self.hash = '#soil_peplinski'
+
+ def create(self, grid, uip):
+
+ try:
+ sand_fraction = self.kwargs['sand_fraction']
+ clay_fraction = self.kwargs['clay_fraction']
+ bulk_density = self.kwargs['bulk_density']
+ sand_density = self.kwargs['sand_density']
+ water_fraction_lower = self.kwargs['water_fraction_lower']
+ water_fraction_upper = self.kwargs['water_fraction_upper']
+ ID = self.kwargs['id']
+
+ except KeyError:
+ raise CmdInputError(self.__str__() + ' requires at exactly seven parameters')
+
+ if sand_fraction < 0:
+ raise CmdInputError(self.__str__() + ' requires a positive value for the sand fraction')
+ if clay_fraction < 0:
+ raise CmdInputError(self.__str__() + ' requires a positive value for the clay fraction')
+ if bulk_density < 0:
+ raise CmdInputError(self.__str__() + ' requires a positive value for the bulk density')
+ if sand_density < 0:
+ raise CmdInputError(self.__str__() + ' requires a positive value for the sand particle density')
+ if water_fraction_lower < 0:
+ raise CmdInputError(self.__str__() + ' requires a positive value for the lower limit of the water volumetric fraction')
+ if water_fraction_upper < 0:
+ raise CmdInputError(self.__str__() + ' requires a positive value for the upper limit of the water volumetric fraction')
+ if any(x.ID == ID for x in grid.mixingmodels):
+ raise CmdInputError(self.__str__() + ' with ID {} already exists'.format(ID))
+
+ # 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))
+
+ if config.is_messages():
+ print('Mixing model (Peplinski) used to create {} with sand fraction {:g}, clay fraction {:g}, bulk density {:g}g/cm3, sand particle density {:g}g/cm3, and water volumetric fraction {:g} to {:g} created.'.format(s.ID, s.S, s.C, s.rb, s.rs, s.mu[0], s.mu[1]))
+
+ # Append the new material object to the materials list
+ grid.mixingmodels.append(s)
+
+
+class GeometryView(UserObjectMulti):
+ """Material User Object."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 18
+ self.hash = '#geometry_view'
+ self.multi_grid = False
+
+ def geometry_view_constructor(self, grid, output_type):
+ # Check if user want geometry output for all grids
+ try:
+ self.kwargs['multi_grid']
+ # there is no voxel output for multi grid output
+ if isinstance(grid, SubGridBase):
+ raise CmdInputError("'{}' Do not add multi_grid output to subgrid user object. Please add to Scene".format(self.__str__()))
+ if output_type == 'n':
+ raise CmdInputError("'{}' Voxel output type (n) is not supported for multigrid output :(".format(self.__str__()))
+ # Change constructor to the multi grid output
+ from .geometry_outputs import GeometryViewFineMultiGrid as GeometryViewUser
+ self.multi_grid = True
+ except KeyError:
+ self.multi_grid = False
+ from .geometry_outputs import GeometryView as GeometryViewUser
+
+ return GeometryViewUser
+
+ def create(self, grid, uip):
+ try:
+ p1 = self.kwargs['p1']
+ p2 = self.kwargs['p2']
+ dl = self.kwargs['dl']
+ output_type = self.kwargs['output_type'].lower()
+ filename = self.kwargs['filename']
+ except KeyError:
+ raise CmdInputError("'{}' requires exactly eleven parameters".format(self.__str__()))
+
+ GeometryViewUser = self.geometry_view_constructor(grid, output_type)
+
+ p1, p2 = uip.check_box_points(p1, p2, self.__str__())
+ xs, ys, zs = p1
+ xf, yf, zf = p2
+
+ dx, dy, dz = uip.discretise_point(dl)
+
+ if dx < 0 or dy < 0 or dz < 0:
+ raise CmdInputError("'{}' the step size should not be less than zero".format(self.__str__()))
+ if dx > grid.nx or dy > grid.ny or dz > grid.nz:
+ raise CmdInputError("'{}' the step size should be less than the domain size".format(self.__str__()))
+ if dx < 1 or dy < 1 or dz < 1:
+ raise CmdInputError("'{}' the step size should not be less than the spatial discretisation".format(self.__str__()))
+ if output_type != 'n' and output_type != 'f':
+ raise CmdInputError("'{}' requires type to be either n (normal) or f (fine)".format(self.__str__()))
+ if output_type == 'f' and (dx * grid.dx != grid.dx or dy * grid.dy != grid.dy or dz * grid.dz != grid.dz):
+ raise CmdInputError("'{}' requires the spatial discretisation for the geometry view to be the same as the model for geometry view of type f (fine)".format(self.__str__()))
+
+ # Set type of geometry file
+ if output_type == 'n':
+ fileext = '.vti'
+ else:
+ fileext = '.vtp'
+
+ g = GeometryViewUser(xs, ys, zs, xf, yf, zf, dx, dy, dz, filename, fileext, grid)
+
+ if config.is_messages():
+ print('Geometry view from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m, discretisation {:g}m, {:g}m, {:g}m, multi_grid {}, grid={}, with filename base {} created.'.format(xs * grid.dx, ys * grid.dy, zs * grid.dz, xf * grid.dx, yf * grid.dy, zf * grid.dz, dx * grid.dx, dy * grid.dy, dz * grid.dz, self.multi_grid, grid.name, g.basefilename))
+
+ # Append the new GeometryView object to the geometry views list
+ grid.geometryviews.append(g)
+
+
+class GeometryObjectsWrite(UserObjectMulti):
+ """Material User Object."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 14
+ self.hash = '#geometry_objects_write'
+
+ def create(self, grid, uip):
+ try:
+ p1 = self.kwargs['p1']
+ p2 = self.kwargs['p2']
+ filename = self.kwargs['filename']
+ except KeyError:
+ raise CmdInputError("'{}' requires exactly seven parameters".format(self.__str__()))
+
+ p1, p2 = uip.check_box_points(p1, p2, self.__str__())
+ x0, y0, z0 = p1
+ x1, y1, z1 = p2
+
+ g = GeometryObjectsUser(x0, y0, z0, x1, y1, z1, filename)
+
+ if config.is_messages():
+ print('Geometry objects in the volume from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m, will be written to {}, with materials written to {}'.format(p1[0] * grid.dx, p1[1] * grid.dy, p1[2] * grid.dz, p2[0] * grid.dx, p2[1] * grid.dy, p2[2] * grid.dz, g.filename, g.materialsfilename))
+
+ # Append the new GeometryView object to the geometry objects to write list
+ grid.geometryobjectswrite.append(g)
+
+
+class PMLCFS(UserObjectMulti):
+ """Material User Object."""
+
+ count = 0
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.order = 15
+ self.hash = '#pml_cfs'
+ PMLCFS.count += 1
+ if PMLCFS.count == 2:
+ raise CmdInputError(self.__str__() + ' can only be used up to two times, for up to a 2nd order PML')
+
+ def create(self, grid, uip):
+
+ try:
+ alphascalingprofile = self.kwargs['alphascalingprofile']
+ alphascalingdirection = self.kwargs['alphascalingdirection']
+ alphamin = self.kwargs['alphamin']
+ alphamax = self.kwargs['alphamax']
+ kappascalingprofile = self.kwargs['kappascalingprofile']
+ kappascalingdirection = self.kwargs['kappascalingdirection']
+ kappamin = self.kwargs['kappamin']
+ kappamax = self.kwargs['kappamax']
+ sigmascalingprofile = self.kwargs['sigmascalingprofile']
+ sigmascalingdirection = self.kwargs['sigmascalingdirection']
+ sigmamin = self.kwargs['sigmamin']
+ sigmamax = self.kwargs['sigmamax']
+
+ except KeyError:
+ raise CmdInputError(self.__str__() + ' requires exactly twelve parameters')
+
+ if alphascalingprofile not in CFSParameter.scalingprofiles.keys() or kappascalingprofile not in CFSParameter.scalingprofiles.keys() or sigmascalingprofile not in CFSParameter.scalingprofiles.keys():
+ raise CmdInputError(self.__str__() + ' must have scaling type {}'.format(','.join(CFSParameter.scalingprofiles.keys())))
+ if alphascalingdirection not in CFSParameter.scalingdirections or kappascalingdirection not in CFSParameter.scalingdirections or sigmascalingdirection not in CFSParameter.scalingdirections:
+ raise CmdInputError(self.__str__() + ' must have scaling type {}'.format(','.join(CFSParameter.scalingdirections)))
+ if float(alphamin) < 0 or float(alphamax) < 0 or float(kappamin) < 0 or float(kappamax) < 0 or float(sigmamin) < 0:
+ raise CmdInputError(self.__str__() + ' minimum and maximum scaling values must be greater than zero')
+ if float(kappamin) < 1:
+ raise CmdInputError(self.__str__() + ' minimum scaling value for kappa must be greater than or equal to one')
+
+ cfsalpha = CFSParameter()
+ cfsalpha.ID = 'alpha'
+ cfsalpha.scalingprofile = alphascalingprofile
+ cfsalpha.scalingdirection = alphascalingdirection
+ cfsalpha.min = float(alphamin)
+ cfsalpha.max = float(alphamax)
+ cfskappa = CFSParameter()
+ cfskappa.ID = 'kappa'
+ cfskappa.scalingprofile = kappascalingprofile
+ cfskappa.scalingdirection = kappascalingdirection
+ cfskappa.min = float(kappamin)
+ cfskappa.max = float(kappamax)
+ cfssigma = CFSParameter()
+ cfssigma.ID = 'sigma'
+ cfssigma.scalingprofile = sigmascalingprofile
+ cfssigma.scalingdirection = sigmascalingdirection
+ cfssigma.min = float(sigmamin)
+ if sigmamax == 'None':
+ cfssigma.max = None
+ else:
+ cfssigma.max = float(sigmamax)
+ cfs = CFS()
+ cfs.alpha = cfsalpha
+ cfs.kappa = cfskappa
+ cfs.sigma = cfssigma
+
+ if config.is_messages():
+ print('PML CFS parameters: alpha (scaling: {}, scaling direction: {}, min: {:g}, max: {:g}), kappa (scaling: {}, scaling direction: {}, min: {:g}, max: {:g}), sigma (scaling: {}, scaling direction: {}, min: {:g}, max: {}) created.'.format(cfsalpha.scalingprofile, cfsalpha.scalingdirection, cfsalpha.min, cfsalpha.max, cfskappa.scalingprofile, cfskappa.scalingdirection, cfskappa.min, cfskappa.max, cfssigma.scalingprofile, cfssigma.scalingdirection, cfssigma.min, cfssigma.max))
+
+ grid.cfs.append(cfs)
+
+class Subgrid(UserObjectMulti):
+
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ self.children_multiple = []
+ self.children_geometry = []
+
+ def add(self, node):
+ if isinstance(node, UserObjectMulti):
+ self.children_multiple.append(node)
+ elif isinstance(node, UserObjectGeometry):
+ self.children_geometry.append(node)
+ else:
+ raise Exception('This Object is Unknown to gprMax')
+
+class SubgridHSG(UserObjectMulti):
+
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
diff --git a/gprMax/cmds_single_use.py b/gprMax/cmds_single_use.py
index fe1aa759..634f01a6 100644
--- a/gprMax/cmds_single_use.py
+++ b/gprMax/cmds_single_use.py
@@ -1,526 +1,526 @@
-# Copyright (C) 2015-2019: The University of Edinburgh
-# Authors: Craig Warren and Antonis Giannopoulos
-#
-# This file is part of gprMax.
-#
-# gprMax is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# gprMax is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with gprMax. If not, see .
-
-import inspect
-import os
-import sys
-import decimal as d
-
-import gprMax.config as config
-from .config import c
-from .config import dtypes
-from .config import hostinfo
-from .exceptions import CmdInputError
-from .waveforms import Waveform
-from .utilities import round_value
-
-import numpy as np
-from scipy import interpolate
-from colorama import init
-from colorama import Fore
-from colorama import Style
-init()
-
-floattype = dtypes['float_or_double']
-
-class UserObjectSingle:
-
- """
- Specific geometry object
- """
-
- def __init__(self, **kwargs):
- # each single command has an order to specify the order in which
- # the commands are constructed. IE. discretisation must be
- # created before the domain
- self.order = None
- self.kwargs = kwargs
-
- def __str__(self):
- pass
-
- def create(self, grid, uip):
- pass
-
-
-class DomainSingle(UserObjectSingle):
-
- def __init__(self, **kwargs):
- # dont need to define parameters in advance. Just catch errors
- # when they occur
- super().__init__(**kwargs)
- self.order = 2
-
- def __str__(self):
- try:
- s = '#domain: {} {} {}'.format(self.kwargs['p1'][0],
- self.kwargs['p1'][1],
- self.kwargs['p1'][2])
- except KeyError:
- print('error message')
-
- return s
-
- def create(self, G, uip):
-
- # code to create the gprMax domain as per input_cmds_singleuse.py
- try:
- G.nx, G.ny, G.nz = uip.discretise_point(self.kwargs['p1'])
- except KeyError:
- raise CmdInputError(self.__str__ + ' Please specify a point')
-
- if G.nx == 0 or G.ny == 0 or G.nz == 0:
- raise CmdInputError(self.__str__ + ' requires at least one cell in every dimension')
- if config.is_messages():
- print('Domain size: {:g} x {:g} x {:g}m ({:d} x {:d} x {:d} = {:g} cells)'.format(self.kwargs['p1'][0], self.kwargs['p1'][1], self.kwargs['p1'][2], G.nx, G.ny, G.nz, (G.nx * G.ny * G.nz)))
-
- # Time step CFL limit (either 2D or 3D); switch off appropriate PMLs for 2D
- if G.nx == 1:
- G.dt = 1 / (c * np.sqrt((1 / G.dy) * (1 / G.dy) + (1 / G.dz) * (1 / G.dz)))
- G.mode = '2D TMx'
- G.pmlthickness['x0'] = 0
- G.pmlthickness['xmax'] = 0
- elif G.ny == 1:
- G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dz) * (1 / G.dz)))
- G.mode = '2D TMy'
- G.pmlthickness['y0'] = 0
- G.pmlthickness['ymax'] = 0
- elif G.nz == 1:
- G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dy) * (1 / G.dy)))
- G.mode = '2D TMz'
- G.pmlthickness['z0'] = 0
- G.pmlthickness['zmax'] = 0
- else:
- G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dy) * (1 / G.dy) + (1 / G.dz) * (1 / G.dz)))
- G.mode = '3D'
-
- # Round down time step to nearest float with precision one less than hardware maximum. Avoids inadvertently exceeding the CFL due to binary representation of floating point number.
- # Round down time step to nearest float with precision one less than hardware maximum.
- # Avoids inadvertently exceeding the CFL due to binary representation of floating point number.
- G.dt = round_value(G.dt, decimalplaces=d.getcontext().prec - 1)
-
- if config.is_messages():
- print('Mode: {}'.format(G.mode))
- print('Time step (at CFL limit): {:g} secs'.format(G.dt))
-
- # Number of threads (OpenMP) to use
- if sys.platform == 'darwin':
- os.environ['OMP_WAIT_POLICY'] = 'ACTIVE' # Should waiting threads consume CPU power (can drastically effect performance)
- os.environ['OMP_DYNAMIC'] = 'FALSE' # Number of threads may be adjusted by the run time environment to best utilize system resources
- os.environ['OMP_PLACES'] = 'cores' # Each place corresponds to a single core (having one or more hardware threads)
- os.environ['OMP_PROC_BIND'] = 'TRUE' # Bind threads to physical cores
- # os.environ['OMP_DISPLAY_ENV'] = 'TRUE' # Prints OMP version and environment variables (useful for debug)
-
- # Catch bug with Windows Subsystem for Linux (https://github.com/Microsoft/BashOnWindows/issues/785)
- if 'Microsoft' in hostinfo['osversion']:
- os.environ['KMP_AFFINITY'] = 'disabled'
- del os.environ['OMP_PLACES']
- del os.environ['OMP_PROC_BIND']
-
- if os.environ.get('OMP_NUM_THREADS'):
- G.nthreads = int(os.environ.get('OMP_NUM_THREADS'))
- else:
- # Set number of threads to number of physical CPU cores
- G.nthreads = hostinfo['physicalcores']
- os.environ['OMP_NUM_THREADS'] = str(G.nthreads)
-
- if config.is_messages():
- print('Number of CPU (OpenMP) threads: {}'.format(G.nthreads))
- if G.nthreads > hostinfo['physicalcores']:
- print(Fore.RED + 'WARNING: You have specified more threads ({}) than available physical CPU cores ({}). This may lead to degraded performance.'.format(G.nthreads, hostinfo['physicalcores']) + Style.RESET_ALL)
-
-
-
-class Domain:
-
- """Restrict user object so there can only be one instance
- https://python-3-patterns-idioms-test.readthedocs.io/en/latest/index.html
- """
-
- instance = None
-
- def __new__(cls, **kwargs): # __new__ always a classmethod
- if not Domain.instance:
- Domain.instance = DomainSingle(**kwargs)
- return Domain.instance
-
-
-class Discretisation(UserObjectSingle):
-
- def __init__(self, **kwargs):
- # dont need to define parameters in advance. Just catch errors
- # when they occur
- super().__init__(**kwargs)
- self.order = 1
-
- def __str__(self):
- try:
- s = '#dx_dy_dz: {} {} {}'.format(self.kwargs['p1'][0],
- self.kwargs['p1'][1],
- self.kwargs['p1'][2])
- except KeyError:
- print('error message')
-
- return s
-
- def create(self, G, uip):
-
- try:
- G.dl = np.array(self.kwargs['p1'])
- G.dx, G.dy, G.dz = self.kwargs['p1']
-
- except KeyError:
- raise CmdInputError('Discretisation requires a point')
-
- if G.dl[0] <= 0:
- raise CmdInputError('Discretisation requires the x-direction spatial step to be greater than zero')
- if G.dl[1] <= 0:
- raise CmdInputError(' Discretisation requires the y-direction spatial step to be greater than zero')
- if G.dl[2] <= 0:
- raise CmdInputError('Discretisation requires the z-direction spatial step to be greater than zero')
-
- if config.is_messages():
- print('Spatial discretisation: {:g} x {:g} x {:g}m'.format(*G.dl))
-
-
-class TimeWindow(UserObjectSingle):
-
- def __init__(self, **kwargs):
- # dont need to define parameters in advance. Just catch errors
- # when they occur
- super().__init__(**kwargs)
- self.order = 4
-
- def __str__(self):
- try:
- s = '#time_window: {}'.format(self.kwargs['time'])
- except KeyError:
- try:
- s = '#time_window: {}'.format(self.kwargs['iterations'])
- except KeyError:
- print('time window error')
-
- return s
-
- def create(self, G, uip):
- # If number of iterations given
- # The +/- 1 used in calculating the number of iterations is to account for
- # the fact that the solver (iterations) loop runs from 0 to < G.iterations
- try:
- iterations = int(self.kwargs['iterations'])
- G.timewindow = (iterations - 1) * G.dt
- G.iterations = iterations
-
- except KeyError:
- pass
-
- try:
- tmp = float(self.kwargs['time'])
- if tmp > 0:
- G.timewindow = tmp
- G.iterations = int(np.ceil(tmp / G.dt)) + 1
- else:
- raise CmdInputError(self.__str__() + ' must have a value greater than zero')
-
- except KeyError:
- pass
-
- if not G.timewindow:
- raise CmdInputError('TimeWindow: Specify a time or number of iterations')
-
- if config.is_messages():
- print('Time window: {:g} secs ({} iterations)'.format(G.timewindow, G.iterations))
-
-
-class Messages(UserObjectSingle):
-
- def __init__(self, **kwargs):
- # dont need to define parameters in advance. Just catch errors
- # when they occur
- super().__init__(**kwargs)
- self.order = 0
-
- def __str__(self):
- try:
- s = '#messages: {}'.format(self.kwargs['yn'])
- except KeyError:
- print('messages problem')
-
- def create(self, G, uip):
- try:
- yn = self.kwargs['yn']
- except KeyError:
- raise CmdInputError(self.__str__() + ' requires exactly one parameter')
-
- if yn.lower() == 'y':
- config.general['messages'] = True
- elif yn.lower() == 'n':
- config.general['messages'] = False
- else:
- raise CmdInputError(self.__str__() + ' requires input values of either y or n')
-
-
-class Title(UserObjectSingle):
-
- def __init__(self, **kwargs):
- # dont need to define parameters in advance. Just catch errors
- # when they occur
- super().__init__(**kwargs)
- self.order = 5
-
- def create(self, G, uip):
- # Title
- try:
- title = self.kwargs['name']
- G.title = title
- except KeyError:
- pass
-
- if config.is_messages():
- print('Model title: {}'.format(G.title))
-
-class NumThreads(UserObjectSingle):
- def __init__(self, **kwargs):
- # dont need to define parameters in advance. Just catch errors
- # when they occur
- super().__init__(**kwargs)
- self.order = 6
-
- def __str__(self):
- try:
- return '#n_threads: {}'.format(self.kwargs['n'])
- except KeyError:
- return '#n_threads:'
-
- def create(self, G, uip):
- # Get information about host machine
-
- try:
- n = self.kwargs['n']
- except KeyError:
- raise CmdInputError(self.__str__() + ' requires exactly one parameter to specify the number of threads to use')
-
- if n < 1:
- raise CmdInputError(self.__str__() + ' requires the value to be an integer not less than one')
-
- G.nthreads = n
- os.environ['OMP_NUM_THREADS'] = str(G.nthreads)
-
- if config.is_messages():
- print('Number of CPU (OpenMP) threads: {}'.format(G.nthreads))
- if G.nthreads > hostinfo['physicalcores']:
- print(Fore.RED + 'WARNING: You have specified more threads ({}) than available physical CPU cores ({}). This may lead to degraded performance.'.format(G.nthreads, hostinfo['physicalcores']) + Style.RESET_ALL)
-
- # Print information about any GPU in use
- if config.is_messages():
- if G.gpu is not None:
- print('GPU solving using: {} - {}'.format(G.gpu.deviceID, G.gpu.name))
-
-
-# Time step stability factor
-class TimeStepStabilityFactor(UserObjectSingle):
-
- def __init__(self, **kwargs):
- # dont need to define parameters in advance. Just catch errors
- # when they occur
- super().__init__(**kwargs)
- self.order = 7
-
- def __str__(self):
-
- try:
- return '#time_step_stability_factor: {}'.format(self.kwargs['f'])
- except KeyError:
- return '#time_step_stability_factor:'
-
- def create(self, G, uip):
-
- try:
- f = self.kwargs['f']
- except KeyError:
- raise CmdInputError(self.__str__() + ' requires exactly one parameter')
-
- if f <= 0 or f > 1:
- raise CmdInputError(self.__str__() + ' requires the value of the time step stability factor to be between zero and one')
- G.dt = G.dt * f
- if config.is_messages():
- print('Time step (modified): {:g} secs'.format(G.dt))
-
-
-class PMLCells(UserObjectSingle):
-
- def __init__(self, **kwargs):
- # dont need to define parameters in advance. Just catch errors
- # when they occur
- super().__init__(**kwargs)
- self.order = 8
-
- def create(self, G, uip):
-
- try:
- thickness = self.kwargs['thickness']
-
- for key in G.pmlthickness.keys():
- G.pmlthickness[key] = int(thickness)
-
- except KeyError:
- try:
- G.pmlthickness['x0'] = int(self.kwargs['x0'])
- G.pmlthickness['y0'] = int(self.kwargs['y0'])
- G.pmlthickness['z0'] = int(self.kwargs['z0'])
- G.pmlthickness['xmax'] = int(self.kwargs['xmax'])
- G.pmlthickness['ymax'] = int(self.kwargs['ymax'])
- G.pmlthickness['zmax'] = int(self.kwargs['zmax'])
- except KeyError:
- raise CmdInputError('#pml_cells: requires either one or six parameter(s)')
-
- if (2 * G.pmlthickness['x0'] >= G.nx or
- 2 * G.pmlthickness['y0'] >= G.ny or
- 2 * G.pmlthickness['z0'] >= G.nz or
- 2 * G.pmlthickness['xmax'] >= G.nx or
- 2 * G.pmlthickness['ymax'] >= G.ny or
- 2 * G.pmlthickness['zmax'] >= G.nz):
- raise CmdInputError('#pml_thickness: has too many cells for the domain size')
-
-
-class SrcSteps(UserObjectSingle):
-
- def __init__(self, **kwargs):
- # dont need to define parameters in advance. Just catch errors
- # when they occur
- super().__init__(**kwargs)
- self.order = 9
-
- def create(self, G, uip):
- try:
- G.srcsteps = uip.discretise_point(self.kwargs['p1'])
- except KeyError:
- raise CmdInputError('#src_steps: requires exactly three parameters')
- # src_steps
- if config.is_messages():
- print('Simple sources will step {:g}m, {:g}m, {:g}m for each model run.'.format(G.srcsteps[0] * G.dx, G.srcsteps[1] * G.dy, G.srcsteps[2] * G.dz))
-
-
-class RxSteps(UserObjectSingle):
-
- def __init__(self, **kwargs):
- # dont need to define parameters in advance. Just catch errors
- # when they occur
- super().__init__(**kwargs)
- self.order = 10
-
- def create(self, G, uip):
- try:
- G.rxsteps = uip.discretise_point(self.kwargs['p1'])
- except KeyError:
- raise CmdInputError('#rx_steps: requires exactly three parameters')
- if config.is_messages():
- print('All receivers will step {:g}m, {:g}m, {:g}m for each model run.'.format(G.rxsteps[0] * G.dx, G.rxsteps[1] * G.dy, G.rxsteps[2] * G.dz))
-
-
-class ExcitationFile(UserObjectSingle):
-
- def create(self, G, uip):
- # Excitation file for user-defined source waveforms
- try:
- kwargs = dict()
- excitationfile = self.kwargs['filepath']
- kwargs['kind'] = self.kwargs['kind']
- kwargs['fill_value'] = self.kwargs['fill_value']
-
- except KeyError:
- try:
- excitationfile = self.kwargs['filepath']
- args, varargs, keywords, defaults = inspect.getargspec(interpolate.interp1d)
- kwargs = dict(zip(reversed(args), reversed(defaults)))
- except KeyError:
- raise CmdInputError('#excitation_file: requires either one or three parameter(s)')
-
- # See if file exists at specified path and if not try input file directory
- if not os.path.isfile(excitationfile):
- excitationfile = os.path.abspath(os.path.join(G.inputdirectory, excitationfile))
-
- if config.is_messages():
- print('\nExcitation file: {}'.format(excitationfile))
-
- # Get waveform names
- with open(excitationfile, 'r') as f:
- waveformIDs = f.readline().split()
-
- # Read all waveform values into an array
- waveformvalues = np.loadtxt(excitationfile, skiprows=1, dtype=floattype)
-
- # Time array (if specified) for interpolation, otherwise use simulation time
- if waveformIDs[0].lower() == 'time':
- waveformIDs = waveformIDs[1:]
- waveformtime = waveformvalues[:, 0]
- waveformvalues = waveformvalues[:, 1:]
- timestr = 'user-defined time array'
- else:
- waveformtime = np.arange(0, G.timewindow + G.dt, G.dt)
- timestr = 'simulation time array'
-
- for waveform in range(len(waveformIDs)):
- if any(x.ID == waveformIDs[waveform] for x in G.waveforms):
- raise CmdInputError('Waveform with ID {} already exists'.format(waveformIDs[waveform]))
- w = Waveform()
- w.ID = waveformIDs[waveform]
- w.type = 'user'
-
- # Select correct column of waveform values depending on array shape
- singlewaveformvalues = waveformvalues[:] if len(waveformvalues.shape) == 1 else waveformvalues[:, waveform]
-
- # Truncate waveform array if it is longer than time array
- if len(singlewaveformvalues) > len(waveformtime):
- singlewaveformvalues = singlewaveformvalues[:len(waveformtime)]
- # Zero-pad end of waveform array if it is shorter than time array
- elif len(singlewaveformvalues) < len(waveformtime):
- singlewaveformvalues = np.lib.pad(singlewaveformvalues, (0, len(singlewaveformvalues) - len(waveformvalues)), 'constant', constant_values=0)
-
- # Interpolate waveform values
- w.userfunc = interpolate.interp1d(waveformtime, singlewaveformvalues, **kwargs)
-
- if config.is_messages():
- print('User waveform {} created using {} and, if required, interpolation parameters (kind: {}, fill value: {}).'.format(w.ID, timestr, kwargs['kind'], kwargs['fill_value']))
-
- G.waveforms.append(w)
-
-
-class OutputDir(UserObjectSingle):
-
- def __init__(self, **kwargs):
- # dont need to define parameters in advance. Just catch errors
- # when they occur
- super().__init__(**kwargs)
- self.order = 11
-
- def create(self, grid, uip):
- grid.outputdirectory = self.kwargs['dir']
-
-
-class NumberOfModelRuns(UserObjectSingle):
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- self.order = 12
-
- def create(self, grid, uip):
- try:
- grid.numberofmodelruns = self.kwargs['n']
- except KeyError:
- raise CmdInputError('#numberofmodelruns: requires exactly one parameter')
+# Copyright (C) 2015-2019: The University of Edinburgh
+# Authors: Craig Warren and Antonis Giannopoulos
+#
+# This file is part of gprMax.
+#
+# gprMax is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# gprMax is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with gprMax. If not, see .
+
+import inspect
+import os
+import sys
+import decimal as d
+
+import gprMax.config as config
+from .config import c
+from .config import dtypes
+from .config import hostinfo
+from .exceptions import CmdInputError
+from .waveforms import Waveform
+from .utilities import round_value
+
+import numpy as np
+from scipy import interpolate
+from colorama import init
+from colorama import Fore
+from colorama import Style
+init()
+
+floattype = dtypes['float_or_double']
+
+class UserObjectSingle:
+
+ """
+ Specific geometry object
+ """
+
+ def __init__(self, **kwargs):
+ # each single command has an order to specify the order in which
+ # the commands are constructed. IE. discretisation must be
+ # created before the domain
+ self.order = None
+ self.kwargs = kwargs
+
+ def __str__(self):
+ pass
+
+ def create(self, grid, uip):
+ pass
+
+
+class DomainSingle(UserObjectSingle):
+
+ def __init__(self, **kwargs):
+ # dont need to define parameters in advance. Just catch errors
+ # when they occur
+ super().__init__(**kwargs)
+ self.order = 2
+
+ def __str__(self):
+ try:
+ s = '#domain: {} {} {}'.format(self.kwargs['p1'][0],
+ self.kwargs['p1'][1],
+ self.kwargs['p1'][2])
+ except KeyError:
+ print('error message')
+
+ return s
+
+ def create(self, G, uip):
+
+ # code to create the gprMax domain as per input_cmds_singleuse.py
+ try:
+ G.nx, G.ny, G.nz = uip.discretise_point(self.kwargs['p1'])
+ except KeyError:
+ raise CmdInputError(self.__str__ + ' Please specify a point')
+
+ if G.nx == 0 or G.ny == 0 or G.nz == 0:
+ raise CmdInputError(self.__str__ + ' requires at least one cell in every dimension')
+ if config.is_messages():
+ print('Domain size: {:g} x {:g} x {:g}m ({:d} x {:d} x {:d} = {:g} cells)'.format(self.kwargs['p1'][0], self.kwargs['p1'][1], self.kwargs['p1'][2], G.nx, G.ny, G.nz, (G.nx * G.ny * G.nz)))
+
+ # Time step CFL limit (either 2D or 3D); switch off appropriate PMLs for 2D
+ if G.nx == 1:
+ G.dt = 1 / (c * np.sqrt((1 / G.dy) * (1 / G.dy) + (1 / G.dz) * (1 / G.dz)))
+ G.mode = '2D TMx'
+ G.pmlthickness['x0'] = 0
+ G.pmlthickness['xmax'] = 0
+ elif G.ny == 1:
+ G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dz) * (1 / G.dz)))
+ G.mode = '2D TMy'
+ G.pmlthickness['y0'] = 0
+ G.pmlthickness['ymax'] = 0
+ elif G.nz == 1:
+ G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dy) * (1 / G.dy)))
+ G.mode = '2D TMz'
+ G.pmlthickness['z0'] = 0
+ G.pmlthickness['zmax'] = 0
+ else:
+ G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dy) * (1 / G.dy) + (1 / G.dz) * (1 / G.dz)))
+ G.mode = '3D'
+
+ # Round down time step to nearest float with precision one less than hardware maximum. Avoids inadvertently exceeding the CFL due to binary representation of floating point number.
+ # Round down time step to nearest float with precision one less than hardware maximum.
+ # Avoids inadvertently exceeding the CFL due to binary representation of floating point number.
+ G.dt = round_value(G.dt, decimalplaces=d.getcontext().prec - 1)
+
+ if config.is_messages():
+ print('Mode: {}'.format(G.mode))
+ print('Time step (at CFL limit): {:g} secs'.format(G.dt))
+
+ # Number of threads (OpenMP) to use
+ if sys.platform == 'darwin':
+ os.environ['OMP_WAIT_POLICY'] = 'ACTIVE' # Should waiting threads consume CPU power (can drastically effect performance)
+ os.environ['OMP_DYNAMIC'] = 'FALSE' # Number of threads may be adjusted by the run time environment to best utilize system resources
+ os.environ['OMP_PLACES'] = 'cores' # Each place corresponds to a single core (having one or more hardware threads)
+ os.environ['OMP_PROC_BIND'] = 'TRUE' # Bind threads to physical cores
+ # os.environ['OMP_DISPLAY_ENV'] = 'TRUE' # Prints OMP version and environment variables (useful for debug)
+
+ # Catch bug with Windows Subsystem for Linux (https://github.com/Microsoft/BashOnWindows/issues/785)
+ if 'Microsoft' in hostinfo['osversion']:
+ os.environ['KMP_AFFINITY'] = 'disabled'
+ del os.environ['OMP_PLACES']
+ del os.environ['OMP_PROC_BIND']
+
+ if os.environ.get('OMP_NUM_THREADS'):
+ G.nthreads = int(os.environ.get('OMP_NUM_THREADS'))
+ else:
+ # Set number of threads to number of physical CPU cores
+ G.nthreads = hostinfo['physicalcores']
+ os.environ['OMP_NUM_THREADS'] = str(G.nthreads)
+
+ if config.is_messages():
+ print('Number of CPU (OpenMP) threads: {}'.format(G.nthreads))
+ if G.nthreads > hostinfo['physicalcores']:
+ print(Fore.RED + 'WARNING: You have specified more threads ({}) than available physical CPU cores ({}). This may lead to degraded performance.'.format(G.nthreads, hostinfo['physicalcores']) + Style.RESET_ALL)
+
+
+
+class Domain:
+
+ """Restrict user object so there can only be one instance
+ https://python-3-patterns-idioms-test.readthedocs.io/en/latest/index.html
+ """
+
+ instance = None
+
+ def __new__(cls, **kwargs): # __new__ always a classmethod
+ if not Domain.instance:
+ Domain.instance = DomainSingle(**kwargs)
+ return Domain.instance
+
+
+class Discretisation(UserObjectSingle):
+
+ def __init__(self, **kwargs):
+ # dont need to define parameters in advance. Just catch errors
+ # when they occur
+ super().__init__(**kwargs)
+ self.order = 1
+
+ def __str__(self):
+ try:
+ s = '#dx_dy_dz: {} {} {}'.format(self.kwargs['p1'][0],
+ self.kwargs['p1'][1],
+ self.kwargs['p1'][2])
+ except KeyError:
+ print('error message')
+
+ return s
+
+ def create(self, G, uip):
+
+ try:
+ G.dl = np.array(self.kwargs['p1'])
+ G.dx, G.dy, G.dz = self.kwargs['p1']
+
+ except KeyError:
+ raise CmdInputError('Discretisation requires a point')
+
+ if G.dl[0] <= 0:
+ raise CmdInputError('Discretisation requires the x-direction spatial step to be greater than zero')
+ if G.dl[1] <= 0:
+ raise CmdInputError(' Discretisation requires the y-direction spatial step to be greater than zero')
+ if G.dl[2] <= 0:
+ raise CmdInputError('Discretisation requires the z-direction spatial step to be greater than zero')
+
+ if config.is_messages():
+ print('Spatial discretisation: {:g} x {:g} x {:g}m'.format(*G.dl))
+
+
+class TimeWindow(UserObjectSingle):
+
+ def __init__(self, **kwargs):
+ # dont need to define parameters in advance. Just catch errors
+ # when they occur
+ super().__init__(**kwargs)
+ self.order = 4
+
+ def __str__(self):
+ try:
+ s = '#time_window: {}'.format(self.kwargs['time'])
+ except KeyError:
+ try:
+ s = '#time_window: {}'.format(self.kwargs['iterations'])
+ except KeyError:
+ print('time window error')
+
+ return s
+
+ def create(self, G, uip):
+ # If number of iterations given
+ # The +/- 1 used in calculating the number of iterations is to account for
+ # the fact that the solver (iterations) loop runs from 0 to < G.iterations
+ try:
+ iterations = int(self.kwargs['iterations'])
+ G.timewindow = (iterations - 1) * G.dt
+ G.iterations = iterations
+
+ except KeyError:
+ pass
+
+ try:
+ tmp = float(self.kwargs['time'])
+ if tmp > 0:
+ G.timewindow = tmp
+ G.iterations = int(np.ceil(tmp / G.dt)) + 1
+ else:
+ raise CmdInputError(self.__str__() + ' must have a value greater than zero')
+
+ except KeyError:
+ pass
+
+ if not G.timewindow:
+ raise CmdInputError('TimeWindow: Specify a time or number of iterations')
+
+ if config.is_messages():
+ print('Time window: {:g} secs ({} iterations)'.format(G.timewindow, G.iterations))
+
+
+class Messages(UserObjectSingle):
+
+ def __init__(self, **kwargs):
+ # dont need to define parameters in advance. Just catch errors
+ # when they occur
+ super().__init__(**kwargs)
+ self.order = 0
+
+ def __str__(self):
+ try:
+ s = '#messages: {}'.format(self.kwargs['yn'])
+ except KeyError:
+ print('messages problem')
+
+ def create(self, G, uip):
+ try:
+ yn = self.kwargs['yn']
+ except KeyError:
+ raise CmdInputError(self.__str__() + ' requires exactly one parameter')
+
+ if yn.lower() == 'y':
+ config.general['messages'] = True
+ elif yn.lower() == 'n':
+ config.general['messages'] = False
+ else:
+ raise CmdInputError(self.__str__() + ' requires input values of either y or n')
+
+
+class Title(UserObjectSingle):
+
+ def __init__(self, **kwargs):
+ # dont need to define parameters in advance. Just catch errors
+ # when they occur
+ super().__init__(**kwargs)
+ self.order = 5
+
+ def create(self, G, uip):
+ # Title
+ try:
+ title = self.kwargs['name']
+ G.title = title
+ except KeyError:
+ pass
+
+ if config.is_messages():
+ print('Model title: {}'.format(G.title))
+
+class NumThreads(UserObjectSingle):
+ def __init__(self, **kwargs):
+ # dont need to define parameters in advance. Just catch errors
+ # when they occur
+ super().__init__(**kwargs)
+ self.order = 6
+
+ def __str__(self):
+ try:
+ return '#n_threads: {}'.format(self.kwargs['n'])
+ except KeyError:
+ return '#n_threads:'
+
+ def create(self, G, uip):
+ # Get information about host machine
+
+ try:
+ n = self.kwargs['n']
+ except KeyError:
+ raise CmdInputError(self.__str__() + ' requires exactly one parameter to specify the number of threads to use')
+
+ if n < 1:
+ raise CmdInputError(self.__str__() + ' requires the value to be an integer not less than one')
+
+ G.nthreads = n
+ os.environ['OMP_NUM_THREADS'] = str(G.nthreads)
+
+ if config.is_messages():
+ print('Number of CPU (OpenMP) threads: {}'.format(G.nthreads))
+ if G.nthreads > hostinfo['physicalcores']:
+ print(Fore.RED + 'WARNING: You have specified more threads ({}) than available physical CPU cores ({}). This may lead to degraded performance.'.format(G.nthreads, hostinfo['physicalcores']) + Style.RESET_ALL)
+
+ # Print information about any GPU in use
+ if config.is_messages():
+ if G.gpu is not None:
+ print('GPU solving using: {} - {}'.format(G.gpu.deviceID, G.gpu.name))
+
+
+# Time step stability factor
+class TimeStepStabilityFactor(UserObjectSingle):
+
+ def __init__(self, **kwargs):
+ # dont need to define parameters in advance. Just catch errors
+ # when they occur
+ super().__init__(**kwargs)
+ self.order = 7
+
+ def __str__(self):
+
+ try:
+ return '#time_step_stability_factor: {}'.format(self.kwargs['f'])
+ except KeyError:
+ return '#time_step_stability_factor:'
+
+ def create(self, G, uip):
+
+ try:
+ f = self.kwargs['f']
+ except KeyError:
+ raise CmdInputError(self.__str__() + ' requires exactly one parameter')
+
+ if f <= 0 or f > 1:
+ raise CmdInputError(self.__str__() + ' requires the value of the time step stability factor to be between zero and one')
+ G.dt = G.dt * f
+ if config.is_messages():
+ print('Time step (modified): {:g} secs'.format(G.dt))
+
+
+class PMLCells(UserObjectSingle):
+
+ def __init__(self, **kwargs):
+ # dont need to define parameters in advance. Just catch errors
+ # when they occur
+ super().__init__(**kwargs)
+ self.order = 8
+
+ def create(self, G, uip):
+
+ try:
+ thickness = self.kwargs['thickness']
+
+ for key in G.pmlthickness.keys():
+ G.pmlthickness[key] = int(thickness)
+
+ except KeyError:
+ try:
+ G.pmlthickness['x0'] = int(self.kwargs['x0'])
+ G.pmlthickness['y0'] = int(self.kwargs['y0'])
+ G.pmlthickness['z0'] = int(self.kwargs['z0'])
+ G.pmlthickness['xmax'] = int(self.kwargs['xmax'])
+ G.pmlthickness['ymax'] = int(self.kwargs['ymax'])
+ G.pmlthickness['zmax'] = int(self.kwargs['zmax'])
+ except KeyError:
+ raise CmdInputError('#pml_cells: requires either one or six parameter(s)')
+
+ if (2 * G.pmlthickness['x0'] >= G.nx or
+ 2 * G.pmlthickness['y0'] >= G.ny or
+ 2 * G.pmlthickness['z0'] >= G.nz or
+ 2 * G.pmlthickness['xmax'] >= G.nx or
+ 2 * G.pmlthickness['ymax'] >= G.ny or
+ 2 * G.pmlthickness['zmax'] >= G.nz):
+ raise CmdInputError('#pml_thickness: has too many cells for the domain size')
+
+
+class SrcSteps(UserObjectSingle):
+
+ def __init__(self, **kwargs):
+ # dont need to define parameters in advance. Just catch errors
+ # when they occur
+ super().__init__(**kwargs)
+ self.order = 9
+
+ def create(self, G, uip):
+ try:
+ G.srcsteps = uip.discretise_point(self.kwargs['p1'])
+ except KeyError:
+ raise CmdInputError('#src_steps: requires exactly three parameters')
+ # src_steps
+ if config.is_messages():
+ print('Simple sources will step {:g}m, {:g}m, {:g}m for each model run.'.format(G.srcsteps[0] * G.dx, G.srcsteps[1] * G.dy, G.srcsteps[2] * G.dz))
+
+
+class RxSteps(UserObjectSingle):
+
+ def __init__(self, **kwargs):
+ # dont need to define parameters in advance. Just catch errors
+ # when they occur
+ super().__init__(**kwargs)
+ self.order = 10
+
+ def create(self, G, uip):
+ try:
+ G.rxsteps = uip.discretise_point(self.kwargs['p1'])
+ except KeyError:
+ raise CmdInputError('#rx_steps: requires exactly three parameters')
+ if config.is_messages():
+ print('All receivers will step {:g}m, {:g}m, {:g}m for each model run.'.format(G.rxsteps[0] * G.dx, G.rxsteps[1] * G.dy, G.rxsteps[2] * G.dz))
+
+
+class ExcitationFile(UserObjectSingle):
+
+ def create(self, G, uip):
+ # Excitation file for user-defined source waveforms
+ try:
+ kwargs = dict()
+ excitationfile = self.kwargs['filepath']
+ kwargs['kind'] = self.kwargs['kind']
+ kwargs['fill_value'] = self.kwargs['fill_value']
+
+ except KeyError:
+ try:
+ excitationfile = self.kwargs['filepath']
+ args, varargs, keywords, defaults = inspect.getargspec(interpolate.interp1d)
+ kwargs = dict(zip(reversed(args), reversed(defaults)))
+ except KeyError:
+ raise CmdInputError('#excitation_file: requires either one or three parameter(s)')
+
+ # See if file exists at specified path and if not try input file directory
+ if not os.path.isfile(excitationfile):
+ excitationfile = os.path.abspath(os.path.join(G.inputdirectory, excitationfile))
+
+ if config.is_messages():
+ print('\nExcitation file: {}'.format(excitationfile))
+
+ # Get waveform names
+ with open(excitationfile, 'r') as f:
+ waveformIDs = f.readline().split()
+
+ # Read all waveform values into an array
+ waveformvalues = np.loadtxt(excitationfile, skiprows=1, dtype=floattype)
+
+ # Time array (if specified) for interpolation, otherwise use simulation time
+ if waveformIDs[0].lower() == 'time':
+ waveformIDs = waveformIDs[1:]
+ waveformtime = waveformvalues[:, 0]
+ waveformvalues = waveformvalues[:, 1:]
+ timestr = 'user-defined time array'
+ else:
+ waveformtime = np.arange(0, G.timewindow + G.dt, G.dt)
+ timestr = 'simulation time array'
+
+ for waveform in range(len(waveformIDs)):
+ if any(x.ID == waveformIDs[waveform] for x in G.waveforms):
+ raise CmdInputError('Waveform with ID {} already exists'.format(waveformIDs[waveform]))
+ w = Waveform()
+ w.ID = waveformIDs[waveform]
+ w.type = 'user'
+
+ # Select correct column of waveform values depending on array shape
+ singlewaveformvalues = waveformvalues[:] if len(waveformvalues.shape) == 1 else waveformvalues[:, waveform]
+
+ # Truncate waveform array if it is longer than time array
+ if len(singlewaveformvalues) > len(waveformtime):
+ singlewaveformvalues = singlewaveformvalues[:len(waveformtime)]
+ # Zero-pad end of waveform array if it is shorter than time array
+ elif len(singlewaveformvalues) < len(waveformtime):
+ singlewaveformvalues = np.lib.pad(singlewaveformvalues, (0, len(singlewaveformvalues) - len(waveformvalues)), 'constant', constant_values=0)
+
+ # Interpolate waveform values
+ w.userfunc = interpolate.interp1d(waveformtime, singlewaveformvalues, **kwargs)
+
+ if config.is_messages():
+ print('User waveform {} created using {} and, if required, interpolation parameters (kind: {}, fill value: {}).'.format(w.ID, timestr, kwargs['kind'], kwargs['fill_value']))
+
+ G.waveforms.append(w)
+
+
+class OutputDir(UserObjectSingle):
+
+ def __init__(self, **kwargs):
+ # dont need to define parameters in advance. Just catch errors
+ # when they occur
+ super().__init__(**kwargs)
+ self.order = 11
+
+ def create(self, grid, uip):
+ grid.outputdirectory = self.kwargs['dir']
+
+
+class NumberOfModelRuns(UserObjectSingle):
+
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ self.order = 12
+
+ def create(self, grid, uip):
+ try:
+ grid.numberofmodelruns = self.kwargs['n']
+ except KeyError:
+ raise CmdInputError('#numberofmodelruns: requires exactly one parameter')
diff --git a/gprMax/config.py b/gprMax/config.py
index 1e6879bf..ad5f54b9 100644
--- a/gprMax/config.py
+++ b/gprMax/config.py
@@ -1,267 +1,267 @@
-# Copyright (C) 2015-2019: The University of Edinburgh
-# Authors: Craig Warren and Antonis Giannopoulos
-#
-# This file is part of gprMax.
-#
-# gprMax is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# gprMax is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with gprMax. If not, see .
-
-import os
-
-import cython
-import numpy as np
-from scipy.constants import c
-from scipy.constants import mu_0 as m0
-from scipy.constants import epsilon_0 as e0
-import sys
-
-from .utilities import get_terminal_width
-from .utilities import get_host_info
-from pathlib import Path
-
-from colorama import init
-from colorama import Fore
-from colorama import Style
-init()
-
-
-# Impedance of free space (Ohms)
-z0 = np.sqrt(m0 / e0)
-
-# General setting for the simulation
-# inputfilepath: path to inputfile location
-# outputfilepath: path to outputfile location
-# messages: whether to print all messages as output to stdout or not
-# progressbars: whether to show progress bars on stdoout or not
-# mode: 2D TMx, 2D TMy, 2D TMz, or 3D
-# cpu, cuda, opencl: solver type
-# autotranslate: 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.
-general = {'inputfilepath': 'gprMax', 'outputfilepath': 'gprMax', 'messages': True,
- 'progressbars': True, 'mode': '3D', 'cpu': True, 'cuda': False, 'opencl': False, 'autotranslate': False}
-
-
-def is_messages():
- """Function to return messages."""
- return general['messages']
-
-
-# Store information about host machine
-hostinfo = get_host_info()
-
-# Store information for CUDA solver type
-# gpus: information about any GPUs as a list of GPU objects
-# snapsgpu2cpu: copy snapshot data from GPU to CPU during simulation
-# N.B. This will happen if the requested snapshots are too large to fit
-# on the memory of the GPU. If True this will slow performance significantly
-cuda = {'gpus': None, 'snapsgpu2cpu': False}
-
-# Numerical dispersion analysis parameters
-# highestfreqthres: threshold (dB) down from maximum power (0dB) of main frequency used
-# to calculate highest frequency for numerical dispersion analysis
-# maxnumericaldisp: maximum allowable percentage physical phase-velocity phase error
-# mingridsampling: minimum grid sampling of smallest wavelength for physical wave propagation
-numdispersion = {'highestfreqthres': 40, 'maxnumericaldisp': 2, 'mingridsampling': 3}
-
-# Materials
-# maxpoles: Maximum number of dispersive material poles in a model
-materials = {'maxpoles': 0, 'dispersivedtype': None, 'dispersiveCdtype': None}
-
-# Data types
-# Solid and ID arrays use 32-bit integers (0 to 4294967295)
-# Rigid arrays use 8-bit integers (the smallest available type to store true/false)
-# Fractal and dispersive coefficient arrays use complex numbers (complex)
-# which are represented as two floats
-# Main field arrays use floats (float_or_double) and complex numbers (complex)
-# Precision of float_or_double and complex: single or double for numpy and C (CUDA) arrays
-precision = 'double'
-
-if precision == 'single':
- dtypes = {'float_or_double': np.float32, 'complex': np.complex64,
- 'cython_float_or_double': cython.float, 'cython_complex': cython.floatcomplex,
- 'C_float_or_double': 'float', 'C_complex': 'pycuda::complex'}
-elif precision == 'double':
- dtypes = {'float_or_double': np.float64, 'complex': np.complex128,
- 'cython_float_or_double': cython.double, 'cython_complex': cython.doublecomplex,
- 'C_float_or_double': 'double', 'C_complex': 'pycuda::complex'}
-
-
-class ModelConfig():
-
- def __init__(self, sim_config, i):
- self.sim_config = sim_config
- self.reuse_geometry = False
-
- # current model number (indexed from 0)
- self.i = i
-
- parts = self.sim_config.output_file_path.parts
-
- if not sim_config.single_model:
- # 1 indexed
- self.appendmodelnumber = str(self.i + 1)
- else:
- self.appendmodelnumber = ''
-
- # outputfilepath for specific model
- self.output_file_path = Path(*parts[:-2], parts[-1] + self.appendmodelnumber)
- self.output_file_path_ext = self.output_file_path.with_suffix('.out')
-
- # make a snapshot directory
- stem = parts[-1] + '_snaps' + self.appendmodelnumber
- self.snapshot_dir = Path(*parts[:-2], stem)
-
- inputfilestr_f = '\n--- Model {}/{}, input file: {}'
- self.inputfilestr = inputfilestr_f.format(self.i + 1, self.sim_config.model_end, self.sim_config.input_file_path)
- # string to print at start of each model run
- self.next_model = Fore.GREEN + '{} {}\n'.format(self.inputfilestr, '-' * (get_terminal_width() - 1 - len(self.inputfilestr))) + Style.RESET_ALL
-
- # Add the current model run to namespace that can be accessed by
- # user in any Python code blocks in input file
- #self.usernamespace['current_model_run'] = self.i + 1
-
- def get_scene(self):
- if self.sim_config.scenes:
- return self.sim_config.scenes[self.i]
- else: return None
-
- def get_usernamespace(self):
- return {'c': c,
- 'e0': e0,
- 'm0': m0,
- 'z0': z0,
- 'number_model_runs': self.sim_config.model_end + 1,
- 'current_model_run': self.i + 1,
- 'inputfile': self.sim_config.input_file_path.resolve()}
-
-
-class SimulationConfig:
-
- def __init__(self, args):
- """Adapter for args into Simulation level configuration"""
-
- # adapt the arg properties to link smoothly with MPIRunner(), CPURunner() etc..
-
- # args.inputfile
- # args.n
- # args.task
- # args.restart
- # args.mpi
- # args.mpi_no_spawn
- # args.mpicomm
- # args.gpu
- # args.benchmark
- # args.geometry_only
- # args.geometry_fixed
- # args.write_processed
-
- self.args = args
- self.n_models = args.n
- self.inputfile = args.inputfile
- self.gpu = args.gpu
- self.mpi = args.mpi
- self.mpi_no_spawn = args.mpi_no_spawn
- self.general = {}
- self.general['messages'] = general['messages']
- self.geometry_fixed = args.geometry_fixed
- self.geometry_only = args.geometry_only
- self.write_processed = args.write_processed
-
- # subgrid parameter may not exist if user uses CLI api
- try:
- self.subgrid = args.subgrid
- except AttributeError:
- # this must be CLI user. No subgrids are available
- self.subgrid = False
-
- # scenes parameter may not exist if user uses CLI api
- try:
- self.scenes = args.scenes
- except AttributeError:
- self.scenes = []
-
- # set more complex parameters
- self.set_input_file_path()
- self.set_output_file_path()
- self.set_model_start_end()
- self.set_single_model()
-
- def set_single_model(self):
- if self.model_start == 0 and self.model_end == 1:
- self.single_model = True
- else:
- self.single_model = False
-
- # for example
- def set_model_start_end(self):
-
- # set range for number of models to run (internally 0 index)
- if self.args.task:
- # Job array feeds args.n number of single tasks
- modelstart = self.args.task - 1
- modelend = self.args.task
- elif self.args.restart:
- modelstart = self.args.restart - 1
- modelend = modelstart + self.args.n - 1
- else:
- modelstart = 0
- modelend = modelstart + self.args.n
-
- self.model_start = modelstart
- self.model_end = modelend
-
- def set_precision(self):
- pass
-
- def set_input_file_path(self):
- """Function to set to inputfile path"""
-
- # if the API is in use an id for the simulation must be provided.
- if self.args.inputfile is None:
- self.input_file_path = Path(self.args.outputfile)
- else:
- self.input_file_path = Path(self.args.inputfile)
-
- def set_output_file_path(self):
- # output file can be provided by the user. if they havent provided None
- # use the inputfilefile path instead
- try:
- self.output_file_path = Path(self.args.outputfile)
- except AttributeError:
- self.output_file_path = Path(self.args.inputfile)
-
-
-class SimulationConfigMPI(SimulationConfig):
-
- def __init__(self, args):
- super().__init__(args)
-
- def set_model_start_end(self):
- # Set range for number of models to run
- self.model_start = self.args.restart if self.args.restart else 1
- self.model_end = self.modelstart + self.args.n
-
-
-def create_simulation_config(args):
-
- if not args.mpi and not args.mpi_no_spawn:
- sc = SimulationConfig(args)
- elif args.mpi:
- sc = SimulationConfigMPI(args)
- return sc
-
-
-def create_model_config(sim_config, i):
- mc = ModelConfig(sim_config, i)
- return mc
+# Copyright (C) 2015-2019: The University of Edinburgh
+# Authors: Craig Warren and Antonis Giannopoulos
+#
+# This file is part of gprMax.
+#
+# gprMax is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# gprMax is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with gprMax. If not, see .
+
+import os
+
+import cython
+import numpy as np
+from scipy.constants import c
+from scipy.constants import mu_0 as m0
+from scipy.constants import epsilon_0 as e0
+import sys
+
+from .utilities import get_terminal_width
+from .utilities import get_host_info
+from pathlib import Path
+
+from colorama import init
+from colorama import Fore
+from colorama import Style
+init()
+
+
+# Impedance of free space (Ohms)
+z0 = np.sqrt(m0 / e0)
+
+# General setting for the simulation
+# inputfilepath: path to inputfile location
+# outputfilepath: path to outputfile location
+# messages: whether to print all messages as output to stdout or not
+# progressbars: whether to show progress bars on stdoout or not
+# mode: 2D TMx, 2D TMy, 2D TMz, or 3D
+# cpu, cuda, opencl: solver type
+# autotranslate: 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.
+general = {'inputfilepath': 'gprMax', 'outputfilepath': 'gprMax', 'messages': True,
+ 'progressbars': True, 'mode': '3D', 'cpu': True, 'cuda': False, 'opencl': False, 'autotranslate': False}
+
+
+def is_messages():
+ """Function to return messages."""
+ return general['messages']
+
+
+# Store information about host machine
+hostinfo = get_host_info()
+
+# Store information for CUDA solver type
+# gpus: information about any GPUs as a list of GPU objects
+# snapsgpu2cpu: copy snapshot data from GPU to CPU during simulation
+# N.B. This will happen if the requested snapshots are too large to fit
+# on the memory of the GPU. If True this will slow performance significantly
+cuda = {'gpus': None, 'snapsgpu2cpu': False}
+
+# Numerical dispersion analysis parameters
+# highestfreqthres: threshold (dB) down from maximum power (0dB) of main frequency used
+# to calculate highest frequency for numerical dispersion analysis
+# maxnumericaldisp: maximum allowable percentage physical phase-velocity phase error
+# mingridsampling: minimum grid sampling of smallest wavelength for physical wave propagation
+numdispersion = {'highestfreqthres': 40, 'maxnumericaldisp': 2, 'mingridsampling': 3}
+
+# Materials
+# maxpoles: Maximum number of dispersive material poles in a model
+materials = {'maxpoles': 0, 'dispersivedtype': None, 'dispersiveCdtype': None}
+
+# Data types
+# Solid and ID arrays use 32-bit integers (0 to 4294967295)
+# Rigid arrays use 8-bit integers (the smallest available type to store true/false)
+# Fractal and dispersive coefficient arrays use complex numbers (complex)
+# which are represented as two floats
+# Main field arrays use floats (float_or_double) and complex numbers (complex)
+# Precision of float_or_double and complex: single or double for numpy and C (CUDA) arrays
+precision = 'double'
+
+if precision == 'single':
+ dtypes = {'float_or_double': np.float32, 'complex': np.complex64,
+ 'cython_float_or_double': cython.float, 'cython_complex': cython.floatcomplex,
+ 'C_float_or_double': 'float', 'C_complex': 'pycuda::complex'}
+elif precision == 'double':
+ dtypes = {'float_or_double': np.float64, 'complex': np.complex128,
+ 'cython_float_or_double': cython.double, 'cython_complex': cython.doublecomplex,
+ 'C_float_or_double': 'double', 'C_complex': 'pycuda::complex'}
+
+
+class ModelConfig():
+
+ def __init__(self, sim_config, i):
+ self.sim_config = sim_config
+ self.reuse_geometry = False
+
+ # current model number (indexed from 0)
+ self.i = i
+
+ parts = self.sim_config.output_file_path.parts
+
+ if not sim_config.single_model:
+ # 1 indexed
+ self.appendmodelnumber = str(self.i + 1)
+ else:
+ self.appendmodelnumber = ''
+
+ # outputfilepath for specific model
+ self.output_file_path = Path(*parts[:-2], parts[-1] + self.appendmodelnumber)
+ self.output_file_path_ext = self.output_file_path.with_suffix('.out')
+
+ # make a snapshot directory
+ stem = parts[-1] + '_snaps' + self.appendmodelnumber
+ self.snapshot_dir = Path(*parts[:-2], stem)
+
+ inputfilestr_f = '\n--- Model {}/{}, input file: {}'
+ self.inputfilestr = inputfilestr_f.format(self.i + 1, self.sim_config.model_end, self.sim_config.input_file_path)
+ # string to print at start of each model run
+ self.next_model = Fore.GREEN + '{} {}\n'.format(self.inputfilestr, '-' * (get_terminal_width() - 1 - len(self.inputfilestr))) + Style.RESET_ALL
+
+ # Add the current model run to namespace that can be accessed by
+ # user in any Python code blocks in input file
+ #self.usernamespace['current_model_run'] = self.i + 1
+
+ def get_scene(self):
+ if self.sim_config.scenes:
+ return self.sim_config.scenes[self.i]
+ else: return None
+
+ def get_usernamespace(self):
+ return {'c': c,
+ 'e0': e0,
+ 'm0': m0,
+ 'z0': z0,
+ 'number_model_runs': self.sim_config.model_end + 1,
+ 'current_model_run': self.i + 1,
+ 'inputfile': self.sim_config.input_file_path.resolve()}
+
+
+class SimulationConfig:
+
+ def __init__(self, args):
+ """Adapter for args into Simulation level configuration"""
+
+ # adapt the arg properties to link smoothly with MPIRunner(), CPURunner() etc..
+
+ # args.inputfile
+ # args.n
+ # args.task
+ # args.restart
+ # args.mpi
+ # args.mpi_no_spawn
+ # args.mpicomm
+ # args.gpu
+ # args.benchmark
+ # args.geometry_only
+ # args.geometry_fixed
+ # args.write_processed
+
+ self.args = args
+ self.n_models = args.n
+ self.inputfile = args.inputfile
+ self.gpu = args.gpu
+ self.mpi = args.mpi
+ self.mpi_no_spawn = args.mpi_no_spawn
+ self.general = {}
+ self.general['messages'] = general['messages']
+ self.geometry_fixed = args.geometry_fixed
+ self.geometry_only = args.geometry_only
+ self.write_processed = args.write_processed
+
+ # subgrid parameter may not exist if user uses CLI api
+ try:
+ self.subgrid = args.subgrid
+ except AttributeError:
+ # this must be CLI user. No subgrids are available
+ self.subgrid = False
+
+ # scenes parameter may not exist if user uses CLI api
+ try:
+ self.scenes = args.scenes
+ except AttributeError:
+ self.scenes = []
+
+ # set more complex parameters
+ self.set_input_file_path()
+ self.set_output_file_path()
+ self.set_model_start_end()
+ self.set_single_model()
+
+ def set_single_model(self):
+ if self.model_start == 0 and self.model_end == 1:
+ self.single_model = True
+ else:
+ self.single_model = False
+
+ # for example
+ def set_model_start_end(self):
+
+ # set range for number of models to run (internally 0 index)
+ if self.args.task:
+ # Job array feeds args.n number of single tasks
+ modelstart = self.args.task - 1
+ modelend = self.args.task
+ elif self.args.restart:
+ modelstart = self.args.restart - 1
+ modelend = modelstart + self.args.n - 1
+ else:
+ modelstart = 0
+ modelend = modelstart + self.args.n
+
+ self.model_start = modelstart
+ self.model_end = modelend
+
+ def set_precision(self):
+ pass
+
+ def set_input_file_path(self):
+ """Function to set to inputfile path"""
+
+ # if the API is in use an id for the simulation must be provided.
+ if self.args.inputfile is None:
+ self.input_file_path = Path(self.args.outputfile)
+ else:
+ self.input_file_path = Path(self.args.inputfile)
+
+ def set_output_file_path(self):
+ # output file can be provided by the user. if they havent provided None
+ # use the inputfilefile path instead
+ try:
+ self.output_file_path = Path(self.args.outputfile)
+ except AttributeError:
+ self.output_file_path = Path(self.args.inputfile)
+
+
+class SimulationConfigMPI(SimulationConfig):
+
+ def __init__(self, args):
+ super().__init__(args)
+
+ def set_model_start_end(self):
+ # Set range for number of models to run
+ self.model_start = self.args.restart if self.args.restart else 1
+ self.model_end = self.modelstart + self.args.n
+
+
+def create_simulation_config(args):
+
+ if not args.mpi and not args.mpi_no_spawn:
+ sc = SimulationConfig(args)
+ elif args.mpi:
+ sc = SimulationConfigMPI(args)
+ return sc
+
+
+def create_model_config(sim_config, i):
+ mc = ModelConfig(sim_config, i)
+ return mc
diff --git a/gprMax/contexts.py b/gprMax/contexts.py
index ca480800..04842070 100644
--- a/gprMax/contexts.py
+++ b/gprMax/contexts.py
@@ -1,119 +1,119 @@
-# Copyright (C) 2015-2019: The University of Edinburgh
-# Authors: Craig Warren and Antonis Giannopoulos
-#
-# This file is part of gprMax.
-#
-# gprMax is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# gprMax is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with gprMax. If not, see .
-from .utilities import get_terminal_width
-from .utilities import timer
-from .model_build_run import ModelBuildRun
-import datetime
-from .config import create_model_config
-from .solvers import create_solver
-from .solvers import create_G
-
-
-class Context():
-
- def __init__(self, sim_config):
- """Context for the model to run in. Sub-class this with contexts
- i.e. an MPI context.
-
- Args:
- sim_config (SimConfig): Simulation level configuration object.
- solver (Solver): FDTD general solver object.
- """
- self.sim_config = sim_config
- self.model_range = range(sim_config.model_start,
- sim_config.model_end)
- self.tsimend = 0
- self.tsimstart = 1
-
- def run(self):
- """Function to run the simulation in the correct context."""
- self.tsimstart = timer()
- self._run()
- self.tsimend = timer()
-
- def print_time_report(self):
- """Function to print the total simulation time based on context."""
- s = self.make_time_report(sim_time)
- print(s)
-
- def make_time_report(self):
- """Function to generate a string for the total simulation time bas"""
- pass
-
-
-class NoMPIContext(Context):
-
- def _run(self):
- """Specialise how the models are farmed out."""
-
- for i in self.model_range:
- model_config = create_model_config(self.sim_config, i)
-
- # always create a solver for the first model
- # the next model to run only gets a new solver if the
- # geometry is not re used.
- if i != 0 and self.sim_config.geometry_fixed:
- model_config.reuse_geometry = True
- else:
- G = create_G(self.sim_config)
-
- model = ModelBuildRun(G, self.sim_config, model_config)
- model.build()
-
- solver = create_solver(G, self.sim_config)
-
- if not self.sim_config.geometry_only:
- model.run_model(solver)
-
- def make_time_report(self):
- """Function to specialise the time reporting for the standard Simulation
- context."""
- sim_time = datetime.timedelta(seconds=self.tsimend - self.tsimstart)
- s = '\n=== Simulation on {} completed in [HH:MM:SS]: {}'
- s = s.format(self.simconfig.hostinfo['hostname'], sim_time)
- return '{} {}\n'.format(s, '=' * (get_terminal_width() - 1 - len(s)))
-
-
-class MPIContext(Context):
-
- def _run(self):
- pass
-
- def make_time_report(self):
- pass
-
-
-class MPINoSpawnContext(Context):
-
- def _run(self):
- pass
-
- def make_time_report(self):
- pass
-
-
-def create_context(sim_config):
- """Create a context in which to run the simulation. i.e MPI."""
- if sim_config.mpi_no_spawn:
- context = MPIContext(sim_config)
- elif sim_config.mpi:
- context = MPINoSpawnContext(sim_config)
- else:
- context = NoMPIContext(sim_config)
-
- return context
+# Copyright (C) 2015-2019: The University of Edinburgh
+# Authors: Craig Warren and Antonis Giannopoulos
+#
+# This file is part of gprMax.
+#
+# gprMax is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# gprMax is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with gprMax. If not, see .
+from .utilities import get_terminal_width
+from .utilities import timer
+from .model_build_run import ModelBuildRun
+import datetime
+from .config import create_model_config
+from .solvers import create_solver
+from .solvers import create_G
+
+
+class Context():
+
+ def __init__(self, sim_config):
+ """Context for the model to run in. Sub-class this with contexts
+ i.e. an MPI context.
+
+ Args:
+ sim_config (SimConfig): Simulation level configuration object.
+ solver (Solver): FDTD general solver object.
+ """
+ self.sim_config = sim_config
+ self.model_range = range(sim_config.model_start,
+ sim_config.model_end)
+ self.tsimend = 0
+ self.tsimstart = 1
+
+ def run(self):
+ """Function to run the simulation in the correct context."""
+ self.tsimstart = timer()
+ self._run()
+ self.tsimend = timer()
+
+ def print_time_report(self):
+ """Function to print the total simulation time based on context."""
+ s = self.make_time_report(sim_time)
+ print(s)
+
+ def make_time_report(self):
+ """Function to generate a string for the total simulation time bas"""
+ pass
+
+
+class NoMPIContext(Context):
+
+ def _run(self):
+ """Specialise how the models are farmed out."""
+
+ for i in self.model_range:
+ model_config = create_model_config(self.sim_config, i)
+
+ # always create a solver for the first model
+ # the next model to run only gets a new solver if the
+ # geometry is not re used.
+ if i != 0 and self.sim_config.geometry_fixed:
+ model_config.reuse_geometry = True
+ else:
+ G = create_G(self.sim_config)
+
+ model = ModelBuildRun(G, self.sim_config, model_config)
+ model.build()
+
+ solver = create_solver(G, self.sim_config)
+
+ if not self.sim_config.geometry_only:
+ model.run_model(solver)
+
+ def make_time_report(self):
+ """Function to specialise the time reporting for the standard Simulation
+ context."""
+ sim_time = datetime.timedelta(seconds=self.tsimend - self.tsimstart)
+ s = '\n=== Simulation on {} completed in [HH:MM:SS]: {}'
+ s = s.format(self.simconfig.hostinfo['hostname'], sim_time)
+ return '{} {}\n'.format(s, '=' * (get_terminal_width() - 1 - len(s)))
+
+
+class MPIContext(Context):
+
+ def _run(self):
+ pass
+
+ def make_time_report(self):
+ pass
+
+
+class MPINoSpawnContext(Context):
+
+ def _run(self):
+ pass
+
+ def make_time_report(self):
+ pass
+
+
+def create_context(sim_config):
+ """Create a context in which to run the simulation. i.e MPI."""
+ if sim_config.mpi_no_spawn:
+ context = MPIContext(sim_config)
+ elif sim_config.mpi:
+ context = MPINoSpawnContext(sim_config)
+ else:
+ context = NoMPIContext(sim_config)
+
+ return context
diff --git a/gprMax/cython/fields_updates_hsg.pyx b/gprMax/cython/fields_updates_hsg.pyx
index 7c56b42a..ea0379ff 100644
--- a/gprMax/cython/fields_updates_hsg.pyx
+++ b/gprMax/cython/fields_updates_hsg.pyx
@@ -1,281 +1,281 @@
-# Copyright (C) 2015-2017: The University of Edinburgh
-# Authors: Craig Warren and Antonis Giannopoulos
-#
-# This file is part of gprMax.
-#
-# gprMax is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# gprMax is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with gprMax. If not, see .
-import numpy as np
-cimport numpy as np
-from cython.parallel import prange
-
-cpdef void cython_update_electric_os(
- np.float64_t[:, :] updatecoeffsE,
- np.uint32_t[:, :, :, :] ID,
- int face,
- int l_l,
- int l_u,
- int m_l,
- int m_u,
- size_t n_l,
- size_t n_u,
- int nwn,
- size_t lookup_id,
- np.float64_t[:, :, :] field,
- np.float64_t[:, :, :] inc_field,
- size_t co,
- int sign_n,
- int sign_f,
- int mid,
- int r,
- int s,
- int nb,
- int nthreads
-):
- """
- Args:
-
- subgrid: (Subgrid)
- n: (String) the normal to the face to update
- nwn: (Int) number of working cell in the normal direction
- to the face
- lookup_id: (Int) id of the H component we wish to update at
- each node
- field: (Numpy array) main grid field to be updated
- inc_field: (Numpy array) incident sub_grid field
- co: (Int) Coefficient used by gprMax update equations which
- is specific to the field component being updated.
- sign_n: (Int) 1 or -1 sign of the incident field on the near face.
- sign_f: (Int) 1 or -1 sign of the incident field on the far face.
- mid: (Bool) is the H node midway along the lower edge?
- r = self.ratio
- s = self.is_os_sep
- nb = self.n_boundary_cells
- """
- # Comments here as as per left and right face
-
- cdef Py_ssize_t l, m, l_s, m_s, n_s_l, n_s_r, material_e_l, material_e_r, i0, j0, k0, i1, j1, k1, i2, j2, k2, i3, j3, k3
- cdef int os
- cdef double inc_n, inc_f
-
- # surface normal index for the subgrid near face h nodes (left i index)
- n_s_l = nb - s * r - r + r // 2
- # surface normal index for the subgrid far face h nodes (right i index)
- n_s_r = nb + nwn + s * r + r // 2
- # OS at the left face
- os = nb - r * s
-
- # Iterate over a slice of the main grid using dummy indices
- for l in prange(l_l, l_u, nogil=True, schedule='static', num_threads=nthreads):
-
- # Calculate the subgrid j component of the H nodes
- # i.e. Hz node of the left or right face
- if mid == 1:
- l_s = os + (l - l_l) * r + r // 2
- # i.e. the Hy node of the left or right face
- else:
- l_s = os + (l - l_l) * r
-
- for m in range(m_l, m_u):
-
- # Calculate the subgrid k component of the H nodes
- if mid == 1:
- m_s = os + (m - m_l) * r
- else:
- m_s = os + (m - m_l) * r + r // 2
-
- # left and right
- if face == 2:
- # main grid index
- i0, j0, k0 = n_l, l, m
- # equivalent subgrid index
- i1, j1, k1 = n_s_l, l_s, m_s
- i2, j2, k2 = n_u, l, m
- i3, j3, k3 = n_s_r, l_s, m_s
- # front and back
- if face == 3:
- i0, j0, k0 = l, n_l, m
- i1, j1, k1 = l_s, n_s_l, m_s
- i2, j2, k2 = l, n_u, m
- i3, j3, k3 = l_s, n_s_r, m_s
- # top bottom
- if face == 1:
- i0, j0, k0 = l, m, n_l
- i1, j1, k1 = l_s, m_s, n_s_l
- i2, j2, k2 = l, m, n_u
- i3, j3, k3 = l_s, m_s, n_s_r
- # Update the left face
-
- # Get the material at main grid index
- material_e_l = ID[lookup_id, i0, j0, k0]
- # Get the associated indident field from the subgrid
- inc_n = inc_field[i1, j1, k1] * sign_n
- # Update the main grid E field with the corrected H field
- field[i0, j0, k0] += updatecoeffsE[material_e_l, co] * inc_n
-
- # Update the right face
- material_e_r = ID[lookup_id, i2, j2, k2]
- inc_f = inc_field[i3, j3, k3] * sign_f
- field[i2, j2, k2] += updatecoeffsE[material_e_r, co] * inc_f
-
-cpdef void cython_update_magnetic_os(
- np.float64_t[:, :] updatecoeffsH,
- np.uint32_t[:, :, :, :] ID,
- int face,
- int l_l,
- int l_u,
- int m_l,
- int m_u,
- size_t n_l,
- size_t n_u,
- int nwn,
- size_t lookup_id,
- np.float64_t[:, :, :] field,
- np.float64_t[:, :, :] inc_field,
- size_t co,
- int sign_n,
- int sign_f,
- int mid,
- int r,
- int s,
- int nb,
- int nthreads
-):
- """
- int r ratio,
- int s is_os_sep,
- int nb n_boundary_cells
- """
-
- cdef Py_ssize_t l, m, l_s, m_s, n_s_l, n_s_r, material_e_l, material_e_r, i0, j0, k0, i1, j1, k1, i2, j2, k2, i3, j3, k3
- cdef int os
- cdef double inc_n, inc_f
-
- # i index (normal to os) for the subgrid near face e node
- n_s_l = nb - r * s
- # Normal index for the subgrid far face e node
- n_s_r = nb + nwn + s * r
-
- # os inner index for the sub grid
- os = nb - r * s
-
- for l in prange(l_l, l_u, nogil=True, schedule='static', num_threads=nthreads):
-
- # y coord of the Ex field component
- if mid == 1:
- l_s = os + (l - l_l) * r + r // 2
- # y coord of the Ez field component
- else:
- l_s = os + (l - l_l) * r
-
- for m in range(m_l, m_u):
-
- # z coordinate of the Ex node in the subgrid
- if mid == 1:
- m_s = os + (m - m_l) * r
- else:
- m_s = os + (m - m_l) * r + r // 2
-
- # associate the given indices with their i, j, k values
-
- # left and right
- if face == 2:
- # main grid index
- i0, j0, k0 = n_l, l, m
- # equivalent subgrid index
- i1, j1, k1 = n_s_l, l_s, m_s
- i2, j2, k2 = n_u, l, m
- i3, j3, k3 = n_s_r, l_s, m_s
- # front and back
- if face == 3:
- i0, j0, k0 = l, n_l, m
- i1, j1, k1 = l_s, n_s_l, m_s
- i2, j2, k2 = l, n_u, m
- i3, j3, k3 = l_s, n_s_r, m_s
- # top bottom
- if face == 1:
- i0, j0, k0 = l, m, n_l
- i1, j1, k1 = l_s, m_s, n_s_l
- i2, j2, k2 = l, m, n_u
- i3, j3, k3 = l_s, m_s, n_s_r
-
- material_e_l = ID[lookup_id, i0, j0, k0]
- inc_n = inc_field[i1, j1, k1] * sign_n
-
- # make sure these are the correct grid
- field[i0, j0, k0] += updatecoeffsH[material_e_l, co] * inc_n
-
- # Far face
- material_e_r = ID[lookup_id, i2, j2, k2]
- inc_f = inc_field[i3, j3, k3] * sign_f
- field[i2, j2, k2] += updatecoeffsH[material_e_r, co] * inc_f
-
-cpdef void cython_update_is(
- int nwx,
- int nwy,
- int nwz,
- np.float64_t[:, :] updatecoeffsE,
- np.uint32_t[:, :, :, :] ID,
- int n,
- int offset,
- int nwl,
- int nwm,
- int nwn,
- int face,
- np.float64_t[:, :, :] field,
- np.float64_t[:, :] inc_field_l,
- np.float64_t[:, :] inc_field_u,
- Py_ssize_t lookup_id,
- int sign_l,
- int sign_u,
- Py_ssize_t co,
- int nthreads
- ):
-
- cdef Py_ssize_t l, m, i1, j1, k1, i2, j2, k2, field_material_l, field_material_u, inc_i, inc_j
- cdef double inc_l, inc_u, f_l, f_u
- # for inner faces H nodes are 1 cell before n boundary cells
- cdef int n_o = n + offset
-
- for l in prange(n, nwl + n, nogil=True, schedule='static', num_threads=nthreads):
- for m in range(n, nwm + n):
-
- # bottom and top
- if face == 1:
- i1, j1, k1 = l, m, n_o
- i2, j2, k2 = l, m, n + nwz
- # left and right
- if face == 2:
- i1, j1, k1 = n_o, l, m
- i2, j2, k2 = n + nwx, l, m
- # front and back
- if face == 3:
- i1, j1, k1 = l, n_o, m
- i2, j2, k2 = l, n + nwy, m
-
- inc_i = l - n
- inc_j = m - n
-
- field_material_l = ID[lookup_id, i1, j1, k1]
- inc_l = inc_field_l[inc_i, inc_j]
- # Additional field at i, j, k
- f_l = updatecoeffsE[field_material_l, co] * inc_l * sign_l
- # Set the new value
- field[i1, j1, k1] += f_l
-
- field_material_u = ID[lookup_id, i2, j2, k2]
- inc_u = inc_field_u[inc_i, inc_j]
- # Additional field at i, j, k
- f_u = updatecoeffsE[field_material_u, co] * inc_u * sign_u
- # Set the new value
- field[i2, j2, k2] += f_u
+# Copyright (C) 2015-2017: The University of Edinburgh
+# Authors: Craig Warren and Antonis Giannopoulos
+#
+# This file is part of gprMax.
+#
+# gprMax is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# gprMax is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with gprMax. If not, see .
+import numpy as np
+cimport numpy as np
+from cython.parallel import prange
+
+cpdef void cython_update_electric_os(
+ np.float64_t[:, :] updatecoeffsE,
+ np.uint32_t[:, :, :, :] ID,
+ int face,
+ int l_l,
+ int l_u,
+ int m_l,
+ int m_u,
+ size_t n_l,
+ size_t n_u,
+ int nwn,
+ size_t lookup_id,
+ np.float64_t[:, :, :] field,
+ np.float64_t[:, :, :] inc_field,
+ size_t co,
+ int sign_n,
+ int sign_f,
+ int mid,
+ int r,
+ int s,
+ int nb,
+ int nthreads
+):
+ """
+ Args:
+
+ subgrid: (Subgrid)
+ n: (String) the normal to the face to update
+ nwn: (Int) number of working cell in the normal direction
+ to the face
+ lookup_id: (Int) id of the H component we wish to update at
+ each node
+ field: (Numpy array) main grid field to be updated
+ inc_field: (Numpy array) incident sub_grid field
+ co: (Int) Coefficient used by gprMax update equations which
+ is specific to the field component being updated.
+ sign_n: (Int) 1 or -1 sign of the incident field on the near face.
+ sign_f: (Int) 1 or -1 sign of the incident field on the far face.
+ mid: (Bool) is the H node midway along the lower edge?
+ r = self.ratio
+ s = self.is_os_sep
+ nb = self.n_boundary_cells
+ """
+ # Comments here as as per left and right face
+
+ cdef Py_ssize_t l, m, l_s, m_s, n_s_l, n_s_r, material_e_l, material_e_r, i0, j0, k0, i1, j1, k1, i2, j2, k2, i3, j3, k3
+ cdef int os
+ cdef double inc_n, inc_f
+
+ # surface normal index for the subgrid near face h nodes (left i index)
+ n_s_l = nb - s * r - r + r // 2
+ # surface normal index for the subgrid far face h nodes (right i index)
+ n_s_r = nb + nwn + s * r + r // 2
+ # OS at the left face
+ os = nb - r * s
+
+ # Iterate over a slice of the main grid using dummy indices
+ for l in prange(l_l, l_u, nogil=True, schedule='static', num_threads=nthreads):
+
+ # Calculate the subgrid j component of the H nodes
+ # i.e. Hz node of the left or right face
+ if mid == 1:
+ l_s = os + (l - l_l) * r + r // 2
+ # i.e. the Hy node of the left or right face
+ else:
+ l_s = os + (l - l_l) * r
+
+ for m in range(m_l, m_u):
+
+ # Calculate the subgrid k component of the H nodes
+ if mid == 1:
+ m_s = os + (m - m_l) * r
+ else:
+ m_s = os + (m - m_l) * r + r // 2
+
+ # left and right
+ if face == 2:
+ # main grid index
+ i0, j0, k0 = n_l, l, m
+ # equivalent subgrid index
+ i1, j1, k1 = n_s_l, l_s, m_s
+ i2, j2, k2 = n_u, l, m
+ i3, j3, k3 = n_s_r, l_s, m_s
+ # front and back
+ if face == 3:
+ i0, j0, k0 = l, n_l, m
+ i1, j1, k1 = l_s, n_s_l, m_s
+ i2, j2, k2 = l, n_u, m
+ i3, j3, k3 = l_s, n_s_r, m_s
+ # top bottom
+ if face == 1:
+ i0, j0, k0 = l, m, n_l
+ i1, j1, k1 = l_s, m_s, n_s_l
+ i2, j2, k2 = l, m, n_u
+ i3, j3, k3 = l_s, m_s, n_s_r
+ # Update the left face
+
+ # Get the material at main grid index
+ material_e_l = ID[lookup_id, i0, j0, k0]
+ # Get the associated indident field from the subgrid
+ inc_n = inc_field[i1, j1, k1] * sign_n
+ # Update the main grid E field with the corrected H field
+ field[i0, j0, k0] += updatecoeffsE[material_e_l, co] * inc_n
+
+ # Update the right face
+ material_e_r = ID[lookup_id, i2, j2, k2]
+ inc_f = inc_field[i3, j3, k3] * sign_f
+ field[i2, j2, k2] += updatecoeffsE[material_e_r, co] * inc_f
+
+cpdef void cython_update_magnetic_os(
+ np.float64_t[:, :] updatecoeffsH,
+ np.uint32_t[:, :, :, :] ID,
+ int face,
+ int l_l,
+ int l_u,
+ int m_l,
+ int m_u,
+ size_t n_l,
+ size_t n_u,
+ int nwn,
+ size_t lookup_id,
+ np.float64_t[:, :, :] field,
+ np.float64_t[:, :, :] inc_field,
+ size_t co,
+ int sign_n,
+ int sign_f,
+ int mid,
+ int r,
+ int s,
+ int nb,
+ int nthreads
+):
+ """
+ int r ratio,
+ int s is_os_sep,
+ int nb n_boundary_cells
+ """
+
+ cdef Py_ssize_t l, m, l_s, m_s, n_s_l, n_s_r, material_e_l, material_e_r, i0, j0, k0, i1, j1, k1, i2, j2, k2, i3, j3, k3
+ cdef int os
+ cdef double inc_n, inc_f
+
+ # i index (normal to os) for the subgrid near face e node
+ n_s_l = nb - r * s
+ # Normal index for the subgrid far face e node
+ n_s_r = nb + nwn + s * r
+
+ # os inner index for the sub grid
+ os = nb - r * s
+
+ for l in prange(l_l, l_u, nogil=True, schedule='static', num_threads=nthreads):
+
+ # y coord of the Ex field component
+ if mid == 1:
+ l_s = os + (l - l_l) * r + r // 2
+ # y coord of the Ez field component
+ else:
+ l_s = os + (l - l_l) * r
+
+ for m in range(m_l, m_u):
+
+ # z coordinate of the Ex node in the subgrid
+ if mid == 1:
+ m_s = os + (m - m_l) * r
+ else:
+ m_s = os + (m - m_l) * r + r // 2
+
+ # associate the given indices with their i, j, k values
+
+ # left and right
+ if face == 2:
+ # main grid index
+ i0, j0, k0 = n_l, l, m
+ # equivalent subgrid index
+ i1, j1, k1 = n_s_l, l_s, m_s
+ i2, j2, k2 = n_u, l, m
+ i3, j3, k3 = n_s_r, l_s, m_s
+ # front and back
+ if face == 3:
+ i0, j0, k0 = l, n_l, m
+ i1, j1, k1 = l_s, n_s_l, m_s
+ i2, j2, k2 = l, n_u, m
+ i3, j3, k3 = l_s, n_s_r, m_s
+ # top bottom
+ if face == 1:
+ i0, j0, k0 = l, m, n_l
+ i1, j1, k1 = l_s, m_s, n_s_l
+ i2, j2, k2 = l, m, n_u
+ i3, j3, k3 = l_s, m_s, n_s_r
+
+ material_e_l = ID[lookup_id, i0, j0, k0]
+ inc_n = inc_field[i1, j1, k1] * sign_n
+
+ # make sure these are the correct grid
+ field[i0, j0, k0] += updatecoeffsH[material_e_l, co] * inc_n
+
+ # Far face
+ material_e_r = ID[lookup_id, i2, j2, k2]
+ inc_f = inc_field[i3, j3, k3] * sign_f
+ field[i2, j2, k2] += updatecoeffsH[material_e_r, co] * inc_f
+
+cpdef void cython_update_is(
+ int nwx,
+ int nwy,
+ int nwz,
+ np.float64_t[:, :] updatecoeffsE,
+ np.uint32_t[:, :, :, :] ID,
+ int n,
+ int offset,
+ int nwl,
+ int nwm,
+ int nwn,
+ int face,
+ np.float64_t[:, :, :] field,
+ np.float64_t[:, :] inc_field_l,
+ np.float64_t[:, :] inc_field_u,
+ Py_ssize_t lookup_id,
+ int sign_l,
+ int sign_u,
+ Py_ssize_t co,
+ int nthreads
+ ):
+
+ cdef Py_ssize_t l, m, i1, j1, k1, i2, j2, k2, field_material_l, field_material_u, inc_i, inc_j
+ cdef double inc_l, inc_u, f_l, f_u
+ # for inner faces H nodes are 1 cell before n boundary cells
+ cdef int n_o = n + offset
+
+ for l in prange(n, nwl + n, nogil=True, schedule='static', num_threads=nthreads):
+ for m in range(n, nwm + n):
+
+ # bottom and top
+ if face == 1:
+ i1, j1, k1 = l, m, n_o
+ i2, j2, k2 = l, m, n + nwz
+ # left and right
+ if face == 2:
+ i1, j1, k1 = n_o, l, m
+ i2, j2, k2 = n + nwx, l, m
+ # front and back
+ if face == 3:
+ i1, j1, k1 = l, n_o, m
+ i2, j2, k2 = l, n + nwy, m
+
+ inc_i = l - n
+ inc_j = m - n
+
+ field_material_l = ID[lookup_id, i1, j1, k1]
+ inc_l = inc_field_l[inc_i, inc_j]
+ # Additional field at i, j, k
+ f_l = updatecoeffsE[field_material_l, co] * inc_l * sign_l
+ # Set the new value
+ field[i1, j1, k1] += f_l
+
+ field_material_u = ID[lookup_id, i2, j2, k2]
+ inc_u = inc_field_u[inc_i, inc_j]
+ # Additional field at i, j, k
+ f_u = updatecoeffsE[field_material_u, co] * inc_u * sign_u
+ # Set the new value
+ field[i2, j2, k2] += f_u
diff --git a/gprMax/exceptions.py b/gprMax/exceptions.py
index 4ec57e02..1beab97b 100644
--- a/gprMax/exceptions.py
+++ b/gprMax/exceptions.py
@@ -1,48 +1,48 @@
-# Copyright (C) 2015-2019: The University of Edinburgh
-# Authors: Craig Warren and Antonis Giannopoulos
-#
-# This file is part of gprMax.
-#
-# gprMax is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# gprMax is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with gprMax. If not, see .
-
-import sys
-
-from colorama import init
-from colorama import Fore
-
-init()
-
-sys.tracebacklimit = None
-
-
-class GeneralError(ValueError):
- """Handles general errors. Subclasses the ValueError class."""
-
- def __init__(self, message, *args):
-
- self.message = message
- super(GeneralError, self).__init__(message, *args)
- print(Fore.RED)
-
-
-class CmdInputError(ValueError):
- """Handles errors in user specified commands. Subclasses the ValueError
- class.
- """
-
- def __init__(self, message, *args):
-
- self.message = message
- super(CmdInputError, self).__init__(message, *args)
- print(Fore.RED)
+# Copyright (C) 2015-2019: The University of Edinburgh
+# Authors: Craig Warren and Antonis Giannopoulos
+#
+# This file is part of gprMax.
+#
+# gprMax is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# gprMax is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with gprMax. If not, see .
+
+import sys
+
+from colorama import init
+from colorama import Fore
+
+init()
+
+sys.tracebacklimit = None
+
+
+class GeneralError(ValueError):
+ """Handles general errors. Subclasses the ValueError class."""
+
+ def __init__(self, message, *args):
+
+ self.message = message
+ super(GeneralError, self).__init__(message, *args)
+ print(Fore.RED)
+
+
+class CmdInputError(ValueError):
+ """Handles errors in user specified commands. Subclasses the ValueError
+ class.
+ """
+
+ def __init__(self, message, *args):
+
+ self.message = message
+ super(CmdInputError, self).__init__(message, *args)
+ print(Fore.RED)
diff --git a/gprMax/fields_outputs.py b/gprMax/fields_outputs.py
index de618e41..26a5212f 100644
--- a/gprMax/fields_outputs.py
+++ b/gprMax/fields_outputs.py
@@ -1,202 +1,202 @@
-# Copyright (C) 2015-2019: The University of Edinburgh
-# Authors: Craig Warren and Antonis Giannopoulos
-#
-# This file is part of gprMax.
-#
-# gprMax is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# gprMax is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with gprMax. If not, see .
-
-from string import Template
-from pathlib import Path
-
-import h5py
-
-from ._version import __version__
-
-
-def store_outputs(G):
- """Stores field component values for every receiver and transmission line.
-
- Args:
- iteration (int): Current iteration number.
- Ex, Ey, Ez, Hx, Hy, Hz (memory view): Current electric and magnetic
- field values.
- G (class): Grid class instance - holds essential parameters describing
- the model.
- """
-
- iteration = G.iteration
- #import pdb; pdb.set_trace()
-
- Ex, Ey, Ez, Hx, Hy, Hz = G.Ex, G.Ey, G.Ez, G.Hx, G.Hy, G.Hz
- for rx in G.rxs:
- for output in rx.outputs:
- # Store electric or magnetic field components
- if 'I' not in output:
- field = locals()[output]
- rx.outputs[output][iteration] = field[rx.xcoord, rx.ycoord, rx.zcoord]
- # Store current component
- else:
- func = globals()[output]
- 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]
- tl.Itotal[iteration] = tl.current[tl.antpos]
-
-
-kernel_template_store_outputs = Template("""
-
-// Macros for converting subscripts to linear index:
-#define INDEX2D_RXCOORDS(m, n) (m)*($NY_RXCOORDS)+(n)
-#define INDEX3D_RXS(i, j, k) (i)*($NY_RXS)*($NZ_RXS)+(j)*($NZ_RXS)+(k)
-#define INDEX3D_FIELDS(i, j, k) (i)*($NY_FIELDS)*($NZ_FIELDS)+(j)*($NZ_FIELDS)+(k)
-
-//////////////////////////////////////////////////////
-// Stores field component values for every receiver //
-//////////////////////////////////////////////////////
-
-__global__ void store_outputs(int NRX, int iteration, const int* __restrict__ rxcoords, $REAL *rxs, const $REAL* __restrict__ Ex, const $REAL* __restrict__ Ey, const $REAL* __restrict__ Ez, const $REAL* __restrict__ Hx, const $REAL* __restrict__ Hy, const $REAL* __restrict__ Hz) {
-
- // This function stores field component values for every receiver in the model.
- //
- // Args:
- // NRX: Total number of receivers in the model
- // rxs: Array to store field components for receivers - rows are field components; columns are iterations; pages are receivers
- // E, H: Access to field component arrays
-
- // Obtain the linear index corresponding to the current thread and use for each receiver
- int rx = blockIdx.x * blockDim.x + threadIdx.x;
-
- int i, j, k;
-
- if (rx < NRX) {
- i = rxcoords[INDEX2D_RXCOORDS(rx,0)];
- j = rxcoords[INDEX2D_RXCOORDS(rx,1)];
- k = rxcoords[INDEX2D_RXCOORDS(rx,2)];
- rxs[INDEX3D_RXS(0,iteration,rx)] = Ex[INDEX3D_FIELDS(i,j,k)];
- rxs[INDEX3D_RXS(1,iteration,rx)] = Ey[INDEX3D_FIELDS(i,j,k)];
- rxs[INDEX3D_RXS(2,iteration,rx)] = Ez[INDEX3D_FIELDS(i,j,k)];
- rxs[INDEX3D_RXS(3,iteration,rx)] = Hx[INDEX3D_FIELDS(i,j,k)];
- rxs[INDEX3D_RXS(4,iteration,rx)] = Hy[INDEX3D_FIELDS(i,j,k)];
- rxs[INDEX3D_RXS(5,iteration,rx)] = Hz[INDEX3D_FIELDS(i,j,k)];
- }
-}
-
-""")
-
-
-def write_hdf5_outputfile(outputfile, G):
- write_hdf5_main_grid_outputfile(outputfile, G)
- write_hdf5_sub_grid_outputfile(outputfile, G)
-
-
-def write_hdf5_main_grid_outputfile(outputfile, G):
- """Write an output file in HDF5 format.
-
- Args:
- outputfile (str): Name of the output file.
- Ex, Ey, Ez, Hx, Hy, Hz (memory view): Current electric and magnetic field values.
- G (class): Grid class instance - holds essential parameters describing the model.
- """
-
- write_data(outputfile, G)
-
-
-def write_hdf5_sub_grid_outputfile(outputfile, G):
- """Write an output file in HDF5 format.
-
- Args:
- outputfile (str): Name of the output file.
- Ex, Ey, Ez, Hx, Hy, Hz (memory view): Current electric and magnetic field values.
- G (class): Grid class instance - holds essential parameters describing the model.
- """
-
- stem = outputfile.stem
- suffix = outputfile.suffix
- parent = outputfile.parent
-
- for sg in G.subgrids:
-
- # create an outputfile for each subgrid
- fp = stem + '_' + sg.name + suffix
- fp = parent / Path(fp)
-
- f = write_data(fp, sg)
-
- # write some additional meta data about the subgrid
- f.attrs['is_os_sep'] = sg.is_os_sep
- f.attrs['pml_separation'] = sg.pml_separation
- f.attrs['subgrid_pml_thickness'] = sg.pmlthickness['x0']
- f.attrs['filter'] = sg.filter
- f.attrs['ratio'] = sg.ratio
- f.attrs['interpolation'] = sg.interpolation
-
-
-def write_data(outputfile, G):
- """Write an output file in HDF5 format.
-
- Args:
- outputfile (str): Name of the output file.
- Ex, Ey, Ez, Hx, Hy, Hz (memory view): Current electric and magnetic
- field values.
- G (class): Grid class instance - holds essential parameters describing
- the model.
- """
-
- f = h5py.File(outputfile, 'w')
- f.attrs['gprMax'] = __version__
- f.attrs['Title'] = G.title
- f.attrs['Iterations'] = G.iterations
- f.attrs['nx_ny_nz'] = (G.nx, G.ny, G.nz)
- f.attrs['dx_dy_dz'] = (G.dx, G.dy, G.dz)
- f.attrs['dt'] = G.dt
- nsrc = len(G.voltagesources + G.hertziandipoles + G.magneticdipoles + G.transmissionlines)
- f.attrs['nsrc'] = nsrc
- f.attrs['nrx'] = len(G.rxs)
- f.attrs['srcsteps'] = G.srcsteps
- f.attrs['rxsteps'] = G.rxsteps
-
- # Create group for sources (except transmission lines); add type and positional data attributes
- srclist = G.voltagesources + G.hertziandipoles + G.magneticdipoles
- for srcindex, src in enumerate(srclist):
- grp = f.create_group('/srcs/src' + str(srcindex + 1))
- grp.attrs['Type'] = type(src).__name__
- grp.attrs['Position'] = (src.xcoord * G.dx, src.ycoord * G.dy, src.zcoord * G.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(G.transmissionlines):
- grp = f.create_group('/tls/tl' + str(tlindex + 1))
- grp.attrs['Position'] = (tl.xcoord * G.dx, tl.ycoord * G.dy, tl.zcoord * G.dz)
- grp.attrs['Resistance'] = tl.resistance
- grp.attrs['dl'] = tl.dl
- # Save incident voltage and current
- grp['Vinc'] = tl.Vinc
- grp['Iinc'] = tl.Iinc
- # Save total voltage and current
- f['/tls/tl' + str(tlindex + 1) + '/Vtotal'] = tl.Vtotal
- f['/tls/tl' + str(tlindex + 1) + '/Itotal'] = tl.Itotal
-
- # Create group, add positional data and write field component arrays for receivers
- for rxindex, rx in enumerate(G.rxs):
- grp = f.create_group('/rxs/rx' + str(rxindex + 1))
- if rx.ID:
- grp.attrs['Name'] = rx.ID
- grp.attrs['Position'] = (rx.xcoord * G.dx, rx.ycoord * G.dy, rx.zcoord * G.dz)
-
- for output in rx.outputs:
- f['/rxs/rx' + str(rxindex + 1) + '/' + output] = rx.outputs[output]
-
- return f
+# Copyright (C) 2015-2019: The University of Edinburgh
+# Authors: Craig Warren and Antonis Giannopoulos
+#
+# This file is part of gprMax.
+#
+# gprMax is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# gprMax is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with gprMax. If not, see .
+
+from string import Template
+from pathlib import Path
+
+import h5py
+
+from ._version import __version__
+
+
+def store_outputs(G):
+ """Stores field component values for every receiver and transmission line.
+
+ Args:
+ iteration (int): Current iteration number.
+ Ex, Ey, Ez, Hx, Hy, Hz (memory view): Current electric and magnetic
+ field values.
+ G (class): Grid class instance - holds essential parameters describing
+ the model.
+ """
+
+ iteration = G.iteration
+ #import pdb; pdb.set_trace()
+
+ Ex, Ey, Ez, Hx, Hy, Hz = G.Ex, G.Ey, G.Ez, G.Hx, G.Hy, G.Hz
+ for rx in G.rxs:
+ for output in rx.outputs:
+ # Store electric or magnetic field components
+ if 'I' not in output:
+ field = locals()[output]
+ rx.outputs[output][iteration] = field[rx.xcoord, rx.ycoord, rx.zcoord]
+ # Store current component
+ else:
+ func = globals()[output]
+ 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]
+ tl.Itotal[iteration] = tl.current[tl.antpos]
+
+
+kernel_template_store_outputs = Template("""
+
+// Macros for converting subscripts to linear index:
+#define INDEX2D_RXCOORDS(m, n) (m)*($NY_RXCOORDS)+(n)
+#define INDEX3D_RXS(i, j, k) (i)*($NY_RXS)*($NZ_RXS)+(j)*($NZ_RXS)+(k)
+#define INDEX3D_FIELDS(i, j, k) (i)*($NY_FIELDS)*($NZ_FIELDS)+(j)*($NZ_FIELDS)+(k)
+
+//////////////////////////////////////////////////////
+// Stores field component values for every receiver //
+//////////////////////////////////////////////////////
+
+__global__ void store_outputs(int NRX, int iteration, const int* __restrict__ rxcoords, $REAL *rxs, const $REAL* __restrict__ Ex, const $REAL* __restrict__ Ey, const $REAL* __restrict__ Ez, const $REAL* __restrict__ Hx, const $REAL* __restrict__ Hy, const $REAL* __restrict__ Hz) {
+
+ // This function stores field component values for every receiver in the model.
+ //
+ // Args:
+ // NRX: Total number of receivers in the model
+ // rxs: Array to store field components for receivers - rows are field components; columns are iterations; pages are receivers
+ // E, H: Access to field component arrays
+
+ // Obtain the linear index corresponding to the current thread and use for each receiver
+ int rx = blockIdx.x * blockDim.x + threadIdx.x;
+
+ int i, j, k;
+
+ if (rx < NRX) {
+ i = rxcoords[INDEX2D_RXCOORDS(rx,0)];
+ j = rxcoords[INDEX2D_RXCOORDS(rx,1)];
+ k = rxcoords[INDEX2D_RXCOORDS(rx,2)];
+ rxs[INDEX3D_RXS(0,iteration,rx)] = Ex[INDEX3D_FIELDS(i,j,k)];
+ rxs[INDEX3D_RXS(1,iteration,rx)] = Ey[INDEX3D_FIELDS(i,j,k)];
+ rxs[INDEX3D_RXS(2,iteration,rx)] = Ez[INDEX3D_FIELDS(i,j,k)];
+ rxs[INDEX3D_RXS(3,iteration,rx)] = Hx[INDEX3D_FIELDS(i,j,k)];
+ rxs[INDEX3D_RXS(4,iteration,rx)] = Hy[INDEX3D_FIELDS(i,j,k)];
+ rxs[INDEX3D_RXS(5,iteration,rx)] = Hz[INDEX3D_FIELDS(i,j,k)];
+ }
+}
+
+""")
+
+
+def write_hdf5_outputfile(outputfile, G):
+ write_hdf5_main_grid_outputfile(outputfile, G)
+ write_hdf5_sub_grid_outputfile(outputfile, G)
+
+
+def write_hdf5_main_grid_outputfile(outputfile, G):
+ """Write an output file in HDF5 format.
+
+ Args:
+ outputfile (str): Name of the output file.
+ Ex, Ey, Ez, Hx, Hy, Hz (memory view): Current electric and magnetic field values.
+ G (class): Grid class instance - holds essential parameters describing the model.
+ """
+
+ write_data(outputfile, G)
+
+
+def write_hdf5_sub_grid_outputfile(outputfile, G):
+ """Write an output file in HDF5 format.
+
+ Args:
+ outputfile (str): Name of the output file.
+ Ex, Ey, Ez, Hx, Hy, Hz (memory view): Current electric and magnetic field values.
+ G (class): Grid class instance - holds essential parameters describing the model.
+ """
+
+ stem = outputfile.stem
+ suffix = outputfile.suffix
+ parent = outputfile.parent
+
+ for sg in G.subgrids:
+
+ # create an outputfile for each subgrid
+ fp = stem + '_' + sg.name + suffix
+ fp = parent / Path(fp)
+
+ f = write_data(fp, sg)
+
+ # write some additional meta data about the subgrid
+ f.attrs['is_os_sep'] = sg.is_os_sep
+ f.attrs['pml_separation'] = sg.pml_separation
+ f.attrs['subgrid_pml_thickness'] = sg.pmlthickness['x0']
+ f.attrs['filter'] = sg.filter
+ f.attrs['ratio'] = sg.ratio
+ f.attrs['interpolation'] = sg.interpolation
+
+
+def write_data(outputfile, G):
+ """Write an output file in HDF5 format.
+
+ Args:
+ outputfile (str): Name of the output file.
+ Ex, Ey, Ez, Hx, Hy, Hz (memory view): Current electric and magnetic
+ field values.
+ G (class): Grid class instance - holds essential parameters describing
+ the model.
+ """
+
+ f = h5py.File(outputfile, 'w')
+ f.attrs['gprMax'] = __version__
+ f.attrs['Title'] = G.title
+ f.attrs['Iterations'] = G.iterations
+ f.attrs['nx_ny_nz'] = (G.nx, G.ny, G.nz)
+ f.attrs['dx_dy_dz'] = (G.dx, G.dy, G.dz)
+ f.attrs['dt'] = G.dt
+ nsrc = len(G.voltagesources + G.hertziandipoles + G.magneticdipoles + G.transmissionlines)
+ f.attrs['nsrc'] = nsrc
+ f.attrs['nrx'] = len(G.rxs)
+ f.attrs['srcsteps'] = G.srcsteps
+ f.attrs['rxsteps'] = G.rxsteps
+
+ # Create group for sources (except transmission lines); add type and positional data attributes
+ srclist = G.voltagesources + G.hertziandipoles + G.magneticdipoles
+ for srcindex, src in enumerate(srclist):
+ grp = f.create_group('/srcs/src' + str(srcindex + 1))
+ grp.attrs['Type'] = type(src).__name__
+ grp.attrs['Position'] = (src.xcoord * G.dx, src.ycoord * G.dy, src.zcoord * G.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(G.transmissionlines):
+ grp = f.create_group('/tls/tl' + str(tlindex + 1))
+ grp.attrs['Position'] = (tl.xcoord * G.dx, tl.ycoord * G.dy, tl.zcoord * G.dz)
+ grp.attrs['Resistance'] = tl.resistance
+ grp.attrs['dl'] = tl.dl
+ # Save incident voltage and current
+ grp['Vinc'] = tl.Vinc
+ grp['Iinc'] = tl.Iinc
+ # Save total voltage and current
+ f['/tls/tl' + str(tlindex + 1) + '/Vtotal'] = tl.Vtotal
+ f['/tls/tl' + str(tlindex + 1) + '/Itotal'] = tl.Itotal
+
+ # Create group, add positional data and write field component arrays for receivers
+ for rxindex, rx in enumerate(G.rxs):
+ grp = f.create_group('/rxs/rx' + str(rxindex + 1))
+ if rx.ID:
+ grp.attrs['Name'] = rx.ID
+ grp.attrs['Position'] = (rx.xcoord * G.dx, rx.ycoord * G.dy, rx.zcoord * G.dz)
+
+ for output in rx.outputs:
+ f['/rxs/rx' + str(rxindex + 1) + '/' + output] = rx.outputs[output]
+
+ return f
diff --git a/gprMax/gprMax.py b/gprMax/gprMax.py
index 3830171b..50f12d7a 100644
--- a/gprMax/gprMax.py
+++ b/gprMax/gprMax.py
@@ -1,94 +1,94 @@
-# Copyright (C) 2015-2019: The University of Edinburgh
-# Authors: Craig Warren and Antonis Giannopoulos
-#
-# This file is part of gprMax.
-#
-# gprMax is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# gprMax is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with gprMax. If not, see .
-
-from .config import create_simulation_config
-from .contexts import create_context
-from .solvers import create_solver
-
-import argparse
-
-def api(
- scenes=None,
- id=None,
- inputfile=None,
- outputfile=None,
- n=1,
- task=None,
- restart=None,
- mpi=False,
- mpi_no_spawn=False,
- mpicomm=None,
- gpu=None,
- subgrid=None,
- benchmark=False,
- geometry_only=False,
- geometry_fixed=False,
- write_processed=False,
-):
- """If installed as a module this is the entry point."""
-
- class ImportArguments:
- pass
-
- args = ImportArguments()
-
- args.scenes = scenes
- args.inputfile = inputfile
- args.outputfile = outputfile
- args.n = n
- args.task = task
- args.restart = restart
- args.mpi = mpi
- args.mpi_no_spawn = mpi_no_spawn
- args.mpicomm = mpicomm
- args.gpu = gpu
- args.subgrid=subgrid
- args.benchmark = benchmark
- args.geometry_only = geometry_only
- args.geometry_fixed = geometry_fixed
- args.write_processed = write_processed
-
- run_main(args)
-
-
-def main():
- """This is the main function for gprMax."""
-
- # Parse command line arguments
- parser = argparse.ArgumentParser(prog='gprMax', formatter_class=argparse.ArgumentDefaultsHelpFormatter)
- parser.add_argument('inputfile', help='path to, and name of inputfile or file object')
- parser.add_argument('-n', default=1, type=int, help='number of times to run the input file, e.g. to create a B-scan')
- parser.add_argument('-task', type=int, help='task identifier (model number) for job array on Open Grid Scheduler/Grid Engine (http://gridscheduler.sourceforge.net/index.html)')
- parser.add_argument('-restart', type=int, help='model number to restart from, e.g. when creating B-scan')
- parser.add_argument('-mpi', type=int, help='number of MPI tasks, i.e. master + workers')
- parser.add_argument('--mpi-no-spawn', action='store_true', default=False, help='flag to use MPI without spawn mechanism')
- parser.add_argument('--mpi-worker', action='store_true', default=False, help=argparse.SUPPRESS)
- parser.add_argument('-gpu', type=int, action='append', nargs='*', help='flag to use Nvidia GPU or option to give list of device ID(s)')
- parser.add_argument('-benchmark', action='store_true', default=False, help='flag to switch on benchmarking mode')
- parser.add_argument('--geometry-only', action='store_true', default=False, help='flag to only build model and produce geometry file(s)')
- parser.add_argument('--geometry-fixed', action='store_true', default=False, help='flag to not reprocess model geometry, e.g. for B-scans where the geometry is fixed')
- parser.add_argument('--write-processed', action='store_true', default=False, help='flag to write an input file after any Python code and include commands in the original input file have been processed')
- args = parser.parse_args()
-
- run_main(args)
-
-def run_main(args):
-
- sim_config = create_simulation_config(args)
- context = create_context(sim_config)
- context.run()
+# Copyright (C) 2015-2019: The University of Edinburgh
+# Authors: Craig Warren and Antonis Giannopoulos
+#
+# This file is part of gprMax.
+#
+# gprMax is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# gprMax is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with gprMax. If not, see .
+
+from .config import create_simulation_config
+from .contexts import create_context
+from .solvers import create_solver
+
+import argparse
+
+def api(
+ scenes=None,
+ id=None,
+ inputfile=None,
+ outputfile=None,
+ n=1,
+ task=None,
+ restart=None,
+ mpi=False,
+ mpi_no_spawn=False,
+ mpicomm=None,
+ gpu=None,
+ subgrid=None,
+ benchmark=False,
+ geometry_only=False,
+ geometry_fixed=False,
+ write_processed=False,
+):
+ """If installed as a module this is the entry point."""
+
+ class ImportArguments:
+ pass
+
+ args = ImportArguments()
+
+ args.scenes = scenes
+ args.inputfile = inputfile
+ args.outputfile = outputfile
+ args.n = n
+ args.task = task
+ args.restart = restart
+ args.mpi = mpi
+ args.mpi_no_spawn = mpi_no_spawn
+ args.mpicomm = mpicomm
+ args.gpu = gpu
+ args.subgrid=subgrid
+ args.benchmark = benchmark
+ args.geometry_only = geometry_only
+ args.geometry_fixed = geometry_fixed
+ args.write_processed = write_processed
+
+ run_main(args)
+
+
+def main():
+ """This is the main function for gprMax."""
+
+ # Parse command line arguments
+ parser = argparse.ArgumentParser(prog='gprMax', formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+ parser.add_argument('inputfile', help='path to, and name of inputfile or file object')
+ parser.add_argument('-n', default=1, type=int, help='number of times to run the input file, e.g. to create a B-scan')
+ parser.add_argument('-task', type=int, help='task identifier (model number) for job array on Open Grid Scheduler/Grid Engine (http://gridscheduler.sourceforge.net/index.html)')
+ parser.add_argument('-restart', type=int, help='model number to restart from, e.g. when creating B-scan')
+ parser.add_argument('-mpi', type=int, help='number of MPI tasks, i.e. master + workers')
+ parser.add_argument('--mpi-no-spawn', action='store_true', default=False, help='flag to use MPI without spawn mechanism')
+ parser.add_argument('--mpi-worker', action='store_true', default=False, help=argparse.SUPPRESS)
+ parser.add_argument('-gpu', type=int, action='append', nargs='*', help='flag to use Nvidia GPU or option to give list of device ID(s)')
+ parser.add_argument('-benchmark', action='store_true', default=False, help='flag to switch on benchmarking mode')
+ parser.add_argument('--geometry-only', action='store_true', default=False, help='flag to only build model and produce geometry file(s)')
+ parser.add_argument('--geometry-fixed', action='store_true', default=False, help='flag to not reprocess model geometry, e.g. for B-scans where the geometry is fixed')
+ parser.add_argument('--write-processed', action='store_true', default=False, help='flag to write an input file after any Python code and include commands in the original input file have been processed')
+ args = parser.parse_args()
+
+ run_main(args)
+
+def run_main(args):
+
+ sim_config = create_simulation_config(args)
+ context = create_context(sim_config)
+ context.run()
diff --git a/gprMax/grid.py b/gprMax/grid.py
index 8f04240e..4fc295c5 100644
--- a/gprMax/grid.py
+++ b/gprMax/grid.py
@@ -1,501 +1,501 @@
-# Copyright (C) 2015-2019: The University of Edinburgh
-# Authors: Craig Warren and Antonis Giannopoulos
-#
-# This file is part of gprMax.
-#
-# gprMax is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# gprMax is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with gprMax. If not, see .
-
-from collections import OrderedDict
-
-from colorama import init
-from colorama import Fore
-from colorama import Style
-init()
-import numpy as np
-np.seterr(invalid='raise')
-
-import gprMax.config as config
-from .exceptions import GeneralError
-from .pml import PML
-from .pml import CFS
-from .utilities import fft_power
-from .utilities import human_size
-from .utilities import round_value
-
-import decimal as d
-from scipy.constants import c
-
-
-
-class Grid(object):
- """Generic grid/mesh."""
-
- def __init__(self, grid):
- self.nx = grid.shape[0]
- self.ny = grid.shape[1]
- self.nz = grid.shape[2]
- self.dx = 1
- self.dy = 1
- self.dz = 1
- self.i_max = self.nx - 1
- self.j_max = self.ny - 1
- self.k_max = self.nz - 1
- self.grid = grid
-
- def n_edges(self):
- i = self.nx
- j = self.ny
- k = self.nz
- e = (i * j * (k - 1)) + (j * k * (i - 1)) + (i * k * (j - 1))
- return e
-
- def n_nodes(self):
- return self.nx * self.ny * self.nz
-
- def n_cells(self):
- return (self.nx - 1) * (self.ny - 1) * (self.nz - 1)
-
- def get(self, i, j, k):
- return self.grid[i, j, k]
-
- def within_bounds(self, p):
- if p[0] < 0 or p[0] > self.nx:
- raise ValueError('x')
- if p[1] < 0 or p[1] > self.ny:
- raise ValueError('y')
- if p[2] < 0 or p[2] > self.nz:
- raise ValueError('z')
-
- def discretise_point(self, p):
- x = round_value(float(p[0]) / self.dx)
- y = round_value(float(p[1]) / self.dy)
- z = round_value(float(p[2]) / self.dz)
- return (x, y, z)
-
- def round_to_grid(self, p):
- p = self.discretise_point(p)
- p_r = (p[0] * self.dx,
- p[1] * self.dy,
- p[2] * self.dz)
- return p_r
-
-
- def within_pml(self, p):
- if (p[0] < self.pmlthickness['x0'] or
- p[0] > self.nx - self.pmlthickness['xmax'] or
- p[1] < self.pmlthickness['y0'] or
- p[1] > self.ny - self.pmlthickness['ymax'] or
- p[2] < self.pmlthickness['z0'] or
- p[2] > self.nz - self.pmlthickness['zmax']):
- return True
- else:
- return False
-
-
-class FDTDGrid(Grid):
- """
- Holds attributes associated with the entire grid. A convenient
- way for accessing regularly used parameters.
- """
-
- def __init__(self):
- self.title = ''
- self.memoryusage = 0
-
- self.nx = 0
- self.ny = 0
- self.nz = 0
- self.dx = 0
- self.dy = 0
- self.dz = 0
- self.dt = 0
- self.iterations = 0
- self.timewindow = 0
-
- # Ordered dictionary required so that PMLs are always updated in the
- # same order. The order itself does not matter, however, if must be the
- # same from model to model otherwise the numerical precision from adding
- # the PML corrections will be different.
- self.pmlthickness = OrderedDict((key, 10) for key in PML.boundaryIDs)
- self.cfs = [CFS()]
- self.pmls = []
- self.pmlformulation = 'HORIPML'
-
- self.materials = []
- self.mixingmodels = []
- self.averagevolumeobjects = True
- self.fractalvolumes = []
- self.geometryviews = []
- self.geometryobjectswrite = []
- self.waveforms = []
- self.voltagesources = []
- self.hertziandipoles = []
- self.magneticdipoles = []
- self.transmissionlines = []
- self.rxs = []
- self.srcsteps = [0, 0, 0]
- self.rxsteps = [0, 0, 0]
- self.snapshots = []
- self.subgrids = []
- self.gpu = None
- self.name = 'Main'
- self.outputdirectory = ''
- self.iteration = 0
-
- def initialise_geometry_arrays(self):
- """
- Initialise an array for volumetric material IDs (solid);
- boolean arrays for specifying whether materials can have dielectric smoothing (rigid);
- and an array for cell edge IDs (ID).
- Solid and ID arrays are initialised to free_space (one);
- rigid arrays to allow dielectric smoothing (zero).
- """
- self.solid = np.ones((self.nx, self.ny, self.nz), dtype=np.uint32)
- self.rigidE = np.zeros((12, self.nx, self.ny, self.nz), dtype=np.int8)
- self.rigidH = np.zeros((6, self.nx, self.ny, self.nz), dtype=np.int8)
- self.ID = np.ones((6, self.nx + 1, self.ny + 1, self.nz + 1), dtype=np.uint32)
- self.IDlookup = {'Ex': 0, 'Ey': 1, 'Ez': 2, 'Hx': 3, 'Hy': 4, 'Hz': 5}
-
- 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.dtypes['float_or_double'])
- self.Ey = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=config.dtypes['float_or_double'])
- self.Ez = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=config.dtypes['float_or_double'])
- self.Hx = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=config.dtypes['float_or_double'])
- self.Hy = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=config.dtypes['float_or_double'])
- self.Hz = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=config.dtypes['float_or_double'])
-
-
- def initialise_grids(self):
- """Function to call the initialisation of all grids."""
- for g in [self] + self.subgrids:
- g.initialise_geometry_arrays()
- g.initialise_field_arrays()
-
- def initialise_std_update_coeff_arrays(self):
- """Initialise arrays for storing update coefficients."""
- self.updatecoeffsE = np.zeros((len(self.materials), 5), dtype=config.dtypes['float_or_double'])
- self.updatecoeffsH = np.zeros((len(self.materials), 5), dtype=config.dtypes['float_or_double'])
-
- def initialise_dispersive_arrays(self, dtype):
- """Initialise arrays for storing coefficients when there are dispersive materials present."""
- self.Tx = np.zeros((config.materials['maxpoles'], self.nx + 1, self.ny + 1, self.nz + 1), dtype=dtype)
- self.Ty = np.zeros((config.materials['maxpoles'], self.nx + 1, self.ny + 1, self.nz + 1), dtype=dtype)
- self.Tz = np.zeros((config.materials['maxpoles'], self.nx + 1, self.ny + 1, self.nz + 1), dtype=dtype)
- self.updatecoeffsdispersive = np.zeros((len(self.materials), 3 * config.materials['maxpoles']), dtype=dtype)
-
- def memory_estimate_basic(self):
- """Estimate the amount of memory (RAM) required to run a model."""
-
- stdoverhead = 50e6
-
- 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
-
- # 6 x field arrays + 6 x ID arrays
- fieldarrays = (6 + 6) * (self.nx + 1) * (self.ny + 1) * (self.nz + 1) * np.dtype(config.dtypes['float_or_double']).itemsize
-
- # PML arrays
- pmlarrays = 0
- for (k, v) in self.pmlthickness.items():
- if v > 0:
- if 'x' in k:
- pmlarrays += ((v + 1) * self.ny * (self.nz + 1))
- pmlarrays += ((v + 1) * (self.ny + 1) * self.nz)
- pmlarrays += (v * self.ny * (self.nz + 1))
- pmlarrays += (v * (self.ny + 1) * self.nz)
- elif 'y' in k:
- pmlarrays += (self.nx * (v + 1) * (self.nz + 1))
- pmlarrays += ((self.nx + 1) * (v + 1) * self.nz)
- pmlarrays += ((self.nx + 1) * v * self.nz)
- pmlarrays += (self.nx * v * (self.nz + 1))
- elif 'z' in k:
- pmlarrays += (self.nx * (self.ny + 1) * (v + 1))
- pmlarrays += ((self.nx + 1) * self.ny * (v + 1))
- pmlarrays += ((self.nx + 1) * self.ny * v)
- pmlarrays += (self.nx * (self.ny + 1) * v)
-
- self.memoryusage = int(stdoverhead + fieldarrays + solidarray + rigidarrays + pmlarrays)
-
- def memory_check(self, snapsmemsize=0):
- """Check if the required amount of memory (RAM) is available on the host and GPU if specified.
-
- Args:
- snapsmemsize (int): amount of memory (bytes) required to store all requested snapshots
- """
-
- # Check if model can be built and/or run on host
- if self.memoryusage > config.hostinfo['ram']:
- raise GeneralError('Memory (RAM) required ~{} exceeds {} detected!\n'.format(human_size(self.memoryusage), human_size(config.hostinfo['ram'], a_kilobyte_is_1024_bytes=True)))
-
- # Check if model can be run on specified GPU if required
- if config.cuda['gpus'] is not None:
- if self.memoryusage - snapsmemsize > config.cuda['gpus'].totalmem:
- raise GeneralError('Memory (RAM) required ~{} exceeds {} detected on specified {} - {} GPU!\n'.format(human_size(self.memoryusage), human_size(config.cuda['gpus'].totalmem, a_kilobyte_is_1024_bytes=True), config.cuda['gpus'].deviceID, config.cuda['gpus'].name))
-
- # If the required memory without the snapshots will fit on the GPU then transfer and store snaphots on host
- if snapsmemsize != 0 and self.memoryusage - snapsmemsize < config.cuda['gpus'].totalmem:
- config.cuda['snapsgpu2cpu'] = True
-
- def gpu_set_blocks_per_grid(self):
- """Set the blocks per grid size used for updating the electric and magnetic field arrays on a GPU."""
- config.cuda['gpus'].bpg = (int(np.ceil(((self.nx + 1) * (self.ny + 1) * (self.nz + 1)) / config.cuda['gpus'].tpb[0])), 1, 1)
-
- def gpu_initialise_arrays(self):
- """Initialise standard field arrays on GPU."""
-
- import pycuda.gpuarray as gpuarray
-
- self.ID_gpu = gpuarray.to_gpu(self.ID)
- self.Ex_gpu = gpuarray.to_gpu(self.Ex)
- self.Ey_gpu = gpuarray.to_gpu(self.Ey)
- self.Ez_gpu = gpuarray.to_gpu(self.Ez)
- self.Hx_gpu = gpuarray.to_gpu(self.Hx)
- self.Hy_gpu = gpuarray.to_gpu(self.Hy)
- self.Hz_gpu = gpuarray.to_gpu(self.Hz)
-
- def gpu_initialise_dispersive_arrays(self):
- """Initialise dispersive material coefficient arrays on GPU."""
-
- import pycuda.gpuarray as gpuarray
-
- self.Tx_gpu = gpuarray.to_gpu(self.Tx)
- self.Ty_gpu = gpuarray.to_gpu(self.Ty)
- self.Tz_gpu = gpuarray.to_gpu(self.Tz)
- self.updatecoeffsdispersive_gpu = gpuarray.to_gpu(self.updatecoeffsdispersive)
-
- # Add PEC boundaries to invariant direction in 2D modes
- # N.B. 2D modes are a single cell slice of 3D grid
- def tmx(self):
- # Ey & Ez components
- self.ID[1, 0, :, :] = 0
- self.ID[1, 1, :, :] = 0
- self.ID[2, 0, :, :] = 0
- self.ID[2, 1, :, :] = 0
-
- def tmy():
- # Ex & Ez components
- G.ID[0, :, 0, :] = 0
- G.ID[0, :, 1, :] = 0
- G.ID[2, :, 0, :] = 0
- G.ID[2, :, 1, :] = 0
-
- def tmz():
- # Ex & Ey components
- G.ID[0, :, :, 0] = 0
- G.ID[0, :, :, 1] = 0
- G.ID[1, :, :, 0] = 0
- G.ID[1, :, :, 1] = 0
-
-
- def reset_fields(self):
- # Clear arrays for field components
- self.initialise_field_arrays()
-
- # Clear arrays for fields in PML
- for pml in self.pmls:
- pml.initialise_field_arrays()
-
- def calculate_dt(self):
- self.dt = 1 / (c * np.sqrt((1 / self.dx) * (1 / self.dx) + (1 / self.dy) * (1 / self.dy) + (1 / self.dz) * (1 / self.dz)))
-
- def round_time_step(self):
- # Round down time step to nearest float with precision one less than hardware maximum. Avoids inadvertently exceeding the CFL due to binary representation of floating point number.
- # Round down time step to nearest float with precision one less than hardware maximum.
- # Avoids inadvertently exceeding the CFL due to binary representation of floating point number.
- self.dt = round_value(self.dt, decimalplaces=d.getcontext().prec - 1)
-
-
-
-def dispersion_analysis(G):
- """
- Analysis of numerical dispersion (Taflove et al, 2005, p112) -
- worse case of maximum frequency and minimum wavelength
-
- Args:
- G (class): Grid class instance - holds essential parameters describing the model.
-
- Returns:
- results (dict): Results from dispersion analysis
- """
-
- # Physical phase velocity error (percentage); grid sampling density;
- # material with maximum permittivity; maximum significant frequency; error message
- results = {'deltavp': False, 'N': False, 'material': False, 'maxfreq': [], 'error': ''}
-
- # Find maximum significant frequency
- if G.waveforms:
- for waveform in G.waveforms:
- if waveform.type == 'sine' or waveform.type == 'contsine':
- results['maxfreq'].append(4 * waveform.freq)
-
- elif waveform.type == 'impulse':
- results['error'] = 'impulse waveform used.'
-
- else:
- # User-defined waveform
- if waveform.type == 'user':
- iterations = G.iterations
-
- # Built-in waveform
- else:
- # Time to analyse waveform - 4*pulse_width as using entire
- # time window can result in demanding FFT
- waveform.calculate_coefficients()
- iterations = round_value(4 * waveform.chi / G.dt)
- if iterations > G.iterations:
- iterations = G.iterations
-
- waveformvalues = np.zeros(G.iterations)
- for iteration in range(G.iterations):
- 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:
- # FFT
- freqs, power = fft_power(waveformvalues, G.dt)
- # Get frequency for max power
- freqmaxpower = np.where(np.isclose(power, 0))[0][0]
-
- # Set maximum frequency to a threshold drop from maximum power, ignoring DC value
- try:
- freqthres = np.where(power[freqmaxpower:] < -config.numdispersion['highestfreqthres'])[0][0] + freqmaxpower
- results['maxfreq'].append(freqs[freqthres])
- except ValueError:
- results['error'] = 'unable to calculate maximum power from waveform, most likely due to undersampling.'
-
- # Ignore case where someone is using a waveform with zero amplitude, i.e. on a receiver
- elif waveform.amp == 0:
- pass
-
- # 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.'
- else:
- results['error'] = 'no waveform detected.'
-
- if results['maxfreq']:
- results['maxfreq'] = max(results['maxfreq'])
-
- # Find minimum wavelength (material with maximum permittivity)
- maxer = 0
- matmaxer = ''
- for x in G.materials:
- if x.se != float('inf'):
- er = x.er
- # If there are dispersive materials calculate the complex relative permittivity
- # at maximum frequency and take the real part
- if x.__class__.__name__ is 'DispersiveMaterial':
- er = x.calculate_er(results['maxfreq'])
- er = er.real
- if er > maxer:
- maxer = er
- matmaxer = x.ID
- results['material'] = next(x for x in G.materials if x.ID == matmaxer)
-
- # Minimum velocity
- minvelocity = config.c / np.sqrt(maxer)
-
- # Minimum wavelength
- minwavelength = minvelocity / results['maxfreq']
-
- # Maximum spatial step
- if '3D' in config.general['mode']:
- delta = max(G.dx, G.dy, G.dz)
- elif '2D' in config.general['mode']:
- if G.nx == 1:
- delta = max(G.dy, G.dz)
- elif G.ny == 1:
- delta = max(G.dx, G.dz)
- elif G.nz == 1:
- delta = max(G.dx, G.dy)
-
- # Courant stability factor
- S = (config.c * G.dt) / delta
-
- # Grid sampling density
- results['N'] = minwavelength / delta
-
- # Check grid sampling will result in physical wave propagation
- if int(np.floor(results['N'])) >= config.numdispersion['mingridsampling']:
- # Numerical phase velocity
- 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
-
- # Store rounded down value of grid sampling density
- results['N'] = int(np.floor(results['N']))
-
- return results
-
-
-def Ix(x, y, z, Hx, Hy, Hz, G):
- """Calculates the x-component of current at a grid position.
-
- Args:
- x, y, z (float): Coordinates of position in grid.
- Hx, Hy, Hz (memory view): numpy array of magnetic field values.
- G (class): Grid class instance - holds essential parameters describing the model.
- """
-
- 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])
-
- return Ix
-
-
-def Iy(x, y, z, Hx, Hy, Hz, G):
- """Calculates the y-component of current at a grid position.
-
- Args:
- x, y, z (float): Coordinates of position in grid.
- Hx, Hy, Hz (memory view): numpy array of magnetic field values.
- G (class): Grid class instance - holds essential parameters describing the model.
- """
-
- 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])
-
- return Iy
-
-
-def Iz(x, y, z, Hx, Hy, Hz, G):
- """Calculates the z-component of current at a grid position.
-
- Args:
- x, y, z (float): Coordinates of position in grid.
- Hx, Hy, Hz (memory view): numpy array of magnetic field values.
- G (class): Grid class instance - holds essential parameters describing the model.
- """
-
- 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])
-
- return Iz
-
-
-class GPUGrid(Grid):
- pass
+# Copyright (C) 2015-2019: The University of Edinburgh
+# Authors: Craig Warren and Antonis Giannopoulos
+#
+# This file is part of gprMax.
+#
+# gprMax is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# gprMax is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with gprMax. If not, see .
+
+from collections import OrderedDict
+
+from colorama import init
+from colorama import Fore
+from colorama import Style
+init()
+import numpy as np
+np.seterr(invalid='raise')
+
+import gprMax.config as config
+from .exceptions import GeneralError
+from .pml import PML
+from .pml import CFS
+from .utilities import fft_power
+from .utilities import human_size
+from .utilities import round_value
+
+import decimal as d
+from scipy.constants import c
+
+
+
+class Grid(object):
+ """Generic grid/mesh."""
+
+ def __init__(self, grid):
+ self.nx = grid.shape[0]
+ self.ny = grid.shape[1]
+ self.nz = grid.shape[2]
+ self.dx = 1
+ self.dy = 1
+ self.dz = 1
+ self.i_max = self.nx - 1
+ self.j_max = self.ny - 1
+ self.k_max = self.nz - 1
+ self.grid = grid
+
+ def n_edges(self):
+ i = self.nx
+ j = self.ny
+ k = self.nz
+ e = (i * j * (k - 1)) + (j * k * (i - 1)) + (i * k * (j - 1))
+ return e
+
+ def n_nodes(self):
+ return self.nx * self.ny * self.nz
+
+ def n_cells(self):
+ return (self.nx - 1) * (self.ny - 1) * (self.nz - 1)
+
+ def get(self, i, j, k):
+ return self.grid[i, j, k]
+
+ def within_bounds(self, p):
+ if p[0] < 0 or p[0] > self.nx:
+ raise ValueError('x')
+ if p[1] < 0 or p[1] > self.ny:
+ raise ValueError('y')
+ if p[2] < 0 or p[2] > self.nz:
+ raise ValueError('z')
+
+ def discretise_point(self, p):
+ x = round_value(float(p[0]) / self.dx)
+ y = round_value(float(p[1]) / self.dy)
+ z = round_value(float(p[2]) / self.dz)
+ return (x, y, z)
+
+ def round_to_grid(self, p):
+ p = self.discretise_point(p)
+ p_r = (p[0] * self.dx,
+ p[1] * self.dy,
+ p[2] * self.dz)
+ return p_r
+
+
+ def within_pml(self, p):
+ if (p[0] < self.pmlthickness['x0'] or
+ p[0] > self.nx - self.pmlthickness['xmax'] or
+ p[1] < self.pmlthickness['y0'] or
+ p[1] > self.ny - self.pmlthickness['ymax'] or
+ p[2] < self.pmlthickness['z0'] or
+ p[2] > self.nz - self.pmlthickness['zmax']):
+ return True
+ else:
+ return False
+
+
+class FDTDGrid(Grid):
+ """
+ Holds attributes associated with the entire grid. A convenient
+ way for accessing regularly used parameters.
+ """
+
+ def __init__(self):
+ self.title = ''
+ self.memoryusage = 0
+
+ self.nx = 0
+ self.ny = 0
+ self.nz = 0
+ self.dx = 0
+ self.dy = 0
+ self.dz = 0
+ self.dt = 0
+ self.iterations = 0
+ self.timewindow = 0
+
+ # Ordered dictionary required so that PMLs are always updated in the
+ # same order. The order itself does not matter, however, if must be the
+ # same from model to model otherwise the numerical precision from adding
+ # the PML corrections will be different.
+ self.pmlthickness = OrderedDict((key, 10) for key in PML.boundaryIDs)
+ self.cfs = [CFS()]
+ self.pmls = []
+ self.pmlformulation = 'HORIPML'
+
+ self.materials = []
+ self.mixingmodels = []
+ self.averagevolumeobjects = True
+ self.fractalvolumes = []
+ self.geometryviews = []
+ self.geometryobjectswrite = []
+ self.waveforms = []
+ self.voltagesources = []
+ self.hertziandipoles = []
+ self.magneticdipoles = []
+ self.transmissionlines = []
+ self.rxs = []
+ self.srcsteps = [0, 0, 0]
+ self.rxsteps = [0, 0, 0]
+ self.snapshots = []
+ self.subgrids = []
+ self.gpu = None
+ self.name = 'Main'
+ self.outputdirectory = ''
+ self.iteration = 0
+
+ def initialise_geometry_arrays(self):
+ """
+ Initialise an array for volumetric material IDs (solid);
+ boolean arrays for specifying whether materials can have dielectric smoothing (rigid);
+ and an array for cell edge IDs (ID).
+ Solid and ID arrays are initialised to free_space (one);
+ rigid arrays to allow dielectric smoothing (zero).
+ """
+ self.solid = np.ones((self.nx, self.ny, self.nz), dtype=np.uint32)
+ self.rigidE = np.zeros((12, self.nx, self.ny, self.nz), dtype=np.int8)
+ self.rigidH = np.zeros((6, self.nx, self.ny, self.nz), dtype=np.int8)
+ self.ID = np.ones((6, self.nx + 1, self.ny + 1, self.nz + 1), dtype=np.uint32)
+ self.IDlookup = {'Ex': 0, 'Ey': 1, 'Ez': 2, 'Hx': 3, 'Hy': 4, 'Hz': 5}
+
+ 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.dtypes['float_or_double'])
+ self.Ey = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=config.dtypes['float_or_double'])
+ self.Ez = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=config.dtypes['float_or_double'])
+ self.Hx = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=config.dtypes['float_or_double'])
+ self.Hy = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=config.dtypes['float_or_double'])
+ self.Hz = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=config.dtypes['float_or_double'])
+
+
+ def initialise_grids(self):
+ """Function to call the initialisation of all grids."""
+ for g in [self] + self.subgrids:
+ g.initialise_geometry_arrays()
+ g.initialise_field_arrays()
+
+ def initialise_std_update_coeff_arrays(self):
+ """Initialise arrays for storing update coefficients."""
+ self.updatecoeffsE = np.zeros((len(self.materials), 5), dtype=config.dtypes['float_or_double'])
+ self.updatecoeffsH = np.zeros((len(self.materials), 5), dtype=config.dtypes['float_or_double'])
+
+ def initialise_dispersive_arrays(self, dtype):
+ """Initialise arrays for storing coefficients when there are dispersive materials present."""
+ self.Tx = np.zeros((config.materials['maxpoles'], self.nx + 1, self.ny + 1, self.nz + 1), dtype=dtype)
+ self.Ty = np.zeros((config.materials['maxpoles'], self.nx + 1, self.ny + 1, self.nz + 1), dtype=dtype)
+ self.Tz = np.zeros((config.materials['maxpoles'], self.nx + 1, self.ny + 1, self.nz + 1), dtype=dtype)
+ self.updatecoeffsdispersive = np.zeros((len(self.materials), 3 * config.materials['maxpoles']), dtype=dtype)
+
+ def memory_estimate_basic(self):
+ """Estimate the amount of memory (RAM) required to run a model."""
+
+ stdoverhead = 50e6
+
+ 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
+
+ # 6 x field arrays + 6 x ID arrays
+ fieldarrays = (6 + 6) * (self.nx + 1) * (self.ny + 1) * (self.nz + 1) * np.dtype(config.dtypes['float_or_double']).itemsize
+
+ # PML arrays
+ pmlarrays = 0
+ for (k, v) in self.pmlthickness.items():
+ if v > 0:
+ if 'x' in k:
+ pmlarrays += ((v + 1) * self.ny * (self.nz + 1))
+ pmlarrays += ((v + 1) * (self.ny + 1) * self.nz)
+ pmlarrays += (v * self.ny * (self.nz + 1))
+ pmlarrays += (v * (self.ny + 1) * self.nz)
+ elif 'y' in k:
+ pmlarrays += (self.nx * (v + 1) * (self.nz + 1))
+ pmlarrays += ((self.nx + 1) * (v + 1) * self.nz)
+ pmlarrays += ((self.nx + 1) * v * self.nz)
+ pmlarrays += (self.nx * v * (self.nz + 1))
+ elif 'z' in k:
+ pmlarrays += (self.nx * (self.ny + 1) * (v + 1))
+ pmlarrays += ((self.nx + 1) * self.ny * (v + 1))
+ pmlarrays += ((self.nx + 1) * self.ny * v)
+ pmlarrays += (self.nx * (self.ny + 1) * v)
+
+ self.memoryusage = int(stdoverhead + fieldarrays + solidarray + rigidarrays + pmlarrays)
+
+ def memory_check(self, snapsmemsize=0):
+ """Check if the required amount of memory (RAM) is available on the host and GPU if specified.
+
+ Args:
+ snapsmemsize (int): amount of memory (bytes) required to store all requested snapshots
+ """
+
+ # Check if model can be built and/or run on host
+ if self.memoryusage > config.hostinfo['ram']:
+ raise GeneralError('Memory (RAM) required ~{} exceeds {} detected!\n'.format(human_size(self.memoryusage), human_size(config.hostinfo['ram'], a_kilobyte_is_1024_bytes=True)))
+
+ # Check if model can be run on specified GPU if required
+ if config.cuda['gpus'] is not None:
+ if self.memoryusage - snapsmemsize > config.cuda['gpus'].totalmem:
+ raise GeneralError('Memory (RAM) required ~{} exceeds {} detected on specified {} - {} GPU!\n'.format(human_size(self.memoryusage), human_size(config.cuda['gpus'].totalmem, a_kilobyte_is_1024_bytes=True), config.cuda['gpus'].deviceID, config.cuda['gpus'].name))
+
+ # If the required memory without the snapshots will fit on the GPU then transfer and store snaphots on host
+ if snapsmemsize != 0 and self.memoryusage - snapsmemsize < config.cuda['gpus'].totalmem:
+ config.cuda['snapsgpu2cpu'] = True
+
+ def gpu_set_blocks_per_grid(self):
+ """Set the blocks per grid size used for updating the electric and magnetic field arrays on a GPU."""
+ config.cuda['gpus'].bpg = (int(np.ceil(((self.nx + 1) * (self.ny + 1) * (self.nz + 1)) / config.cuda['gpus'].tpb[0])), 1, 1)
+
+ def gpu_initialise_arrays(self):
+ """Initialise standard field arrays on GPU."""
+
+ import pycuda.gpuarray as gpuarray
+
+ self.ID_gpu = gpuarray.to_gpu(self.ID)
+ self.Ex_gpu = gpuarray.to_gpu(self.Ex)
+ self.Ey_gpu = gpuarray.to_gpu(self.Ey)
+ self.Ez_gpu = gpuarray.to_gpu(self.Ez)
+ self.Hx_gpu = gpuarray.to_gpu(self.Hx)
+ self.Hy_gpu = gpuarray.to_gpu(self.Hy)
+ self.Hz_gpu = gpuarray.to_gpu(self.Hz)
+
+ def gpu_initialise_dispersive_arrays(self):
+ """Initialise dispersive material coefficient arrays on GPU."""
+
+ import pycuda.gpuarray as gpuarray
+
+ self.Tx_gpu = gpuarray.to_gpu(self.Tx)
+ self.Ty_gpu = gpuarray.to_gpu(self.Ty)
+ self.Tz_gpu = gpuarray.to_gpu(self.Tz)
+ self.updatecoeffsdispersive_gpu = gpuarray.to_gpu(self.updatecoeffsdispersive)
+
+ # Add PEC boundaries to invariant direction in 2D modes
+ # N.B. 2D modes are a single cell slice of 3D grid
+ def tmx(self):
+ # Ey & Ez components
+ self.ID[1, 0, :, :] = 0
+ self.ID[1, 1, :, :] = 0
+ self.ID[2, 0, :, :] = 0
+ self.ID[2, 1, :, :] = 0
+
+ def tmy():
+ # Ex & Ez components
+ G.ID[0, :, 0, :] = 0
+ G.ID[0, :, 1, :] = 0
+ G.ID[2, :, 0, :] = 0
+ G.ID[2, :, 1, :] = 0
+
+ def tmz():
+ # Ex & Ey components
+ G.ID[0, :, :, 0] = 0
+ G.ID[0, :, :, 1] = 0
+ G.ID[1, :, :, 0] = 0
+ G.ID[1, :, :, 1] = 0
+
+
+ def reset_fields(self):
+ # Clear arrays for field components
+ self.initialise_field_arrays()
+
+ # Clear arrays for fields in PML
+ for pml in self.pmls:
+ pml.initialise_field_arrays()
+
+ def calculate_dt(self):
+ self.dt = 1 / (c * np.sqrt((1 / self.dx) * (1 / self.dx) + (1 / self.dy) * (1 / self.dy) + (1 / self.dz) * (1 / self.dz)))
+
+ def round_time_step(self):
+ # Round down time step to nearest float with precision one less than hardware maximum. Avoids inadvertently exceeding the CFL due to binary representation of floating point number.
+ # Round down time step to nearest float with precision one less than hardware maximum.
+ # Avoids inadvertently exceeding the CFL due to binary representation of floating point number.
+ self.dt = round_value(self.dt, decimalplaces=d.getcontext().prec - 1)
+
+
+
+def dispersion_analysis(G):
+ """
+ Analysis of numerical dispersion (Taflove et al, 2005, p112) -
+ worse case of maximum frequency and minimum wavelength
+
+ Args:
+ G (class): Grid class instance - holds essential parameters describing the model.
+
+ Returns:
+ results (dict): Results from dispersion analysis
+ """
+
+ # Physical phase velocity error (percentage); grid sampling density;
+ # material with maximum permittivity; maximum significant frequency; error message
+ results = {'deltavp': False, 'N': False, 'material': False, 'maxfreq': [], 'error': ''}
+
+ # Find maximum significant frequency
+ if G.waveforms:
+ for waveform in G.waveforms:
+ if waveform.type == 'sine' or waveform.type == 'contsine':
+ results['maxfreq'].append(4 * waveform.freq)
+
+ elif waveform.type == 'impulse':
+ results['error'] = 'impulse waveform used.'
+
+ else:
+ # User-defined waveform
+ if waveform.type == 'user':
+ iterations = G.iterations
+
+ # Built-in waveform
+ else:
+ # Time to analyse waveform - 4*pulse_width as using entire
+ # time window can result in demanding FFT
+ waveform.calculate_coefficients()
+ iterations = round_value(4 * waveform.chi / G.dt)
+ if iterations > G.iterations:
+ iterations = G.iterations
+
+ waveformvalues = np.zeros(G.iterations)
+ for iteration in range(G.iterations):
+ 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:
+ # FFT
+ freqs, power = fft_power(waveformvalues, G.dt)
+ # Get frequency for max power
+ freqmaxpower = np.where(np.isclose(power, 0))[0][0]
+
+ # Set maximum frequency to a threshold drop from maximum power, ignoring DC value
+ try:
+ freqthres = np.where(power[freqmaxpower:] < -config.numdispersion['highestfreqthres'])[0][0] + freqmaxpower
+ results['maxfreq'].append(freqs[freqthres])
+ except ValueError:
+ results['error'] = 'unable to calculate maximum power from waveform, most likely due to undersampling.'
+
+ # Ignore case where someone is using a waveform with zero amplitude, i.e. on a receiver
+ elif waveform.amp == 0:
+ pass
+
+ # 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.'
+ else:
+ results['error'] = 'no waveform detected.'
+
+ if results['maxfreq']:
+ results['maxfreq'] = max(results['maxfreq'])
+
+ # Find minimum wavelength (material with maximum permittivity)
+ maxer = 0
+ matmaxer = ''
+ for x in G.materials:
+ if x.se != float('inf'):
+ er = x.er
+ # If there are dispersive materials calculate the complex relative permittivity
+ # at maximum frequency and take the real part
+ if x.__class__.__name__ is 'DispersiveMaterial':
+ er = x.calculate_er(results['maxfreq'])
+ er = er.real
+ if er > maxer:
+ maxer = er
+ matmaxer = x.ID
+ results['material'] = next(x for x in G.materials if x.ID == matmaxer)
+
+ # Minimum velocity
+ minvelocity = config.c / np.sqrt(maxer)
+
+ # Minimum wavelength
+ minwavelength = minvelocity / results['maxfreq']
+
+ # Maximum spatial step
+ if '3D' in config.general['mode']:
+ delta = max(G.dx, G.dy, G.dz)
+ elif '2D' in config.general['mode']:
+ if G.nx == 1:
+ delta = max(G.dy, G.dz)
+ elif G.ny == 1:
+ delta = max(G.dx, G.dz)
+ elif G.nz == 1:
+ delta = max(G.dx, G.dy)
+
+ # Courant stability factor
+ S = (config.c * G.dt) / delta
+
+ # Grid sampling density
+ results['N'] = minwavelength / delta
+
+ # Check grid sampling will result in physical wave propagation
+ if int(np.floor(results['N'])) >= config.numdispersion['mingridsampling']:
+ # Numerical phase velocity
+ 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
+
+ # Store rounded down value of grid sampling density
+ results['N'] = int(np.floor(results['N']))
+
+ return results
+
+
+def Ix(x, y, z, Hx, Hy, Hz, G):
+ """Calculates the x-component of current at a grid position.
+
+ Args:
+ x, y, z (float): Coordinates of position in grid.
+ Hx, Hy, Hz (memory view): numpy array of magnetic field values.
+ G (class): Grid class instance - holds essential parameters describing the model.
+ """
+
+ 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])
+
+ return Ix
+
+
+def Iy(x, y, z, Hx, Hy, Hz, G):
+ """Calculates the y-component of current at a grid position.
+
+ Args:
+ x, y, z (float): Coordinates of position in grid.
+ Hx, Hy, Hz (memory view): numpy array of magnetic field values.
+ G (class): Grid class instance - holds essential parameters describing the model.
+ """
+
+ 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])
+
+ return Iy
+
+
+def Iz(x, y, z, Hx, Hy, Hz, G):
+ """Calculates the z-component of current at a grid position.
+
+ Args:
+ x, y, z (float): Coordinates of position in grid.
+ Hx, Hy, Hz (memory view): numpy array of magnetic field values.
+ G (class): Grid class instance - holds essential parameters describing the model.
+ """
+
+ 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])
+
+ return Iz
+
+
+class GPUGrid(Grid):
+ pass
diff --git a/gprMax/input_cmds_geometry.py b/gprMax/input_cmds_geometry.py
index a25f0ec0..a3793082 100644
--- a/gprMax/input_cmds_geometry.py
+++ b/gprMax/input_cmds_geometry.py
@@ -1,318 +1,318 @@
-# Copyright (C) 2015-2019: The University of Edinburgh
-# Authors: Craig Warren and Antonis Giannopoulos
-#
-# This file is part of gprMax.
-#
-# gprMax is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# gprMax is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with gprMax. If not, see .
-import sys
-
-from .utilities import get_terminal_width
-
-from .cmds_geometry.geometry_objects_read import GeometryObjectsRead
-from .cmds_geometry.edge import Edge
-from .cmds_geometry.plate import Plate
-from .cmds_geometry.triangle import Triangle
-from .cmds_geometry.box import Box
-from .cmds_geometry.cylinder import Cylinder
-from .cmds_geometry.cylindrical_sector import CylindricalSector
-from .cmds_geometry.fractal_box import FractalBox
-from .cmds_geometry.sphere import Sphere
-from .cmds_geometry.add_surface_roughness import AddSurfaceRoughness
-from .cmds_geometry.add_surface_water import AddSurfaceWater
-from .cmds_geometry.add_grass import AddGrass
-
-from .utilities import round_value
-
-
-from tqdm import tqdm
-import numpy as np
-
-
-def process_geometrycmds(geometry):
- """
- This function checks the validity of command parameters, creates instances
- of classes of parameters, and calls functions to directly set arrays
- solid, rigid and ID.
-
- Args:
- geometry (list): Geometry commands in the model
- """
-
- scene_objects = []
-
- # Disable progress bar if on Windows as it does not update properly
- # when messages are printed
- #if sys.platform == 'win32':
-# tqdmdisable = True
- #else:
- # tqdmdisable = G.tqdmdisable
-
- tqdmdisable = False
-
- for object in tqdm(geometry, desc='Processing geometry related cmds', unit='cmds', ncols=get_terminal_width() - 1, file=sys.stdout, disable=tqdmdisable):
- tmp = object.split()
-
- if tmp[0] == '#geometry_objects_read:':
- if len(tmp) != 6:
- raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires exactly five parameters')
-
- p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
-
- gor = GeometryObjectsRead(p1=p1, geofile=tmp[4], matfile=tmp[5])
- scene_objects.append(gor)
-
- elif tmp[0] == '#edge:':
- if len(tmp) != 8:
- raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires exactly seven parameters')
-
- edge = Edge(p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])),
- p2=(float(tmp[4]), float(tmp[5]), float(tmp[6])),
- material_id=tmp[7])
-
- scene_objects.append(edge)
-
- elif tmp[0] == '#plate:':
- if len(tmp) < 8:
- raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires at least seven parameters')
-
- # Isotropic case
- if len(tmp) == 8:
- plate = Plate(p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])),
- p2=(float(tmp[4]), float(tmp[5]), float(tmp[6])),
- material_id=tmp[7])
-
- # Anisotropic case
- elif len(tmp) == 9:
- plate = Plate(p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])),
- p2=(float(tmp[4]), float(tmp[5]), float(tmp[6])),
- material_ids=tmp[7:])
-
- else:
- raise CmdInputError("'" + ' '.join(tmp) + "'" + ' too many parameters have been given')
-
- scene_objects.append(plate)
-
- elif tmp[0] == '#triangle:':
- if len(tmp) < 12:
- raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires at least eleven parameters')
-
- p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
- p2 = (float(tmp[4]), float(tmp[5]), float(tmp[6]))
- p3 = (float(tmp[7]), float(tmp[8]), float(tmp[9]))
- thickness = float(tmp[10])
-
- # 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])
-
- # 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())
-
- # Uniaxial anisotropic case
- elif len(tmp) == 14:
- triangle = Triangle(p1=p1, p2=p2, p3=p3, thickness=thickness, material_ids=tmp[11:])
-
- else:
- raise CmdInputError("'" + ' '.join(tmp) + "'" + ' too many parameters have been given')
-
- scene_objects.append(triangle)
-
- elif tmp[0] == '#box:':
- if len(tmp) < 8:
- raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires at least seven parameters')
-
- p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
- p2 = (float(tmp[4]), float(tmp[5]), float(tmp[6]))
-
- # Isotropic case with no user specified averaging
- if len(tmp) == 8:
- box = Box(p1=p1, p2=p2, material_id=tmp[7])
-
- # Isotropic case with user specified averaging
- elif len(tmp) == 9:
- box = Box(p1=p1, p2=p2, material_id=tmp[7], averaging=tmp[8])
-
- # Uniaxial anisotropic case
- elif len(tmp) == 10:
- box = Box(p1=p1, p2=p2, material_ids=tmp[7:])
-
- else:
- raise CmdInputError("'" + ' '.join(tmp) + "'" + ' too many parameters have been given')
-
- scene_objects.append(box)
-
- elif tmp[0] == '#cylinder:':
- if len(tmp) < 9:
- raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires at least eight parameters')
-
- p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
- p2 = (float(tmp[4]), float(tmp[5]), float(tmp[6]))
- r = float(tmp[7])
-
- # Isotropic case with no user specified averaging
- if len(tmp) == 9:
- cylinder = Cylinder(p1=p1, p2=p2, r=r, material_id=tmp[8])
-
- # 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])
-
- # Uniaxial anisotropic case
- elif len(tmp) == 11:
- cylinder = Cylinder(p1=p1, p2=p2, r=r, material_ids=tmp[8:])
- else:
- raise CmdInputError("'" + ' '.join(tmp) + "'" + ' too many parameters have been given')
-
- scene_objects.append(cylinder)
-
- elif tmp[0] == '#cylindrical_sector:':
- if len(tmp) < 10:
- raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires at least nine parameters')
-
- normal = tmp[1].lower()
- ctr1 = float(tmp[2])
- ctr2 = float(tmp[3])
- extent1 = float(tmp[4])
- extent2 = float(tmp[5])
- r = float(tmp[6])
- start = float(tmp[7])
- end = float(tmp[8])
-
- # Isotropic case with no user specified averaging
- if len(tmp) == 10:
- CylindricalSector(normal=normal, ctl1=ctl1, ctl2=ctl2, extent1=extent1, extent2=extent2, r=r, start=start, end=end, msterial_id=tmp[9])
-
- # Isotropic case with user specified averaging
- elif len(tmp) == 11:
- CylindricalSector(normal=normal, ctl1=ctl1, ctl2=ctl2, extent1=extent1, extent2=extent2, r=r, start=start, end=end, averaging=tmp[10], material_id=tmp[9])
-
- # Uniaxial anisotropic case
- elif len(tmp) == 12:
- CylindricalSector(normal=normal, ctl1=ctl1, ctl2=ctl2, extent1=extent1, extent2=extent2, r=r, start=start, end=end, material_ids=tmp[9:])
-
- else:
- raise CmdInputError("'" + ' '.join(tmp) + "'" + ' too many parameters have been given')
-
- scene_objects.append(cylindrical_sector)
-
- elif tmp[0] == '#sphere:':
- if len(tmp) < 6:
- raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires at least five parameters')
-
- p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
- r = float(tmp[4])
-
- # Isotropic case with no user specified averaging
- if len(tmp) == 6:
- sphere = Sphere(p1=p1, r=r, material_id=tmp[5])
-
- # Isotropic case with user specified averaging
- elif len(tmp) == 7:
- sphere = Sphere(p1=p1, r=r, material_id=tmp[5], averaging=tmp[6])
-
- # Uniaxial anisotropic case
- elif len(tmp) == 8:
- sphere = Sphere(p1=p1, r=r, material_id=tmp[5:])
-
- else:
- raise CmdInputError("'" + ' '.join(tmp) + "'" + ' too many parameters have been given')
-
- scene_objects.append(sphere)
-
- elif tmp[0] == '#fractal_box:':
- # Default is no dielectric smoothing for a fractal box
-
- if len(tmp) < 14:
- raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires at least thirteen parameters')
-
- p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
- p2 = (float(tmp[4]), float(tmp[5]), float(tmp[6]))
- frac_dim = float(tmp[7])
- weighting = np.array([float(tmp[8]), float(tmp[9]), float(tmp[10])])
- n_materials = round_value(tmp[11])
- mixing_model_id = tmp[12]
- ID = tmp[13]
- # without seed
- if len(tmp) == 14:
- fb = FractalBox(p1=p1, p2=p2, frac_dim=frac_dim, weighting=weighting, mixing_model_id=mixing_model_id, id=ID, n_materials=n_materials)
- # with seed
- elif len(tmp) == 15:
- fb = FractalBox(p1=p1, p2=p2, frac_dim=frac_dim, weighting=weighting, mixing_model_id=mixing_model_id, id=ID, n_materials=n_materials, seed=tmp[14])
- # user specified averaging
- elif len(tmp) == 16:
- fb = FractalBox(p1=p1, p2=p2, frac_dim=frac_dim, weighting=weighting, mixing_model_id=mixing_model_id, id=ID, n_materials=n_materials, seed=tmp[14], averaging=tmp[15].lower())
- else:
- raise CmdInputError("'" + ' '.join(tmp) + "'" + ' too many parameters have been given')
-
- scene_objects.append(fb)
-
- # Search and process any modifiers for the fractal box
- for object in geometry:
- tmp = object.split()
-
- if tmp[0] == '#add_surface_roughness:':
- if len(tmp) < 13:
- raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires at least twelve parameters')
-
- p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
- p2 = (float(tmp[4]), float(tmp[5]), float(tmp[6]))
- frac_dim = float(tmp[7])
- weighting = np.array([float(tmp[8]), float(tmp[9])])
- limits = [float(tmp[10]), float(tmp[11])]
- fractal_box_id = tmp[12]
-
- # No seed
- if len(tmp) == 13:
- asr = AddSurfaceRoughness(p1=p1, p2=p2, frac_dim=frac_dim, weighting=weighting, limits=limits, fractal_box_id=fractal_box_id)
- elif len(tmp) == 14:
- asr = AddSurfaceRoughness(p1=p1, p2=p2, frac_dim=frac_dim, weighting=weighting, limits=limits, fractal_box_id=fractal_box_id, seed=int(tmp[13]))
- else:
- raise CmdInputError("'" + ' '.join(tmp) + "'" + ' too many parameters have been given')
-
- scene_objects.append(asr)
-
- if tmp[0] == '#add_surface_water:':
- if len(tmp) != 9:
- raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires exactly eight parameters')
-
- p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
- p2 = (float(tmp[4]), float(tmp[5]), float(tmp[6]))
- depth = float(tmp[7])
- fractal_box_id = tmp[8]
-
- 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:
- raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires at least eleven parameters')
-
- p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
- p2 = (float(tmp[4]), float(tmp[5]), float(tmp[6]))
- frac_dim = float(tmp[7])
- limits = [float(tmp[8]), float(tmp[9])]
- n_blades = int(tmp[10])
- fractal_box_id = tmp[11]
-
- # no seed
- if len(tmp) == 12:
- grass = AddGrass(p1=p1, p2=p2, frac_dim=frac_dim, limits=limits, n_blades=n_blades, fractal_box_id=fractal_box_id)
- elif len(tmp) == 13:
- grass = AddGrass(p1=p1, p2=p2, frac_dim=frac_dim, limits=limits, n_blades=n_blades, fractal_box_id=fractal_box_id, seed=int(tmp[12]))
- else:
- raise CmdInputError("'" + ' '.join(tmp) + "'" + ' too many parameters have been given')
- scene_objects.append(grass)
-
- return scene_objects
+# Copyright (C) 2015-2019: The University of Edinburgh
+# Authors: Craig Warren and Antonis Giannopoulos
+#
+# This file is part of gprMax.
+#
+# gprMax is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# gprMax is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with gprMax. If not, see .
+import sys
+
+from .utilities import get_terminal_width
+
+from .cmds_geometry.geometry_objects_read import GeometryObjectsRead
+from .cmds_geometry.edge import Edge
+from .cmds_geometry.plate import Plate
+from .cmds_geometry.triangle import Triangle
+from .cmds_geometry.box import Box
+from .cmds_geometry.cylinder import Cylinder
+from .cmds_geometry.cylindrical_sector import CylindricalSector
+from .cmds_geometry.fractal_box import FractalBox
+from .cmds_geometry.sphere import Sphere
+from .cmds_geometry.add_surface_roughness import AddSurfaceRoughness
+from .cmds_geometry.add_surface_water import AddSurfaceWater
+from .cmds_geometry.add_grass import AddGrass
+
+from .utilities import round_value
+
+
+from tqdm import tqdm
+import numpy as np
+
+
+def process_geometrycmds(geometry):
+ """
+ This function checks the validity of command parameters, creates instances
+ of classes of parameters, and calls functions to directly set arrays
+ solid, rigid and ID.
+
+ Args:
+ geometry (list): Geometry commands in the model
+ """
+
+ scene_objects = []
+
+ # Disable progress bar if on Windows as it does not update properly
+ # when messages are printed
+ #if sys.platform == 'win32':
+# tqdmdisable = True
+ #else:
+ # tqdmdisable = G.tqdmdisable
+
+ tqdmdisable = False
+
+ for object in tqdm(geometry, desc='Processing geometry related cmds', unit='cmds', ncols=get_terminal_width() - 1, file=sys.stdout, disable=tqdmdisable):
+ tmp = object.split()
+
+ if tmp[0] == '#geometry_objects_read:':
+ if len(tmp) != 6:
+ raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires exactly five parameters')
+
+ p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
+
+ gor = GeometryObjectsRead(p1=p1, geofile=tmp[4], matfile=tmp[5])
+ scene_objects.append(gor)
+
+ elif tmp[0] == '#edge:':
+ if len(tmp) != 8:
+ raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires exactly seven parameters')
+
+ edge = Edge(p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])),
+ p2=(float(tmp[4]), float(tmp[5]), float(tmp[6])),
+ material_id=tmp[7])
+
+ scene_objects.append(edge)
+
+ elif tmp[0] == '#plate:':
+ if len(tmp) < 8:
+ raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires at least seven parameters')
+
+ # Isotropic case
+ if len(tmp) == 8:
+ plate = Plate(p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])),
+ p2=(float(tmp[4]), float(tmp[5]), float(tmp[6])),
+ material_id=tmp[7])
+
+ # Anisotropic case
+ elif len(tmp) == 9:
+ plate = Plate(p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])),
+ p2=(float(tmp[4]), float(tmp[5]), float(tmp[6])),
+ material_ids=tmp[7:])
+
+ else:
+ raise CmdInputError("'" + ' '.join(tmp) + "'" + ' too many parameters have been given')
+
+ scene_objects.append(plate)
+
+ elif tmp[0] == '#triangle:':
+ if len(tmp) < 12:
+ raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires at least eleven parameters')
+
+ p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
+ p2 = (float(tmp[4]), float(tmp[5]), float(tmp[6]))
+ p3 = (float(tmp[7]), float(tmp[8]), float(tmp[9]))
+ thickness = float(tmp[10])
+
+ # 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])
+
+ # 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())
+
+ # Uniaxial anisotropic case
+ elif len(tmp) == 14:
+ triangle = Triangle(p1=p1, p2=p2, p3=p3, thickness=thickness, material_ids=tmp[11:])
+
+ else:
+ raise CmdInputError("'" + ' '.join(tmp) + "'" + ' too many parameters have been given')
+
+ scene_objects.append(triangle)
+
+ elif tmp[0] == '#box:':
+ if len(tmp) < 8:
+ raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires at least seven parameters')
+
+ p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
+ p2 = (float(tmp[4]), float(tmp[5]), float(tmp[6]))
+
+ # Isotropic case with no user specified averaging
+ if len(tmp) == 8:
+ box = Box(p1=p1, p2=p2, material_id=tmp[7])
+
+ # Isotropic case with user specified averaging
+ elif len(tmp) == 9:
+ box = Box(p1=p1, p2=p2, material_id=tmp[7], averaging=tmp[8])
+
+ # Uniaxial anisotropic case
+ elif len(tmp) == 10:
+ box = Box(p1=p1, p2=p2, material_ids=tmp[7:])
+
+ else:
+ raise CmdInputError("'" + ' '.join(tmp) + "'" + ' too many parameters have been given')
+
+ scene_objects.append(box)
+
+ elif tmp[0] == '#cylinder:':
+ if len(tmp) < 9:
+ raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires at least eight parameters')
+
+ p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
+ p2 = (float(tmp[4]), float(tmp[5]), float(tmp[6]))
+ r = float(tmp[7])
+
+ # Isotropic case with no user specified averaging
+ if len(tmp) == 9:
+ cylinder = Cylinder(p1=p1, p2=p2, r=r, material_id=tmp[8])
+
+ # 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])
+
+ # Uniaxial anisotropic case
+ elif len(tmp) == 11:
+ cylinder = Cylinder(p1=p1, p2=p2, r=r, material_ids=tmp[8:])
+ else:
+ raise CmdInputError("'" + ' '.join(tmp) + "'" + ' too many parameters have been given')
+
+ scene_objects.append(cylinder)
+
+ elif tmp[0] == '#cylindrical_sector:':
+ if len(tmp) < 10:
+ raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires at least nine parameters')
+
+ normal = tmp[1].lower()
+ ctr1 = float(tmp[2])
+ ctr2 = float(tmp[3])
+ extent1 = float(tmp[4])
+ extent2 = float(tmp[5])
+ r = float(tmp[6])
+ start = float(tmp[7])
+ end = float(tmp[8])
+
+ # Isotropic case with no user specified averaging
+ if len(tmp) == 10:
+ CylindricalSector(normal=normal, ctl1=ctl1, ctl2=ctl2, extent1=extent1, extent2=extent2, r=r, start=start, end=end, msterial_id=tmp[9])
+
+ # Isotropic case with user specified averaging
+ elif len(tmp) == 11:
+ CylindricalSector(normal=normal, ctl1=ctl1, ctl2=ctl2, extent1=extent1, extent2=extent2, r=r, start=start, end=end, averaging=tmp[10], material_id=tmp[9])
+
+ # Uniaxial anisotropic case
+ elif len(tmp) == 12:
+ CylindricalSector(normal=normal, ctl1=ctl1, ctl2=ctl2, extent1=extent1, extent2=extent2, r=r, start=start, end=end, material_ids=tmp[9:])
+
+ else:
+ raise CmdInputError("'" + ' '.join(tmp) + "'" + ' too many parameters have been given')
+
+ scene_objects.append(cylindrical_sector)
+
+ elif tmp[0] == '#sphere:':
+ if len(tmp) < 6:
+ raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires at least five parameters')
+
+ p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
+ r = float(tmp[4])
+
+ # Isotropic case with no user specified averaging
+ if len(tmp) == 6:
+ sphere = Sphere(p1=p1, r=r, material_id=tmp[5])
+
+ # Isotropic case with user specified averaging
+ elif len(tmp) == 7:
+ sphere = Sphere(p1=p1, r=r, material_id=tmp[5], averaging=tmp[6])
+
+ # Uniaxial anisotropic case
+ elif len(tmp) == 8:
+ sphere = Sphere(p1=p1, r=r, material_id=tmp[5:])
+
+ else:
+ raise CmdInputError("'" + ' '.join(tmp) + "'" + ' too many parameters have been given')
+
+ scene_objects.append(sphere)
+
+ elif tmp[0] == '#fractal_box:':
+ # Default is no dielectric smoothing for a fractal box
+
+ if len(tmp) < 14:
+ raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires at least thirteen parameters')
+
+ p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
+ p2 = (float(tmp[4]), float(tmp[5]), float(tmp[6]))
+ frac_dim = float(tmp[7])
+ weighting = np.array([float(tmp[8]), float(tmp[9]), float(tmp[10])])
+ n_materials = round_value(tmp[11])
+ mixing_model_id = tmp[12]
+ ID = tmp[13]
+ # without seed
+ if len(tmp) == 14:
+ fb = FractalBox(p1=p1, p2=p2, frac_dim=frac_dim, weighting=weighting, mixing_model_id=mixing_model_id, id=ID, n_materials=n_materials)
+ # with seed
+ elif len(tmp) == 15:
+ fb = FractalBox(p1=p1, p2=p2, frac_dim=frac_dim, weighting=weighting, mixing_model_id=mixing_model_id, id=ID, n_materials=n_materials, seed=tmp[14])
+ # user specified averaging
+ elif len(tmp) == 16:
+ fb = FractalBox(p1=p1, p2=p2, frac_dim=frac_dim, weighting=weighting, mixing_model_id=mixing_model_id, id=ID, n_materials=n_materials, seed=tmp[14], averaging=tmp[15].lower())
+ else:
+ raise CmdInputError("'" + ' '.join(tmp) + "'" + ' too many parameters have been given')
+
+ scene_objects.append(fb)
+
+ # Search and process any modifiers for the fractal box
+ for object in geometry:
+ tmp = object.split()
+
+ if tmp[0] == '#add_surface_roughness:':
+ if len(tmp) < 13:
+ raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires at least twelve parameters')
+
+ p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
+ p2 = (float(tmp[4]), float(tmp[5]), float(tmp[6]))
+ frac_dim = float(tmp[7])
+ weighting = np.array([float(tmp[8]), float(tmp[9])])
+ limits = [float(tmp[10]), float(tmp[11])]
+ fractal_box_id = tmp[12]
+
+ # No seed
+ if len(tmp) == 13:
+ asr = AddSurfaceRoughness(p1=p1, p2=p2, frac_dim=frac_dim, weighting=weighting, limits=limits, fractal_box_id=fractal_box_id)
+ elif len(tmp) == 14:
+ asr = AddSurfaceRoughness(p1=p1, p2=p2, frac_dim=frac_dim, weighting=weighting, limits=limits, fractal_box_id=fractal_box_id, seed=int(tmp[13]))
+ else:
+ raise CmdInputError("'" + ' '.join(tmp) + "'" + ' too many parameters have been given')
+
+ scene_objects.append(asr)
+
+ if tmp[0] == '#add_surface_water:':
+ if len(tmp) != 9:
+ raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires exactly eight parameters')
+
+ p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
+ p2 = (float(tmp[4]), float(tmp[5]), float(tmp[6]))
+ depth = float(tmp[7])
+ fractal_box_id = tmp[8]
+
+ 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:
+ raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires at least eleven parameters')
+
+ p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
+ p2 = (float(tmp[4]), float(tmp[5]), float(tmp[6]))
+ frac_dim = float(tmp[7])
+ limits = [float(tmp[8]), float(tmp[9])]
+ n_blades = int(tmp[10])
+ fractal_box_id = tmp[11]
+
+ # no seed
+ if len(tmp) == 12:
+ grass = AddGrass(p1=p1, p2=p2, frac_dim=frac_dim, limits=limits, n_blades=n_blades, fractal_box_id=fractal_box_id)
+ elif len(tmp) == 13:
+ grass = AddGrass(p1=p1, p2=p2, frac_dim=frac_dim, limits=limits, n_blades=n_blades, fractal_box_id=fractal_box_id, seed=int(tmp[12]))
+ else:
+ raise CmdInputError("'" + ' '.join(tmp) + "'" + ' too many parameters have been given')
+ scene_objects.append(grass)
+
+ return scene_objects
diff --git a/gprMax/input_cmds_multiuse.py b/gprMax/input_cmds_multiuse.py
index 5ead64de..20922e70 100644
--- a/gprMax/input_cmds_multiuse.py
+++ b/gprMax/input_cmds_multiuse.py
@@ -1,338 +1,338 @@
-# Copyright (C) 2015-2019: The University of Edinburgh
-# Authors: Craig Warren and Antonis Giannopoulos
-#
-# This file is part of gprMax.
-#
-# gprMax is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# gprMax is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with gprMax. If not, see .
-# Copyright (C) 2015-2018: The University of Edinburgh
-# Authors: Craig Warren and Antonis Giannopoulos
-#
-# This file is part of gprMax.
-#
-# gprMax is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# gprMax is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with gprMax. If not, see .
-from .exceptions import CmdInputError
-from .cmds_multiple import Waveform
-from .cmds_multiple import VoltageSource
-from .cmds_multiple import HertzianDipole
-from .cmds_multiple import MagneticDipole
-from .cmds_multiple import TransmissionLine
-from .cmds_multiple import Material
-from .cmds_multiple import Snapshot
-from .cmds_multiple import AddDebyeDispersion
-from .cmds_multiple import AddLorentzDispersion
-from .cmds_multiple import AddDrudeDispersion
-from .cmds_multiple import SoilPeplinski
-from .cmds_multiple import GeometryView
-from .cmds_multiple import GeometryObjectsWrite
-from .cmds_multiple import PMLCFS
-from .cmds_multiple import Rx
-
-
-def process_multicmds(multicmds):
- """
- Checks the validity of command parameters and creates instances of
- classes of parameters.
-
- Args:
- multicmds (dict): Commands that can have multiple instances in the model.
- G (class): Grid class instance - holds essential parameters describing the model.
- """
-
- scene_objects = []
-
- # Waveform definitions
- cmdname = '#waveform'
- if multicmds[cmdname] is not None:
- for cmdinstance in multicmds[cmdname]:
- tmp = cmdinstance.split()
- if len(tmp) != 4:
- raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly four parameters')
-
- waveform = Waveform(wave_type=tmp[0], amp=float(tmp[1]), freq=float(tmp[2]), id=tmp[3])
- scene_objects.append(waveform)
-
- # Voltage source
- cmdname = '#voltage_source'
- if multicmds[cmdname] is not None:
- for cmdinstance in multicmds[cmdname]:
- tmp = cmdinstance.split()
- if len(tmp) == 6:
- voltage_source = VoltageSource(polarisation=tmp[0].lower(), p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])), resistance=float(tmp[4]), waveform_id=tmp[5])
- elif len(tmp) == 8:
- voltage_source = VoltageSource(polarisation=tmp[0].lower(), p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])), resistance=float(tmp[4]), waveform_id=tmp[5], start=float(tmp[6]), end=float(tmp[7]))
- else:
- raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least six parameters')
-
- scene_objects.append(voltage_source)
-
- # Hertzian dipole
- cmdname = '#hertzian_dipole'
- if multicmds[cmdname] is not None:
- for cmdinstance in multicmds[cmdname]:
- tmp = cmdinstance.split()
- if len(tmp) < 5:
- raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least five parameters')
- if len(tmp) == 5:
- hertzian_dipole = HertzianDipole(polarisation=tmp[0], p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])), waveform_id=tmp[4])
- elif len(tmp) == 7:
- hertzian_dipole = HertzianDipole(polarisation=tmp[0], p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])), waveform_id=tmp[4], start=float(tmp[5]), end=float(tmp[6]))
- else:
- raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' too many parameters')
-
- scene_objects.append(hertzian_dipole)
-
- # Magnetic dipole
- cmdname = '#magnetic_dipole'
- if multicmds[cmdname] is not None:
- for cmdinstance in multicmds[cmdname]:
- tmp = cmdinstance.split()
- if len(tmp) < 5:
- raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least five parameters')
- if len(tmp) == 5:
- magnetic_dipole = MagneticDipole(polarisation=tmp[0], p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])), waveform_id=tmp[4])
- elif len(tmp) == 7:
- magnetic_dipole = MagneticDipole(polarisation=tmp[0], p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])), waveform_id=tmp[4], start=float(tmp[5]), end=float(tmp[6]))
- else:
- raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' too many parameters')
-
- scene_objects.append(magnetic_dipole)
-
- # Transmission line
- cmdname = '#transmission_line'
- if multicmds[cmdname] is not None:
- for cmdinstance in multicmds[cmdname]:
- tmp = cmdinstance.split()
- if len(tmp) < 6:
- raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least six parameters')
-
- if len(tmp) == 6:
- tl = TransmissionLine(polarisation=tmp[0], p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])), resistance=float(tmp[4]), waveform_id=tmp[5])
- elif len(tmp) == 8:
- tl = TransmissionLine(polarisation=tmp[0], p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])), resistance=float(tmp[4]), waveform_id=tmp[5], start=tmp[6], end=tmp[7])
- else:
- raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' too many parameters')
-
- scene_objects.append(tl)
-
- # Receiver
- cmdname = '#rx'
- if multicmds[cmdname] is not None:
- for cmdinstance in multicmds[cmdname]:
- tmp = cmdinstance.split()
- if len(tmp) != 3 and len(tmp) < 5:
- raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' has an incorrect number of parameters')
- 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=tmp[4:])
- scene_objects.append(rx)
-
- # Receiver array
- cmdname = '#rx_array'
- if multicmds[cmdname] is not None:
- for cmdinstance in multicmds[cmdname]:
- tmp = cmdinstance.split()
- if len(tmp) != 9:
- raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly nine parameters')
-
- 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]]))
-
- rx_array = RxArray(p1=p1, p2=p2, dl=dl)
- scene_objects.append(rx_array)
-
- # Snapshot
- cmdname = '#snapshot'
- if multicmds[cmdname] is not None:
- for cmdinstance in multicmds[cmdname]:
- tmp = cmdinstance.split()
- if len(tmp) != 11:
- raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly eleven parameters')
-
- 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]]))
- filename = tmp[10]
-
- try:
- iterations = int(tmp[9])
- snapshot = Snapshot(p1=p1, p2=p2, dl=dl, iterations=iterations, filename=filename)
-
- except ValueError:
- time = float(tmp[9])
- snapshot = Snapshot(p1=p1, p2=p2, dl=dl, time=time, filename=filename)
-
- scene_objects.append(snapshot)
-
- # Materials
- cmdname = '#material'
- if multicmds[cmdname] is not None:
- for cmdinstance in multicmds[cmdname]:
- tmp = cmdinstance.split()
- if len(tmp) != 5:
- raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly five parameters')
-
- 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'
- if multicmds[cmdname] is not None:
- for cmdinstance in multicmds[cmdname]:
- tmp = cmdinstance.split()
-
- if len(tmp) < 4:
- raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least four parameters')
-
- poles = int(tmp[0])
- er_delta = []
- tau = []
- material_ids = tmp[(2 * poles) + 1:len(tmp)]
-
- for pole in range(1, 2 * poles, 2):
- er_delta.append(float(tmp[pole]))
- tau.append(float(tmp[pole + 1]))
-
- debye_dispersion = AddDebyeDispersion(pole=poles, er_delta=er_delta, tau=tau, material_ids=material_ids)
- scene_objects.append(debye_dispersion)
-
- cmdname = '#add_dispersion_lorentz'
- if multicmds[cmdname] is not None:
- for cmdinstance in multicmds[cmdname]:
- tmp = cmdinstance.split()
-
- if len(tmp) < 5:
- raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least five parameters')
-
- poles = int(tmp[0])
- material_ids = tmp[(3 * poles) + 1:len(tmp)]
- er_delta = []
- tau = []
- alpha = []
-
- for pole in range(1, 3 * poles, 3):
- er_delta.append(float(tmp[pole]))
- tau.append(float(tmp[pole + 1]))
- alpha.append(float(tmp[pole + 2]))
-
- lorentz_dispersion = AddLorentzDispersion(poles=poles, material_ids=material_ids, er_delta=er_delta, tau=tau, alpha=alpha)
- scene_objects.append(lorentz_dispersion)
-
- cmdname = '#add_dispersion_drude'
- if multicmds[cmdname] is not None:
- for cmdinstance in multicmds[cmdname]:
- tmp = cmdinstance.split()
-
- if len(tmp) < 5:
- raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least five parameters')
-
- poles = int(tmp[0])
- material_ids = tmp[(3 * poles) + 1:len(tmp)]
- tau = []
- alpha = []
-
- for pole in range(1, 2 * poles, 2):
- tau.append(float(tmp[pole]))
- alpha.append(float(tmp[pole + 1]))
-
- drude_dispersion = AddDrudeDispersion(poles=poles, material_ids=material_ids, tau=tau, alpha=alpha)
- scene_objects.append(drude_dispersion)
-
- cmdname = '#soil_peplinski'
- if multicmds[cmdname] is not None:
- for cmdinstance in multicmds[cmdname]:
- tmp = cmdinstance.split()
-
- if len(tmp) != 7:
- raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at exactly seven parameters')
- soil = SoilPeplinski(sand_fraction=float(tmp[0]),
- clay_fraction=float(tmp[1]),
- bulk_density=float(tmp[2]),
- sand_density=float(tmp[3]),
- water_fraction_lower=float(tmp[4]),
- water_fraction_upper=float(tmp[5]),
- id=tmp[6])
- scene_objects.append(soil)
-
- # Geometry views (creates VTK-based geometry files)
- cmdname = '#geometry_view'
- if multicmds[cmdname] is not None:
- for cmdinstance in multicmds[cmdname]:
- tmp = cmdinstance.split()
- if len(tmp) != 11:
- raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly eleven parameters')
-
- 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])
-
- scene_objects.append(geometry_view)
-
- # Geometry object(s) output
- cmdname = '#geometry_objects_write'
- if multicmds[cmdname] is not None:
- for cmdinstance in multicmds[cmdname]:
- tmp = cmdinstance.split()
- if len(tmp) != 7:
- raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly seven parameters')
-
- p1 = float(tmp[0]), float(tmp[1]), float(tmp[2])
- p2 = float(tmp[3]), float(tmp[4]), float(tmp[5])
- gow = GeometryObjectsWrite(p1=p1, p2=p2, filename=tmp[6])
- scene_objects.append(gow)
-
-
- # Complex frequency shifted (CFS) PML parameter
- cmdname = '#pml_cfs'
- if multicmds[cmdname] is not None:
- if len(multicmds[cmdname]) > 2:
- raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' can only be used up to two times, for up to a 2nd order PML')
- for cmdinstance in multicmds[cmdname]:
- tmp = cmdinstance.split()
- if len(tmp) != 12:
- raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly twelve parameters')
-
- pml_cfs = PMLCFS(alphascalingprofile=tmp[0],
- alphascalingdirection=tmp[1],
- alphamin=tmp[2],
- alphamax=tmp[3],
- kappascalingprofile=tmp[4],
- kappascalingdirection=tmp[5],
- kappamin=tmp[6],
- kappamax=tmp[7],
- sigmascalingprofile=tmp[8],
- sigmascalingdirection=tmp[9],
- sigmamin=tmp[10],
- sigmamax=tmp[11])
-
- scene_objects.append(pml_cfs)
-
- return scene_objects
-
-def process_subgrid_hsg(cmdinstance):
-
- pass
+# Copyright (C) 2015-2019: The University of Edinburgh
+# Authors: Craig Warren and Antonis Giannopoulos
+#
+# This file is part of gprMax.
+#
+# gprMax is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# gprMax is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with gprMax. If not, see .
+# Copyright (C) 2015-2018: The University of Edinburgh
+# Authors: Craig Warren and Antonis Giannopoulos
+#
+# This file is part of gprMax.
+#
+# gprMax is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# gprMax is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with gprMax. If not, see .
+from .exceptions import CmdInputError
+from .cmds_multiple import Waveform
+from .cmds_multiple import VoltageSource
+from .cmds_multiple import HertzianDipole
+from .cmds_multiple import MagneticDipole
+from .cmds_multiple import TransmissionLine
+from .cmds_multiple import Material
+from .cmds_multiple import Snapshot
+from .cmds_multiple import AddDebyeDispersion
+from .cmds_multiple import AddLorentzDispersion
+from .cmds_multiple import AddDrudeDispersion
+from .cmds_multiple import SoilPeplinski
+from .cmds_multiple import GeometryView
+from .cmds_multiple import GeometryObjectsWrite
+from .cmds_multiple import PMLCFS
+from .cmds_multiple import Rx
+
+
+def process_multicmds(multicmds):
+ """
+ Checks the validity of command parameters and creates instances of
+ classes of parameters.
+
+ Args:
+ multicmds (dict): Commands that can have multiple instances in the model.
+ G (class): Grid class instance - holds essential parameters describing the model.
+ """
+
+ scene_objects = []
+
+ # Waveform definitions
+ cmdname = '#waveform'
+ if multicmds[cmdname] is not None:
+ for cmdinstance in multicmds[cmdname]:
+ tmp = cmdinstance.split()
+ if len(tmp) != 4:
+ raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly four parameters')
+
+ waveform = Waveform(wave_type=tmp[0], amp=float(tmp[1]), freq=float(tmp[2]), id=tmp[3])
+ scene_objects.append(waveform)
+
+ # Voltage source
+ cmdname = '#voltage_source'
+ if multicmds[cmdname] is not None:
+ for cmdinstance in multicmds[cmdname]:
+ tmp = cmdinstance.split()
+ if len(tmp) == 6:
+ voltage_source = VoltageSource(polarisation=tmp[0].lower(), p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])), resistance=float(tmp[4]), waveform_id=tmp[5])
+ elif len(tmp) == 8:
+ voltage_source = VoltageSource(polarisation=tmp[0].lower(), p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])), resistance=float(tmp[4]), waveform_id=tmp[5], start=float(tmp[6]), end=float(tmp[7]))
+ else:
+ raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least six parameters')
+
+ scene_objects.append(voltage_source)
+
+ # Hertzian dipole
+ cmdname = '#hertzian_dipole'
+ if multicmds[cmdname] is not None:
+ for cmdinstance in multicmds[cmdname]:
+ tmp = cmdinstance.split()
+ if len(tmp) < 5:
+ raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least five parameters')
+ if len(tmp) == 5:
+ hertzian_dipole = HertzianDipole(polarisation=tmp[0], p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])), waveform_id=tmp[4])
+ elif len(tmp) == 7:
+ hertzian_dipole = HertzianDipole(polarisation=tmp[0], p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])), waveform_id=tmp[4], start=float(tmp[5]), end=float(tmp[6]))
+ else:
+ raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' too many parameters')
+
+ scene_objects.append(hertzian_dipole)
+
+ # Magnetic dipole
+ cmdname = '#magnetic_dipole'
+ if multicmds[cmdname] is not None:
+ for cmdinstance in multicmds[cmdname]:
+ tmp = cmdinstance.split()
+ if len(tmp) < 5:
+ raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least five parameters')
+ if len(tmp) == 5:
+ magnetic_dipole = MagneticDipole(polarisation=tmp[0], p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])), waveform_id=tmp[4])
+ elif len(tmp) == 7:
+ magnetic_dipole = MagneticDipole(polarisation=tmp[0], p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])), waveform_id=tmp[4], start=float(tmp[5]), end=float(tmp[6]))
+ else:
+ raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' too many parameters')
+
+ scene_objects.append(magnetic_dipole)
+
+ # Transmission line
+ cmdname = '#transmission_line'
+ if multicmds[cmdname] is not None:
+ for cmdinstance in multicmds[cmdname]:
+ tmp = cmdinstance.split()
+ if len(tmp) < 6:
+ raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least six parameters')
+
+ if len(tmp) == 6:
+ tl = TransmissionLine(polarisation=tmp[0], p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])), resistance=float(tmp[4]), waveform_id=tmp[5])
+ elif len(tmp) == 8:
+ tl = TransmissionLine(polarisation=tmp[0], p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])), resistance=float(tmp[4]), waveform_id=tmp[5], start=tmp[6], end=tmp[7])
+ else:
+ raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' too many parameters')
+
+ scene_objects.append(tl)
+
+ # Receiver
+ cmdname = '#rx'
+ if multicmds[cmdname] is not None:
+ for cmdinstance in multicmds[cmdname]:
+ tmp = cmdinstance.split()
+ if len(tmp) != 3 and len(tmp) < 5:
+ raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' has an incorrect number of parameters')
+ 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=tmp[4:])
+ scene_objects.append(rx)
+
+ # Receiver array
+ cmdname = '#rx_array'
+ if multicmds[cmdname] is not None:
+ for cmdinstance in multicmds[cmdname]:
+ tmp = cmdinstance.split()
+ if len(tmp) != 9:
+ raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly nine parameters')
+
+ 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]]))
+
+ rx_array = RxArray(p1=p1, p2=p2, dl=dl)
+ scene_objects.append(rx_array)
+
+ # Snapshot
+ cmdname = '#snapshot'
+ if multicmds[cmdname] is not None:
+ for cmdinstance in multicmds[cmdname]:
+ tmp = cmdinstance.split()
+ if len(tmp) != 11:
+ raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly eleven parameters')
+
+ 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]]))
+ filename = tmp[10]
+
+ try:
+ iterations = int(tmp[9])
+ snapshot = Snapshot(p1=p1, p2=p2, dl=dl, iterations=iterations, filename=filename)
+
+ except ValueError:
+ time = float(tmp[9])
+ snapshot = Snapshot(p1=p1, p2=p2, dl=dl, time=time, filename=filename)
+
+ scene_objects.append(snapshot)
+
+ # Materials
+ cmdname = '#material'
+ if multicmds[cmdname] is not None:
+ for cmdinstance in multicmds[cmdname]:
+ tmp = cmdinstance.split()
+ if len(tmp) != 5:
+ raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly five parameters')
+
+ 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'
+ if multicmds[cmdname] is not None:
+ for cmdinstance in multicmds[cmdname]:
+ tmp = cmdinstance.split()
+
+ if len(tmp) < 4:
+ raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least four parameters')
+
+ poles = int(tmp[0])
+ er_delta = []
+ tau = []
+ material_ids = tmp[(2 * poles) + 1:len(tmp)]
+
+ for pole in range(1, 2 * poles, 2):
+ er_delta.append(float(tmp[pole]))
+ tau.append(float(tmp[pole + 1]))
+
+ debye_dispersion = AddDebyeDispersion(pole=poles, er_delta=er_delta, tau=tau, material_ids=material_ids)
+ scene_objects.append(debye_dispersion)
+
+ cmdname = '#add_dispersion_lorentz'
+ if multicmds[cmdname] is not None:
+ for cmdinstance in multicmds[cmdname]:
+ tmp = cmdinstance.split()
+
+ if len(tmp) < 5:
+ raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least five parameters')
+
+ poles = int(tmp[0])
+ material_ids = tmp[(3 * poles) + 1:len(tmp)]
+ er_delta = []
+ tau = []
+ alpha = []
+
+ for pole in range(1, 3 * poles, 3):
+ er_delta.append(float(tmp[pole]))
+ tau.append(float(tmp[pole + 1]))
+ alpha.append(float(tmp[pole + 2]))
+
+ lorentz_dispersion = AddLorentzDispersion(poles=poles, material_ids=material_ids, er_delta=er_delta, tau=tau, alpha=alpha)
+ scene_objects.append(lorentz_dispersion)
+
+ cmdname = '#add_dispersion_drude'
+ if multicmds[cmdname] is not None:
+ for cmdinstance in multicmds[cmdname]:
+ tmp = cmdinstance.split()
+
+ if len(tmp) < 5:
+ raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least five parameters')
+
+ poles = int(tmp[0])
+ material_ids = tmp[(3 * poles) + 1:len(tmp)]
+ tau = []
+ alpha = []
+
+ for pole in range(1, 2 * poles, 2):
+ tau.append(float(tmp[pole]))
+ alpha.append(float(tmp[pole + 1]))
+
+ drude_dispersion = AddDrudeDispersion(poles=poles, material_ids=material_ids, tau=tau, alpha=alpha)
+ scene_objects.append(drude_dispersion)
+
+ cmdname = '#soil_peplinski'
+ if multicmds[cmdname] is not None:
+ for cmdinstance in multicmds[cmdname]:
+ tmp = cmdinstance.split()
+
+ if len(tmp) != 7:
+ raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at exactly seven parameters')
+ soil = SoilPeplinski(sand_fraction=float(tmp[0]),
+ clay_fraction=float(tmp[1]),
+ bulk_density=float(tmp[2]),
+ sand_density=float(tmp[3]),
+ water_fraction_lower=float(tmp[4]),
+ water_fraction_upper=float(tmp[5]),
+ id=tmp[6])
+ scene_objects.append(soil)
+
+ # Geometry views (creates VTK-based geometry files)
+ cmdname = '#geometry_view'
+ if multicmds[cmdname] is not None:
+ for cmdinstance in multicmds[cmdname]:
+ tmp = cmdinstance.split()
+ if len(tmp) != 11:
+ raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly eleven parameters')
+
+ 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])
+
+ scene_objects.append(geometry_view)
+
+ # Geometry object(s) output
+ cmdname = '#geometry_objects_write'
+ if multicmds[cmdname] is not None:
+ for cmdinstance in multicmds[cmdname]:
+ tmp = cmdinstance.split()
+ if len(tmp) != 7:
+ raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly seven parameters')
+
+ p1 = float(tmp[0]), float(tmp[1]), float(tmp[2])
+ p2 = float(tmp[3]), float(tmp[4]), float(tmp[5])
+ gow = GeometryObjectsWrite(p1=p1, p2=p2, filename=tmp[6])
+ scene_objects.append(gow)
+
+
+ # Complex frequency shifted (CFS) PML parameter
+ cmdname = '#pml_cfs'
+ if multicmds[cmdname] is not None:
+ if len(multicmds[cmdname]) > 2:
+ raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' can only be used up to two times, for up to a 2nd order PML')
+ for cmdinstance in multicmds[cmdname]:
+ tmp = cmdinstance.split()
+ if len(tmp) != 12:
+ raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly twelve parameters')
+
+ pml_cfs = PMLCFS(alphascalingprofile=tmp[0],
+ alphascalingdirection=tmp[1],
+ alphamin=tmp[2],
+ alphamax=tmp[3],
+ kappascalingprofile=tmp[4],
+ kappascalingdirection=tmp[5],
+ kappamin=tmp[6],
+ kappamax=tmp[7],
+ sigmascalingprofile=tmp[8],
+ sigmascalingdirection=tmp[9],
+ sigmamin=tmp[10],
+ sigmamax=tmp[11])
+
+ scene_objects.append(pml_cfs)
+
+ return scene_objects
+
+def process_subgrid_hsg(cmdinstance):
+
+ pass
diff --git a/gprMax/model_build_run.py b/gprMax/model_build_run.py
index e9af5b99..4f7a708f 100644
--- a/gprMax/model_build_run.py
+++ b/gprMax/model_build_run.py
@@ -1,364 +1,364 @@
-# Copyright (C) 2015-2019: The University of Edinburgh
-# Authors: Craig Warren and Antonis Giannopoulos
-#
-# This file is part of gprMax.
-#
-# gprMax is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# gprMax is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with gprMax. If not, see .
-
-import datetime
-from importlib import import_module
-import itertools
-import os
-import psutil
-import sys
-
-from colorama import init
-from colorama import Fore
-from colorama import Style
-init()
-import cython
-import numpy as np
-from terminaltables import SingleTable
-from tqdm import tqdm
-from pathlib import Path
-
-import gprMax.config as config
-from .cuda.fields_updates import kernel_template_fields
-from .cuda.snapshots import kernel_template_store_snapshot
-from .cuda.source_updates import kernel_template_sources
-from .cython.yee_cell_build import build_electric_components
-from .cython.yee_cell_build import build_magnetic_components
-from .exceptions import GeneralError
-from .fields_outputs import kernel_template_store_outputs
-from .fields_outputs import write_hdf5_outputfile
-from .grid import FDTDGrid
-from .grid import dispersion_analysis
-from .input_cmds_geometry import process_geometrycmds
-from .input_cmds_file import process_python_include_code
-from .input_cmds_file import write_processed_file
-from .input_cmds_file import check_cmd_names
-from .input_cmds_file import parse_hash_commands
-from .input_cmds_singleuse import process_singlecmds
-from .input_cmds_multiuse import process_multicmds
-from .materials import Material
-from .materials import process_materials
-from .pml import CFS
-from .pml import PML
-from .pml import build_pml
-from .pml import pml_information
-from .receivers import gpu_initialise_rx_arrays
-from .receivers import gpu_get_rx_array
-from .receivers import Rx
-from .snapshots import Snapshot
-from .snapshots import gpu_initialise_snapshot_array
-from .snapshots import gpu_get_snapshot_array
-from .sources import gpu_initialise_src_arrays
-from .utilities import get_terminal_width
-from .utilities import human_size
-from .utilities import open_path_file
-from .utilities import round32
-from .utilities import timer
-from .utilities import Printer
-from .scene import Scene
-from .solvers import create_solver
-
-
-
-class ModelBuildRun:
-
- def __init__(self, G, sim_config, model_config):
- self.G = G
- self.sim_config = sim_config
- self.model_config = model_config
- self.printer = Printer(config)
- # Monitor memory usage
- self.p = None
-
- def build(self):
- """Runs a model - processes the input file; builds the Yee cells; calculates update coefficients; runs main FDTD loop.
-
- Args:
- args (dict): Namespace with command line arguments
- currentmodelrun (int): Current model run number.
- modelend (int): Number of last model to run.
- numbermodelruns (int): Total number of model runs.
- inputfile (object): File object for the input file.
- usernamespace (dict): Namespace that can be accessed by user
- in any Python code blocks in input file.
-
- Returns:
- tsolve (int): Length of time (seconds) of main FDTD calculations
- """
- # Monitor memory usage
- self.p = psutil.Process()
-
- # Normal model reading/building process; bypassed if geometry information to be reused
- if self.model_config.reuse_geometry:
- self.reuse_geometry()
- else:
- self.build_geometry()
-
- G = self.G
-
- # Adjust position of simple sources and receivers if required
- if G.srcsteps[0] != 0 or G.srcsteps[1] != 0 or G.srcsteps[2] != 0:
- for source in itertools.chain(G.hertziandipoles, G.magneticdipoles):
- if currentmodelrun == 1:
- if source.xcoord + G.srcsteps[0] * modelend < 0 or source.xcoord + G.srcsteps[0] * modelend > G.nx or source.ycoord + G.srcsteps[1] * modelend < 0 or source.ycoord + G.srcsteps[1] * modelend > G.ny or source.zcoord + G.srcsteps[2] * modelend < 0 or source.zcoord + G.srcsteps[2] * modelend > G.nz:
- raise GeneralError('Source(s) will be stepped to a position outside the domain.')
- source.xcoord = source.xcoordorigin + (currentmodelrun - 1) * G.srcsteps[0]
- source.ycoord = source.ycoordorigin + (currentmodelrun - 1) * G.srcsteps[1]
- source.zcoord = source.zcoordorigin + (currentmodelrun - 1) * G.srcsteps[2]
- if G.rxsteps[0] != 0 or G.rxsteps[1] != 0 or G.rxsteps[2] != 0:
- for receiver in G.rxs:
- if currentmodelrun == 1:
- if receiver.xcoord + G.rxsteps[0] * modelend < 0 or receiver.xcoord + G.rxsteps[0] * modelend > G.nx or receiver.ycoord + G.rxsteps[1] * modelend < 0 or receiver.ycoord + G.rxsteps[1] * modelend > G.ny or receiver.zcoord + G.rxsteps[2] * modelend < 0 or receiver.zcoord + G.rxsteps[2] * modelend > G.nz:
- raise GeneralError('Receiver(s) will be stepped to a position outside the domain.')
- receiver.xcoord = receiver.xcoordorigin + (currentmodelrun - 1) * G.rxsteps[0]
- receiver.ycoord = receiver.ycoordorigin + (currentmodelrun - 1) * G.rxsteps[1]
- receiver.zcoord = receiver.zcoordorigin + (currentmodelrun - 1) * G.rxsteps[2]
-
- # Write files for any geometry views and geometry object outputs
- if not (G.geometryviews or G.geometryobjectswrite) and self.sim_config.geometry_only and config.is_messages():
- print(Fore.RED + '\nWARNING: No geometry views or geometry objects to output found.' + Style.RESET_ALL)
- if config.is_messages(): print()
- for i, geometryview in enumerate(G.geometryviews):
- geometryview.set_filename(self.model_config.appendmodelnumber)
- pbar = tqdm(total=geometryview.datawritesize, unit='byte', unit_scale=True, desc='Writing geometry view file {}/{}, {}'.format(i + 1, len(G.geometryviews), os.path.split(geometryview.filename)[1]), ncols=get_terminal_width() - 1, file=sys.stdout, disable=not config.general['progressbars'])
- geometryview.write_vtk(G, pbar)
- pbar.close()
- for i, geometryobject in enumerate(G.geometryobjectswrite):
- pbar = tqdm(total=geometryobject.datawritesize, unit='byte', unit_scale=True, desc='Writing geometry object file {}/{}, {}'.format(i + 1, len(G.geometryobjectswrite), os.path.split(geometryobject.filename)[1]), ncols=get_terminal_width() - 1, file=sys.stdout, disable=not config.general['progressbars'])
- geometryobject.write_hdf5(G, pbar)
- pbar.close()
-
- # If only writing geometry information
- if self.sim_config.geometry_only:
- tsolve = 0
-
- def build_geometry(self):
- model_config = self.model_config
- sim_config = self.sim_config
-
- G = self.G
-
- printer = Printer(config)
- printer.print(model_config.next_model)
-
- scene = self.build_scene()
-
- # combine available grids
- grids = [G] + G.subgrids
- gridbuilders = [GridBuilder(grid, self.printer) for grid in grids]
-
- for gb in gridbuilders:
- gb.printer.print(pml_information(gb.grid))
- gb.build_pmls()
- gb.build_components()
- gb.tm_grid_update()
- gb.update_voltage_source_materials()
- gb.grid.initialise_std_update_coeff_arrays()
-
- # Set datatype for dispersive arrays if there are any dispersive materials.
- if config.materials['maxpoles'] != 0:
- drudelorentz = any([m for m in G.materials if 'drude' in m.type or 'lorentz' in m.type])
- if drudelorentz:
- config.materials['dispersivedtype'] = config.dtypes['complex']
- config.materials['dispersiveCdtype'] = config.dtypes['C_complex']
- else:
- config.materials['dispersivedtype'] = config.dtypes['float_or_double']
- config.materials['dispersiveCdtype'] = config.dtypes['C_float_or_double']
-
- # Update estimated memory (RAM) usage
- G.memoryusage += int(3 * config.materials['maxpoles'] * (G.nx + 1) * (G.ny + 1) * (G.nz + 1) * np.dtype(config.materials['dispersivedtype']).itemsize)
- G.memory_check()
- printer.print('\nMemory (RAM) required - updated (dispersive): ~{}\n'.format(human_size(G.memoryusage)))
-
- for gb in gridbuilders:
- gb.grid.initialise_dispersive_arrays(config.materials['dispersivedtype'])
-
- # Check there is sufficient memory to store any snapshots
- if G.snapshots:
- snapsmemsize = 0
- for snap in G.snapshots:
- # 2 x required to account for electric and magnetic fields
- snapsmemsize += (2 * snap.datasizefield)
- G.memoryusage += int(snapsmemsize)
- G.memory_check(snapsmemsize=int(snapsmemsize))
-
- printer.print('\nMemory (RAM) required - updated (snapshots): ~{}\n'.format(human_size(G.memoryusage)))
-
- for gb in gridbuilders:
- gb.build_materials()
-
- # Check to see if numerical dispersion might be a problem
- results = dispersion_analysis(G)
- if results['error']:
- printer.print(Fore.RED + "\nWARNING: Numerical dispersion analysis not carried out as {}".format(results['error']) + Style.RESET_ALL)
- elif results['N'] < config.numdispersion['mingridsampling']:
- raise GeneralError("Non-physical wave propagation: Material '{}' has wavelength sampled by {} cells, less than required minimum for physical wave propagation. Maximum significant frequency estimated as {:g}Hz".format(results['material'].ID, results['N'], results['maxfreq']))
- elif results['deltavp'] and np.abs(results['deltavp']) > config.numdispersion['maxnumericaldisp']:
- printer.print(Fore.RED + "\nWARNING: Potentially significant numerical dispersion. Estimated largest physical phase-velocity error is {:.2f}% in material '{}' whose wavelength sampled by {} cells. Maximum significant frequency estimated as {:g}Hz".format(results['deltavp'], results['material'].ID, results['N'], results['maxfreq']) + Style.RESET_ALL)
- elif results['deltavp']:
- printer.print("\nNumerical dispersion analysis: estimated largest physical phase-velocity error is {:.2f}% in material '{}' whose wavelength sampled by {} cells. Maximum significant frequency estimated as {:g}Hz".format(results['deltavp'], results['material'].ID, results['N'], results['maxfreq']))
-
-
- def reuse_geometry(self):
- G = self.G
- inputfilestr = '\n--- Model {}/{}, input file (not re-processed, i.e. geometry fixed): {}'.format(self.model_config.appendmodelnumber, self.sim_config.model_end, self.sim_config.input_file_path)
- self.printer.print(Fore.GREEN + '{} {}\n'.format(self.model_config.inputfilestr, '-' * (get_terminal_width() - 1 - len(inputfilestr))) + Style.RESET_ALL)
- for grid in [G] + G.subgrids:
- grid.reset_fields()
-
- def build_scene(self):
- G = self.G
- # api for multiple scenes / model runs
- scene = self.model_config.get_scene()
-
- # if there is no scene - process the hash commands instead
- if not scene:
- scene = Scene()
- # parse the input file into user objects and add them to the scene
- scene = parse_hash_commands(self.model_config, G, scene)
-
- # Creates the internal simulation objects.
- scene.create_internal_objects(G)
- return scene
-
- def create_output_directory(self):
-
- if self.G.outputdirectory:
- # Check and set output directory and filename
- try:
- os.mkdir(self.G.outputdirectory)
- self.printer.print('\nCreated output directory: {}'.format(self.G.outputdirectory))
- except FileExistsError:
- pass
- # modify the output path (hack)
- self.model_config.output_file_path_ext = Path(self.G.outputdirectory, self.model_config.output_file_path_ext)
-
-
- def run_model(self, solver):
-
- G = self.G
-
- self.create_output_directory()
- self.printer.print('\nOutput file: {}\n'.format(self.model_config.output_file_path_ext))
-
- tsolve = self.solve(solver)
-
- # Write an output file in HDF5 format
- write_hdf5_outputfile(self.model_config.output_file_path_ext, G)
-
- # Write any snapshots to file
- if G.snapshots:
- # Create directory and construct filename from user-supplied name and model run number
- snapshotdir = self.model_config.snapshot_dir
- if not os.path.exists(snapshotdir):
- os.mkdir(snapshotdir)
-
- self.printer.print()
- for i, snap in enumerate(G.snapshots):
- snap.filename = snapshotdir + snap.basefilename + '.vti'
- pbar = tqdm(total=snap.vtkdatawritesize, leave=True, unit='byte', unit_scale=True, desc='Writing snapshot file {} of {}, {}'.format(i + 1, len(G.snapshots), os.path.split(snap.filename)[1]), ncols=get_terminal_width() - 1, file=sys.stdout, disable=not config.general['progressbars'])
- snap.write_vtk_imagedata(pbar, G)
- pbar.close()
- self.printer.print()
-
- memGPU = ''
- if config.cuda['gpus']:
- memGPU = ' host + ~{} GPU'.format(human_size(self.solver.get_memsolve()))
-
- self.printer.print('\nMemory (RAM) used: ~{}{}'.format(human_size(self.p.memory_full_info().uss), memGPU))
- self.printer.print('Solving time [HH:MM:SS]: {}'.format(datetime.timedelta(seconds=tsolve)))
-
- return tsolve
-
- def solve(self, solver):
- """
- Solving using FDTD method on CPU. Parallelised using Cython (OpenMP) for
- electric and magnetic field updates, and PML updates.
-
- Args:
- currentmodelrun (int): Current model run number.
- modelend (int): Number of last model to run.
- G (class): Grid class instance - holds essential parameters describing the model.
-
- Returns:
- tsolve (float): Time taken to execute solving (seconds)
- """
- G = self.G
-
- if config.is_messages():
- iterator = tqdm(range(G.iterations), desc='Running simulation, model ' + str(self.model_config
- .i + 1) + '/' + str(self.sim_config.model_end), ncols=get_terminal_width() - 1, file=sys.stdout, disable=not config.general['progressbars'])
- else:
- iterator = range(0, G.iterations)
-
- tsolve = solver.solve(iterator)
-
- return tsolve
-
-
-class GridBuilder:
- def __init__(self, grid, printer):
- self.grid = grid
- self.printer = printer
-
- def build_pmls(self):
-
- grid = self.grid
- # build the PMLS
- pbar = tqdm(total=sum(1 for value in grid.pmlthickness.values() if value > 0), desc='Building {} Grid PML boundaries'.format(self.grid.name), ncols=get_terminal_width() - 1, file=sys.stdout, disable=not config.general['progressbars'])
-
- for pml_id, thickness in grid.pmlthickness.items():
- build_pml(grid, pml_id, thickness)
- pbar.update()
- pbar.close()
-
- def build_components(self):
- # Build the model, i.e. set the material properties (ID) for every edge
- # of every Yee cell
- self.printer.print('')
- pbar = tqdm(total=2, desc='Building {} grid'.format(self.grid.name), ncols=get_terminal_width() - 1, file=sys.stdout, disable=not config.general['progressbars'])
- 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)
- pbar.update()
- pbar.close()
-
- def tm_grid_update(self):
- if '2D TMx' == config.general['mode']:
- self.grid.tmx()
- elif '2D TMy' == config.general['mode']:
- self.grid.tmy()
- elif '2D TMz' == config.general['mode']:
- self.grid.tmz()
-
- def update_voltage_source_materials(self):
- # Process any voltage sources (that have resistance) to create a new
- # material at the source location
- for voltagesource in self.grid.voltagesources:
- voltagesource.create_material(self.grid)
-
- def build_materials(self):
- # Process complete list of materials - calculate update coefficients,
- # store in arrays, and build text list of materials/properties
- materialsdata = process_materials(self.grid)
- materialstable = SingleTable(materialsdata)
- materialstable.outer_border = False
- materialstable.justify_columns[0] = 'right'
-
- self.printer.print('\n{} Grid Materials:'.format(self.grid.name))
- self.printer.print(materialstable.table)
+# Copyright (C) 2015-2019: The University of Edinburgh
+# Authors: Craig Warren and Antonis Giannopoulos
+#
+# This file is part of gprMax.
+#
+# gprMax is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# gprMax is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with gprMax. If not, see .
+
+import datetime
+from importlib import import_module
+import itertools
+import os
+import psutil
+import sys
+
+from colorama import init
+from colorama import Fore
+from colorama import Style
+init()
+import cython
+import numpy as np
+from terminaltables import SingleTable
+from tqdm import tqdm
+from pathlib import Path
+
+import gprMax.config as config
+from .cuda.fields_updates import kernel_template_fields
+from .cuda.snapshots import kernel_template_store_snapshot
+from .cuda.source_updates import kernel_template_sources
+from .cython.yee_cell_build import build_electric_components
+from .cython.yee_cell_build import build_magnetic_components
+from .exceptions import GeneralError
+from .fields_outputs import kernel_template_store_outputs
+from .fields_outputs import write_hdf5_outputfile
+from .grid import FDTDGrid
+from .grid import dispersion_analysis
+from .input_cmds_geometry import process_geometrycmds
+from .input_cmds_file import process_python_include_code
+from .input_cmds_file import write_processed_file
+from .input_cmds_file import check_cmd_names
+from .input_cmds_file import parse_hash_commands
+from .input_cmds_singleuse import process_singlecmds
+from .input_cmds_multiuse import process_multicmds
+from .materials import Material
+from .materials import process_materials
+from .pml import CFS
+from .pml import PML
+from .pml import build_pml
+from .pml import pml_information
+from .receivers import gpu_initialise_rx_arrays
+from .receivers import gpu_get_rx_array
+from .receivers import Rx
+from .snapshots import Snapshot
+from .snapshots import gpu_initialise_snapshot_array
+from .snapshots import gpu_get_snapshot_array
+from .sources import gpu_initialise_src_arrays
+from .utilities import get_terminal_width
+from .utilities import human_size
+from .utilities import open_path_file
+from .utilities import round32
+from .utilities import timer
+from .utilities import Printer
+from .scene import Scene
+from .solvers import create_solver
+
+
+
+class ModelBuildRun:
+
+ def __init__(self, G, sim_config, model_config):
+ self.G = G
+ self.sim_config = sim_config
+ self.model_config = model_config
+ self.printer = Printer(config)
+ # Monitor memory usage
+ self.p = None
+
+ def build(self):
+ """Runs a model - processes the input file; builds the Yee cells; calculates update coefficients; runs main FDTD loop.
+
+ Args:
+ args (dict): Namespace with command line arguments
+ currentmodelrun (int): Current model run number.
+ modelend (int): Number of last model to run.
+ numbermodelruns (int): Total number of model runs.
+ inputfile (object): File object for the input file.
+ usernamespace (dict): Namespace that can be accessed by user
+ in any Python code blocks in input file.
+
+ Returns:
+ tsolve (int): Length of time (seconds) of main FDTD calculations
+ """
+ # Monitor memory usage
+ self.p = psutil.Process()
+
+ # Normal model reading/building process; bypassed if geometry information to be reused
+ if self.model_config.reuse_geometry:
+ self.reuse_geometry()
+ else:
+ self.build_geometry()
+
+ G = self.G
+
+ # Adjust position of simple sources and receivers if required
+ if G.srcsteps[0] != 0 or G.srcsteps[1] != 0 or G.srcsteps[2] != 0:
+ for source in itertools.chain(G.hertziandipoles, G.magneticdipoles):
+ if currentmodelrun == 1:
+ if source.xcoord + G.srcsteps[0] * modelend < 0 or source.xcoord + G.srcsteps[0] * modelend > G.nx or source.ycoord + G.srcsteps[1] * modelend < 0 or source.ycoord + G.srcsteps[1] * modelend > G.ny or source.zcoord + G.srcsteps[2] * modelend < 0 or source.zcoord + G.srcsteps[2] * modelend > G.nz:
+ raise GeneralError('Source(s) will be stepped to a position outside the domain.')
+ source.xcoord = source.xcoordorigin + (currentmodelrun - 1) * G.srcsteps[0]
+ source.ycoord = source.ycoordorigin + (currentmodelrun - 1) * G.srcsteps[1]
+ source.zcoord = source.zcoordorigin + (currentmodelrun - 1) * G.srcsteps[2]
+ if G.rxsteps[0] != 0 or G.rxsteps[1] != 0 or G.rxsteps[2] != 0:
+ for receiver in G.rxs:
+ if currentmodelrun == 1:
+ if receiver.xcoord + G.rxsteps[0] * modelend < 0 or receiver.xcoord + G.rxsteps[0] * modelend > G.nx or receiver.ycoord + G.rxsteps[1] * modelend < 0 or receiver.ycoord + G.rxsteps[1] * modelend > G.ny or receiver.zcoord + G.rxsteps[2] * modelend < 0 or receiver.zcoord + G.rxsteps[2] * modelend > G.nz:
+ raise GeneralError('Receiver(s) will be stepped to a position outside the domain.')
+ receiver.xcoord = receiver.xcoordorigin + (currentmodelrun - 1) * G.rxsteps[0]
+ receiver.ycoord = receiver.ycoordorigin + (currentmodelrun - 1) * G.rxsteps[1]
+ receiver.zcoord = receiver.zcoordorigin + (currentmodelrun - 1) * G.rxsteps[2]
+
+ # Write files for any geometry views and geometry object outputs
+ if not (G.geometryviews or G.geometryobjectswrite) and self.sim_config.geometry_only and config.is_messages():
+ print(Fore.RED + '\nWARNING: No geometry views or geometry objects to output found.' + Style.RESET_ALL)
+ if config.is_messages(): print()
+ for i, geometryview in enumerate(G.geometryviews):
+ geometryview.set_filename(self.model_config.appendmodelnumber)
+ pbar = tqdm(total=geometryview.datawritesize, unit='byte', unit_scale=True, desc='Writing geometry view file {}/{}, {}'.format(i + 1, len(G.geometryviews), os.path.split(geometryview.filename)[1]), ncols=get_terminal_width() - 1, file=sys.stdout, disable=not config.general['progressbars'])
+ geometryview.write_vtk(G, pbar)
+ pbar.close()
+ for i, geometryobject in enumerate(G.geometryobjectswrite):
+ pbar = tqdm(total=geometryobject.datawritesize, unit='byte', unit_scale=True, desc='Writing geometry object file {}/{}, {}'.format(i + 1, len(G.geometryobjectswrite), os.path.split(geometryobject.filename)[1]), ncols=get_terminal_width() - 1, file=sys.stdout, disable=not config.general['progressbars'])
+ geometryobject.write_hdf5(G, pbar)
+ pbar.close()
+
+ # If only writing geometry information
+ if self.sim_config.geometry_only:
+ tsolve = 0
+
+ def build_geometry(self):
+ model_config = self.model_config
+ sim_config = self.sim_config
+
+ G = self.G
+
+ printer = Printer(config)
+ printer.print(model_config.next_model)
+
+ scene = self.build_scene()
+
+ # combine available grids
+ grids = [G] + G.subgrids
+ gridbuilders = [GridBuilder(grid, self.printer) for grid in grids]
+
+ for gb in gridbuilders:
+ gb.printer.print(pml_information(gb.grid))
+ gb.build_pmls()
+ gb.build_components()
+ gb.tm_grid_update()
+ gb.update_voltage_source_materials()
+ gb.grid.initialise_std_update_coeff_arrays()
+
+ # Set datatype for dispersive arrays if there are any dispersive materials.
+ if config.materials['maxpoles'] != 0:
+ drudelorentz = any([m for m in G.materials if 'drude' in m.type or 'lorentz' in m.type])
+ if drudelorentz:
+ config.materials['dispersivedtype'] = config.dtypes['complex']
+ config.materials['dispersiveCdtype'] = config.dtypes['C_complex']
+ else:
+ config.materials['dispersivedtype'] = config.dtypes['float_or_double']
+ config.materials['dispersiveCdtype'] = config.dtypes['C_float_or_double']
+
+ # Update estimated memory (RAM) usage
+ G.memoryusage += int(3 * config.materials['maxpoles'] * (G.nx + 1) * (G.ny + 1) * (G.nz + 1) * np.dtype(config.materials['dispersivedtype']).itemsize)
+ G.memory_check()
+ printer.print('\nMemory (RAM) required - updated (dispersive): ~{}\n'.format(human_size(G.memoryusage)))
+
+ for gb in gridbuilders:
+ gb.grid.initialise_dispersive_arrays(config.materials['dispersivedtype'])
+
+ # Check there is sufficient memory to store any snapshots
+ if G.snapshots:
+ snapsmemsize = 0
+ for snap in G.snapshots:
+ # 2 x required to account for electric and magnetic fields
+ snapsmemsize += (2 * snap.datasizefield)
+ G.memoryusage += int(snapsmemsize)
+ G.memory_check(snapsmemsize=int(snapsmemsize))
+
+ printer.print('\nMemory (RAM) required - updated (snapshots): ~{}\n'.format(human_size(G.memoryusage)))
+
+ for gb in gridbuilders:
+ gb.build_materials()
+
+ # Check to see if numerical dispersion might be a problem
+ results = dispersion_analysis(G)
+ if results['error']:
+ printer.print(Fore.RED + "\nWARNING: Numerical dispersion analysis not carried out as {}".format(results['error']) + Style.RESET_ALL)
+ elif results['N'] < config.numdispersion['mingridsampling']:
+ raise GeneralError("Non-physical wave propagation: Material '{}' has wavelength sampled by {} cells, less than required minimum for physical wave propagation. Maximum significant frequency estimated as {:g}Hz".format(results['material'].ID, results['N'], results['maxfreq']))
+ elif results['deltavp'] and np.abs(results['deltavp']) > config.numdispersion['maxnumericaldisp']:
+ printer.print(Fore.RED + "\nWARNING: Potentially significant numerical dispersion. Estimated largest physical phase-velocity error is {:.2f}% in material '{}' whose wavelength sampled by {} cells. Maximum significant frequency estimated as {:g}Hz".format(results['deltavp'], results['material'].ID, results['N'], results['maxfreq']) + Style.RESET_ALL)
+ elif results['deltavp']:
+ printer.print("\nNumerical dispersion analysis: estimated largest physical phase-velocity error is {:.2f}% in material '{}' whose wavelength sampled by {} cells. Maximum significant frequency estimated as {:g}Hz".format(results['deltavp'], results['material'].ID, results['N'], results['maxfreq']))
+
+
+ def reuse_geometry(self):
+ G = self.G
+ inputfilestr = '\n--- Model {}/{}, input file (not re-processed, i.e. geometry fixed): {}'.format(self.model_config.appendmodelnumber, self.sim_config.model_end, self.sim_config.input_file_path)
+ self.printer.print(Fore.GREEN + '{} {}\n'.format(self.model_config.inputfilestr, '-' * (get_terminal_width() - 1 - len(inputfilestr))) + Style.RESET_ALL)
+ for grid in [G] + G.subgrids:
+ grid.reset_fields()
+
+ def build_scene(self):
+ G = self.G
+ # api for multiple scenes / model runs
+ scene = self.model_config.get_scene()
+
+ # if there is no scene - process the hash commands instead
+ if not scene:
+ scene = Scene()
+ # parse the input file into user objects and add them to the scene
+ scene = parse_hash_commands(self.model_config, G, scene)
+
+ # Creates the internal simulation objects.
+ scene.create_internal_objects(G)
+ return scene
+
+ def create_output_directory(self):
+
+ if self.G.outputdirectory:
+ # Check and set output directory and filename
+ try:
+ os.mkdir(self.G.outputdirectory)
+ self.printer.print('\nCreated output directory: {}'.format(self.G.outputdirectory))
+ except FileExistsError:
+ pass
+ # modify the output path (hack)
+ self.model_config.output_file_path_ext = Path(self.G.outputdirectory, self.model_config.output_file_path_ext)
+
+
+ def run_model(self, solver):
+
+ G = self.G
+
+ self.create_output_directory()
+ self.printer.print('\nOutput file: {}\n'.format(self.model_config.output_file_path_ext))
+
+ tsolve = self.solve(solver)
+
+ # Write an output file in HDF5 format
+ write_hdf5_outputfile(self.model_config.output_file_path_ext, G)
+
+ # Write any snapshots to file
+ if G.snapshots:
+ # Create directory and construct filename from user-supplied name and model run number
+ snapshotdir = self.model_config.snapshot_dir
+ if not os.path.exists(snapshotdir):
+ os.mkdir(snapshotdir)
+
+ self.printer.print()
+ for i, snap in enumerate(G.snapshots):
+ snap.filename = snapshotdir + snap.basefilename + '.vti'
+ pbar = tqdm(total=snap.vtkdatawritesize, leave=True, unit='byte', unit_scale=True, desc='Writing snapshot file {} of {}, {}'.format(i + 1, len(G.snapshots), os.path.split(snap.filename)[1]), ncols=get_terminal_width() - 1, file=sys.stdout, disable=not config.general['progressbars'])
+ snap.write_vtk_imagedata(pbar, G)
+ pbar.close()
+ self.printer.print()
+
+ memGPU = ''
+ if config.cuda['gpus']:
+ memGPU = ' host + ~{} GPU'.format(human_size(self.solver.get_memsolve()))
+
+ self.printer.print('\nMemory (RAM) used: ~{}{}'.format(human_size(self.p.memory_full_info().uss), memGPU))
+ self.printer.print('Solving time [HH:MM:SS]: {}'.format(datetime.timedelta(seconds=tsolve)))
+
+ return tsolve
+
+ def solve(self, solver):
+ """
+ Solving using FDTD method on CPU. Parallelised using Cython (OpenMP) for
+ electric and magnetic field updates, and PML updates.
+
+ Args:
+ currentmodelrun (int): Current model run number.
+ modelend (int): Number of last model to run.
+ G (class): Grid class instance - holds essential parameters describing the model.
+
+ Returns:
+ tsolve (float): Time taken to execute solving (seconds)
+ """
+ G = self.G
+
+ if config.is_messages():
+ iterator = tqdm(range(G.iterations), desc='Running simulation, model ' + str(self.model_config
+ .i + 1) + '/' + str(self.sim_config.model_end), ncols=get_terminal_width() - 1, file=sys.stdout, disable=not config.general['progressbars'])
+ else:
+ iterator = range(0, G.iterations)
+
+ tsolve = solver.solve(iterator)
+
+ return tsolve
+
+
+class GridBuilder:
+ def __init__(self, grid, printer):
+ self.grid = grid
+ self.printer = printer
+
+ def build_pmls(self):
+
+ grid = self.grid
+ # build the PMLS
+ pbar = tqdm(total=sum(1 for value in grid.pmlthickness.values() if value > 0), desc='Building {} Grid PML boundaries'.format(self.grid.name), ncols=get_terminal_width() - 1, file=sys.stdout, disable=not config.general['progressbars'])
+
+ for pml_id, thickness in grid.pmlthickness.items():
+ build_pml(grid, pml_id, thickness)
+ pbar.update()
+ pbar.close()
+
+ def build_components(self):
+ # Build the model, i.e. set the material properties (ID) for every edge
+ # of every Yee cell
+ self.printer.print('')
+ pbar = tqdm(total=2, desc='Building {} grid'.format(self.grid.name), ncols=get_terminal_width() - 1, file=sys.stdout, disable=not config.general['progressbars'])
+ 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)
+ pbar.update()
+ pbar.close()
+
+ def tm_grid_update(self):
+ if '2D TMx' == config.general['mode']:
+ self.grid.tmx()
+ elif '2D TMy' == config.general['mode']:
+ self.grid.tmy()
+ elif '2D TMz' == config.general['mode']:
+ self.grid.tmz()
+
+ def update_voltage_source_materials(self):
+ # Process any voltage sources (that have resistance) to create a new
+ # material at the source location
+ for voltagesource in self.grid.voltagesources:
+ voltagesource.create_material(self.grid)
+
+ def build_materials(self):
+ # Process complete list of materials - calculate update coefficients,
+ # store in arrays, and build text list of materials/properties
+ materialsdata = process_materials(self.grid)
+ materialstable = SingleTable(materialsdata)
+ materialstable.outer_border = False
+ materialstable.justify_columns[0] = 'right'
+
+ self.printer.print('\n{} Grid Materials:'.format(self.grid.name))
+ self.printer.print(materialstable.table)
diff --git a/gprMax/scene.py b/gprMax/scene.py
index a298b887..20ef2125 100644
--- a/gprMax/scene.py
+++ b/gprMax/scene.py
@@ -1,121 +1,121 @@
-from .user_inputs import create_user_input_points
-from .materials import create_built_in_materials
-from .cmds_single_use import UserObjectSingle
-from .cmds_single_use import DomainSingle
-from .cmds_single_use import Discretisation
-from .cmds_single_use import TimeWindow
-from .cmds_multiple import UserObjectMulti
-from .subgrids.user_objects import SubGridBase as SubGridUserBase
-from .cmds_geometry.cmds_geometry import UserObjectGeometry
-from .exceptions import CmdInputError
-from .cmds_geometry.fractal_box_builder import FractalBoxBuilder
-from .utilities import human_size
-
-
-class Scene:
-
- """Scene stores all of the user created objects
- """
-
- def __init__(self):
- self.multiple_cmds = []
- self.single_cmds = []
- self.geometry_cmds = []
- self.essential_cmds = [DomainSingle, TimeWindow, Discretisation]
-
- # fractal box commands have an additional nonuser object which
- # process modifications
- fbb = FractalBoxBuilder()
- self.add(fbb)
-
- def add(self, node):
- if isinstance(node, UserObjectMulti):
- self.multiple_cmds.append(node)
- elif isinstance(node, UserObjectGeometry):
- self.geometry_cmds.append(node)
- elif isinstance(node, UserObjectSingle):
- self.single_cmds.append(node)
- else:
- raise Exception('This Object is Unknown to gprMax')
-
- def process_subgrid_commands(self, subgrids):
-
- # check for subgrid user objects
- def func(obj):
- if isinstance(obj, SubGridUserBase):
- return True
- else:
- return False
-
- # subgrid user objects
- subgrid_cmds = list(filter(func, self.multiple_cmds))
-
- # iterate through the user command objects under the subgrid user object
- for sg_cmd in subgrid_cmds:
- # when the subgrid is created its reference is attached to its user
- # object. this reference allows the multi and geo user objects
- # to build in the correct subgrid.
- sg = sg_cmd.subgrid
- self.process_cmds(sg_cmd.children_multiple, sg)
- self.process_cmds(sg_cmd.children_geometry, sg)
-
- def process_cmds(self, commands, grid):
- cmds_sorted = sorted(commands, key=lambda cmd: cmd.order)
- for obj in cmds_sorted:
- # in the first level all objects belong to the main grid
- uip = create_user_input_points(grid)
- # Create an instance to check the geometry points provided by the
- # user. The way the point are checked depends on which grid the
- # points belong to.
- obj.create(grid, uip)
-
- return self
-
- def process_singlecmds(self, G):
-
- # check for duplicate commands and warn user if they exist
- cmds_unique = list(set(self.single_cmds))
- if len(cmds_unique) != len(self.single_cmds):
- raise CmdInputError('Duplicate Single Commands exist in the input.')
-
- # check essential cmds and warn user if missing
- for cmd_type in self.essential_cmds:
- d = any([isinstance(cmd, cmd_type) for cmd in cmds_unique])
- if not d:
- raise CmdInputError('Your input file is missing essential commands required to run a model. Essential commands are: Domain, Discretisation, Time Window')
-
- self.process_cmds(cmds_unique, G)
-
-
- def create_internal_objects(self, G):
-
- # gprMax API presents the user with UserObjects in order to build
- # the internal Rx(), Cylinder() etc... objects. This function
- # essentially calls the UserObject.create() function in the correct
- # way
-
- # Traverse all the user objects in the correct order and create them.
- create_built_in_materials(G)
-
- # process commands that can onlyhave a single instance
- self.process_singlecmds(G)
-
- # Process main grid multiple commands
- self.process_cmds(self.multiple_cmds, G)
-
- # Estimate and check memory (RAM) usage
- G.memory_check()
- #snapshot_memory_check(G)
-
- # Initialise an array for volumetric material IDs (solid), boolean
- # arrays for specifying materials not to be averaged (rigid),
- # an array for cell edge IDs (ID)
- G.initialise_grids()
-
- # Process the main grid geometry commands
- self.process_cmds(self.geometry_cmds, G)
-
- # Process all the commands for the subgrid
- self.process_subgrid_commands(G.subgrids)
-
- return self
+from .user_inputs import create_user_input_points
+from .materials import create_built_in_materials
+from .cmds_single_use import UserObjectSingle
+from .cmds_single_use import DomainSingle
+from .cmds_single_use import Discretisation
+from .cmds_single_use import TimeWindow
+from .cmds_multiple import UserObjectMulti
+from .subgrids.user_objects import SubGridBase as SubGridUserBase
+from .cmds_geometry.cmds_geometry import UserObjectGeometry
+from .exceptions import CmdInputError
+from .cmds_geometry.fractal_box_builder import FractalBoxBuilder
+from .utilities import human_size
+
+
+class Scene:
+
+ """Scene stores all of the user created objects
+ """
+
+ def __init__(self):
+ self.multiple_cmds = []
+ self.single_cmds = []
+ self.geometry_cmds = []
+ self.essential_cmds = [DomainSingle, TimeWindow, Discretisation]
+
+ # fractal box commands have an additional nonuser object which
+ # process modifications
+ fbb = FractalBoxBuilder()
+ self.add(fbb)
+
+ def add(self, node):
+ if isinstance(node, UserObjectMulti):
+ self.multiple_cmds.append(node)
+ elif isinstance(node, UserObjectGeometry):
+ self.geometry_cmds.append(node)
+ elif isinstance(node, UserObjectSingle):
+ self.single_cmds.append(node)
+ else:
+ raise Exception('This Object is Unknown to gprMax')
+
+ def process_subgrid_commands(self, subgrids):
+
+ # check for subgrid user objects
+ def func(obj):
+ if isinstance(obj, SubGridUserBase):
+ return True
+ else:
+ return False
+
+ # subgrid user objects
+ subgrid_cmds = list(filter(func, self.multiple_cmds))
+
+ # iterate through the user command objects under the subgrid user object
+ for sg_cmd in subgrid_cmds:
+ # when the subgrid is created its reference is attached to its user
+ # object. this reference allows the multi and geo user objects
+ # to build in the correct subgrid.
+ sg = sg_cmd.subgrid
+ self.process_cmds(sg_cmd.children_multiple, sg)
+ self.process_cmds(sg_cmd.children_geometry, sg)
+
+ def process_cmds(self, commands, grid):
+ cmds_sorted = sorted(commands, key=lambda cmd: cmd.order)
+ for obj in cmds_sorted:
+ # in the first level all objects belong to the main grid
+ uip = create_user_input_points(grid)
+ # Create an instance to check the geometry points provided by the
+ # user. The way the point are checked depends on which grid the
+ # points belong to.
+ obj.create(grid, uip)
+
+ return self
+
+ def process_singlecmds(self, G):
+
+ # check for duplicate commands and warn user if they exist
+ cmds_unique = list(set(self.single_cmds))
+ if len(cmds_unique) != len(self.single_cmds):
+ raise CmdInputError('Duplicate Single Commands exist in the input.')
+
+ # check essential cmds and warn user if missing
+ for cmd_type in self.essential_cmds:
+ d = any([isinstance(cmd, cmd_type) for cmd in cmds_unique])
+ if not d:
+ raise CmdInputError('Your input file is missing essential commands required to run a model. Essential commands are: Domain, Discretisation, Time Window')
+
+ self.process_cmds(cmds_unique, G)
+
+
+ def create_internal_objects(self, G):
+
+ # gprMax API presents the user with UserObjects in order to build
+ # the internal Rx(), Cylinder() etc... objects. This function
+ # essentially calls the UserObject.create() function in the correct
+ # way
+
+ # Traverse all the user objects in the correct order and create them.
+ create_built_in_materials(G)
+
+ # process commands that can onlyhave a single instance
+ self.process_singlecmds(G)
+
+ # Process main grid multiple commands
+ self.process_cmds(self.multiple_cmds, G)
+
+ # Estimate and check memory (RAM) usage
+ G.memory_check()
+ #snapshot_memory_check(G)
+
+ # Initialise an array for volumetric material IDs (solid), boolean
+ # arrays for specifying materials not to be averaged (rigid),
+ # an array for cell edge IDs (ID)
+ G.initialise_grids()
+
+ # Process the main grid geometry commands
+ self.process_cmds(self.geometry_cmds, G)
+
+ # Process all the commands for the subgrid
+ self.process_subgrid_commands(G.subgrids)
+
+ return self
diff --git a/gprMax/solvers.py b/gprMax/solvers.py
index a323f64f..9370a908 100644
--- a/gprMax/solvers.py
+++ b/gprMax/solvers.py
@@ -1,93 +1,96 @@
-# Copyright (C) 2015-2019: The University of Edinburgh
-# Authors: Craig Warren and Antonis Giannopoulos
-#
-# This file is part of gprMax.
-#
-# gprMax is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# gprMax is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with gprMax. If not, see .
-from gprMax.updates import CPUUpdates
-from gprMax.updates import GPUUpdates
-from gprMax.utilities import timer
-from .grid import FDTDGrid
-from .grid import GPUGrid
-import gprMax.config as config
-from .subgrids.solver import create_updates as create_subgrid_updates
-from .subgrids.solver import SubGridSolver
-
-
-def create_G(sim_config):
- """Returns the configured solver."""
- if sim_config.gpu:
- G = GPUGrid()
- elif sim_config.subgrid:
- G = FDTDGrid()
- else:
- G = FDTDGrid()
-
- return G
-
-
-def create_solver(G, sim_config):
- """Returns the configured solver."""
- if sim_config.gpu:
- updates = GPUUpdates(G)
- solver = Solver(updates)
- elif sim_config.subgrid:
- updates = create_subgrid_updates(G)
- solver = SubGridSolver(G, updates)
- else:
- updates = CPUUpdates(G)
- solver = Solver(updates)
- # a large range of function exist to advance the time step for dispersive
- # materials. The correct function is set here based on the
- # the required numerical precision and dispersive material type.
- props = updates.adapt_dispersive_config(config)
- updates.set_dispersive_updates(props)
- return solver
-
-
-
-class Solver:
-
- """Generic solver for Update objects"""
-
- def __init__(self, updates):
- """Context for the model to run in. Sub-class this with contexts
- i.e. an MPI context.
-
- Args:
- updates (Updates): updates contains methods to run FDTD algorithm
- iterator (iterator): can be range() or tqdm()
- """
- self.updates = updates
-
- def get_G(self):
- return self.updates.G
-
- def solve(self, iterator):
- """Time step the FDTD model."""
- tsolvestart = timer()
- for iteration in iterator:
- self.updates.grid.iteration = iteration
- self.updates.store_outputs()
- self.updates.store_snapshots(iteration)
- self.updates.update_magnetic()
- self.updates.update_magnetic_pml()
- self.updates.update_magnetic_sources(iteration)
- self.updates.update_electric_a()
- self.updates.update_electric_pml()
- self.updates.update_electric_sources(iteration)
- self.updates.update_electric_b()
-
- tsolve = timer() - tsolvestart
- return tsolve
+# Copyright (C) 2015-2019: The University of Edinburgh
+# Authors: Craig Warren and Antonis Giannopoulos
+#
+# This file is part of gprMax.
+#
+# gprMax is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# gprMax is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with gprMax. If not, see .
+from gprMax.updates import CPUUpdates
+from gprMax.updates import GPUUpdates
+from .subgrids.updates import create_updates as create_subgrid_updates
+from gprMax.utilities import timer
+from .grid import FDTDGrid
+from .grid import GPUGrid
+import gprMax.config as config
+
+
+def create_G(sim_config):
+ """Returns the configured solver."""
+ if sim_config.gpu:
+ G = GPUGrid()
+ elif sim_config.subgrid:
+ G = FDTDGrid()
+ else:
+ G = FDTDGrid()
+
+ return G
+
+
+def create_solver(G, sim_config):
+ """Returns the configured solver."""
+ if sim_config.gpu:
+ updates = GPUUpdates(G)
+ solver = Solver(updates)
+ elif sim_config.subgrid:
+ updates = create_subgrid_updates(G)
+ solver = Solver(updates, hsg=True)
+ else:
+ updates = CPUUpdates(G)
+ solver = Solver(updates)
+ # a large range of function exist to advance the time step for dispersive
+ # materials. The correct function is set here based on the
+ # the required numerical precision and dispersive material type.
+ props = updates.adapt_dispersive_config(config)
+ updates.set_dispersive_updates(props)
+ return solver
+
+
+
+class Solver:
+
+ """Generic solver for Update objects"""
+
+ def __init__(self, updates, hsg=False):
+ """Context for the model to run in. Sub-class this with contexts
+ i.e. an MPI context.
+
+ Args:
+ updates (Updates): updates contains methods to run FDTD algorithm
+ iterator (iterator): can be range() or tqdm()
+ """
+ self.updates = updates
+ self.hsg = hsg
+
+ def get_G(self):
+ return self.updates.G
+
+ def solve(self, iterator):
+ """Time step the FDTD model."""
+ tsolvestart = timer()
+ for iteration in iterator:
+ self.updates.store_outputs()
+ self.updates.store_snapshots(iteration)
+ self.updates.update_magnetic()
+ self.updates.update_magnetic_pml()
+ self.updates.update_magnetic_sources()
+ if self.hsg:
+ self.updates.hsg_2()
+ self.updates.update_electric_a()
+ self.updates.update_electric_pml()
+ self.updates.update_electric_sources()
+ if self.hsg:
+ self.updates.hsg_1()
+ self.updates.update_electric_b()
+
+ tsolve = timer() - tsolvestart
+ return tsolve
diff --git a/gprMax/sources.py b/gprMax/sources.py
index a1a9039f..0bd15189 100644
--- a/gprMax/sources.py
+++ b/gprMax/sources.py
@@ -1,413 +1,413 @@
-# Copyright (C) 2015-2019: The University of Edinburgh
-# Authors: Craig Warren and Antonis Giannopoulos
-#
-# This file is part of gprMax.
-#
-# gprMax is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# gprMax is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with gprMax. If not, see .
-
-from copy import deepcopy
-
-import numpy as np
-
-import gprMax.config as config
-from .grid import Ix
-from .grid import Iy
-from .grid import Iz
-from .utilities import round_value
-
-
-class Source(object):
- """Super-class which describes a generic source."""
-
- def __init__(self):
- self.ID = None
- self.polarisation = None
- self.xcoord = None
- self.ycoord = None
- self.zcoord = None
- self.xcoordorigin = None
- self.ycoordorigin = None
- self.zcoordorigin = None
- self.start = None
- self.stop = None
- self.waveformID = None
-
- def calculate_waveform_values(self, G):
- """Calculates all waveform values for source for duration of simulation.
-
- Args:
- G (class): Grid class instance - holds essential parameters describing the model.
- """
- # Waveform values for electric sources - calculated half a timestep later
- self.waveformvaluesJ = np.zeros((G.iterations), dtype=config.dtypes['float_or_double'])
-
- # Waveform values for magnetic sources
- self.waveformvaluesM = np.zeros((G.iterations), dtype=config.dtypes['float_or_double'])
-
- waveform = next(x for x in G.waveforms if x.ID == self.waveformID)
-
- for iteration in range(G.iterations):
- time = G.dt * iteration
- if time >= self.start and time <= self.stop:
- # Set the time of the waveform evaluation to account for any delay in the start
- time -= self.start
- self.waveformvaluesJ[iteration] = waveform.calculate_value(time + 0.5 * G.dt, G.dt)
- self.waveformvaluesM[iteration] = waveform.calculate_value(time, G.dt)
-
-
-class VoltageSource(Source):
- """
- A voltage source can be a hard source if it's resistance is zero, i.e. the
- time variation of the specified electric field component is prescribed.
- If it's resistance is non-zero it behaves as a resistive voltage source.
- """
-
- def __init__(self):
- super().__init__()
- self.resistance = None
-
- def update_electric(self, iteration, updatecoeffsE, ID, Ex, Ey, Ez, G):
- """Updates electric field values for a voltage source.
-
- Args:
- iteration (int): Current iteration (timestep).
- updatecoeffsE (memory view): numpy array of electric field update coefficients.
- ID (memory view): numpy array of numeric IDs corresponding to materials in the model.
- Ex, Ey, Ez (memory view): numpy array of electric field values.
- G (class): Grid class instance - holds essential parameters describing the model.
- """
-
- if iteration * G.dt >= self.start and iteration * G.dt <= self.stop:
- i = self.xcoord
- j = self.ycoord
- k = self.zcoord
- componentID = 'E' + self.polarisation
-
- if self.polarisation == 'x':
- if self.resistance != 0:
- Ex[i, j, k] -= updatecoeffsE[ID[G.IDlookup[componentID], i, j, k], 4] * self.waveformvaluesJ[iteration] * (1 / (self.resistance * G.dy * G.dz))
- else:
- Ex[i, j, k] = -1 * self.waveformvaluesJ[iteration] / G.dx
-
- elif self.polarisation == 'y':
- if self.resistance != 0:
- Ey[i, j, k] -= updatecoeffsE[ID[G.IDlookup[componentID], i, j, k], 4] * self.waveformvaluesJ[iteration] * (1 / (self.resistance * G.dx * G.dz))
- else:
- Ey[i, j, k] = -1 * self.waveformvaluesJ[iteration] / G.dy
-
- elif self.polarisation == 'z':
- if self.resistance != 0:
- Ez[i, j, k] -= updatecoeffsE[ID[G.IDlookup[componentID], i, j, k], 4] * self.waveformvaluesJ[iteration] * (1 / (self.resistance * G.dx * G.dy))
- else:
- Ez[i, j, k] = -1 * self.waveformvaluesJ[iteration] / G.dz
-
- def create_material(self, G):
- """Create a new material at the voltage source location that adds the
- voltage source conductivity to the underlying parameters.
-
- Args:
- G (class): Grid class instance - holds essential parameters describing the model.
- """
-
- if self.resistance != 0:
- i = self.xcoord
- j = self.ycoord
- k = self.zcoord
-
- componentID = 'E' + self.polarisation
- requirednumID = G.ID[G.IDlookup[componentID], i, j, k]
- material = next(x for x in G.materials if x.numID == requirednumID)
- newmaterial = deepcopy(material)
- newmaterial.ID = material.ID + '+' + self.ID
- newmaterial.numID = len(G.materials)
- newmaterial.averagable = False
- newmaterial.type += ',\nvoltage-source'
-
- # Add conductivity of voltage source to underlying conductivity
- if self.polarisation == 'x':
- newmaterial.se += G.dx / (self.resistance * G.dy * G.dz)
- elif self.polarisation == 'y':
- newmaterial.se += G.dy / (self.resistance * G.dx * G.dz)
- elif self.polarisation == 'z':
- newmaterial.se += G.dz / (self.resistance * G.dx * G.dy)
-
- G.ID[G.IDlookup[componentID], i, j, k] = newmaterial.numID
- G.materials.append(newmaterial)
-
-
-class HertzianDipole(Source):
- """A Hertzian dipole is an additive source (electric current density)."""
-
- def __init__(self):
- super().__init__()
- self.dl = None
-
- def update_electric(self, iteration, updatecoeffsE, ID, Ex, Ey, Ez, G):
- """Updates electric field values for a Hertzian dipole.
-
- Args:
- iteration (int): Current iteration (timestep).
- updatecoeffsE (memory view): numpy array of electric field update coefficients.
- ID (memory view): numpy array of numeric IDs corresponding to materials in the model.
- Ex, Ey, Ez (memory view): numpy array of electric field values.
- G (class): Grid class instance - holds essential parameters describing the model.
- """
-
- if iteration * G.dt >= self.start and iteration * G.dt <= self.stop:
- i = self.xcoord
- j = self.ycoord
- k = self.zcoord
- componentID = 'E' + self.polarisation
- if self.polarisation == 'x':
- Ex[i, j, k] -= updatecoeffsE[ID[G.IDlookup[componentID], i, j, k], 4] * self.waveformvaluesJ[iteration] * self.dl * (1 / (G.dx * G.dy * G.dz))
-
- elif self.polarisation == 'y':
- Ey[i, j, k] -= updatecoeffsE[ID[G.IDlookup[componentID], i, j, k], 4] * self.waveformvaluesJ[iteration] * self.dl * (1 / (G.dx * G.dy * G.dz))
-
- elif self.polarisation == 'z':
- Ez[i, j, k] -= updatecoeffsE[ID[G.IDlookup[componentID], i, j, k], 4] * self.waveformvaluesJ[iteration] * self.dl * (1 / (G.dx * G.dy * G.dz))
-
-
-class MagneticDipole(Source):
- """A magnetic dipole is an additive source (magnetic current density)."""
-
- def __init__(self):
- super().__init__()
-
- def update_magnetic(self, iteration, updatecoeffsH, ID, Hx, Hy, Hz, G):
- """Updates magnetic field values for a magnetic dipole.
-
- Args:
- iteration (int): Current iteration (timestep).
- updatecoeffsH (memory view): numpy array of magnetic field update coefficients.
- ID (memory view): numpy array of numeric IDs corresponding to materials in the model.
- Hx, Hy, Hz (memory view): numpy array of magnetic field values.
- G (class): Grid class instance - holds essential parameters describing the model.
- """
-
- if iteration * G.dt >= self.start and iteration * G.dt <= self.stop:
- i = self.xcoord
- j = self.ycoord
- k = self.zcoord
- componentID = 'H' + self.polarisation
-
- if self.polarisation == 'x':
- Hx[i, j, k] -= updatecoeffsH[ID[G.IDlookup[componentID], i, j, k], 4] * self.waveformvaluesM[iteration] * (1 / (G.dx * G.dy * G.dz))
-
- elif self.polarisation == 'y':
- Hy[i, j, k] -= updatecoeffsH[ID[G.IDlookup[componentID], i, j, k], 4] * self.waveformvaluesM[iteration] * (1 / (G.dx * G.dy * G.dz))
-
- elif self.polarisation == 'z':
- Hz[i, j, k] -= updatecoeffsH[ID[G.IDlookup[componentID], i, j, k], 4] * self.waveformvaluesM[iteration] * (1 / (G.dx * G.dy * G.dz))
-
-
-def gpu_initialise_src_arrays(sources, G):
- """Initialise arrays on GPU for source coordinates/polarisation, other source information, and source waveform values.
-
- Args:
- sources (list): List of sources of one class, e.g. HertzianDipoles.
- G (class): Grid class instance - holds essential parameters describing the model.
-
- Returns:
- srcinfo1_gpu (int): numpy array of source cell coordinates and polarisation information.
- srcinfo2_gpu (float): numpy array of other source information, e.g. length, resistance etc...
- srcwaves_gpu (float): numpy array of source waveform values.
- """
-
- import pycuda.gpuarray as gpuarray
-
- srcinfo1 = np.zeros((len(sources), 4), dtype=np.int32)
- srcinfo2 = np.zeros((len(sources)), dtype=config.dtypes['float_or_double'])
- srcwaves = np.zeros((len(sources), G.iterations), dtype=config.dtypes['float_or_double'])
- for i, src in enumerate(sources):
- srcinfo1[i, 0] = src.xcoord
- srcinfo1[i, 1] = src.ycoord
- srcinfo1[i, 2] = src.zcoord
-
- if src.polarisation == 'x':
- srcinfo1[i, 3] = 0
- elif src.polarisation == 'y':
- srcinfo1[i, 3] = 1
- elif src.polarisation == 'z':
- srcinfo1[i, 3] = 2
-
- if src.__class__.__name__ == 'HertzianDipole':
- srcinfo2[i] = src.dl
- srcwaves[i, :] = src.waveformvaluesJ
- elif src.__class__.__name__ == 'VoltageSource':
- srcinfo2[i] = src.resistance
- srcwaves[i, :] = src.waveformvaluesJ
- elif src.__class__.__name__ == 'MagneticDipole':
- srcwaves[i, :] = src.waveformvaluesM
-
- srcinfo1_gpu = gpuarray.to_gpu(srcinfo1)
- srcinfo2_gpu = gpuarray.to_gpu(srcinfo2)
- srcwaves_gpu = gpuarray.to_gpu(srcwaves)
-
- return srcinfo1_gpu, srcinfo2_gpu, srcwaves_gpu
-
-
-class TransmissionLine(Source):
- """A transmission line source is a one-dimensional transmission line
- which is attached virtually to a grid cell.
- """
-
- def __init__(self, G):
- """
- Args:
- G (class): Grid class instance - holds essential parameters describing the model.
- """
-
- super().__init__()
- self.resistance = None
-
- # Coefficients for ABC termination of end of the transmission line
- self.abcv0 = 0
- self.abcv1 = 0
-
- # Spatial step of transmission line (N.B if the magic time step is
- # used it results in instabilities for certain impedances)
- self.dl = np.sqrt(3) * config.c * G.dt
-
- # Number of cells in the transmission line (initially a long line to
- # calculate incident voltage and current); consider putting ABCs/PML at end
- self.nl = round_value(0.667 * G.iterations)
-
- # Cell position of the one-way injector excitation in the transmission line
- self.srcpos = 5
-
- # Cell position of where line connects to antenna/main grid
- self.antpos = 10
-
- self.voltage = np.zeros(self.nl, dtype=config.dtypes['float_or_double'])
- self.current = np.zeros(self.nl, dtype=config.dtypes['float_or_double'])
- self.Vinc = np.zeros(G.iterations, dtype=config.dtypes['float_or_double'])
- self.Iinc = np.zeros(G.iterations, dtype=config.dtypes['float_or_double'])
- self.Vtotal = np.zeros(G.iterations, dtype=config.dtypes['float_or_double'])
- self.Itotal = np.zeros(G.iterations, dtype=config.dtypes['float_or_double'])
-
- def calculate_incident_V_I(self, G):
- """Calculates the incident voltage and current with a long length
- transmission line not connected to the main grid from: http://dx.doi.org/10.1002/mop.10415
-
- Args:
- G (class): Grid class instance - holds essential parameters describing the model.
- """
-
- for iteration in range(G.iterations):
- self.Iinc[iteration] = self.current[self.antpos]
- self.Vinc[iteration] = self.voltage[self.antpos]
- self.update_current(iteration, G)
- self.update_voltage(iteration, G)
-
- # Shorten number of cells in the transmission line before use with main grid
- self.nl = self.antpos + 1
-
- def update_abc(self, G):
- """Updates absorbing boundary condition at end of the transmission line.
-
- Args:
- G (class): Grid class instance - holds essential parameters describing the model.
- """
-
- h = (config.c * G.dt - self.dl) / (config.c * G.dt + self.dl)
-
- self.voltage[0] = h * (self.voltage[1] - self.abcv0) + self.abcv1
- self.abcv0 = self.voltage[0]
- self.abcv1 = self.voltage[1]
-
- def update_voltage(self, iteration, G):
- """Updates voltage values along the transmission line.
-
- Args:
- iteration (int): Current iteration (timestep).
- G (class): Grid class instance - holds essential parameters describing the model.
- """
-
- # 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])
-
- # Update the voltage at the position of the one-way injector excitation
- self.voltage[self.srcpos] += (config.c * G.dt / self.dl) * self.waveformvaluesJ[iteration]
-
- # Update ABC before updating current
- self.update_abc(G)
-
- def update_current(self, iteration, G):
- """Updates current values along the transmission line.
-
- Args:
- iteration (int): Current iteration (timestep).
- G (class): Grid class instance - holds essential parameters describing the model.
- """
-
- # Update all the current values along the line
- self.current[0:self.nl - 1] -= (1 / self.resistance) * (config.c * G.dt / self.dl) * (self.voltage[1:self.nl] - self.voltage[0:self.nl - 1])
-
- # 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.waveformvaluesM[iteration]
-
- def update_electric(self, iteration, updatecoeffsE, ID, Ex, Ey, Ez, G):
- """Updates electric field value in the main grid from voltage value in the transmission line.
-
- Args:
- iteration (int): Current iteration (timestep).
- updatecoeffsE (memory view): numpy array of electric field update coefficients.
- ID (memory view): numpy array of numeric IDs corresponding to materials in the model.
- Ex, Ey, Ez (memory view): numpy array of electric field values.
- G (class): Grid class instance - holds essential parameters describing the model.
- """
-
- if iteration * G.dt >= self.start and iteration * G.dt <= self.stop:
- i = self.xcoord
- j = self.ycoord
- k = self.zcoord
-
- self.update_voltage(iteration, G)
-
- if self.polarisation == 'x':
- Ex[i, j, k] = - self.voltage[self.antpos] / G.dx
-
- elif self.polarisation == 'y':
- Ey[i, j, k] = - self.voltage[self.antpos] / G.dy
-
- elif self.polarisation == 'z':
- Ez[i, j, k] = - self.voltage[self.antpos] / G.dz
-
- def update_magnetic(self, iteration, updatecoeffsH, ID, Hx, Hy, Hz, G):
- """Updates current value in transmission line from magnetic field values in the main grid.
-
- Args:
- iteration (int): Current iteration (timestep).
- updatecoeffsH (memory view): numpy array of magnetic field update coefficients.
- ID (memory view): numpy array of numeric IDs corresponding to materials in the model.
- Hx, Hy, Hz (memory view): numpy array of magnetic field values.
- G (class): Grid class instance - holds essential parameters describing the model.
- """
-
- if iteration * G.dt >= self.start and iteration * G.dt <= self.stop:
- i = self.xcoord
- j = self.ycoord
- k = self.zcoord
-
- if self.polarisation == 'x':
- self.current[self.antpos] = Ix(i, j, k, G.Hx, G.Hy, G.Hz, G)
-
- elif self.polarisation == 'y':
- self.current[self.antpos] = Iy(i, j, k, G.Hx, G.Hy, G.Hz, G)
-
- elif self.polarisation == 'z':
- self.current[self.antpos] = Iz(i, j, k, G.Hx, G.Hy, G.Hz, G)
-
- self.update_current(iteration, G)
+# Copyright (C) 2015-2019: The University of Edinburgh
+# Authors: Craig Warren and Antonis Giannopoulos
+#
+# This file is part of gprMax.
+#
+# gprMax is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# gprMax is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with gprMax. If not, see .
+
+from copy import deepcopy
+
+import numpy as np
+
+import gprMax.config as config
+from .grid import Ix
+from .grid import Iy
+from .grid import Iz
+from .utilities import round_value
+
+
+class Source(object):
+ """Super-class which describes a generic source."""
+
+ def __init__(self):
+ self.ID = None
+ self.polarisation = None
+ self.xcoord = None
+ self.ycoord = None
+ self.zcoord = None
+ self.xcoordorigin = None
+ self.ycoordorigin = None
+ self.zcoordorigin = None
+ self.start = None
+ self.stop = None
+ self.waveformID = None
+
+ def calculate_waveform_values(self, G):
+ """Calculates all waveform values for source for duration of simulation.
+
+ Args:
+ G (class): Grid class instance - holds essential parameters describing the model.
+ """
+ # Waveform values for electric sources - calculated half a timestep later
+ self.waveformvaluesJ = np.zeros((G.iterations), dtype=config.dtypes['float_or_double'])
+
+ # Waveform values for magnetic sources
+ self.waveformvaluesM = np.zeros((G.iterations), dtype=config.dtypes['float_or_double'])
+
+ waveform = next(x for x in G.waveforms if x.ID == self.waveformID)
+
+ for iteration in range(G.iterations):
+ time = G.dt * iteration
+ if time >= self.start and time <= self.stop:
+ # Set the time of the waveform evaluation to account for any delay in the start
+ time -= self.start
+ self.waveformvaluesJ[iteration] = waveform.calculate_value(time + 0.5 * G.dt, G.dt)
+ self.waveformvaluesM[iteration] = waveform.calculate_value(time, G.dt)
+
+
+class VoltageSource(Source):
+ """
+ A voltage source can be a hard source if it's resistance is zero, i.e. the
+ time variation of the specified electric field component is prescribed.
+ If it's resistance is non-zero it behaves as a resistive voltage source.
+ """
+
+ def __init__(self):
+ super().__init__()
+ self.resistance = None
+
+ def update_electric(self, iteration, updatecoeffsE, ID, Ex, Ey, Ez, G):
+ """Updates electric field values for a voltage source.
+
+ Args:
+ iteration (int): Current iteration (timestep).
+ updatecoeffsE (memory view): numpy array of electric field update coefficients.
+ ID (memory view): numpy array of numeric IDs corresponding to materials in the model.
+ Ex, Ey, Ez (memory view): numpy array of electric field values.
+ G (class): Grid class instance - holds essential parameters describing the model.
+ """
+
+ if iteration * G.dt >= self.start and iteration * G.dt <= self.stop:
+ i = self.xcoord
+ j = self.ycoord
+ k = self.zcoord
+ componentID = 'E' + self.polarisation
+
+ if self.polarisation == 'x':
+ if self.resistance != 0:
+ Ex[i, j, k] -= updatecoeffsE[ID[G.IDlookup[componentID], i, j, k], 4] * self.waveformvaluesJ[iteration] * (1 / (self.resistance * G.dy * G.dz))
+ else:
+ Ex[i, j, k] = -1 * self.waveformvaluesJ[iteration] / G.dx
+
+ elif self.polarisation == 'y':
+ if self.resistance != 0:
+ Ey[i, j, k] -= updatecoeffsE[ID[G.IDlookup[componentID], i, j, k], 4] * self.waveformvaluesJ[iteration] * (1 / (self.resistance * G.dx * G.dz))
+ else:
+ Ey[i, j, k] = -1 * self.waveformvaluesJ[iteration] / G.dy
+
+ elif self.polarisation == 'z':
+ if self.resistance != 0:
+ Ez[i, j, k] -= updatecoeffsE[ID[G.IDlookup[componentID], i, j, k], 4] * self.waveformvaluesJ[iteration] * (1 / (self.resistance * G.dx * G.dy))
+ else:
+ Ez[i, j, k] = -1 * self.waveformvaluesJ[iteration] / G.dz
+
+ def create_material(self, G):
+ """Create a new material at the voltage source location that adds the
+ voltage source conductivity to the underlying parameters.
+
+ Args:
+ G (class): Grid class instance - holds essential parameters describing the model.
+ """
+
+ if self.resistance != 0:
+ i = self.xcoord
+ j = self.ycoord
+ k = self.zcoord
+
+ componentID = 'E' + self.polarisation
+ requirednumID = G.ID[G.IDlookup[componentID], i, j, k]
+ material = next(x for x in G.materials if x.numID == requirednumID)
+ newmaterial = deepcopy(material)
+ newmaterial.ID = material.ID + '+' + self.ID
+ newmaterial.numID = len(G.materials)
+ newmaterial.averagable = False
+ newmaterial.type += ',\nvoltage-source'
+
+ # Add conductivity of voltage source to underlying conductivity
+ if self.polarisation == 'x':
+ newmaterial.se += G.dx / (self.resistance * G.dy * G.dz)
+ elif self.polarisation == 'y':
+ newmaterial.se += G.dy / (self.resistance * G.dx * G.dz)
+ elif self.polarisation == 'z':
+ newmaterial.se += G.dz / (self.resistance * G.dx * G.dy)
+
+ G.ID[G.IDlookup[componentID], i, j, k] = newmaterial.numID
+ G.materials.append(newmaterial)
+
+
+class HertzianDipole(Source):
+ """A Hertzian dipole is an additive source (electric current density)."""
+
+ def __init__(self):
+ super().__init__()
+ self.dl = None
+
+ def update_electric(self, iteration, updatecoeffsE, ID, Ex, Ey, Ez, G):
+ """Updates electric field values for a Hertzian dipole.
+
+ Args:
+ iteration (int): Current iteration (timestep).
+ updatecoeffsE (memory view): numpy array of electric field update coefficients.
+ ID (memory view): numpy array of numeric IDs corresponding to materials in the model.
+ Ex, Ey, Ez (memory view): numpy array of electric field values.
+ G (class): Grid class instance - holds essential parameters describing the model.
+ """
+
+ if iteration * G.dt >= self.start and iteration * G.dt <= self.stop:
+ i = self.xcoord
+ j = self.ycoord
+ k = self.zcoord
+ componentID = 'E' + self.polarisation
+ if self.polarisation == 'x':
+ Ex[i, j, k] -= updatecoeffsE[ID[G.IDlookup[componentID], i, j, k], 4] * self.waveformvaluesJ[iteration] * self.dl * (1 / (G.dx * G.dy * G.dz))
+
+ elif self.polarisation == 'y':
+ Ey[i, j, k] -= updatecoeffsE[ID[G.IDlookup[componentID], i, j, k], 4] * self.waveformvaluesJ[iteration] * self.dl * (1 / (G.dx * G.dy * G.dz))
+
+ elif self.polarisation == 'z':
+ Ez[i, j, k] -= updatecoeffsE[ID[G.IDlookup[componentID], i, j, k], 4] * self.waveformvaluesJ[iteration] * self.dl * (1 / (G.dx * G.dy * G.dz))
+
+
+class MagneticDipole(Source):
+ """A magnetic dipole is an additive source (magnetic current density)."""
+
+ def __init__(self):
+ super().__init__()
+
+ def update_magnetic(self, iteration, updatecoeffsH, ID, Hx, Hy, Hz, G):
+ """Updates magnetic field values for a magnetic dipole.
+
+ Args:
+ iteration (int): Current iteration (timestep).
+ updatecoeffsH (memory view): numpy array of magnetic field update coefficients.
+ ID (memory view): numpy array of numeric IDs corresponding to materials in the model.
+ Hx, Hy, Hz (memory view): numpy array of magnetic field values.
+ G (class): Grid class instance - holds essential parameters describing the model.
+ """
+
+ if iteration * G.dt >= self.start and iteration * G.dt <= self.stop:
+ i = self.xcoord
+ j = self.ycoord
+ k = self.zcoord
+ componentID = 'H' + self.polarisation
+
+ if self.polarisation == 'x':
+ Hx[i, j, k] -= updatecoeffsH[ID[G.IDlookup[componentID], i, j, k], 4] * self.waveformvaluesM[iteration] * (1 / (G.dx * G.dy * G.dz))
+
+ elif self.polarisation == 'y':
+ Hy[i, j, k] -= updatecoeffsH[ID[G.IDlookup[componentID], i, j, k], 4] * self.waveformvaluesM[iteration] * (1 / (G.dx * G.dy * G.dz))
+
+ elif self.polarisation == 'z':
+ Hz[i, j, k] -= updatecoeffsH[ID[G.IDlookup[componentID], i, j, k], 4] * self.waveformvaluesM[iteration] * (1 / (G.dx * G.dy * G.dz))
+
+
+def gpu_initialise_src_arrays(sources, G):
+ """Initialise arrays on GPU for source coordinates/polarisation, other source information, and source waveform values.
+
+ Args:
+ sources (list): List of sources of one class, e.g. HertzianDipoles.
+ G (class): Grid class instance - holds essential parameters describing the model.
+
+ Returns:
+ srcinfo1_gpu (int): numpy array of source cell coordinates and polarisation information.
+ srcinfo2_gpu (float): numpy array of other source information, e.g. length, resistance etc...
+ srcwaves_gpu (float): numpy array of source waveform values.
+ """
+
+ import pycuda.gpuarray as gpuarray
+
+ srcinfo1 = np.zeros((len(sources), 4), dtype=np.int32)
+ srcinfo2 = np.zeros((len(sources)), dtype=config.dtypes['float_or_double'])
+ srcwaves = np.zeros((len(sources), G.iterations), dtype=config.dtypes['float_or_double'])
+ for i, src in enumerate(sources):
+ srcinfo1[i, 0] = src.xcoord
+ srcinfo1[i, 1] = src.ycoord
+ srcinfo1[i, 2] = src.zcoord
+
+ if src.polarisation == 'x':
+ srcinfo1[i, 3] = 0
+ elif src.polarisation == 'y':
+ srcinfo1[i, 3] = 1
+ elif src.polarisation == 'z':
+ srcinfo1[i, 3] = 2
+
+ if src.__class__.__name__ == 'HertzianDipole':
+ srcinfo2[i] = src.dl
+ srcwaves[i, :] = src.waveformvaluesJ
+ elif src.__class__.__name__ == 'VoltageSource':
+ srcinfo2[i] = src.resistance
+ srcwaves[i, :] = src.waveformvaluesJ
+ elif src.__class__.__name__ == 'MagneticDipole':
+ srcwaves[i, :] = src.waveformvaluesM
+
+ srcinfo1_gpu = gpuarray.to_gpu(srcinfo1)
+ srcinfo2_gpu = gpuarray.to_gpu(srcinfo2)
+ srcwaves_gpu = gpuarray.to_gpu(srcwaves)
+
+ return srcinfo1_gpu, srcinfo2_gpu, srcwaves_gpu
+
+
+class TransmissionLine(Source):
+ """A transmission line source is a one-dimensional transmission line
+ which is attached virtually to a grid cell.
+ """
+
+ def __init__(self, G):
+ """
+ Args:
+ G (class): Grid class instance - holds essential parameters describing the model.
+ """
+
+ super().__init__()
+ self.resistance = None
+
+ # Coefficients for ABC termination of end of the transmission line
+ self.abcv0 = 0
+ self.abcv1 = 0
+
+ # Spatial step of transmission line (N.B if the magic time step is
+ # used it results in instabilities for certain impedances)
+ self.dl = np.sqrt(3) * config.c * G.dt
+
+ # Number of cells in the transmission line (initially a long line to
+ # calculate incident voltage and current); consider putting ABCs/PML at end
+ self.nl = round_value(0.667 * G.iterations)
+
+ # Cell position of the one-way injector excitation in the transmission line
+ self.srcpos = 5
+
+ # Cell position of where line connects to antenna/main grid
+ self.antpos = 10
+
+ self.voltage = np.zeros(self.nl, dtype=config.dtypes['float_or_double'])
+ self.current = np.zeros(self.nl, dtype=config.dtypes['float_or_double'])
+ self.Vinc = np.zeros(G.iterations, dtype=config.dtypes['float_or_double'])
+ self.Iinc = np.zeros(G.iterations, dtype=config.dtypes['float_or_double'])
+ self.Vtotal = np.zeros(G.iterations, dtype=config.dtypes['float_or_double'])
+ self.Itotal = np.zeros(G.iterations, dtype=config.dtypes['float_or_double'])
+
+ def calculate_incident_V_I(self, G):
+ """Calculates the incident voltage and current with a long length
+ transmission line not connected to the main grid from: http://dx.doi.org/10.1002/mop.10415
+
+ Args:
+ G (class): Grid class instance - holds essential parameters describing the model.
+ """
+
+ for iteration in range(G.iterations):
+ self.Iinc[iteration] = self.current[self.antpos]
+ self.Vinc[iteration] = self.voltage[self.antpos]
+ self.update_current(iteration, G)
+ self.update_voltage(iteration, G)
+
+ # Shorten number of cells in the transmission line before use with main grid
+ self.nl = self.antpos + 1
+
+ def update_abc(self, G):
+ """Updates absorbing boundary condition at end of the transmission line.
+
+ Args:
+ G (class): Grid class instance - holds essential parameters describing the model.
+ """
+
+ h = (config.c * G.dt - self.dl) / (config.c * G.dt + self.dl)
+
+ self.voltage[0] = h * (self.voltage[1] - self.abcv0) + self.abcv1
+ self.abcv0 = self.voltage[0]
+ self.abcv1 = self.voltage[1]
+
+ def update_voltage(self, iteration, G):
+ """Updates voltage values along the transmission line.
+
+ Args:
+ iteration (int): Current iteration (timestep).
+ G (class): Grid class instance - holds essential parameters describing the model.
+ """
+
+ # 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])
+
+ # Update the voltage at the position of the one-way injector excitation
+ self.voltage[self.srcpos] += (config.c * G.dt / self.dl) * self.waveformvaluesJ[iteration]
+
+ # Update ABC before updating current
+ self.update_abc(G)
+
+ def update_current(self, iteration, G):
+ """Updates current values along the transmission line.
+
+ Args:
+ iteration (int): Current iteration (timestep).
+ G (class): Grid class instance - holds essential parameters describing the model.
+ """
+
+ # Update all the current values along the line
+ self.current[0:self.nl - 1] -= (1 / self.resistance) * (config.c * G.dt / self.dl) * (self.voltage[1:self.nl] - self.voltage[0:self.nl - 1])
+
+ # 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.waveformvaluesM[iteration]
+
+ def update_electric(self, iteration, updatecoeffsE, ID, Ex, Ey, Ez, G):
+ """Updates electric field value in the main grid from voltage value in the transmission line.
+
+ Args:
+ iteration (int): Current iteration (timestep).
+ updatecoeffsE (memory view): numpy array of electric field update coefficients.
+ ID (memory view): numpy array of numeric IDs corresponding to materials in the model.
+ Ex, Ey, Ez (memory view): numpy array of electric field values.
+ G (class): Grid class instance - holds essential parameters describing the model.
+ """
+
+ if iteration * G.dt >= self.start and iteration * G.dt <= self.stop:
+ i = self.xcoord
+ j = self.ycoord
+ k = self.zcoord
+
+ self.update_voltage(iteration, G)
+
+ if self.polarisation == 'x':
+ Ex[i, j, k] = - self.voltage[self.antpos] / G.dx
+
+ elif self.polarisation == 'y':
+ Ey[i, j, k] = - self.voltage[self.antpos] / G.dy
+
+ elif self.polarisation == 'z':
+ Ez[i, j, k] = - self.voltage[self.antpos] / G.dz
+
+ def update_magnetic(self, iteration, updatecoeffsH, ID, Hx, Hy, Hz, G):
+ """Updates current value in transmission line from magnetic field values in the main grid.
+
+ Args:
+ iteration (int): Current iteration (timestep).
+ updatecoeffsH (memory view): numpy array of magnetic field update coefficients.
+ ID (memory view): numpy array of numeric IDs corresponding to materials in the model.
+ Hx, Hy, Hz (memory view): numpy array of magnetic field values.
+ G (class): Grid class instance - holds essential parameters describing the model.
+ """
+
+ if iteration * G.dt >= self.start and iteration * G.dt <= self.stop:
+ i = self.xcoord
+ j = self.ycoord
+ k = self.zcoord
+
+ if self.polarisation == 'x':
+ self.current[self.antpos] = Ix(i, j, k, G.Hx, G.Hy, G.Hz, G)
+
+ elif self.polarisation == 'y':
+ self.current[self.antpos] = Iy(i, j, k, G.Hx, G.Hy, G.Hz, G)
+
+ elif self.polarisation == 'z':
+ self.current[self.antpos] = Iz(i, j, k, G.Hx, G.Hy, G.Hz, G)
+
+ self.update_current(iteration, G)
diff --git a/gprMax/subgrids/base.py b/gprMax/subgrids/base.py
index 7b0fb741..d16ad879 100644
--- a/gprMax/subgrids/base.py
+++ b/gprMax/subgrids/base.py
@@ -1,72 +1,72 @@
-from ..grid import FDTDGrid
-
-from ..materials import Material
-
-from scipy.constants import mu_0
-from scipy.constants import epsilon_0
-from scipy.constants import c
-
-import numpy as np
-
-from colorama import init
-from colorama import Fore
-from colorama import Style
-init()
-
-
-class SubGridBase(FDTDGrid):
-
- def __init__(self, **kwargs):
- super().__init__()
-
- self.mode = '3D'
- self.ratio = kwargs['ratio']
-
- if self.ratio % 2 == 0:
- raise ValueError('Subgrid Error: Only odd ratios are supported')
-
- # Name of the grid
- self.name = kwargs['ID']
-
- self.filter = kwargs['filter']
-
- # Number of main grid cells between the IS and OS
- self.is_os_sep = kwargs['is_os_sep']
- # Number of subgrid grid cells between the IS and OS
- self.s_is_os_sep = self.is_os_sep * self.ratio
-
- # Distance from OS to pml or the edge of the grid when pml is off
- self.pml_separation = kwargs['pml_separation']
-
- self.pmlthickness['x0'] = kwargs['subgrid_pml_thickness']
- self.pmlthickness['y0'] = kwargs['subgrid_pml_thickness']
- self.pmlthickness['z0'] = kwargs['subgrid_pml_thickness']
- self.pmlthickness['xmax'] = kwargs['subgrid_pml_thickness']
- self.pmlthickness['ymax'] = kwargs['subgrid_pml_thickness']
- self.pmlthickness['zmax'] = kwargs['subgrid_pml_thickness']
-
- # Number of sub cells to extend the sub grid beyond the IS boundary
- d_to_pml = self.s_is_os_sep + self.pml_separation
- self.n_boundary_cells = d_to_pml + self.pmlthickness['x0']
- self.n_boundary_cells_x = d_to_pml + self.pmlthickness['x0']
- self.n_boundary_cells_y = d_to_pml + self.pmlthickness['y0']
- self.n_boundary_cells_z = d_to_pml + self.pmlthickness['z0']
-
- self.interpolation = kwargs['interpolation']
-
- def calculate_dt(self):
- self.dt = (1 / (c * np.sqrt(
- (1 / self.dx) * (1 / self.dx) +
- (1 / self.dy) * (1 / self.dy) +
- (1 / self.dz) * (1 / self.dz))))
-
-
- def main_grid_index_to_subgrid_index(self, i, j, k):
- i_s = self.n_boundary_cells_x + (i - self.i0) * self.ratio
- j_s = self.n_boundary_cells_y + (j - self.j0) * self.ratio
- k_s = self.n_boundary_cells_z + (k - self.k0) * self.ratio
-
- return (i_s, j_s, k_s)
-
- def initialise_geometry_arrays(self):
- super().initialise_geometry_arrays()
+from ..grid import FDTDGrid
+
+from ..materials import Material
+
+from scipy.constants import mu_0
+from scipy.constants import epsilon_0
+from scipy.constants import c
+
+import numpy as np
+
+from colorama import init
+from colorama import Fore
+from colorama import Style
+init()
+
+
+class SubGridBase(FDTDGrid):
+
+ def __init__(self, **kwargs):
+ super().__init__()
+
+ self.mode = '3D'
+ self.ratio = kwargs['ratio']
+
+ if self.ratio % 2 == 0:
+ raise ValueError('Subgrid Error: Only odd ratios are supported')
+
+ # Name of the grid
+ self.name = kwargs['ID']
+
+ self.filter = kwargs['filter']
+
+ # Number of main grid cells between the IS and OS
+ self.is_os_sep = kwargs['is_os_sep']
+ # Number of subgrid grid cells between the IS and OS
+ self.s_is_os_sep = self.is_os_sep * self.ratio
+
+ # Distance from OS to pml or the edge of the grid when pml is off
+ self.pml_separation = kwargs['pml_separation']
+
+ self.pmlthickness['x0'] = kwargs['subgrid_pml_thickness']
+ self.pmlthickness['y0'] = kwargs['subgrid_pml_thickness']
+ self.pmlthickness['z0'] = kwargs['subgrid_pml_thickness']
+ self.pmlthickness['xmax'] = kwargs['subgrid_pml_thickness']
+ self.pmlthickness['ymax'] = kwargs['subgrid_pml_thickness']
+ self.pmlthickness['zmax'] = kwargs['subgrid_pml_thickness']
+
+ # Number of sub cells to extend the sub grid beyond the IS boundary
+ d_to_pml = self.s_is_os_sep + self.pml_separation
+ self.n_boundary_cells = d_to_pml + self.pmlthickness['x0']
+ self.n_boundary_cells_x = d_to_pml + self.pmlthickness['x0']
+ self.n_boundary_cells_y = d_to_pml + self.pmlthickness['y0']
+ self.n_boundary_cells_z = d_to_pml + self.pmlthickness['z0']
+
+ self.interpolation = kwargs['interpolation']
+
+ def calculate_dt(self):
+ self.dt = (1 / (c * np.sqrt(
+ (1 / self.dx) * (1 / self.dx) +
+ (1 / self.dy) * (1 / self.dy) +
+ (1 / self.dz) * (1 / self.dz))))
+
+
+ def main_grid_index_to_subgrid_index(self, i, j, k):
+ i_s = self.n_boundary_cells_x + (i - self.i0) * self.ratio
+ j_s = self.n_boundary_cells_y + (j - self.j0) * self.ratio
+ k_s = self.n_boundary_cells_z + (k - self.k0) * self.ratio
+
+ return (i_s, j_s, k_s)
+
+ def initialise_geometry_arrays(self):
+ super().initialise_geometry_arrays()
diff --git a/gprMax/subgrids/multi.py b/gprMax/subgrids/multi.py
index 7888c01b..60151d0f 100644
--- a/gprMax/subgrids/multi.py
+++ b/gprMax/subgrids/multi.py
@@ -1,146 +1,146 @@
-from ..receivers import Rx
-
-
-class ReferenceRx(Rx):
- """Receiver that micks a receiver in coarse grid."""
-
- """We often want to compare an output in a fine reference solution with a
- the solution in the coarse grid of a subgridded solution. This receiver
- moves the output points in the fine grid such that they are in the same
- position as the coarse grid.
- """
- def __init__(self):
- """Constructor."""
- super().__init__()
-
- def get_field(self, str_id, field):
- """Return the field value at the equivalent coarse yee cell.
-
- Parameters
- ----------
- str_id : str
- 'Ex' etc...
- field : np array
- e.g. numpy array of grid.Ez
-
- Returns
- -------
- float
- Field value
-
- """
- d = {
- 'Ex': self.get_Ex_from_field,
- 'Ey': self.get_Ey_from_field,
- 'Ez': self.get_Ez_from_field,
- 'Hx': self.get_Hx_from_field,
- 'Hy': self.get_Hy_from_field,
- 'Hz': self.get_Hz_from_field
- }
-
- e = d[str_id](field)
- return e
-
- def get_Ex_from_field(self, Ex):
- """Return the Ex field value from the equivalent coarse yee cell.
-
- Parameters
- ----------
- Ex : 3d numpy array
- e.g. grid.Ex
-
- Returns
- -------
- float
- Ex field value
-
- """
-
- # offset = ratio // 2
- e = Ex[self.xcoord + self.offset, self.ycoord, self.zcoord]
- return e
-
- def get_Ey_from_field(self, Ey):
- """Return the Ey field value from the equivalent coarse yee cell.
-
- Parameters
- ----------
- Ex : 3d numpy array
- e.g. grid.Ex
-
- Returns
- -------
- float
- Ey field value
-
- """
- e = Ey[self.xcoord, self.ycoord + self.offset, self.zcoord]
- return e
-
- def get_Ez_from_field(self, Ez):
- """Return the Ez field value from the equivalent coarse yee cell.
-
- Parameters
- ----------
- Ex : 3d numpy array
- e.g. grid.Ez
-
- Returns
- -------
- float
- Ez field value
-
- """
- e = Ez[self.xcoord, self.ycoord, self.zcoord + self.offset]
- return e
-
- def get_Hx_from_field(self, Hx):
- """Return the Hx field value from the equivalent coarse yee cell.
-
- Parameters
- ----------
- Ex : 3d numpy array
- e.g. grid.Hx
-
- Returns
- -------
- float
- Hx field value
-
- """
- e = Hx[self.xcoord, self.ycoord + self.offset, self.zcoord + self.offset]
- return e
-
- def get_Hy_from_field(self, Hy):
- """Return the Hy field value from the equivalent coarse yee cell.
-
- Parameters
- ----------
- Ex : 3d numpy array
- e.g. grid.Hx
-
- Returns
- -------
- float
- Hy field value
-
- """
- e = Hy[self.xcoord + self.offset, self.ycoord, self.zcoord + self.offset]
- return e
-
- def get_Hz_from_field(self, Hz):
- """Return the Hz field value from the equivalent coarse yee cell.
-
- Parameters
- ----------
- Ex : 3d numpy array
- e.g. grid.Hx
-
- Returns
- -------
- float
- Hz field value
-
- """
- e = Hz[self.xcoord + self.offset, self.ycoord + self.offset, self.zcoord]
- return e
+from ..receivers import Rx
+
+
+class ReferenceRx(Rx):
+ """Receiver that micks a receiver in coarse grid."""
+
+ """We often want to compare an output in a fine reference solution with a
+ the solution in the coarse grid of a subgridded solution. This receiver
+ moves the output points in the fine grid such that they are in the same
+ position as the coarse grid.
+ """
+ def __init__(self):
+ """Constructor."""
+ super().__init__()
+
+ def get_field(self, str_id, field):
+ """Return the field value at the equivalent coarse yee cell.
+
+ Parameters
+ ----------
+ str_id : str
+ 'Ex' etc...
+ field : np array
+ e.g. numpy array of grid.Ez
+
+ Returns
+ -------
+ float
+ Field value
+
+ """
+ d = {
+ 'Ex': self.get_Ex_from_field,
+ 'Ey': self.get_Ey_from_field,
+ 'Ez': self.get_Ez_from_field,
+ 'Hx': self.get_Hx_from_field,
+ 'Hy': self.get_Hy_from_field,
+ 'Hz': self.get_Hz_from_field
+ }
+
+ e = d[str_id](field)
+ return e
+
+ def get_Ex_from_field(self, Ex):
+ """Return the Ex field value from the equivalent coarse yee cell.
+
+ Parameters
+ ----------
+ Ex : 3d numpy array
+ e.g. grid.Ex
+
+ Returns
+ -------
+ float
+ Ex field value
+
+ """
+
+ # offset = ratio // 2
+ e = Ex[self.xcoord + self.offset, self.ycoord, self.zcoord]
+ return e
+
+ def get_Ey_from_field(self, Ey):
+ """Return the Ey field value from the equivalent coarse yee cell.
+
+ Parameters
+ ----------
+ Ex : 3d numpy array
+ e.g. grid.Ex
+
+ Returns
+ -------
+ float
+ Ey field value
+
+ """
+ e = Ey[self.xcoord, self.ycoord + self.offset, self.zcoord]
+ return e
+
+ def get_Ez_from_field(self, Ez):
+ """Return the Ez field value from the equivalent coarse yee cell.
+
+ Parameters
+ ----------
+ Ex : 3d numpy array
+ e.g. grid.Ez
+
+ Returns
+ -------
+ float
+ Ez field value
+
+ """
+ e = Ez[self.xcoord, self.ycoord, self.zcoord + self.offset]
+ return e
+
+ def get_Hx_from_field(self, Hx):
+ """Return the Hx field value from the equivalent coarse yee cell.
+
+ Parameters
+ ----------
+ Ex : 3d numpy array
+ e.g. grid.Hx
+
+ Returns
+ -------
+ float
+ Hx field value
+
+ """
+ e = Hx[self.xcoord, self.ycoord + self.offset, self.zcoord + self.offset]
+ return e
+
+ def get_Hy_from_field(self, Hy):
+ """Return the Hy field value from the equivalent coarse yee cell.
+
+ Parameters
+ ----------
+ Ex : 3d numpy array
+ e.g. grid.Hx
+
+ Returns
+ -------
+ float
+ Hy field value
+
+ """
+ e = Hy[self.xcoord + self.offset, self.ycoord, self.zcoord + self.offset]
+ return e
+
+ def get_Hz_from_field(self, Hz):
+ """Return the Hz field value from the equivalent coarse yee cell.
+
+ Parameters
+ ----------
+ Ex : 3d numpy array
+ e.g. grid.Hx
+
+ Returns
+ -------
+ float
+ Hz field value
+
+ """
+ e = Hz[self.xcoord + self.offset, self.ycoord + self.offset, self.zcoord]
+ return e
diff --git a/gprMax/subgrids/precursor_nodes.py b/gprMax/subgrids/precursor_nodes.py
index a837d18a..6e8a3890 100644
--- a/gprMax/subgrids/precursor_nodes.py
+++ b/gprMax/subgrids/precursor_nodes.py
@@ -1,633 +1,633 @@
-import numpy as np
-from scipy import interpolate
-import sys
-
-
-def calculate_weighting_coefficients(x1, x):
- c1 = (x - x1) / x
- c2 = x1 / x
- return (c1, c2)
-
-
-class PrecusorNodes2dBase(object):
-
- def __init__(self, fdtd_grid, sub_grid):
- self.G = fdtd_grid
- self.ratio = sub_grid.ratio
- self.nwx = sub_grid.nwx
- self.nwy = sub_grid.nwy
- self.sub_grid = sub_grid
- self.interpolation = sub_grid.interpolation
-
- self.Hx = fdtd_grid.Hx
- self.Hy = fdtd_grid.Hy
- self.Hz = fdtd_grid.Hz
- self.Ex = fdtd_grid.Ex
- self.Ey = fdtd_grid.Ey
- self.Ez = fdtd_grid.Ez
-
- # Main grid indices of subgrids
- self.i0 = sub_grid.i0
- self.j0 = sub_grid.j0
- self.k0 = sub_grid.k0
- self.i1 = sub_grid.i1
- self.j1 = sub_grid.j1
- self.k1 = sub_grid.k1
-
- # dl / 2 sub cell
- self.d = 1 / (2 * self.ratio)
-
- self._initialize_fields()
- self._initialize_field_names()
-
- self.l_weight = self.ratio // 2
- self.r_weight = self.ratio - self.l_weight
- #self.l_weight = 1
- #self.r_weight = 2
-
- def _initialize_fields(self):
- print('dont call me')
- sys.exit()
- pass
-
- def _initialize_field_names(self):
- print('dont call me')
- sys.exit()
- pass
-
- def interpolate_magnetic_in_time(self, m):
- self.weight_pre_and_current_fields(m, self.fn_m)
-
- def interpolate_electric_in_time(self, m):
- self.weight_pre_and_current_fields(m, self.fn_e)
-
- def weight_pre_and_current_fields(self, m, field_names):
- c1, c2 = calculate_weighting_coefficients(m, self.ratio)
-
- for f in field_names:
- try:
- val = c1 * getattr(self, f + '_0') + c2 * getattr(self, f + '_1')
- except ValueError:
- print(self.ex_front_0.shape)
- print(self.ex_front_1.shape)
- raise Exception(f)
- setattr(self, f, val)
-
- def calc_exact_field(self, field_names):
- """
- Function to set the fields used in update calculations to the
- values at the current main time step.
- i.e. ey_left = copy.ey_left_1
- """
- for f in field_names:
- val = np.copy(getattr(self, f + '_1'))
- setattr(self, f, val)
-
- def calc_exact_magnetic_in_time(self):
- self.calc_exact_field(self.fn_m)
-
- def calc_exact_electric_in_time(self):
- self.calc_exact_field(self.fn_e)
-
-
-class PrecursorNodes2dTM(PrecusorNodes2dBase):
-
- def __init__(self, fdtd_grid, sub_grid):
- super().__init__(fdtd_grid, sub_grid)
-
- def _initialize_fields(self):
-
- # H precursors
- self.hx_front_0 = np.zeros(self.nwx + 1)
- self.hx_front_1 = np.zeros(self.nwx + 1)
-
- self.hx_back_0 = np.zeros(self.nwx + 1)
- self.hx_back_1 = np.zeros(self.nwx + 1)
-
- self.hy_left_0 = np.zeros(self.nwy + 1)
- self.hy_left_1 = np.zeros(self.nwy + 1)
-
- self.hy_right_0 = np.zeros(self.nwy + 1)
- self.hy_right_1 = np.zeros(self.nwy + 1)
-
- # E precursors
- self.ez_front_0 = np.zeros(self.nwx + 1)
- self.ez_front_1 = np.zeros(self.nwx + 1)
-
- self.ez_back_0 = np.zeros(self.nwx + 1)
- self.ez_back_1 = np.zeros(self.nwx + 1)
-
- self.ez_left_0 = np.zeros(self.nwy + 1)
- self.ez_left_1 = np.zeros(self.nwy + 1)
-
- self.ez_right_0 = np.zeros(self.nwy + 1)
- self.ez_right_1 = np.zeros(self.nwy + 1)
-
- def _initialize_field_names(self):
- self.fn_m = ['hy_left', 'hy_right', 'hx_back', 'hx_front']
- self.fn_e = ['ez_left', 'ez_right', 'ez_back', 'ez_front']
-
- def interpolate_across_sub_cells(self, y, n0, n1):
- n = n1 - n0
- x = np.arange(0, y.shape[0], 1.0)
- x_new = np.linspace(1, y.shape[0] - 2, n * self.ratio + 1)
- f = interpolate.interp1d(x, y, kind='linear')
- y_new = f(x_new)
- """plt.plot(x, y, 'x', x_new, y_new, '.')
- plt.show()
- sys.exit()"""
- return y_new
-
- def update_electric(self):
- # f = np.sin(np.arange(0, 13))
-
- # line of Ez nodes along the left edge of the IS
- self.ez_left_0 = np.copy(self.ez_left_1)
- # interpolate nodes between two Ez nodes 1 cell beyond/infront the IS
- f = self.Ez[self.i0, self.j0 - 1:self.j1 + 2, 0]
- self.ez_left_1 = self.interpolate_across_sub_cells(f, self.j0, self.j1)
-
- self.ez_right_0 = np.copy(self.ez_right_1)
- f = self.Ez[self.i1, self.j0 - 1:self.j1 + 2, 0]
- self.ez_right_1 = self.interpolate_across_sub_cells(f, self.j0, self.j1)
-
- self.ez_front_0 = np.copy(self.ez_front_1)
- f = self.Ez[self.i0 - 1:self.i1 + 2, self.j0, 0]
- self.ez_front_1 = self.interpolate_across_sub_cells(f, self.i0, self.i1)
-
- self.ez_back_0 = np.copy(self.ez_back_1)
- f = self.Ez[self.i0 - 1:self.i1 + 2, self.j1, 0]
- self.ez_back_1 = self.interpolate_across_sub_cells(f, self.i0, self.i1)
-
- def update_hy_left_1(self):
-
- self.hy_left_0 = np.copy(self.hy_left_1)
- c1, c2 = calculate_weighting_coefficients(self.l_weight, self.ratio)
- l = self.Hy[self.i0 - 1, self.j0 - 1:self.j1 + 2, 0]
- r = self.Hy[self.i0, self.j0 - 1:self.j1 + 2, 0]
-
- # Tranverse interpolated hz
- hz_t_i = c1 * l + c2 * r
- # Tangential interpolated hz
- self.hy_left_1 = self.interpolate_across_sub_cells(hz_t_i, self.j0, self.j1)
-
- def update_hy_right_1(self):
-
- self.hy_right_0 = np.copy(self.hy_right_1)
- c1, c2 = calculate_weighting_coefficients(self.r_weight, self.ratio)
- l = self.Hy[self.i1 - 1, self.j0 - 1:self.j1 + 2, 0]
- r = self.Hy[self.i1, self.j0 - 1:self.j1 + 2, 0]
- hz_t_i = c1 * l + c2 * r
- self.hy_right_1 = self.interpolate_across_sub_cells(hz_t_i, self.j0, self.j1)
-
- def update_hx_back_1(self):
- self.hx_back_0 = np.copy(self.hx_back_1)
- c1, c2 = calculate_weighting_coefficients(self.r_weight, self.ratio)
- l = self.Hx[self.i0 - 1:self.i1 + 2, self.j1 - 1, 0]
- r = self.Hx[self.i0 - 1:self.i1 + 2, self.j1, 0]
- hz_t_i = c1 * l + c2 * r
- self.hx_back_1 = self.interpolate_across_sub_cells(hz_t_i, self.i0, self.i1)
-
- def update_hx_front_1(self):
-
- self.hx_front_0 = np.copy(self.hx_front_1)
- c1, c2 = calculate_weighting_coefficients(self.l_weight, self.ratio)
- l = self.Hx[self.i0 - 1:self.i1 + 2, self.j0 - 1, 0]
- r = self.Hx[self.i0 - 1:self.i1 + 2, self.j0, 0]
- hz_t_i = c1 * l + c2 * r
- self.hx_front_1 = self.interpolate_across_sub_cells(hz_t_i, self.i0, self.i1)
-
- def update_magnetic(self):
- self.update_hy_left_1()
- self.update_hy_right_1()
- self.update_hx_back_1()
- self.update_hx_front_1()
-
-
-class PrecursorNodes2d(PrecusorNodes2dBase):
-
- def __init__(self, fdtd_grid, sub_grid):
- super().__init__(fdtd_grid, sub_grid)
-
- def _initialize_fields(self):
- # E precursors
- self.ex_front_0 = np.zeros(self.nwx)
- self.ex_front_1 = np.zeros(self.nwx)
-
- self.ex_back_0 = np.zeros(self.nwx)
- self.ex_back_1 = np.zeros(self.nwx)
-
- self.ey_left_0 = np.zeros(self.nwy)
- self.ey_left_1 = np.zeros(self.nwy)
-
- self.ey_right_0 = np.zeros(self.nwy)
- self.ey_right_1 = np.zeros(self.nwy)
-
- # H precursors
- self.hz_front_0 = np.zeros(self.nwx)
- self.hz_front_1 = np.zeros(self.nwx)
-
- self.hz_back_0 = np.zeros(self.nwx)
- self.hz_back_1 = np.zeros(self.nwx)
-
- self.hz_left_0 = np.zeros(self.nwy)
- self.hz_left_1 = np.zeros(self.nwy)
-
- self.hz_right_0 = np.zeros(self.nwy)
- self.hz_right_1 = np.zeros(self.nwy)
-
- def _initialize_field_names(self):
- # Field names
- self.fn_m = ['hz_left', 'hz_right', 'hz_back', 'hz_front']
- self.fn_e = ['ey_left', 'ey_right', 'ex_back', 'ex_front']
-
- def update_hz_left_1(self):
- self.hz_left_0 = np.copy(self.hz_left_1)
- c1, c2 = calculate_weighting_coefficients(1, self.ratio)
- l = self.Hz[self.i0 - 1, self.j0 - 1:self.j1 + 1, 1]
- r = self.Hz[self.i0, self.j0 - 1:self.j1 + 1, 1]
-
- # Tranverse interpolated hz
- hz_t_i = c1 * l + c2 * r
- # Tangential interpolated hz
- self.hz_left_1 = self.interpolate_across_sub_cells(hz_t_i, self.j0, self.j1)
-
- def update_hz_right_1(self):
-
- self.hz_right_0 = np.copy(self.hz_right_1)
- c1, c2 = calculate_weighting_coefficients(2, self.ratio)
- l = self.Hz[self.i1 - 1, self.j0 - 1:self.j1 + 1, 1]
- r = self.Hz[self.i1, self.j0 - 1:self.j1 + 1, 1]
- hz_t_i = c1 * l + c2 * r
- self.hz_right_1 = self.interpolate_across_sub_cells(hz_t_i, self.j0, self.j1)
-
- def update_hz_back_1(self):
- self.hz_back_0 = np.copy(self.hz_back_1)
- c1, c2 = calculate_weighting_coefficients(2, self.ratio)
- l = self.Hz[self.i0 - 1:self.i1 + 1, self.j1 - 1, 1]
- r = self.Hz[self.i0 - 1:self.i1 + 1, self.j1, 1]
- hz_t_i = c1 * l + c2 * r
- self.hz_back_1 = self.interpolate_across_sub_cells(hz_t_i, self.i0, self.i1)
-
- def update_hz_front_1(self):
- self.hz_front_0 = np.copy(self.hz_front_1)
- c1, c2 = calculate_weighting_coefficients(1, self.ratio)
- l = self.Hz[self.i0 - 1:self.i1 + 1, self.j0 - 1, 1]
- r = self.Hz[self.i0 - 1:self.i1 + 1, self.j0, 1]
- hz_t_i = c1 * l + c2 * r
- self.hz_front_1 = self.interpolate_across_sub_cells(hz_t_i, self.i0, self.i1)
-
- def update_magnetic(self):
- self.update_hz_left_1()
- self.update_hz_right_1()
- self.update_hz_back_1()
- self.update_hz_front_1()
-
- def update_electric(self):
-
- """Function to calculate the precursor nodes at the next main
- grid time-step
- """
-
- # LEFT BOUNDARY
- # Save the last precursor node values - these will be used interpolate
- # field values at sub grid time step values.
- self.ey_left_0 = np.copy(self.ey_left_1)
- # line of Ex nodes along the left edge of the IS
- f = self.Ey[self.i0, self.j0 - 1:self.j1 + 1, 1]
- self.ey_left_1 = self.interpolate_across_sub_cells(f, self.j0, self.j1)
-
- # RIGHT BOUNDARY
- self.ey_right_0 = np.copy(self.ey_right_1)
- f = self.Ey[self.i1, self.j0 - 1:self.j1 + 1, 1]
- self.ey_right_1 = self.interpolate_across_sub_cells(f, self.j0, self.j1)
-
- # BACK BOUNDARY
- self.ex_back_0 = np.copy(self.ex_back_1)
- f = self.Ex[self.i0 - 1:self.i1 + 1, self.j1, 1]
- self.ex_back_1 = self.interpolate_across_sub_cells(f, self.i0, self.i1)
-
- # FRONT BOUNDARY
- self.ex_front_0 = np.copy(self.ex_front_1)
- f = self.Ex[self.i0 - 1:self.i1 + 1, self.j0, 1]
- self.ex_front_1 = self.interpolate_across_sub_cells(f, self.i0, self.i1)
-
- def interpolate_across_sub_cells(self, y, n0, n1):
- n = n1 - n0
- x = np.arange(0.5, y.shape[0], 1.0)
- x_new = np.linspace(1 + self.d, (1 + n) - self.d, n * self.ratio)
- f = interpolate.interp1d(x, y, kind='linear')
- y_new = f(x_new)
-
- return y_new
-
-
-class PrecursorNodes(PrecusorNodes2dBase):
-
- def __init__(self, fdtd_grid, sub_grid):
-
- self.nwz = sub_grid.nwz
- super().__init__(fdtd_grid, sub_grid)
- self.initialize_magnetic_slices_array()
- self.initialize_electric_slices_array()
-
- def build_coefficient_matrix(self, update_coefficients, lookup_id, inc_field, name):
- """Function builds a 2d matrix of update coefficients for each face and field.
- This allows the is nodes to be updated via slicing rather than iterating which is much faster
- """
- pass
-
- def _initialize_fields(self):
-
- # Initialise the precursor arrays
-
- # The precursors are divided up into the 6. Each represent 1
- # face of a huygens cube surface. We represent each face as a 2d array
- # containing a field in a particular direction.
-
- # _1 are the fields at the current main grid timestep
- # _0 are the fields at the previous main grid timestep
- # We store both fields so we can do different interpolations between
- # them on the fly.
-
- # Front face
- self.ex_front_1 = np.zeros((self.nwx, self.nwz + 1))
- self.ex_front_0 = np.copy(self.ex_front_1)
- self.ez_front_1 = np.zeros((self.nwx + 1, self.nwz))
- self.ez_front_0 = np.copy(self.ez_front_1)
-
- # The same as the opposite face
- self.ex_back_1 = np.copy(self.ex_front_1)
- self.ex_back_0 = np.copy(self.ex_front_1)
- self.ez_back_1 = np.copy(self.ez_front_1)
- self.ez_back_0 = np.copy(self.ez_front_1)
-
- self.ey_left_1 = np.zeros((self.nwy, self.nwz + 1))
- self.ey_left_0 = np.copy(self.ey_left_1)
- self.ez_left_1 = np.zeros((self.nwy + 1, self.nwz))
- self.ez_left_0 = np.copy(self.ez_left_1)
-
- self.ey_right_1 = np.copy(self.ey_left_1)
- self.ey_right_0 = np.copy(self.ey_left_1)
- self.ez_right_1 = np.copy(self.ez_left_1)
- self.ez_right_0 = np.copy(self.ez_left_1)
-
- self.ex_bottom_1 = np.zeros((self.nwx, self.nwy + 1))
- self.ex_bottom_0 = np.copy(self.ex_bottom_1)
- self.ey_bottom_1 = np.zeros((self.nwx + 1, self.nwy))
- self.ey_bottom_0 = np.copy(self.ey_bottom_1)
-
- self.ex_top_1 = np.copy(self.ex_bottom_1)
- self.ex_top_0 = np.copy(self.ex_bottom_1)
- self.ey_top_1 = np.copy(self.ey_bottom_1)
- self.ey_top_0 = np.copy(self.ey_bottom_1)
-
- # Initialize the H precursor fields
- self.hx_front_1 = np.copy(self.ez_front_1)
- self.hx_front_0 = np.copy(self.ez_front_1)
- self.hz_front_1 = np.copy(self.ex_front_1)
- self.hz_front_0 = np.copy(self.ex_front_1)
-
- self.hx_back_1 = np.copy(self.hx_front_1)
- self.hx_back_0 = np.copy(self.hx_front_1)
- self.hz_back_1 = np.copy(self.hz_front_1)
- self.hz_back_0 = np.copy(self.hz_front_1)
-
- self.hy_left_1 = np.copy(self.ez_left_1)
- self.hy_left_0 = np.copy(self.ez_left_1)
- self.hz_left_1 = np.copy(self.ey_left_1)
- self.hz_left_0 = np.copy(self.ey_left_1)
-
- self.hy_right_1 = np.copy(self.hy_left_1)
- self.hy_right_0 = np.copy(self.hy_left_1)
- self.hz_right_1 = np.copy(self.hz_left_1)
- self.hz_right_0 = np.copy(self.hz_left_1)
-
- self.hx_top_1 = np.copy(self.ey_top_1)
- self.hx_top_0 = np.copy(self.ey_top_1)
- self.hy_top_1 = np.copy(self.ex_top_1)
- self.hy_top_0 = np.copy(self.ex_top_1)
-
- self.hx_bottom_1 = np.copy(self.hx_top_1)
- self.hx_bottom_0 = np.copy(self.hx_top_1)
- self.hy_bottom_1 = np.copy(self.hy_top_1)
- self.hy_bottom_0 = np.copy(self.hy_top_1)
-
- def _initialize_field_names(self):
-
- self.fn_m = [
- 'hx_front', 'hz_front',
- 'hx_back', 'hz_back',
- 'hy_left', 'hz_left',
- 'hy_right', 'hz_right',
- 'hx_top', 'hy_top',
- 'hx_bottom', 'hy_bottom'
- ]
-
- self.fn_e = [
- 'ex_front', 'ez_front',
- 'ex_back', 'ez_back',
- 'ey_left', 'ez_left',
- 'ey_right', 'ez_right',
- 'ex_top', 'ey_top',
- 'ex_bottom', 'ey_bottom'
- ]
-
- def update_previous_timestep_fields(self, field_names):
- for fn in field_names:
- val = getattr(self, fn + '_1')
- val_c = np.copy(val)
- setattr(self, fn + '_0', val_c)
-
- def initialize_magnetic_slices_array(self):
-
- # Array contains the indices at which the main grid should be sliced
- # to obtain the 2 2d array of H nodes required for interpolation
- # across the IS boundary for each h field on each face of the subgrid
-
- # Extend the surface so that the outer fields can be interpolated
- # more accurately
- #i0 = self.i0 - 1
- #j0 = self.j0 - 1
- #k0 = self.k0 - 1
- #i1 = self.i1 + 1
- #j1 = self.j1 + 1
- #k1 = self.k1 + 1
-
- # not extended
- i0 = self.i0
- j0 = self.j0
- k0 = self.k0
- i1 = self.i1
- j1 = self.j1
- k1 = self.k1
-
- slices = [
- ['hy_left_1', False,
- (self.i0 - 1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
- (self.i0, slice(j0, j1 + 1, 1), slice(k0, k1, 1)), self.Hy],
- ['hy_right_1', False,
- (self.i1 - 1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
- (self.i1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)), self.Hy],
- ['hz_left_1', True,
- (self.i0 - 1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
- (self.i0, slice(j0, j1, 1), slice(k0, k1 + 1, 1)), self.Hz],
- ['hz_right_1', True,
- (self.i1 - 1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
- (self.i1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)), self.Hz],
- ['hx_front_1', False,
- (slice(i0, i1 + 1, 1), self.j0 - 1, slice(k0, k1, 1)),
- (slice(i0, i1 + 1, 1), self.j0, slice(k0, k1, 1)), self.Hx],
- ['hx_back_1', False,
- (slice(i0, i1 + 1, 1), self.j1 - 1, slice(k0, k1, 1)),
- (slice(i0, i1 + 1, 1), self.j1, slice(k0, k1, 1)), self.Hx],
- ['hz_front_1', True,
- (slice(i0, i1, 1), self.j0 - 1, slice(k0, k1 + 1, 1)),
- (slice(i0, i1, 1), self.j0, slice(k0, k1 + 1, 1)), self.Hz],
- ['hz_back_1', True,
- (slice(i0, i1, 1), self.j1 - 1, slice(k0, k1 + 1, 1)),
- (slice(i0, i1, 1), self.j1, slice(k0, k1 + 1, 1)), self.Hz],
- ['hx_bottom_1', False,
- # check these indexes
- (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k0 - 1),
- (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k0), self.Hx],
- ['hx_top_1', False,
- (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k1 - 1),
- (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k1), self.Hx],
- ['hy_bottom_1', True,
- (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k0 - 1),
- (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k0), self.Hy],
- ['hy_top_1', True,
- (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k1 - 1),
- (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k1), self.Hy]
- ]
-
- for obj in slices:
- sliced_field = obj[-1][obj[2]]
- obj[1] = self.create_interpolated_coords(obj[1], sliced_field)
-
- self.magnetic_slices = slices
-
- def initialize_electric_slices_array(self):
- # Extend the region sliced from the main grid by 1 cell.
- # this allows more accurate interpolation for the outernodes
- #i0 = self.i0 - 1
- #j0 = self.j0 - 1
- #k0 = self.k0 - 1
- #i1 = self.i1 + 1
- #j1 = self.j1 + 1
- #k1 = self.k1 + 1
-
- # not extended
- i0 = self.i0
- j0 = self.j0
- k0 = self.k0
- i1 = self.i1
- j1 = self.j1
- k1 = self.k1
-
- # 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]
- ]
-
- for obj in slices:
- sliced_field = obj[-1][obj[2]]
- obj[1] = self.create_interpolated_coords(obj[1], sliced_field)
-
- self.electric_slices = slices
-
- def create_interpolated_coords(self, mid, field):
-
- n_x = field.shape[0]
- n_y = field.shape[1]
-
- if mid:
- x = np.arange(0.5, n_x, 1.0)
- z = np.arange(0, n_y, 1.0)
-
- # Coordinates that require interpolated values
- x_sg = np.linspace(self.d, n_x - self.d, n_x * self.ratio)
- z_sg = np.linspace(0, n_y - 1, (n_y - 1) * self.ratio + 1)
-
- else:
- x = np.arange(0, n_x, 1.0)
- z = np.arange(0.5, n_y, 1.0)
-
- # Coordinates that require interpolated values
- x_sg = np.linspace(0, n_x - 1, (n_x - 1) * self.ratio + 1)
- z_sg = np.linspace(self.d, n_y - self.d, n_y * self.ratio)
-
- return (x, z, x_sg, z_sg)
-
- def update_magnetic(self):
-
- # Copy previous time step magnetic field values to the previous
- # time step variables
- self.update_previous_timestep_fields(self.fn_m)
-
- for obj in self.magnetic_slices:
-
- # Grab the main grid fields used to interpolate across the IS
- # f = self.Hi[slice]
- f_1 = obj[-1][obj[2]]
- f_2 = obj[-1][obj[3]]
- if ('left' in obj[0] or
- 'bottom' in obj[0] or
- 'front' in obj[0]):
- w = self.l_weight
- else:
- w = self.r_weight
- c1, c2 = calculate_weighting_coefficients(w, self.ratio)
- # transverse interpolated h field
- f_t = c1 * f_1 + c2 * f_2
-
- # interpolate over a fine grid
- f_i = self.interpolate_to_sub_grid(f_t, obj[1])
-
- if f_i == f_t:
- raise ValueError
-
- # discard the outer nodes only required for interpolation
- #f = f_i[self.ratio:-self.ratio, self.ratio:-self.ratio]
- f = f_i
- setattr(self, obj[0], f)
-
- def update_electric(self):
-
- self.update_previous_timestep_fields(self.fn_e)
-
- for obj in self.electric_slices:
- f_m = obj[-1][obj[2]]
- f_i = self.interpolate_to_sub_grid(f_m, obj[1])
- f = f_i
- #f = f_i[self.ratio:-self.ratio, self.ratio:-self.ratio]
- setattr(self, obj[0], f)
-
- def interpolate_to_sub_grid(self, field, coords):
- x, z, x_sg, z_sg = coords
- ex_t = np.transpose(field)
- f = interpolate.interp2d(x, z, ex_t, kind=self.interpolation)
- #f = interpolate.RectBivariateSpline(x, z, field)
- ex_inter_t = f(x_sg, z_sg)
- ex_inter = np.transpose(ex_inter_t)
- #ex_inter = ex_inter_t
-
- return ex_inter
-
-
-class PlaneError(Exception):
- pass
+import numpy as np
+from scipy import interpolate
+import sys
+
+
+def calculate_weighting_coefficients(x1, x):
+ c1 = (x - x1) / x
+ c2 = x1 / x
+ return (c1, c2)
+
+
+class PrecusorNodes2dBase(object):
+
+ def __init__(self, fdtd_grid, sub_grid):
+ self.G = fdtd_grid
+ self.ratio = sub_grid.ratio
+ self.nwx = sub_grid.nwx
+ self.nwy = sub_grid.nwy
+ self.sub_grid = sub_grid
+ self.interpolation = sub_grid.interpolation
+
+ self.Hx = fdtd_grid.Hx
+ self.Hy = fdtd_grid.Hy
+ self.Hz = fdtd_grid.Hz
+ self.Ex = fdtd_grid.Ex
+ self.Ey = fdtd_grid.Ey
+ self.Ez = fdtd_grid.Ez
+
+ # Main grid indices of subgrids
+ self.i0 = sub_grid.i0
+ self.j0 = sub_grid.j0
+ self.k0 = sub_grid.k0
+ self.i1 = sub_grid.i1
+ self.j1 = sub_grid.j1
+ self.k1 = sub_grid.k1
+
+ # dl / 2 sub cell
+ self.d = 1 / (2 * self.ratio)
+
+ self._initialize_fields()
+ self._initialize_field_names()
+
+ self.l_weight = self.ratio // 2
+ self.r_weight = self.ratio - self.l_weight
+ #self.l_weight = 1
+ #self.r_weight = 2
+
+ def _initialize_fields(self):
+ print('dont call me')
+ sys.exit()
+ pass
+
+ def _initialize_field_names(self):
+ print('dont call me')
+ sys.exit()
+ pass
+
+ def interpolate_magnetic_in_time(self, m):
+ self.weight_pre_and_current_fields(m, self.fn_m)
+
+ def interpolate_electric_in_time(self, m):
+ self.weight_pre_and_current_fields(m, self.fn_e)
+
+ def weight_pre_and_current_fields(self, m, field_names):
+ c1, c2 = calculate_weighting_coefficients(m, self.ratio)
+
+ for f in field_names:
+ try:
+ val = c1 * getattr(self, f + '_0') + c2 * getattr(self, f + '_1')
+ except ValueError:
+ print(self.ex_front_0.shape)
+ print(self.ex_front_1.shape)
+ raise Exception(f)
+ setattr(self, f, val)
+
+ def calc_exact_field(self, field_names):
+ """
+ Function to set the fields used in update calculations to the
+ values at the current main time step.
+ i.e. ey_left = copy.ey_left_1
+ """
+ for f in field_names:
+ val = np.copy(getattr(self, f + '_1'))
+ setattr(self, f, val)
+
+ def calc_exact_magnetic_in_time(self):
+ self.calc_exact_field(self.fn_m)
+
+ def calc_exact_electric_in_time(self):
+ self.calc_exact_field(self.fn_e)
+
+
+class PrecursorNodes2dTM(PrecusorNodes2dBase):
+
+ def __init__(self, fdtd_grid, sub_grid):
+ super().__init__(fdtd_grid, sub_grid)
+
+ def _initialize_fields(self):
+
+ # H precursors
+ self.hx_front_0 = np.zeros(self.nwx + 1)
+ self.hx_front_1 = np.zeros(self.nwx + 1)
+
+ self.hx_back_0 = np.zeros(self.nwx + 1)
+ self.hx_back_1 = np.zeros(self.nwx + 1)
+
+ self.hy_left_0 = np.zeros(self.nwy + 1)
+ self.hy_left_1 = np.zeros(self.nwy + 1)
+
+ self.hy_right_0 = np.zeros(self.nwy + 1)
+ self.hy_right_1 = np.zeros(self.nwy + 1)
+
+ # E precursors
+ self.ez_front_0 = np.zeros(self.nwx + 1)
+ self.ez_front_1 = np.zeros(self.nwx + 1)
+
+ self.ez_back_0 = np.zeros(self.nwx + 1)
+ self.ez_back_1 = np.zeros(self.nwx + 1)
+
+ self.ez_left_0 = np.zeros(self.nwy + 1)
+ self.ez_left_1 = np.zeros(self.nwy + 1)
+
+ self.ez_right_0 = np.zeros(self.nwy + 1)
+ self.ez_right_1 = np.zeros(self.nwy + 1)
+
+ def _initialize_field_names(self):
+ self.fn_m = ['hy_left', 'hy_right', 'hx_back', 'hx_front']
+ self.fn_e = ['ez_left', 'ez_right', 'ez_back', 'ez_front']
+
+ def interpolate_across_sub_cells(self, y, n0, n1):
+ n = n1 - n0
+ x = np.arange(0, y.shape[0], 1.0)
+ x_new = np.linspace(1, y.shape[0] - 2, n * self.ratio + 1)
+ f = interpolate.interp1d(x, y, kind='linear')
+ y_new = f(x_new)
+ """plt.plot(x, y, 'x', x_new, y_new, '.')
+ plt.show()
+ sys.exit()"""
+ return y_new
+
+ def update_electric(self):
+ # f = np.sin(np.arange(0, 13))
+
+ # line of Ez nodes along the left edge of the IS
+ self.ez_left_0 = np.copy(self.ez_left_1)
+ # interpolate nodes between two Ez nodes 1 cell beyond/infront the IS
+ f = self.Ez[self.i0, self.j0 - 1:self.j1 + 2, 0]
+ self.ez_left_1 = self.interpolate_across_sub_cells(f, self.j0, self.j1)
+
+ self.ez_right_0 = np.copy(self.ez_right_1)
+ f = self.Ez[self.i1, self.j0 - 1:self.j1 + 2, 0]
+ self.ez_right_1 = self.interpolate_across_sub_cells(f, self.j0, self.j1)
+
+ self.ez_front_0 = np.copy(self.ez_front_1)
+ f = self.Ez[self.i0 - 1:self.i1 + 2, self.j0, 0]
+ self.ez_front_1 = self.interpolate_across_sub_cells(f, self.i0, self.i1)
+
+ self.ez_back_0 = np.copy(self.ez_back_1)
+ f = self.Ez[self.i0 - 1:self.i1 + 2, self.j1, 0]
+ self.ez_back_1 = self.interpolate_across_sub_cells(f, self.i0, self.i1)
+
+ def update_hy_left_1(self):
+
+ self.hy_left_0 = np.copy(self.hy_left_1)
+ c1, c2 = calculate_weighting_coefficients(self.l_weight, self.ratio)
+ l = self.Hy[self.i0 - 1, self.j0 - 1:self.j1 + 2, 0]
+ r = self.Hy[self.i0, self.j0 - 1:self.j1 + 2, 0]
+
+ # Tranverse interpolated hz
+ hz_t_i = c1 * l + c2 * r
+ # Tangential interpolated hz
+ self.hy_left_1 = self.interpolate_across_sub_cells(hz_t_i, self.j0, self.j1)
+
+ def update_hy_right_1(self):
+
+ self.hy_right_0 = np.copy(self.hy_right_1)
+ c1, c2 = calculate_weighting_coefficients(self.r_weight, self.ratio)
+ l = self.Hy[self.i1 - 1, self.j0 - 1:self.j1 + 2, 0]
+ r = self.Hy[self.i1, self.j0 - 1:self.j1 + 2, 0]
+ hz_t_i = c1 * l + c2 * r
+ self.hy_right_1 = self.interpolate_across_sub_cells(hz_t_i, self.j0, self.j1)
+
+ def update_hx_back_1(self):
+ self.hx_back_0 = np.copy(self.hx_back_1)
+ c1, c2 = calculate_weighting_coefficients(self.r_weight, self.ratio)
+ l = self.Hx[self.i0 - 1:self.i1 + 2, self.j1 - 1, 0]
+ r = self.Hx[self.i0 - 1:self.i1 + 2, self.j1, 0]
+ hz_t_i = c1 * l + c2 * r
+ self.hx_back_1 = self.interpolate_across_sub_cells(hz_t_i, self.i0, self.i1)
+
+ def update_hx_front_1(self):
+
+ self.hx_front_0 = np.copy(self.hx_front_1)
+ c1, c2 = calculate_weighting_coefficients(self.l_weight, self.ratio)
+ l = self.Hx[self.i0 - 1:self.i1 + 2, self.j0 - 1, 0]
+ r = self.Hx[self.i0 - 1:self.i1 + 2, self.j0, 0]
+ hz_t_i = c1 * l + c2 * r
+ self.hx_front_1 = self.interpolate_across_sub_cells(hz_t_i, self.i0, self.i1)
+
+ def update_magnetic(self):
+ self.update_hy_left_1()
+ self.update_hy_right_1()
+ self.update_hx_back_1()
+ self.update_hx_front_1()
+
+
+class PrecursorNodes2d(PrecusorNodes2dBase):
+
+ def __init__(self, fdtd_grid, sub_grid):
+ super().__init__(fdtd_grid, sub_grid)
+
+ def _initialize_fields(self):
+ # E precursors
+ self.ex_front_0 = np.zeros(self.nwx)
+ self.ex_front_1 = np.zeros(self.nwx)
+
+ self.ex_back_0 = np.zeros(self.nwx)
+ self.ex_back_1 = np.zeros(self.nwx)
+
+ self.ey_left_0 = np.zeros(self.nwy)
+ self.ey_left_1 = np.zeros(self.nwy)
+
+ self.ey_right_0 = np.zeros(self.nwy)
+ self.ey_right_1 = np.zeros(self.nwy)
+
+ # H precursors
+ self.hz_front_0 = np.zeros(self.nwx)
+ self.hz_front_1 = np.zeros(self.nwx)
+
+ self.hz_back_0 = np.zeros(self.nwx)
+ self.hz_back_1 = np.zeros(self.nwx)
+
+ self.hz_left_0 = np.zeros(self.nwy)
+ self.hz_left_1 = np.zeros(self.nwy)
+
+ self.hz_right_0 = np.zeros(self.nwy)
+ self.hz_right_1 = np.zeros(self.nwy)
+
+ def _initialize_field_names(self):
+ # Field names
+ self.fn_m = ['hz_left', 'hz_right', 'hz_back', 'hz_front']
+ self.fn_e = ['ey_left', 'ey_right', 'ex_back', 'ex_front']
+
+ def update_hz_left_1(self):
+ self.hz_left_0 = np.copy(self.hz_left_1)
+ c1, c2 = calculate_weighting_coefficients(1, self.ratio)
+ l = self.Hz[self.i0 - 1, self.j0 - 1:self.j1 + 1, 1]
+ r = self.Hz[self.i0, self.j0 - 1:self.j1 + 1, 1]
+
+ # Tranverse interpolated hz
+ hz_t_i = c1 * l + c2 * r
+ # Tangential interpolated hz
+ self.hz_left_1 = self.interpolate_across_sub_cells(hz_t_i, self.j0, self.j1)
+
+ def update_hz_right_1(self):
+
+ self.hz_right_0 = np.copy(self.hz_right_1)
+ c1, c2 = calculate_weighting_coefficients(2, self.ratio)
+ l = self.Hz[self.i1 - 1, self.j0 - 1:self.j1 + 1, 1]
+ r = self.Hz[self.i1, self.j0 - 1:self.j1 + 1, 1]
+ hz_t_i = c1 * l + c2 * r
+ self.hz_right_1 = self.interpolate_across_sub_cells(hz_t_i, self.j0, self.j1)
+
+ def update_hz_back_1(self):
+ self.hz_back_0 = np.copy(self.hz_back_1)
+ c1, c2 = calculate_weighting_coefficients(2, self.ratio)
+ l = self.Hz[self.i0 - 1:self.i1 + 1, self.j1 - 1, 1]
+ r = self.Hz[self.i0 - 1:self.i1 + 1, self.j1, 1]
+ hz_t_i = c1 * l + c2 * r
+ self.hz_back_1 = self.interpolate_across_sub_cells(hz_t_i, self.i0, self.i1)
+
+ def update_hz_front_1(self):
+ self.hz_front_0 = np.copy(self.hz_front_1)
+ c1, c2 = calculate_weighting_coefficients(1, self.ratio)
+ l = self.Hz[self.i0 - 1:self.i1 + 1, self.j0 - 1, 1]
+ r = self.Hz[self.i0 - 1:self.i1 + 1, self.j0, 1]
+ hz_t_i = c1 * l + c2 * r
+ self.hz_front_1 = self.interpolate_across_sub_cells(hz_t_i, self.i0, self.i1)
+
+ def update_magnetic(self):
+ self.update_hz_left_1()
+ self.update_hz_right_1()
+ self.update_hz_back_1()
+ self.update_hz_front_1()
+
+ def update_electric(self):
+
+ """Function to calculate the precursor nodes at the next main
+ grid time-step
+ """
+
+ # LEFT BOUNDARY
+ # Save the last precursor node values - these will be used interpolate
+ # field values at sub grid time step values.
+ self.ey_left_0 = np.copy(self.ey_left_1)
+ # line of Ex nodes along the left edge of the IS
+ f = self.Ey[self.i0, self.j0 - 1:self.j1 + 1, 1]
+ self.ey_left_1 = self.interpolate_across_sub_cells(f, self.j0, self.j1)
+
+ # RIGHT BOUNDARY
+ self.ey_right_0 = np.copy(self.ey_right_1)
+ f = self.Ey[self.i1, self.j0 - 1:self.j1 + 1, 1]
+ self.ey_right_1 = self.interpolate_across_sub_cells(f, self.j0, self.j1)
+
+ # BACK BOUNDARY
+ self.ex_back_0 = np.copy(self.ex_back_1)
+ f = self.Ex[self.i0 - 1:self.i1 + 1, self.j1, 1]
+ self.ex_back_1 = self.interpolate_across_sub_cells(f, self.i0, self.i1)
+
+ # FRONT BOUNDARY
+ self.ex_front_0 = np.copy(self.ex_front_1)
+ f = self.Ex[self.i0 - 1:self.i1 + 1, self.j0, 1]
+ self.ex_front_1 = self.interpolate_across_sub_cells(f, self.i0, self.i1)
+
+ def interpolate_across_sub_cells(self, y, n0, n1):
+ n = n1 - n0
+ x = np.arange(0.5, y.shape[0], 1.0)
+ x_new = np.linspace(1 + self.d, (1 + n) - self.d, n * self.ratio)
+ f = interpolate.interp1d(x, y, kind='linear')
+ y_new = f(x_new)
+
+ return y_new
+
+
+class PrecursorNodes(PrecusorNodes2dBase):
+
+ def __init__(self, fdtd_grid, sub_grid):
+
+ self.nwz = sub_grid.nwz
+ super().__init__(fdtd_grid, sub_grid)
+ self.initialize_magnetic_slices_array()
+ self.initialize_electric_slices_array()
+
+ def build_coefficient_matrix(self, update_coefficients, lookup_id, inc_field, name):
+ """Function builds a 2d matrix of update coefficients for each face and field.
+ This allows the is nodes to be updated via slicing rather than iterating which is much faster
+ """
+ pass
+
+ def _initialize_fields(self):
+
+ # Initialise the precursor arrays
+
+ # The precursors are divided up into the 6. Each represent 1
+ # face of a huygens cube surface. We represent each face as a 2d array
+ # containing a field in a particular direction.
+
+ # _1 are the fields at the current main grid timestep
+ # _0 are the fields at the previous main grid timestep
+ # We store both fields so we can do different interpolations between
+ # them on the fly.
+
+ # Front face
+ self.ex_front_1 = np.zeros((self.nwx, self.nwz + 1))
+ self.ex_front_0 = np.copy(self.ex_front_1)
+ self.ez_front_1 = np.zeros((self.nwx + 1, self.nwz))
+ self.ez_front_0 = np.copy(self.ez_front_1)
+
+ # The same as the opposite face
+ self.ex_back_1 = np.copy(self.ex_front_1)
+ self.ex_back_0 = np.copy(self.ex_front_1)
+ self.ez_back_1 = np.copy(self.ez_front_1)
+ self.ez_back_0 = np.copy(self.ez_front_1)
+
+ self.ey_left_1 = np.zeros((self.nwy, self.nwz + 1))
+ self.ey_left_0 = np.copy(self.ey_left_1)
+ self.ez_left_1 = np.zeros((self.nwy + 1, self.nwz))
+ self.ez_left_0 = np.copy(self.ez_left_1)
+
+ self.ey_right_1 = np.copy(self.ey_left_1)
+ self.ey_right_0 = np.copy(self.ey_left_1)
+ self.ez_right_1 = np.copy(self.ez_left_1)
+ self.ez_right_0 = np.copy(self.ez_left_1)
+
+ self.ex_bottom_1 = np.zeros((self.nwx, self.nwy + 1))
+ self.ex_bottom_0 = np.copy(self.ex_bottom_1)
+ self.ey_bottom_1 = np.zeros((self.nwx + 1, self.nwy))
+ self.ey_bottom_0 = np.copy(self.ey_bottom_1)
+
+ self.ex_top_1 = np.copy(self.ex_bottom_1)
+ self.ex_top_0 = np.copy(self.ex_bottom_1)
+ self.ey_top_1 = np.copy(self.ey_bottom_1)
+ self.ey_top_0 = np.copy(self.ey_bottom_1)
+
+ # Initialize the H precursor fields
+ self.hx_front_1 = np.copy(self.ez_front_1)
+ self.hx_front_0 = np.copy(self.ez_front_1)
+ self.hz_front_1 = np.copy(self.ex_front_1)
+ self.hz_front_0 = np.copy(self.ex_front_1)
+
+ self.hx_back_1 = np.copy(self.hx_front_1)
+ self.hx_back_0 = np.copy(self.hx_front_1)
+ self.hz_back_1 = np.copy(self.hz_front_1)
+ self.hz_back_0 = np.copy(self.hz_front_1)
+
+ self.hy_left_1 = np.copy(self.ez_left_1)
+ self.hy_left_0 = np.copy(self.ez_left_1)
+ self.hz_left_1 = np.copy(self.ey_left_1)
+ self.hz_left_0 = np.copy(self.ey_left_1)
+
+ self.hy_right_1 = np.copy(self.hy_left_1)
+ self.hy_right_0 = np.copy(self.hy_left_1)
+ self.hz_right_1 = np.copy(self.hz_left_1)
+ self.hz_right_0 = np.copy(self.hz_left_1)
+
+ self.hx_top_1 = np.copy(self.ey_top_1)
+ self.hx_top_0 = np.copy(self.ey_top_1)
+ self.hy_top_1 = np.copy(self.ex_top_1)
+ self.hy_top_0 = np.copy(self.ex_top_1)
+
+ self.hx_bottom_1 = np.copy(self.hx_top_1)
+ self.hx_bottom_0 = np.copy(self.hx_top_1)
+ self.hy_bottom_1 = np.copy(self.hy_top_1)
+ self.hy_bottom_0 = np.copy(self.hy_top_1)
+
+ def _initialize_field_names(self):
+
+ self.fn_m = [
+ 'hx_front', 'hz_front',
+ 'hx_back', 'hz_back',
+ 'hy_left', 'hz_left',
+ 'hy_right', 'hz_right',
+ 'hx_top', 'hy_top',
+ 'hx_bottom', 'hy_bottom'
+ ]
+
+ self.fn_e = [
+ 'ex_front', 'ez_front',
+ 'ex_back', 'ez_back',
+ 'ey_left', 'ez_left',
+ 'ey_right', 'ez_right',
+ 'ex_top', 'ey_top',
+ 'ex_bottom', 'ey_bottom'
+ ]
+
+ def update_previous_timestep_fields(self, field_names):
+ for fn in field_names:
+ val = getattr(self, fn + '_1')
+ val_c = np.copy(val)
+ setattr(self, fn + '_0', val_c)
+
+ def initialize_magnetic_slices_array(self):
+
+ # Array contains the indices at which the main grid should be sliced
+ # to obtain the 2 2d array of H nodes required for interpolation
+ # across the IS boundary for each h field on each face of the subgrid
+
+ # Extend the surface so that the outer fields can be interpolated
+ # more accurately
+ #i0 = self.i0 - 1
+ #j0 = self.j0 - 1
+ #k0 = self.k0 - 1
+ #i1 = self.i1 + 1
+ #j1 = self.j1 + 1
+ #k1 = self.k1 + 1
+
+ # not extended
+ i0 = self.i0
+ j0 = self.j0
+ k0 = self.k0
+ i1 = self.i1
+ j1 = self.j1
+ k1 = self.k1
+
+ slices = [
+ ['hy_left_1', False,
+ (self.i0 - 1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
+ (self.i0, slice(j0, j1 + 1, 1), slice(k0, k1, 1)), self.Hy],
+ ['hy_right_1', False,
+ (self.i1 - 1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
+ (self.i1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)), self.Hy],
+ ['hz_left_1', True,
+ (self.i0 - 1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
+ (self.i0, slice(j0, j1, 1), slice(k0, k1 + 1, 1)), self.Hz],
+ ['hz_right_1', True,
+ (self.i1 - 1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
+ (self.i1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)), self.Hz],
+ ['hx_front_1', False,
+ (slice(i0, i1 + 1, 1), self.j0 - 1, slice(k0, k1, 1)),
+ (slice(i0, i1 + 1, 1), self.j0, slice(k0, k1, 1)), self.Hx],
+ ['hx_back_1', False,
+ (slice(i0, i1 + 1, 1), self.j1 - 1, slice(k0, k1, 1)),
+ (slice(i0, i1 + 1, 1), self.j1, slice(k0, k1, 1)), self.Hx],
+ ['hz_front_1', True,
+ (slice(i0, i1, 1), self.j0 - 1, slice(k0, k1 + 1, 1)),
+ (slice(i0, i1, 1), self.j0, slice(k0, k1 + 1, 1)), self.Hz],
+ ['hz_back_1', True,
+ (slice(i0, i1, 1), self.j1 - 1, slice(k0, k1 + 1, 1)),
+ (slice(i0, i1, 1), self.j1, slice(k0, k1 + 1, 1)), self.Hz],
+ ['hx_bottom_1', False,
+ # check these indexes
+ (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k0 - 1),
+ (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k0), self.Hx],
+ ['hx_top_1', False,
+ (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k1 - 1),
+ (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k1), self.Hx],
+ ['hy_bottom_1', True,
+ (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k0 - 1),
+ (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k0), self.Hy],
+ ['hy_top_1', True,
+ (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k1 - 1),
+ (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k1), self.Hy]
+ ]
+
+ for obj in slices:
+ sliced_field = obj[-1][obj[2]]
+ obj[1] = self.create_interpolated_coords(obj[1], sliced_field)
+
+ self.magnetic_slices = slices
+
+ def initialize_electric_slices_array(self):
+ # Extend the region sliced from the main grid by 1 cell.
+ # this allows more accurate interpolation for the outernodes
+ #i0 = self.i0 - 1
+ #j0 = self.j0 - 1
+ #k0 = self.k0 - 1
+ #i1 = self.i1 + 1
+ #j1 = self.j1 + 1
+ #k1 = self.k1 + 1
+
+ # not extended
+ i0 = self.i0
+ j0 = self.j0
+ k0 = self.k0
+ i1 = self.i1
+ j1 = self.j1
+ k1 = self.k1
+
+ # 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]
+ ]
+
+ for obj in slices:
+ sliced_field = obj[-1][obj[2]]
+ obj[1] = self.create_interpolated_coords(obj[1], sliced_field)
+
+ self.electric_slices = slices
+
+ def create_interpolated_coords(self, mid, field):
+
+ n_x = field.shape[0]
+ n_y = field.shape[1]
+
+ if mid:
+ x = np.arange(0.5, n_x, 1.0)
+ z = np.arange(0, n_y, 1.0)
+
+ # Coordinates that require interpolated values
+ x_sg = np.linspace(self.d, n_x - self.d, n_x * self.ratio)
+ z_sg = np.linspace(0, n_y - 1, (n_y - 1) * self.ratio + 1)
+
+ else:
+ x = np.arange(0, n_x, 1.0)
+ z = np.arange(0.5, n_y, 1.0)
+
+ # Coordinates that require interpolated values
+ x_sg = np.linspace(0, n_x - 1, (n_x - 1) * self.ratio + 1)
+ z_sg = np.linspace(self.d, n_y - self.d, n_y * self.ratio)
+
+ return (x, z, x_sg, z_sg)
+
+ def update_magnetic(self):
+
+ # Copy previous time step magnetic field values to the previous
+ # time step variables
+ self.update_previous_timestep_fields(self.fn_m)
+
+ for obj in self.magnetic_slices:
+
+ # Grab the main grid fields used to interpolate across the IS
+ # f = self.Hi[slice]
+ f_1 = obj[-1][obj[2]]
+ f_2 = obj[-1][obj[3]]
+ if ('left' in obj[0] or
+ 'bottom' in obj[0] or
+ 'front' in obj[0]):
+ w = self.l_weight
+ else:
+ w = self.r_weight
+ c1, c2 = calculate_weighting_coefficients(w, self.ratio)
+ # transverse interpolated h field
+ f_t = c1 * f_1 + c2 * f_2
+
+ # interpolate over a fine grid
+ f_i = self.interpolate_to_sub_grid(f_t, obj[1])
+
+ if f_i == f_t:
+ raise ValueError
+
+ # discard the outer nodes only required for interpolation
+ #f = f_i[self.ratio:-self.ratio, self.ratio:-self.ratio]
+ f = f_i
+ setattr(self, obj[0], f)
+
+ def update_electric(self):
+
+ self.update_previous_timestep_fields(self.fn_e)
+
+ for obj in self.electric_slices:
+ f_m = obj[-1][obj[2]]
+ f_i = self.interpolate_to_sub_grid(f_m, obj[1])
+ f = f_i
+ #f = f_i[self.ratio:-self.ratio, self.ratio:-self.ratio]
+ setattr(self, obj[0], f)
+
+ def interpolate_to_sub_grid(self, field, coords):
+ x, z, x_sg, z_sg = coords
+ ex_t = np.transpose(field)
+ f = interpolate.interp2d(x, z, ex_t, kind=self.interpolation)
+ #f = interpolate.RectBivariateSpline(x, z, field)
+ ex_inter_t = f(x_sg, z_sg)
+ ex_inter = np.transpose(ex_inter_t)
+ #ex_inter = ex_inter_t
+
+ return ex_inter
+
+
+class PlaneError(Exception):
+ pass
diff --git a/gprMax/subgrids/precursor_nodes_filtered.py b/gprMax/subgrids/precursor_nodes_filtered.py
index a1046c76..145330cd 100644
--- a/gprMax/subgrids/precursor_nodes_filtered.py
+++ b/gprMax/subgrids/precursor_nodes_filtered.py
@@ -1,738 +1,738 @@
-import numpy as np
-from scipy import interpolate
-import sys
-
-
-def calculate_weighting_coefficients(x1, x):
- c1 = (x - x1) / x
- c2 = x1 / x
- return (c1, c2)
-
-
-class PrecusorNodes2dBase(object):
-
- def __init__(self, fdtd_grid, sub_grid):
- self.G = fdtd_grid
- self.ratio = sub_grid.ratio
- self.nwx = sub_grid.nwx
- self.nwy = sub_grid.nwy
- self.sub_grid = sub_grid
- self.interpolation = sub_grid.interpolation
-
- self.Hx = fdtd_grid.Hx
- self.Hy = fdtd_grid.Hy
- self.Hz = fdtd_grid.Hz
- self.Ex = fdtd_grid.Ex
- self.Ey = fdtd_grid.Ey
- self.Ez = fdtd_grid.Ez
-
- # Main grid indices of subgrids
- self.i0 = sub_grid.i0
- self.j0 = sub_grid.j0
- self.k0 = sub_grid.k0
- self.i1 = sub_grid.i1
- self.j1 = sub_grid.j1
- self.k1 = sub_grid.k1
-
- # dl / 2 sub cell
- self.d = 1 / (2 * self.ratio)
-
- self._initialize_fields()
- self._initialize_field_names()
-
- self.l_weight = self.ratio // 2
- self.r_weight = self.ratio - self.l_weight
- #self.l_weight = 1
- #self.r_weight = 2
-
- def _initialize_fields(self):
- print('dont call me')
- sys.exit()
- pass
-
- def _initialize_field_names(self):
- print('dont call me')
- sys.exit()
- pass
-
- def interpolate_magnetic_in_time(self, m):
- self.weight_pre_and_current_fields(m, self.fn_m)
-
- def interpolate_electric_in_time(self, m):
- self.weight_pre_and_current_fields(m, self.fn_e)
-
- def weight_pre_and_current_fields(self, m, field_names):
- c1, c2 = calculate_weighting_coefficients(m, self.ratio)
-
- for f in field_names:
- try:
- val = c1 * getattr(self, f + '_0') + c2 * getattr(self, f + '_1')
- except ValueError:
- print(self.ex_front_0.shape)
- print(self.ex_front_1.shape)
- raise Exception(f)
- setattr(self, f, val)
-
- def calc_exact_field(self, field_names):
- """
- Function to set the fields used in update calculations to the
- values at the current main time step.
- i.e. ey_left = copy.ey_left_1
- """
- for f in field_names:
- val = np.copy(getattr(self, f + '_1'))
- setattr(self, f, val)
-
- def calc_exact_magnetic_in_time(self):
- self.calc_exact_field(self.fn_m)
-
- def calc_exact_electric_in_time(self):
- self.calc_exact_field(self.fn_e)
-
-
-class PrecursorNodes2dTM(PrecusorNodes2dBase):
-
- def __init__(self, fdtd_grid, sub_grid):
- super().__init__(fdtd_grid, sub_grid)
-
- def _initialize_fields(self):
-
- # H precursors
- self.hx_front_0 = np.zeros(self.nwx + 1)
- self.hx_front_1 = np.zeros(self.nwx + 1)
-
- self.hx_back_0 = np.zeros(self.nwx + 1)
- self.hx_back_1 = np.zeros(self.nwx + 1)
-
- self.hy_left_0 = np.zeros(self.nwy + 1)
- self.hy_left_1 = np.zeros(self.nwy + 1)
-
- self.hy_right_0 = np.zeros(self.nwy + 1)
- self.hy_right_1 = np.zeros(self.nwy + 1)
-
- # E precursors
- self.ez_front_0 = np.zeros(self.nwx + 1)
- self.ez_front_1 = np.zeros(self.nwx + 1)
-
- self.ez_back_0 = np.zeros(self.nwx + 1)
- self.ez_back_1 = np.zeros(self.nwx + 1)
-
- self.ez_left_0 = np.zeros(self.nwy + 1)
- self.ez_left_1 = np.zeros(self.nwy + 1)
-
- self.ez_right_0 = np.zeros(self.nwy + 1)
- self.ez_right_1 = np.zeros(self.nwy + 1)
-
- def _initialize_field_names(self):
- self.fn_m = ['hy_left', 'hy_right', 'hx_back', 'hx_front']
- self.fn_e = ['ez_left', 'ez_right', 'ez_back', 'ez_front']
-
- def interpolate_across_sub_cells(self, y, n0, n1):
- n = n1 - n0
- x = np.arange(0, y.shape[0], 1.0)
- x_new = np.linspace(1, y.shape[0] - 2, n * self.ratio + 1)
- f = interpolate.interp1d(x, y, kind='linear')
- y_new = f(x_new)
- """plt.plot(x, y, 'x', x_new, y_new, '.')
- plt.show()
- sys.exit()"""
- return y_new
-
- def update_electric(self):
- # f = np.sin(np.arange(0, 13))
-
- # line of Ez nodes along the left edge of the IS
- self.ez_left_0 = np.copy(self.ez_left_1)
- # interpolate nodes between two Ez nodes 1 cell beyond/infront the IS
- f = self.Ez[self.i0, self.j0 - 1:self.j1 + 2, 0]
- self.ez_left_1 = self.interpolate_across_sub_cells(f, self.j0, self.j1)
-
- self.ez_right_0 = np.copy(self.ez_right_1)
- f = self.Ez[self.i1, self.j0 - 1:self.j1 + 2, 0]
- self.ez_right_1 = self.interpolate_across_sub_cells(f, self.j0, self.j1)
-
- self.ez_front_0 = np.copy(self.ez_front_1)
- f = self.Ez[self.i0 - 1:self.i1 + 2, self.j0, 0]
- self.ez_front_1 = self.interpolate_across_sub_cells(f, self.i0, self.i1)
-
- self.ez_back_0 = np.copy(self.ez_back_1)
- f = self.Ez[self.i0 - 1:self.i1 + 2, self.j1, 0]
- self.ez_back_1 = self.interpolate_across_sub_cells(f, self.i0, self.i1)
-
- def update_hy_left_1(self):
-
- self.hy_left_0 = np.copy(self.hy_left_1)
- c1, c2 = calculate_weighting_coefficients(self.l_weight, self.ratio)
- l = self.Hy[self.i0 - 1, self.j0 - 1:self.j1 + 2, 0]
- r = self.Hy[self.i0, self.j0 - 1:self.j1 + 2, 0]
-
- # Tranverse interpolated hz
- hz_t_i = c1 * l + c2 * r
- # Tangential interpolated hz
- self.hy_left_1 = self.interpolate_across_sub_cells(hz_t_i, self.j0, self.j1)
-
- def update_hy_right_1(self):
-
- self.hy_right_0 = np.copy(self.hy_right_1)
- c1, c2 = calculate_weighting_coefficients(self.r_weight, self.ratio)
- l = self.Hy[self.i1 - 1, self.j0 - 1:self.j1 + 2, 0]
- r = self.Hy[self.i1, self.j0 - 1:self.j1 + 2, 0]
- hz_t_i = c1 * l + c2 * r
- self.hy_right_1 = self.interpolate_across_sub_cells(hz_t_i, self.j0, self.j1)
-
- def update_hx_back_1(self):
- self.hx_back_0 = np.copy(self.hx_back_1)
- c1, c2 = calculate_weighting_coefficients(self.r_weight, self.ratio)
- l = self.Hx[self.i0 - 1:self.i1 + 2, self.j1 - 1, 0]
- r = self.Hx[self.i0 - 1:self.i1 + 2, self.j1, 0]
- hz_t_i = c1 * l + c2 * r
- self.hx_back_1 = self.interpolate_across_sub_cells(hz_t_i, self.i0, self.i1)
-
- def update_hx_front_1(self):
-
- self.hx_front_0 = np.copy(self.hx_front_1)
- c1, c2 = calculate_weighting_coefficients(self.l_weight, self.ratio)
- l = self.Hx[self.i0 - 1:self.i1 + 2, self.j0 - 1, 0]
- r = self.Hx[self.i0 - 1:self.i1 + 2, self.j0, 0]
- hz_t_i = c1 * l + c2 * r
- self.hx_front_1 = self.interpolate_across_sub_cells(hz_t_i, self.i0, self.i1)
-
- def update_magnetic(self):
- self.update_hy_left_1()
- self.update_hy_right_1()
- self.update_hx_back_1()
- self.update_hx_front_1()
-
-
-class PrecursorNodes2d(PrecusorNodes2dBase):
-
- def __init__(self, fdtd_grid, sub_grid):
- super().__init__(fdtd_grid, sub_grid)
-
- def _initialize_fields(self):
- # E precursors
- self.ex_front_0 = np.zeros(self.nwx)
- self.ex_front_1 = np.zeros(self.nwx)
-
- self.ex_back_0 = np.zeros(self.nwx)
- self.ex_back_1 = np.zeros(self.nwx)
-
- self.ey_left_0 = np.zeros(self.nwy)
- self.ey_left_1 = np.zeros(self.nwy)
-
- self.ey_right_0 = np.zeros(self.nwy)
- self.ey_right_1 = np.zeros(self.nwy)
-
- # H precursors
- self.hz_front_0 = np.zeros(self.nwx)
- self.hz_front_1 = np.zeros(self.nwx)
-
- self.hz_back_0 = np.zeros(self.nwx)
- self.hz_back_1 = np.zeros(self.nwx)
-
- self.hz_left_0 = np.zeros(self.nwy)
- self.hz_left_1 = np.zeros(self.nwy)
-
- self.hz_right_0 = np.zeros(self.nwy)
- self.hz_right_1 = np.zeros(self.nwy)
-
- def _initialize_field_names(self):
- # Field names
- self.fn_m = ['hz_left', 'hz_right', 'hz_back', 'hz_front']
- self.fn_e = ['ey_left', 'ey_right', 'ex_back', 'ex_front']
-
- def update_hz_left_1(self):
- self.hz_left_0 = np.copy(self.hz_left_1)
- c1, c2 = calculate_weighting_coefficients(1, self.ratio)
- l = self.Hz[self.i0 - 1, self.j0 - 1:self.j1 + 1, 1]
- r = self.Hz[self.i0, self.j0 - 1:self.j1 + 1, 1]
-
- # Tranverse interpolated hz
- hz_t_i = c1 * l + c2 * r
- # Tangential interpolated hz
- self.hz_left_1 = self.interpolate_across_sub_cells(hz_t_i, self.j0, self.j1)
-
- def update_hz_right_1(self):
-
- self.hz_right_0 = np.copy(self.hz_right_1)
- c1, c2 = calculate_weighting_coefficients(2, self.ratio)
- l = self.Hz[self.i1 - 1, self.j0 - 1:self.j1 + 1, 1]
- r = self.Hz[self.i1, self.j0 - 1:self.j1 + 1, 1]
- hz_t_i = c1 * l + c2 * r
- self.hz_right_1 = self.interpolate_across_sub_cells(hz_t_i, self.j0, self.j1)
-
- def update_hz_back_1(self):
- self.hz_back_0 = np.copy(self.hz_back_1)
- c1, c2 = calculate_weighting_coefficients(2, self.ratio)
- l = self.Hz[self.i0 - 1:self.i1 + 1, self.j1 - 1, 1]
- r = self.Hz[self.i0 - 1:self.i1 + 1, self.j1, 1]
- hz_t_i = c1 * l + c2 * r
- self.hz_back_1 = self.interpolate_across_sub_cells(hz_t_i, self.i0, self.i1)
-
- def update_hz_front_1(self):
- self.hz_front_0 = np.copy(self.hz_front_1)
- c1, c2 = calculate_weighting_coefficients(1, self.ratio)
- l = self.Hz[self.i0 - 1:self.i1 + 1, self.j0 - 1, 1]
- r = self.Hz[self.i0 - 1:self.i1 + 1, self.j0, 1]
- hz_t_i = c1 * l + c2 * r
- self.hz_front_1 = self.interpolate_across_sub_cells(hz_t_i, self.i0, self.i1)
-
- def update_magnetic(self):
- self.update_hz_left_1()
- self.update_hz_right_1()
- self.update_hz_back_1()
- self.update_hz_front_1()
-
- def update_electric(self):
-
- """Function to calculate the precursor nodes at the next main
- grid time-step
- """
-
- # LEFT BOUNDARY
- # Save the last precursor node values - these will be used interpolate
- # field values at sub grid time step values.
- self.ey_left_0 = np.copy(self.ey_left_1)
- # line of Ex nodes along the left edge of the IS
- f = self.Ey[self.i0, self.j0 - 1:self.j1 + 1, 1]
- self.ey_left_1 = self.interpolate_across_sub_cells(f, self.j0, self.j1)
-
- # RIGHT BOUNDARY
- self.ey_right_0 = np.copy(self.ey_right_1)
- f = self.Ey[self.i1, self.j0 - 1:self.j1 + 1, 1]
- self.ey_right_1 = self.interpolate_across_sub_cells(f, self.j0, self.j1)
-
- # BACK BOUNDARY
- self.ex_back_0 = np.copy(self.ex_back_1)
- f = self.Ex[self.i0 - 1:self.i1 + 1, self.j1, 1]
- self.ex_back_1 = self.interpolate_across_sub_cells(f, self.i0, self.i1)
-
- # FRONT BOUNDARY
- self.ex_front_0 = np.copy(self.ex_front_1)
- f = self.Ex[self.i0 - 1:self.i1 + 1, self.j0, 1]
- self.ex_front_1 = self.interpolate_across_sub_cells(f, self.i0, self.i1)
-
- def interpolate_across_sub_cells(self, y, n0, n1):
- n = n1 - n0
- x = np.arange(0.5, y.shape[0], 1.0)
- x_new = np.linspace(1 + self.d, (1 + n) - self.d, n * self.ratio)
- f = interpolate.interp1d(x, y, kind='linear')
- y_new = f(x_new)
-
- return y_new
-
-
-class PrecursorNodes(PrecusorNodes2dBase):
-
- def __init__(self, fdtd_grid, sub_grid):
-
- self.nwz = sub_grid.nwz
- super().__init__(fdtd_grid, sub_grid)
- self.initialize_magnetic_slices_array()
- self.initialize_electric_slices_array()
-
- def build_coefficient_matrix(self, update_coefficients, lookup_id, inc_field, name):
- """Function builds a 2d matrix of update coefficients for each face and field.
- This allows the is nodes to be updated via slicing rather than iterating which is much faster
- """
- pass
-
- def _initialize_fields(self):
-
- # Initialise the precursor arrays
-
- # The precursors are divided up into the 6. Each represent 1
- # face of a huygens cube surface. We represent each face as a 2d array
- # containing a field in a particular direction.
-
- # _1 are the fields at the current main grid timestep
- # _0 are the fields at the previous main grid timestep
- # We store both fields so we can do different interpolations between
- # them on the fly.
-
- # Front face
- self.ex_front_1 = np.zeros((self.nwx, self.nwz + 1))
- self.ex_front_0 = np.copy(self.ex_front_1)
- self.ez_front_1 = np.zeros((self.nwx + 1, self.nwz))
- self.ez_front_0 = np.copy(self.ez_front_1)
-
- # The same as the opposite face
- self.ex_back_1 = np.copy(self.ex_front_1)
- self.ex_back_0 = np.copy(self.ex_front_1)
- self.ez_back_1 = np.copy(self.ez_front_1)
- self.ez_back_0 = np.copy(self.ez_front_1)
-
- self.ey_left_1 = np.zeros((self.nwy, self.nwz + 1))
- self.ey_left_0 = np.copy(self.ey_left_1)
- self.ez_left_1 = np.zeros((self.nwy + 1, self.nwz))
- self.ez_left_0 = np.copy(self.ez_left_1)
-
- self.ey_right_1 = np.copy(self.ey_left_1)
- self.ey_right_0 = np.copy(self.ey_left_1)
- self.ez_right_1 = np.copy(self.ez_left_1)
- self.ez_right_0 = np.copy(self.ez_left_1)
-
- self.ex_bottom_1 = np.zeros((self.nwx, self.nwy + 1))
- self.ex_bottom_0 = np.copy(self.ex_bottom_1)
- self.ey_bottom_1 = np.zeros((self.nwx + 1, self.nwy))
- self.ey_bottom_0 = np.copy(self.ey_bottom_1)
-
- self.ex_top_1 = np.copy(self.ex_bottom_1)
- self.ex_top_0 = np.copy(self.ex_bottom_1)
- self.ey_top_1 = np.copy(self.ey_bottom_1)
- self.ey_top_0 = np.copy(self.ey_bottom_1)
-
- # Initialize the H precursor fields
- self.hx_front_1 = np.copy(self.ez_front_1)
- self.hx_front_0 = np.copy(self.ez_front_1)
- self.hz_front_1 = np.copy(self.ex_front_1)
- self.hz_front_0 = np.copy(self.ex_front_1)
-
- self.hx_back_1 = np.copy(self.hx_front_1)
- self.hx_back_0 = np.copy(self.hx_front_1)
- self.hz_back_1 = np.copy(self.hz_front_1)
- self.hz_back_0 = np.copy(self.hz_front_1)
-
- self.hy_left_1 = np.copy(self.ez_left_1)
- self.hy_left_0 = np.copy(self.ez_left_1)
- self.hz_left_1 = np.copy(self.ey_left_1)
- self.hz_left_0 = np.copy(self.ey_left_1)
-
- self.hy_right_1 = np.copy(self.hy_left_1)
- self.hy_right_0 = np.copy(self.hy_left_1)
- self.hz_right_1 = np.copy(self.hz_left_1)
- self.hz_right_0 = np.copy(self.hz_left_1)
-
- self.hx_top_1 = np.copy(self.ey_top_1)
- self.hx_top_0 = np.copy(self.ey_top_1)
- self.hy_top_1 = np.copy(self.ex_top_1)
- self.hy_top_0 = np.copy(self.ex_top_1)
-
- self.hx_bottom_1 = np.copy(self.hx_top_1)
- self.hx_bottom_0 = np.copy(self.hx_top_1)
- self.hy_bottom_1 = np.copy(self.hy_top_1)
- self.hy_bottom_0 = np.copy(self.hy_top_1)
-
- def _initialize_field_names(self):
-
- self.fn_m = [
- 'hx_front', 'hz_front',
- 'hx_back', 'hz_back',
- 'hy_left', 'hz_left',
- 'hy_right', 'hz_right',
- 'hx_top', 'hy_top',
- 'hx_bottom', 'hy_bottom'
- ]
-
- self.fn_e = [
- 'ex_front', 'ez_front',
- 'ex_back', 'ez_back',
- 'ey_left', 'ez_left',
- 'ey_right', 'ez_right',
- 'ex_top', 'ey_top',
- 'ex_bottom', 'ey_bottom'
- ]
-
- def update_previous_timestep_fields(self, field_names):
- for fn in field_names:
- val = getattr(self, fn + '_1')
- val_c = np.copy(val)
- setattr(self, fn + '_0', val_c)
-
- def initialize_magnetic_slices_array(self):
-
- # Array contains the indices at which the main grid should be sliced
- # to obtain the 2 2d array of H nodes required for interpolation
- # across the IS boundary for each h field on each face of the subgrid
-
- # Extend the surface so that the outer fields can be interpolated
- # more accurately
- #i0 = self.i0 - 1
- #j0 = self.j0 - 1
- #k0 = self.k0 - 1
- #i1 = self.i1 + 1
- #j1 = self.j1 + 1
- #k1 = self.k1 + 1
-
- # not extended
- i0 = self.i0
- j0 = self.j0
- k0 = self.k0
- i1 = self.i1
- j1 = self.j1
- k1 = self.k1
-
- slices = [
- ['hy_left_1', False,
- (self.i0 - 2, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
- (self.i0 - 1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
- (self.i0, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
- (self.i0 + 1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)), self.Hy],
- ['hy_right_1', False,
- (self.i1 - 2, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
- (self.i1 - 1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
- (self.i1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
- (self.i1 + 1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)), self.Hy],
- ['hz_left_1', True,
- (self.i0 - 2, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
- (self.i0 - 1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
- (self.i0, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
- (self.i0 + 1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)), self.Hz],
- ['hz_right_1', True,
- (self.i1 - 2, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
- (self.i1 - 1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
- (self.i1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
- (self.i1 + 1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)), self.Hz],
- ['hx_front_1', False,
- (slice(i0, i1 + 1, 1), self.j0 - 2, slice(k0, k1, 1)),
- (slice(i0, i1 + 1, 1), self.j0 - 1, slice(k0, k1, 1)),
- (slice(i0, i1 + 1, 1), self.j0, slice(k0, k1, 1)),
- (slice(i0, i1 + 1, 1), self.j0 + 1, slice(k0, k1, 1)), self.Hx],
- ['hx_back_1', False,
- (slice(i0, i1 + 1, 1), self.j1 - 2, slice(k0, k1, 1)),
- (slice(i0, i1 + 1, 1), self.j1 - 1, slice(k0, k1, 1)),
- (slice(i0, i1 + 1, 1), self.j1, slice(k0, k1, 1)),
- (slice(i0, i1 + 1, 1), self.j1 + 1, slice(k0, k1, 1)), self.Hx],
- ['hz_front_1', True,
- (slice(i0, i1, 1), self.j0 - 2, slice(k0, k1 + 1, 1)),
- (slice(i0, i1, 1), self.j0 - 1, slice(k0, k1 + 1, 1)),
- (slice(i0, i1, 1), self.j0, slice(k0, k1 + 1, 1)),
- (slice(i0, i1, 1), self.j0 + 1, slice(k0, k1 + 1, 1)), self.Hz],
- ['hz_back_1', True,
- (slice(i0, i1, 1), self.j1 - 2, slice(k0, k1 + 1, 1)),
- (slice(i0, i1, 1), self.j1 - 1, slice(k0, k1 + 1, 1)),
- (slice(i0, i1, 1), self.j1, slice(k0, k1 + 1, 1)),
- (slice(i0, i1, 1), self.j1 + 1, slice(k0, k1 + 1, 1)), self.Hz],
- ['hx_bottom_1', False,
- # check these indexes
- (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k0 - 2),
- (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k0 - 1),
- (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k0),
- (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k0 + 1), self.Hx],
- ['hx_top_1', False,
- (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k1 - 2),
- (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k1 - 1),
- (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k1),
- (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k1 + 1), self.Hx],
- ['hy_bottom_1', True,
- (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k0 - 2),
- (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k0 - 1),
- (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k0),
- (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k0 + 1), self.Hy],
- ['hy_top_1', True,
- (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k1 - 2),
- (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k1 - 1),
- (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k1),
- (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k1 + 1), self.Hy]
- ]
-
- for obj in slices:
- sliced_field = obj[-1][obj[2]]
- obj[1] = self.create_interpolated_coords(obj[1], sliced_field)
-
- self.magnetic_slices = slices
-
- def initialize_electric_slices_array(self):
- # Extend the region sliced from the main grid by 1 cell.
- # this allows more accurate interpolation for the outernodes
- #i0 = self.i0 - 1
- #j0 = self.j0 - 1
- #k0 = self.k0 - 1
- #i1 = self.i1 + 1
- #j1 = self.j1 + 1
- #k1 = self.k1 + 1
-
- # not extended
- i0 = self.i0
- j0 = self.j0
- k0 = self.k0
- i1 = self.i1
- j1 = self.j1
- k1 = self.k1
-
- # Spatially interpolate nodes
- slices = [
- ['ex_front_1', True,
- (slice(i0, i1, 1), self.j0 - 1, slice(k0, k1 + 1, 1)),
- (slice(i0, i1, 1), self.j0, slice(k0, k1 + 1, 1)),
- (slice(i0, i1, 1), self.j0 + 1, slice(k0, k1 + 1, 1)),
- self.Ex],
- ['ex_back_1', True,
- (slice(i0, i1, 1), self.j1 - 1, slice(k0, k1 + 1, 1)),
- (slice(i0, i1, 1), self.j1, slice(k0, k1 + 1, 1)),
- (slice(i0, i1, 1), self.j1 + 1, slice(k0, k1 + 1, 1)),
- self.Ex],
- ['ez_front_1', False,
- (slice(i0, i1 + 1, 1), self.j0 - 1, slice(k0, k1, 1)),
- (slice(i0, i1 + 1, 1), self.j0, slice(k0, k1, 1)),
- (slice(i0, i1 + 1, 1), self.j0 + 1, slice(k0, k1, 1)),
- self.Ez],
- ['ez_back_1', False,
- (slice(i0, i1 + 1, 1), self.j1 - 1, slice(k0, k1, 1)),
- (slice(i0, i1 + 1, 1), self.j1, slice(k0, k1, 1)),
- (slice(i0, i1 + 1, 1), self.j1 + 1, slice(k0, k1, 1)),
- self.Ez],
- ['ey_left_1', True,
- (self.i0 - 1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
- (self.i0, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
- (self.i0 + 1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
- self.Ey],
- ['ey_right_1', True,
- (self.i1 - 1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
- (self.i1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
- (self.i1 + 1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
- self.Ey],
- ['ez_left_1', False,
- (self.i0 - 1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
- (self.i0, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
- (self.i0 + 1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
- self.Ez],
- ['ez_right_1', False,
- (self.i1 - 1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
- (self.i1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
- (self.i1 + 1, 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 - 1),
- (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k0),
- (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k0 + 1),
- self.Ex],
- ['ex_top_1', True,
- (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k1 - 1),
- (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k1),
- (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k1 + 1),
- self.Ex],
- ['ey_bottom_1', False,
- (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k0 - 1),
- (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k0),
- (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k0 + 1),
- self.Ey],
- ['ey_top_1', False,
- (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k1 - 1),
- (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k1),
- (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k1 + 1),
- self.Ey]
- ]
-
- for obj in slices:
- sliced_field = obj[-1][obj[2]]
- obj[1] = self.create_interpolated_coords(obj[1], sliced_field)
-
- self.electric_slices = slices
-
- def create_interpolated_coords(self, mid, field):
-
- n_x = field.shape[0]
- n_y = field.shape[1]
-
- if mid:
- x = np.arange(0.5, n_x, 1.0)
- z = np.arange(0, n_y, 1.0)
-
- # Coordinates that require interpolated values
- x_sg = np.linspace(self.d, n_x - self.d, n_x * self.ratio)
- z_sg = np.linspace(0, n_y - 1, (n_y - 1) * self.ratio + 1)
-
- else:
- x = np.arange(0, n_x, 1.0)
- z = np.arange(0.5, n_y, 1.0)
-
- # Coordinates that require interpolated values
- x_sg = np.linspace(0, n_x - 1, (n_x - 1) * self.ratio + 1)
- z_sg = np.linspace(self.d, n_y - self.d, n_y * self.ratio)
-
- return (x, z, x_sg, z_sg)
-
- def update_magnetic(self):
-
- # Copy previous time step magnetic field values to the previous
- # time step variables
- self.update_previous_timestep_fields(self.fn_m)
-
- for obj in self.magnetic_slices:
-
- # Grab the main grid fields used to interpolate across the IS
- # f = self.Hi[slice]
-
- f_u_1 = obj[-1][obj[2]]
- f_u_2 = obj[-1][obj[3]]
- f_u_3 = obj[-1][obj[4]]
- f_u_4 = obj[-1][obj[5]]
-
- f_1 = 0.25 * f_u_1 + 0.5 * f_u_2 + 0.25 * f_u_3
- f_2 = 0.25 * f_u_2 + 0.5 * f_u_3 + 0.25 * f_u_4
-
- #f_1 = obj[-1][obj[2]]
- #f_2 = obj[-1][obj[3]]
- if ('left' in obj[0] or
- 'bottom' in obj[0] or
- 'front' in obj[0]):
- w = self.l_weight
- else:
- w = self.r_weight
- c1, c2 = calculate_weighting_coefficients(w, self.ratio)
- # transverse interpolated h field
- f_t = c1 * f_1 + c2 * f_2
-
- # interpolate over a fine grid
- f_i = self.interpolate_to_sub_grid(f_t, obj[1])
-
- if f_i == f_t:
- raise ValueError
-
- if obj[0] == 'hz_front_1' or obj[0] == 'hz_back_1':
-
- pre_0 = int(f_i.shape[0] / 2 - 1 - self.ratio // 2)
- pre_1 = int(f_i.shape[0] / 2 + self.ratio // 2)
-
- f_i[pre_0 + 1:pre_0 + 1 + self.ratio // 2, :] = np.stack((f_i[pre_0, :], f_i[pre_0, :]))
- f_i[pre_1 - self.ratio // 2:pre_1, :] = np.stack((f_i[pre_1, :], f_i[pre_1, :]))
-
-
- # discard the outer nodes only required for interpolation
- #f = f_i[self.ratio:-self.ratio, self.ratio:-self.ratio]
- f = f_i
- setattr(self, obj[0], f)
-
- def update_electric(self):
-
- self.update_previous_timestep_fields(self.fn_e)
-
- for obj in self.electric_slices:
-
- f_u_1 = obj[-1][obj[2]]
- f_u_2 = obj[-1][obj[3]]
- f_u_3 = obj[-1][obj[4]]
-
- f_m = 0.25 * f_u_1 + 0.5 * f_u_2 + 0.25 * f_u_3
-
- f_i = self.interpolate_to_sub_grid(f_m, obj[1])
-
- # correction interpolated points at the pec traversal point
- if obj[0] == 'ex_front_1' or obj[0] == 'ex_back_1':
-
- pre_0 = int(f_i.shape[0] / 2 - 1 - self.ratio // 2)
- pre_1 = int(f_i.shape[0] / 2 + self.ratio // 2)
-
- f_i[pre_0 + 1:pre_0 + 1 + self.ratio // 2, :] = np.stack((f_i[pre_0, :], f_i[pre_0, :]))
- f_i[pre_1 - self.ratio // 2:pre_1, :] = np.stack((f_i[pre_1, :], f_i[pre_1, :]))
-
- f = f_i
- #f = f_i[self.ratio:-self.ratio, self.ratio:-self.ratio]
- setattr(self, obj[0], f)
-
- def interpolate_to_sub_grid(self, field, coords):
- x, z, x_sg, z_sg = coords
- ex_t = np.transpose(field)
- f = interpolate.interp2d(x, z, ex_t, kind=self.interpolation)
- #f = interpolate.RectBivariateSpline(x, z, field)
- ex_inter_t = f(x_sg, z_sg)
- ex_inter = np.transpose(ex_inter_t)
- #ex_inter = ex_inter_t
-
- return ex_inter
-
-
-class PlaneError(Exception):
- pass
+import numpy as np
+from scipy import interpolate
+import sys
+
+
+def calculate_weighting_coefficients(x1, x):
+ c1 = (x - x1) / x
+ c2 = x1 / x
+ return (c1, c2)
+
+
+class PrecusorNodes2dBase(object):
+
+ def __init__(self, fdtd_grid, sub_grid):
+ self.G = fdtd_grid
+ self.ratio = sub_grid.ratio
+ self.nwx = sub_grid.nwx
+ self.nwy = sub_grid.nwy
+ self.sub_grid = sub_grid
+ self.interpolation = sub_grid.interpolation
+
+ self.Hx = fdtd_grid.Hx
+ self.Hy = fdtd_grid.Hy
+ self.Hz = fdtd_grid.Hz
+ self.Ex = fdtd_grid.Ex
+ self.Ey = fdtd_grid.Ey
+ self.Ez = fdtd_grid.Ez
+
+ # Main grid indices of subgrids
+ self.i0 = sub_grid.i0
+ self.j0 = sub_grid.j0
+ self.k0 = sub_grid.k0
+ self.i1 = sub_grid.i1
+ self.j1 = sub_grid.j1
+ self.k1 = sub_grid.k1
+
+ # dl / 2 sub cell
+ self.d = 1 / (2 * self.ratio)
+
+ self._initialize_fields()
+ self._initialize_field_names()
+
+ self.l_weight = self.ratio // 2
+ self.r_weight = self.ratio - self.l_weight
+ #self.l_weight = 1
+ #self.r_weight = 2
+
+ def _initialize_fields(self):
+ print('dont call me')
+ sys.exit()
+ pass
+
+ def _initialize_field_names(self):
+ print('dont call me')
+ sys.exit()
+ pass
+
+ def interpolate_magnetic_in_time(self, m):
+ self.weight_pre_and_current_fields(m, self.fn_m)
+
+ def interpolate_electric_in_time(self, m):
+ self.weight_pre_and_current_fields(m, self.fn_e)
+
+ def weight_pre_and_current_fields(self, m, field_names):
+ c1, c2 = calculate_weighting_coefficients(m, self.ratio)
+
+ for f in field_names:
+ try:
+ val = c1 * getattr(self, f + '_0') + c2 * getattr(self, f + '_1')
+ except ValueError:
+ print(self.ex_front_0.shape)
+ print(self.ex_front_1.shape)
+ raise Exception(f)
+ setattr(self, f, val)
+
+ def calc_exact_field(self, field_names):
+ """
+ Function to set the fields used in update calculations to the
+ values at the current main time step.
+ i.e. ey_left = copy.ey_left_1
+ """
+ for f in field_names:
+ val = np.copy(getattr(self, f + '_1'))
+ setattr(self, f, val)
+
+ def calc_exact_magnetic_in_time(self):
+ self.calc_exact_field(self.fn_m)
+
+ def calc_exact_electric_in_time(self):
+ self.calc_exact_field(self.fn_e)
+
+
+class PrecursorNodes2dTM(PrecusorNodes2dBase):
+
+ def __init__(self, fdtd_grid, sub_grid):
+ super().__init__(fdtd_grid, sub_grid)
+
+ def _initialize_fields(self):
+
+ # H precursors
+ self.hx_front_0 = np.zeros(self.nwx + 1)
+ self.hx_front_1 = np.zeros(self.nwx + 1)
+
+ self.hx_back_0 = np.zeros(self.nwx + 1)
+ self.hx_back_1 = np.zeros(self.nwx + 1)
+
+ self.hy_left_0 = np.zeros(self.nwy + 1)
+ self.hy_left_1 = np.zeros(self.nwy + 1)
+
+ self.hy_right_0 = np.zeros(self.nwy + 1)
+ self.hy_right_1 = np.zeros(self.nwy + 1)
+
+ # E precursors
+ self.ez_front_0 = np.zeros(self.nwx + 1)
+ self.ez_front_1 = np.zeros(self.nwx + 1)
+
+ self.ez_back_0 = np.zeros(self.nwx + 1)
+ self.ez_back_1 = np.zeros(self.nwx + 1)
+
+ self.ez_left_0 = np.zeros(self.nwy + 1)
+ self.ez_left_1 = np.zeros(self.nwy + 1)
+
+ self.ez_right_0 = np.zeros(self.nwy + 1)
+ self.ez_right_1 = np.zeros(self.nwy + 1)
+
+ def _initialize_field_names(self):
+ self.fn_m = ['hy_left', 'hy_right', 'hx_back', 'hx_front']
+ self.fn_e = ['ez_left', 'ez_right', 'ez_back', 'ez_front']
+
+ def interpolate_across_sub_cells(self, y, n0, n1):
+ n = n1 - n0
+ x = np.arange(0, y.shape[0], 1.0)
+ x_new = np.linspace(1, y.shape[0] - 2, n * self.ratio + 1)
+ f = interpolate.interp1d(x, y, kind='linear')
+ y_new = f(x_new)
+ """plt.plot(x, y, 'x', x_new, y_new, '.')
+ plt.show()
+ sys.exit()"""
+ return y_new
+
+ def update_electric(self):
+ # f = np.sin(np.arange(0, 13))
+
+ # line of Ez nodes along the left edge of the IS
+ self.ez_left_0 = np.copy(self.ez_left_1)
+ # interpolate nodes between two Ez nodes 1 cell beyond/infront the IS
+ f = self.Ez[self.i0, self.j0 - 1:self.j1 + 2, 0]
+ self.ez_left_1 = self.interpolate_across_sub_cells(f, self.j0, self.j1)
+
+ self.ez_right_0 = np.copy(self.ez_right_1)
+ f = self.Ez[self.i1, self.j0 - 1:self.j1 + 2, 0]
+ self.ez_right_1 = self.interpolate_across_sub_cells(f, self.j0, self.j1)
+
+ self.ez_front_0 = np.copy(self.ez_front_1)
+ f = self.Ez[self.i0 - 1:self.i1 + 2, self.j0, 0]
+ self.ez_front_1 = self.interpolate_across_sub_cells(f, self.i0, self.i1)
+
+ self.ez_back_0 = np.copy(self.ez_back_1)
+ f = self.Ez[self.i0 - 1:self.i1 + 2, self.j1, 0]
+ self.ez_back_1 = self.interpolate_across_sub_cells(f, self.i0, self.i1)
+
+ def update_hy_left_1(self):
+
+ self.hy_left_0 = np.copy(self.hy_left_1)
+ c1, c2 = calculate_weighting_coefficients(self.l_weight, self.ratio)
+ l = self.Hy[self.i0 - 1, self.j0 - 1:self.j1 + 2, 0]
+ r = self.Hy[self.i0, self.j0 - 1:self.j1 + 2, 0]
+
+ # Tranverse interpolated hz
+ hz_t_i = c1 * l + c2 * r
+ # Tangential interpolated hz
+ self.hy_left_1 = self.interpolate_across_sub_cells(hz_t_i, self.j0, self.j1)
+
+ def update_hy_right_1(self):
+
+ self.hy_right_0 = np.copy(self.hy_right_1)
+ c1, c2 = calculate_weighting_coefficients(self.r_weight, self.ratio)
+ l = self.Hy[self.i1 - 1, self.j0 - 1:self.j1 + 2, 0]
+ r = self.Hy[self.i1, self.j0 - 1:self.j1 + 2, 0]
+ hz_t_i = c1 * l + c2 * r
+ self.hy_right_1 = self.interpolate_across_sub_cells(hz_t_i, self.j0, self.j1)
+
+ def update_hx_back_1(self):
+ self.hx_back_0 = np.copy(self.hx_back_1)
+ c1, c2 = calculate_weighting_coefficients(self.r_weight, self.ratio)
+ l = self.Hx[self.i0 - 1:self.i1 + 2, self.j1 - 1, 0]
+ r = self.Hx[self.i0 - 1:self.i1 + 2, self.j1, 0]
+ hz_t_i = c1 * l + c2 * r
+ self.hx_back_1 = self.interpolate_across_sub_cells(hz_t_i, self.i0, self.i1)
+
+ def update_hx_front_1(self):
+
+ self.hx_front_0 = np.copy(self.hx_front_1)
+ c1, c2 = calculate_weighting_coefficients(self.l_weight, self.ratio)
+ l = self.Hx[self.i0 - 1:self.i1 + 2, self.j0 - 1, 0]
+ r = self.Hx[self.i0 - 1:self.i1 + 2, self.j0, 0]
+ hz_t_i = c1 * l + c2 * r
+ self.hx_front_1 = self.interpolate_across_sub_cells(hz_t_i, self.i0, self.i1)
+
+ def update_magnetic(self):
+ self.update_hy_left_1()
+ self.update_hy_right_1()
+ self.update_hx_back_1()
+ self.update_hx_front_1()
+
+
+class PrecursorNodes2d(PrecusorNodes2dBase):
+
+ def __init__(self, fdtd_grid, sub_grid):
+ super().__init__(fdtd_grid, sub_grid)
+
+ def _initialize_fields(self):
+ # E precursors
+ self.ex_front_0 = np.zeros(self.nwx)
+ self.ex_front_1 = np.zeros(self.nwx)
+
+ self.ex_back_0 = np.zeros(self.nwx)
+ self.ex_back_1 = np.zeros(self.nwx)
+
+ self.ey_left_0 = np.zeros(self.nwy)
+ self.ey_left_1 = np.zeros(self.nwy)
+
+ self.ey_right_0 = np.zeros(self.nwy)
+ self.ey_right_1 = np.zeros(self.nwy)
+
+ # H precursors
+ self.hz_front_0 = np.zeros(self.nwx)
+ self.hz_front_1 = np.zeros(self.nwx)
+
+ self.hz_back_0 = np.zeros(self.nwx)
+ self.hz_back_1 = np.zeros(self.nwx)
+
+ self.hz_left_0 = np.zeros(self.nwy)
+ self.hz_left_1 = np.zeros(self.nwy)
+
+ self.hz_right_0 = np.zeros(self.nwy)
+ self.hz_right_1 = np.zeros(self.nwy)
+
+ def _initialize_field_names(self):
+ # Field names
+ self.fn_m = ['hz_left', 'hz_right', 'hz_back', 'hz_front']
+ self.fn_e = ['ey_left', 'ey_right', 'ex_back', 'ex_front']
+
+ def update_hz_left_1(self):
+ self.hz_left_0 = np.copy(self.hz_left_1)
+ c1, c2 = calculate_weighting_coefficients(1, self.ratio)
+ l = self.Hz[self.i0 - 1, self.j0 - 1:self.j1 + 1, 1]
+ r = self.Hz[self.i0, self.j0 - 1:self.j1 + 1, 1]
+
+ # Tranverse interpolated hz
+ hz_t_i = c1 * l + c2 * r
+ # Tangential interpolated hz
+ self.hz_left_1 = self.interpolate_across_sub_cells(hz_t_i, self.j0, self.j1)
+
+ def update_hz_right_1(self):
+
+ self.hz_right_0 = np.copy(self.hz_right_1)
+ c1, c2 = calculate_weighting_coefficients(2, self.ratio)
+ l = self.Hz[self.i1 - 1, self.j0 - 1:self.j1 + 1, 1]
+ r = self.Hz[self.i1, self.j0 - 1:self.j1 + 1, 1]
+ hz_t_i = c1 * l + c2 * r
+ self.hz_right_1 = self.interpolate_across_sub_cells(hz_t_i, self.j0, self.j1)
+
+ def update_hz_back_1(self):
+ self.hz_back_0 = np.copy(self.hz_back_1)
+ c1, c2 = calculate_weighting_coefficients(2, self.ratio)
+ l = self.Hz[self.i0 - 1:self.i1 + 1, self.j1 - 1, 1]
+ r = self.Hz[self.i0 - 1:self.i1 + 1, self.j1, 1]
+ hz_t_i = c1 * l + c2 * r
+ self.hz_back_1 = self.interpolate_across_sub_cells(hz_t_i, self.i0, self.i1)
+
+ def update_hz_front_1(self):
+ self.hz_front_0 = np.copy(self.hz_front_1)
+ c1, c2 = calculate_weighting_coefficients(1, self.ratio)
+ l = self.Hz[self.i0 - 1:self.i1 + 1, self.j0 - 1, 1]
+ r = self.Hz[self.i0 - 1:self.i1 + 1, self.j0, 1]
+ hz_t_i = c1 * l + c2 * r
+ self.hz_front_1 = self.interpolate_across_sub_cells(hz_t_i, self.i0, self.i1)
+
+ def update_magnetic(self):
+ self.update_hz_left_1()
+ self.update_hz_right_1()
+ self.update_hz_back_1()
+ self.update_hz_front_1()
+
+ def update_electric(self):
+
+ """Function to calculate the precursor nodes at the next main
+ grid time-step
+ """
+
+ # LEFT BOUNDARY
+ # Save the last precursor node values - these will be used interpolate
+ # field values at sub grid time step values.
+ self.ey_left_0 = np.copy(self.ey_left_1)
+ # line of Ex nodes along the left edge of the IS
+ f = self.Ey[self.i0, self.j0 - 1:self.j1 + 1, 1]
+ self.ey_left_1 = self.interpolate_across_sub_cells(f, self.j0, self.j1)
+
+ # RIGHT BOUNDARY
+ self.ey_right_0 = np.copy(self.ey_right_1)
+ f = self.Ey[self.i1, self.j0 - 1:self.j1 + 1, 1]
+ self.ey_right_1 = self.interpolate_across_sub_cells(f, self.j0, self.j1)
+
+ # BACK BOUNDARY
+ self.ex_back_0 = np.copy(self.ex_back_1)
+ f = self.Ex[self.i0 - 1:self.i1 + 1, self.j1, 1]
+ self.ex_back_1 = self.interpolate_across_sub_cells(f, self.i0, self.i1)
+
+ # FRONT BOUNDARY
+ self.ex_front_0 = np.copy(self.ex_front_1)
+ f = self.Ex[self.i0 - 1:self.i1 + 1, self.j0, 1]
+ self.ex_front_1 = self.interpolate_across_sub_cells(f, self.i0, self.i1)
+
+ def interpolate_across_sub_cells(self, y, n0, n1):
+ n = n1 - n0
+ x = np.arange(0.5, y.shape[0], 1.0)
+ x_new = np.linspace(1 + self.d, (1 + n) - self.d, n * self.ratio)
+ f = interpolate.interp1d(x, y, kind='linear')
+ y_new = f(x_new)
+
+ return y_new
+
+
+class PrecursorNodes(PrecusorNodes2dBase):
+
+ def __init__(self, fdtd_grid, sub_grid):
+
+ self.nwz = sub_grid.nwz
+ super().__init__(fdtd_grid, sub_grid)
+ self.initialize_magnetic_slices_array()
+ self.initialize_electric_slices_array()
+
+ def build_coefficient_matrix(self, update_coefficients, lookup_id, inc_field, name):
+ """Function builds a 2d matrix of update coefficients for each face and field.
+ This allows the is nodes to be updated via slicing rather than iterating which is much faster
+ """
+ pass
+
+ def _initialize_fields(self):
+
+ # Initialise the precursor arrays
+
+ # The precursors are divided up into the 6. Each represent 1
+ # face of a huygens cube surface. We represent each face as a 2d array
+ # containing a field in a particular direction.
+
+ # _1 are the fields at the current main grid timestep
+ # _0 are the fields at the previous main grid timestep
+ # We store both fields so we can do different interpolations between
+ # them on the fly.
+
+ # Front face
+ self.ex_front_1 = np.zeros((self.nwx, self.nwz + 1))
+ self.ex_front_0 = np.copy(self.ex_front_1)
+ self.ez_front_1 = np.zeros((self.nwx + 1, self.nwz))
+ self.ez_front_0 = np.copy(self.ez_front_1)
+
+ # The same as the opposite face
+ self.ex_back_1 = np.copy(self.ex_front_1)
+ self.ex_back_0 = np.copy(self.ex_front_1)
+ self.ez_back_1 = np.copy(self.ez_front_1)
+ self.ez_back_0 = np.copy(self.ez_front_1)
+
+ self.ey_left_1 = np.zeros((self.nwy, self.nwz + 1))
+ self.ey_left_0 = np.copy(self.ey_left_1)
+ self.ez_left_1 = np.zeros((self.nwy + 1, self.nwz))
+ self.ez_left_0 = np.copy(self.ez_left_1)
+
+ self.ey_right_1 = np.copy(self.ey_left_1)
+ self.ey_right_0 = np.copy(self.ey_left_1)
+ self.ez_right_1 = np.copy(self.ez_left_1)
+ self.ez_right_0 = np.copy(self.ez_left_1)
+
+ self.ex_bottom_1 = np.zeros((self.nwx, self.nwy + 1))
+ self.ex_bottom_0 = np.copy(self.ex_bottom_1)
+ self.ey_bottom_1 = np.zeros((self.nwx + 1, self.nwy))
+ self.ey_bottom_0 = np.copy(self.ey_bottom_1)
+
+ self.ex_top_1 = np.copy(self.ex_bottom_1)
+ self.ex_top_0 = np.copy(self.ex_bottom_1)
+ self.ey_top_1 = np.copy(self.ey_bottom_1)
+ self.ey_top_0 = np.copy(self.ey_bottom_1)
+
+ # Initialize the H precursor fields
+ self.hx_front_1 = np.copy(self.ez_front_1)
+ self.hx_front_0 = np.copy(self.ez_front_1)
+ self.hz_front_1 = np.copy(self.ex_front_1)
+ self.hz_front_0 = np.copy(self.ex_front_1)
+
+ self.hx_back_1 = np.copy(self.hx_front_1)
+ self.hx_back_0 = np.copy(self.hx_front_1)
+ self.hz_back_1 = np.copy(self.hz_front_1)
+ self.hz_back_0 = np.copy(self.hz_front_1)
+
+ self.hy_left_1 = np.copy(self.ez_left_1)
+ self.hy_left_0 = np.copy(self.ez_left_1)
+ self.hz_left_1 = np.copy(self.ey_left_1)
+ self.hz_left_0 = np.copy(self.ey_left_1)
+
+ self.hy_right_1 = np.copy(self.hy_left_1)
+ self.hy_right_0 = np.copy(self.hy_left_1)
+ self.hz_right_1 = np.copy(self.hz_left_1)
+ self.hz_right_0 = np.copy(self.hz_left_1)
+
+ self.hx_top_1 = np.copy(self.ey_top_1)
+ self.hx_top_0 = np.copy(self.ey_top_1)
+ self.hy_top_1 = np.copy(self.ex_top_1)
+ self.hy_top_0 = np.copy(self.ex_top_1)
+
+ self.hx_bottom_1 = np.copy(self.hx_top_1)
+ self.hx_bottom_0 = np.copy(self.hx_top_1)
+ self.hy_bottom_1 = np.copy(self.hy_top_1)
+ self.hy_bottom_0 = np.copy(self.hy_top_1)
+
+ def _initialize_field_names(self):
+
+ self.fn_m = [
+ 'hx_front', 'hz_front',
+ 'hx_back', 'hz_back',
+ 'hy_left', 'hz_left',
+ 'hy_right', 'hz_right',
+ 'hx_top', 'hy_top',
+ 'hx_bottom', 'hy_bottom'
+ ]
+
+ self.fn_e = [
+ 'ex_front', 'ez_front',
+ 'ex_back', 'ez_back',
+ 'ey_left', 'ez_left',
+ 'ey_right', 'ez_right',
+ 'ex_top', 'ey_top',
+ 'ex_bottom', 'ey_bottom'
+ ]
+
+ def update_previous_timestep_fields(self, field_names):
+ for fn in field_names:
+ val = getattr(self, fn + '_1')
+ val_c = np.copy(val)
+ setattr(self, fn + '_0', val_c)
+
+ def initialize_magnetic_slices_array(self):
+
+ # Array contains the indices at which the main grid should be sliced
+ # to obtain the 2 2d array of H nodes required for interpolation
+ # across the IS boundary for each h field on each face of the subgrid
+
+ # Extend the surface so that the outer fields can be interpolated
+ # more accurately
+ #i0 = self.i0 - 1
+ #j0 = self.j0 - 1
+ #k0 = self.k0 - 1
+ #i1 = self.i1 + 1
+ #j1 = self.j1 + 1
+ #k1 = self.k1 + 1
+
+ # not extended
+ i0 = self.i0
+ j0 = self.j0
+ k0 = self.k0
+ i1 = self.i1
+ j1 = self.j1
+ k1 = self.k1
+
+ slices = [
+ ['hy_left_1', False,
+ (self.i0 - 2, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
+ (self.i0 - 1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
+ (self.i0, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
+ (self.i0 + 1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)), self.Hy],
+ ['hy_right_1', False,
+ (self.i1 - 2, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
+ (self.i1 - 1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
+ (self.i1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
+ (self.i1 + 1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)), self.Hy],
+ ['hz_left_1', True,
+ (self.i0 - 2, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
+ (self.i0 - 1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
+ (self.i0, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
+ (self.i0 + 1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)), self.Hz],
+ ['hz_right_1', True,
+ (self.i1 - 2, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
+ (self.i1 - 1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
+ (self.i1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
+ (self.i1 + 1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)), self.Hz],
+ ['hx_front_1', False,
+ (slice(i0, i1 + 1, 1), self.j0 - 2, slice(k0, k1, 1)),
+ (slice(i0, i1 + 1, 1), self.j0 - 1, slice(k0, k1, 1)),
+ (slice(i0, i1 + 1, 1), self.j0, slice(k0, k1, 1)),
+ (slice(i0, i1 + 1, 1), self.j0 + 1, slice(k0, k1, 1)), self.Hx],
+ ['hx_back_1', False,
+ (slice(i0, i1 + 1, 1), self.j1 - 2, slice(k0, k1, 1)),
+ (slice(i0, i1 + 1, 1), self.j1 - 1, slice(k0, k1, 1)),
+ (slice(i0, i1 + 1, 1), self.j1, slice(k0, k1, 1)),
+ (slice(i0, i1 + 1, 1), self.j1 + 1, slice(k0, k1, 1)), self.Hx],
+ ['hz_front_1', True,
+ (slice(i0, i1, 1), self.j0 - 2, slice(k0, k1 + 1, 1)),
+ (slice(i0, i1, 1), self.j0 - 1, slice(k0, k1 + 1, 1)),
+ (slice(i0, i1, 1), self.j0, slice(k0, k1 + 1, 1)),
+ (slice(i0, i1, 1), self.j0 + 1, slice(k0, k1 + 1, 1)), self.Hz],
+ ['hz_back_1', True,
+ (slice(i0, i1, 1), self.j1 - 2, slice(k0, k1 + 1, 1)),
+ (slice(i0, i1, 1), self.j1 - 1, slice(k0, k1 + 1, 1)),
+ (slice(i0, i1, 1), self.j1, slice(k0, k1 + 1, 1)),
+ (slice(i0, i1, 1), self.j1 + 1, slice(k0, k1 + 1, 1)), self.Hz],
+ ['hx_bottom_1', False,
+ # check these indexes
+ (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k0 - 2),
+ (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k0 - 1),
+ (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k0),
+ (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k0 + 1), self.Hx],
+ ['hx_top_1', False,
+ (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k1 - 2),
+ (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k1 - 1),
+ (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k1),
+ (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k1 + 1), self.Hx],
+ ['hy_bottom_1', True,
+ (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k0 - 2),
+ (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k0 - 1),
+ (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k0),
+ (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k0 + 1), self.Hy],
+ ['hy_top_1', True,
+ (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k1 - 2),
+ (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k1 - 1),
+ (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k1),
+ (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k1 + 1), self.Hy]
+ ]
+
+ for obj in slices:
+ sliced_field = obj[-1][obj[2]]
+ obj[1] = self.create_interpolated_coords(obj[1], sliced_field)
+
+ self.magnetic_slices = slices
+
+ def initialize_electric_slices_array(self):
+ # Extend the region sliced from the main grid by 1 cell.
+ # this allows more accurate interpolation for the outernodes
+ #i0 = self.i0 - 1
+ #j0 = self.j0 - 1
+ #k0 = self.k0 - 1
+ #i1 = self.i1 + 1
+ #j1 = self.j1 + 1
+ #k1 = self.k1 + 1
+
+ # not extended
+ i0 = self.i0
+ j0 = self.j0
+ k0 = self.k0
+ i1 = self.i1
+ j1 = self.j1
+ k1 = self.k1
+
+ # Spatially interpolate nodes
+ slices = [
+ ['ex_front_1', True,
+ (slice(i0, i1, 1), self.j0 - 1, slice(k0, k1 + 1, 1)),
+ (slice(i0, i1, 1), self.j0, slice(k0, k1 + 1, 1)),
+ (slice(i0, i1, 1), self.j0 + 1, slice(k0, k1 + 1, 1)),
+ self.Ex],
+ ['ex_back_1', True,
+ (slice(i0, i1, 1), self.j1 - 1, slice(k0, k1 + 1, 1)),
+ (slice(i0, i1, 1), self.j1, slice(k0, k1 + 1, 1)),
+ (slice(i0, i1, 1), self.j1 + 1, slice(k0, k1 + 1, 1)),
+ self.Ex],
+ ['ez_front_1', False,
+ (slice(i0, i1 + 1, 1), self.j0 - 1, slice(k0, k1, 1)),
+ (slice(i0, i1 + 1, 1), self.j0, slice(k0, k1, 1)),
+ (slice(i0, i1 + 1, 1), self.j0 + 1, slice(k0, k1, 1)),
+ self.Ez],
+ ['ez_back_1', False,
+ (slice(i0, i1 + 1, 1), self.j1 - 1, slice(k0, k1, 1)),
+ (slice(i0, i1 + 1, 1), self.j1, slice(k0, k1, 1)),
+ (slice(i0, i1 + 1, 1), self.j1 + 1, slice(k0, k1, 1)),
+ self.Ez],
+ ['ey_left_1', True,
+ (self.i0 - 1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
+ (self.i0, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
+ (self.i0 + 1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
+ self.Ey],
+ ['ey_right_1', True,
+ (self.i1 - 1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
+ (self.i1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
+ (self.i1 + 1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
+ self.Ey],
+ ['ez_left_1', False,
+ (self.i0 - 1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
+ (self.i0, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
+ (self.i0 + 1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
+ self.Ez],
+ ['ez_right_1', False,
+ (self.i1 - 1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
+ (self.i1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
+ (self.i1 + 1, 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 - 1),
+ (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k0),
+ (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k0 + 1),
+ self.Ex],
+ ['ex_top_1', True,
+ (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k1 - 1),
+ (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k1),
+ (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k1 + 1),
+ self.Ex],
+ ['ey_bottom_1', False,
+ (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k0 - 1),
+ (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k0),
+ (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k0 + 1),
+ self.Ey],
+ ['ey_top_1', False,
+ (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k1 - 1),
+ (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k1),
+ (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k1 + 1),
+ self.Ey]
+ ]
+
+ for obj in slices:
+ sliced_field = obj[-1][obj[2]]
+ obj[1] = self.create_interpolated_coords(obj[1], sliced_field)
+
+ self.electric_slices = slices
+
+ def create_interpolated_coords(self, mid, field):
+
+ n_x = field.shape[0]
+ n_y = field.shape[1]
+
+ if mid:
+ x = np.arange(0.5, n_x, 1.0)
+ z = np.arange(0, n_y, 1.0)
+
+ # Coordinates that require interpolated values
+ x_sg = np.linspace(self.d, n_x - self.d, n_x * self.ratio)
+ z_sg = np.linspace(0, n_y - 1, (n_y - 1) * self.ratio + 1)
+
+ else:
+ x = np.arange(0, n_x, 1.0)
+ z = np.arange(0.5, n_y, 1.0)
+
+ # Coordinates that require interpolated values
+ x_sg = np.linspace(0, n_x - 1, (n_x - 1) * self.ratio + 1)
+ z_sg = np.linspace(self.d, n_y - self.d, n_y * self.ratio)
+
+ return (x, z, x_sg, z_sg)
+
+ def update_magnetic(self):
+
+ # Copy previous time step magnetic field values to the previous
+ # time step variables
+ self.update_previous_timestep_fields(self.fn_m)
+
+ for obj in self.magnetic_slices:
+
+ # Grab the main grid fields used to interpolate across the IS
+ # f = self.Hi[slice]
+
+ f_u_1 = obj[-1][obj[2]]
+ f_u_2 = obj[-1][obj[3]]
+ f_u_3 = obj[-1][obj[4]]
+ f_u_4 = obj[-1][obj[5]]
+
+ f_1 = 0.25 * f_u_1 + 0.5 * f_u_2 + 0.25 * f_u_3
+ f_2 = 0.25 * f_u_2 + 0.5 * f_u_3 + 0.25 * f_u_4
+
+ #f_1 = obj[-1][obj[2]]
+ #f_2 = obj[-1][obj[3]]
+ if ('left' in obj[0] or
+ 'bottom' in obj[0] or
+ 'front' in obj[0]):
+ w = self.l_weight
+ else:
+ w = self.r_weight
+ c1, c2 = calculate_weighting_coefficients(w, self.ratio)
+ # transverse interpolated h field
+ f_t = c1 * f_1 + c2 * f_2
+
+ # interpolate over a fine grid
+ f_i = self.interpolate_to_sub_grid(f_t, obj[1])
+
+ if f_i == f_t:
+ raise ValueError
+
+ if obj[0] == 'hz_front_1' or obj[0] == 'hz_back_1':
+
+ pre_0 = int(f_i.shape[0] / 2 - 1 - self.ratio // 2)
+ pre_1 = int(f_i.shape[0] / 2 + self.ratio // 2)
+
+ f_i[pre_0 + 1:pre_0 + 1 + self.ratio // 2, :] = np.stack((f_i[pre_0, :], f_i[pre_0, :]))
+ f_i[pre_1 - self.ratio // 2:pre_1, :] = np.stack((f_i[pre_1, :], f_i[pre_1, :]))
+
+
+ # discard the outer nodes only required for interpolation
+ #f = f_i[self.ratio:-self.ratio, self.ratio:-self.ratio]
+ f = f_i
+ setattr(self, obj[0], f)
+
+ def update_electric(self):
+
+ self.update_previous_timestep_fields(self.fn_e)
+
+ for obj in self.electric_slices:
+
+ f_u_1 = obj[-1][obj[2]]
+ f_u_2 = obj[-1][obj[3]]
+ f_u_3 = obj[-1][obj[4]]
+
+ f_m = 0.25 * f_u_1 + 0.5 * f_u_2 + 0.25 * f_u_3
+
+ f_i = self.interpolate_to_sub_grid(f_m, obj[1])
+
+ # correction interpolated points at the pec traversal point
+ if obj[0] == 'ex_front_1' or obj[0] == 'ex_back_1':
+
+ pre_0 = int(f_i.shape[0] / 2 - 1 - self.ratio // 2)
+ pre_1 = int(f_i.shape[0] / 2 + self.ratio // 2)
+
+ f_i[pre_0 + 1:pre_0 + 1 + self.ratio // 2, :] = np.stack((f_i[pre_0, :], f_i[pre_0, :]))
+ f_i[pre_1 - self.ratio // 2:pre_1, :] = np.stack((f_i[pre_1, :], f_i[pre_1, :]))
+
+ f = f_i
+ #f = f_i[self.ratio:-self.ratio, self.ratio:-self.ratio]
+ setattr(self, obj[0], f)
+
+ def interpolate_to_sub_grid(self, field, coords):
+ x, z, x_sg, z_sg = coords
+ ex_t = np.transpose(field)
+ f = interpolate.interp2d(x, z, ex_t, kind=self.interpolation)
+ #f = interpolate.RectBivariateSpline(x, z, field)
+ ex_inter_t = f(x_sg, z_sg)
+ ex_inter = np.transpose(ex_inter_t)
+ #ex_inter = ex_inter_t
+
+ return ex_inter
+
+
+class PlaneError(Exception):
+ pass
diff --git a/gprMax/subgrids/subgrid_hsg.py b/gprMax/subgrids/subgrid_hsg.py
index 8aa1d1a1..29be160d 100644
--- a/gprMax/subgrids/subgrid_hsg.py
+++ b/gprMax/subgrids/subgrid_hsg.py
@@ -1,148 +1,148 @@
-from .base import SubGridBase
-from ..cython.fields_updates_hsg import cython_update_is
-from ..cython.fields_updates_hsg import cython_update_magnetic_os
-from ..cython.fields_updates_hsg import cython_update_electric_os
-from ..utilities import human_size
-
-from colorama import init, Fore, Style
-init()
-
-
-class SubGridHSG(SubGridBase):
-
- gridtype = '3DSUBGRID'
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- self.gridtype = SubGridHSG.gridtype
-
- def update_magnetic_is(self, precursors):
- """Update the subgrid nodes at the IS with the currents derived
- from the main grid.
- Args: nwl, nwm, nwn, face, field, inc_field, lookup_id, sign, mod, co
- """
- # Hz = c0Hz - c1Ey + c2Ex
- # Hy = c0Hy - c3Ex + c1Ez
- # Hx = c0Hx - c2Ez + c3Ey
- # bottom and top
- cython_update_is(self.nwx, self.nwy, self.nwz, self.updatecoeffsH, self.ID, self.n_boundary_cells, -1, self.nwx, self.nwy + 1, self.nwz, 1, self.Hy, precursors.ex_bottom, precursors.ex_top, self.IDlookup['Hy'], 1, -1, 3, self.nthreads)
- cython_update_is(self.nwx, self.nwy, self.nwz, self.updatecoeffsH, self.ID, self.n_boundary_cells, -1, self.nwx + 1, self.nwy, self.nwz, 1, self.Hx, precursors.ey_bottom, precursors.ey_top, self.IDlookup['Hx'], -1, 1, 3, self.nthreads)
-
- # left and right
- cython_update_is(self.nwx, self.nwy, self.nwz, self.updatecoeffsH, self.ID, self.n_boundary_cells, -1, self.nwy, self.nwz + 1, self.nwx, 2, self.Hz, precursors.ey_left, precursors.ey_right, self.IDlookup['Hz'], 1, -1, 1, self.nthreads)
- cython_update_is(self.nwx, self.nwy, self.nwz, self.updatecoeffsH, self.ID, self.n_boundary_cells, -1, self.nwy + 1, self.nwz, self.nwx, 2, self.Hy, precursors.ez_left, precursors.ez_right, self.IDlookup['Hy'], -1, 1, 1, self.nthreads)
-
- # front and back
- cython_update_is(self.nwx, self.nwy, self.nwz, self.updatecoeffsH, self.ID, self.n_boundary_cells, -1, self.nwx, self.nwz + 1, self.nwy, 3, self.Hz, precursors.ex_front, precursors.ex_back, self.IDlookup['Hz'], -1, 1, 2, self.nthreads)
- cython_update_is(self.nwx, self.nwy, self.nwz, self.updatecoeffsH, self.ID, self.n_boundary_cells, -1, self.nwx + 1, self.nwz, self.nwy, 3, self.Hx, precursors.ez_front, precursors.ez_back, self.IDlookup['Hx'], 1, -1, 2, self.nthreads)
-
- def update_electric_is(self, precursors):
- # Args: nwl, nwm, nwn, face, field, inc_field, lookup_id, sign, mod, co
-
- # Ex = c0(Ex) + c2(dHz) - c3(dHy)
- # Ey = c0(Ey) + c3(dHx) - c1(dHz)
- # Ez = c0(Ez) + c1(dHy) - c2(dHx)
-
- # bottom and top
- cython_update_is(self.nwx, self.nwy, self.nwz, self.updatecoeffsE, self.ID, self.n_boundary_cells, 0, self.nwx, self.nwy + 1, self.nwz, 1, self.Ex, precursors.hy_bottom, precursors.hy_top, self.IDlookup['Ex'], 1, -1, 3, self.nthreads)
- cython_update_is(self.nwx, self.nwy, self.nwz, self.updatecoeffsE, self.ID, self.n_boundary_cells, 0, self.nwx + 1, self.nwy, self.nwz, 1, self.Ey, precursors.hx_bottom, precursors.hx_top, self.IDlookup['Ey'], -1, 1, 3, self.nthreads)
-
- # left and right
- cython_update_is(self.nwx, self.nwy, self.nwz, self.updatecoeffsE, self.ID, self.n_boundary_cells, 0, self.nwy, self.nwz + 1, self.nwx, 2, self.Ey, precursors.hz_left, precursors.hz_right, self.IDlookup['Ey'], 1, -1, 1, self.nthreads)
- cython_update_is(self.nwx, self.nwy, self.nwz, self.updatecoeffsE, self.ID, self.n_boundary_cells, 0, self.nwy + 1, self.nwz, self.nwx, 2, self.Ez, precursors.hy_left, precursors.hy_right, self.IDlookup['Ez'], -1, 1, 1, self.nthreads)
-
- # front and back
- cython_update_is(self.nwx, self.nwy, self.nwz, self.updatecoeffsE, self.ID, self.n_boundary_cells, 0, self.nwx, self.nwz + 1, self.nwy, 3, self.Ex, precursors.hz_front, precursors.hz_back, self.IDlookup['Ex'], -1, 1, 2, self.nthreads)
- cython_update_is(self.nwx, self.nwy, self.nwz, self.updatecoeffsE, self.ID, self.n_boundary_cells, 0, self.nwx + 1, self.nwz, self.nwy, 3, self.Ez, precursors.hx_front, precursors.hx_back, self.IDlookup['Ez'], 1, -1, 2, self.nthreads)
-
- def update_electric_os(self, main_grid):
- i_l = self.i0 - self.is_os_sep
- i_u = self.i1 + self.is_os_sep
- j_l = self.j0 - self.is_os_sep
- j_u = self.j1 + self.is_os_sep
- k_l = self.k0 - self.is_os_sep
- k_u = self.k1 + self.is_os_sep
-
- # Args: sub_grid, normal, l_l, l_u, m_l, m_u, n_l, n_u, nwn, lookup_id, field, inc_field, co, sign_n, sign_f
-
- # Form of FDTD update equations for E
- # Ex = c0(Ex) + c2(dHz) - c3(dHy)
- # Ey = c0(Ey) + c3(dHx) - c1(dHz)
- # Ez = c0(Ez) + c1(dHy) - c2(dHx)
-
- cython_update_electric_os(main_grid.updatecoeffsE, main_grid.ID, 3, i_l, i_u, k_l, k_u + 1, j_l, j_u, self.nwy, main_grid.IDlookup['Ex'], main_grid.Ex, self.Hz, 2, 1, -1, 1, self.ratio, self.is_os_sep, self.n_boundary_cells, main_grid.nthreads)
- cython_update_electric_os(main_grid.updatecoeffsE, main_grid.ID, 3, i_l, i_u + 1, k_l, k_u, j_l, j_u, self.nwy, main_grid.IDlookup['Ez'], main_grid.Ez, self.Hx, 2, -1, 1, 0, self.ratio, self.is_os_sep, self.n_boundary_cells, main_grid.nthreads)
-
- # Left and Right
- cython_update_electric_os(main_grid.updatecoeffsE, main_grid.ID, 2, j_l, j_u, k_l, k_u + 1, i_l, i_u, self.nwx, main_grid.IDlookup['Ey'], main_grid.Ey, self.Hz, 1, -1, 1, 1, self.ratio, self.is_os_sep, self.n_boundary_cells, main_grid.nthreads)
- cython_update_electric_os(main_grid.updatecoeffsE, main_grid.ID, 2, j_l, j_u + 1, k_l, k_u, i_l, i_u, self.nwx, main_grid.IDlookup['Ez'], main_grid.Ez, self.Hy, 1, 1, -1, 0, self.ratio, self.is_os_sep, self.n_boundary_cells, main_grid.nthreads)
-
- # Bottom and Top
- cython_update_electric_os(main_grid.updatecoeffsE, main_grid.ID, 1, i_l, i_u, j_l, j_u + 1, k_l, k_u, self.nwz, main_grid.IDlookup['Ex'], main_grid.Ex, self.Hy, 3, -1, 1, 1, self.ratio, self.is_os_sep, self.n_boundary_cells, main_grid.nthreads)
- cython_update_electric_os(main_grid.updatecoeffsE, main_grid.ID, 1, i_l, i_u + 1, j_l, j_u, k_l, k_u, self.nwz, main_grid.IDlookup['Ey'], main_grid.Ey, self.Hx, 3, 1, -1, 0, self.ratio, self.is_os_sep, self.n_boundary_cells, main_grid.nthreads)
-
- def update_magnetic_os(self, main_grid):
-
- i_l = self.i0 - self.is_os_sep
- i_u = self.i1 + self.is_os_sep
- j_l = self.j0 - self.is_os_sep
- j_u = self.j1 + self.is_os_sep
- k_l = self.k0 - self.is_os_sep
- k_u = self.k1 + self.is_os_sep
-
- # Form of FDTD update equations for H
- # Hz = c0Hz - c1Ey + c2Ex
- # Hy = c0Hy - c3Ex + c1Ez
- # Hx = c0Hx - c2Ez + c3Ey
-
- # Args: sub_grid, normal, l_l, l_u, m_l, m_u, n_l, n_u, nwn, lookup_id, field, inc_field, co, sign_n, sign_f):
-
- # Front and back
- cython_update_magnetic_os(main_grid.updatecoeffsH, main_grid.ID, 3, i_l, i_u, k_l, k_u + 1, j_l - 1, j_u, self.nwy, main_grid.IDlookup['Hz'], main_grid.Hz, self.Ex, 2, 1, -1, 1, self.ratio, self.is_os_sep, self.n_boundary_cells, main_grid.nthreads)
- cython_update_magnetic_os(main_grid.updatecoeffsH, main_grid.ID, 3, i_l, i_u + 1, k_l, k_u, j_l - 1, j_u, self.nwy, main_grid.IDlookup['Hx'], main_grid.Hx, self.Ez, 2, -1, 1, 0, self.ratio, self.is_os_sep, self.n_boundary_cells, main_grid.nthreads)
-
- # Left and Right
- cython_update_magnetic_os(main_grid.updatecoeffsH, main_grid.ID, 2, j_l, j_u, k_l, k_u + 1, i_l - 1, i_u, self.nwx, main_grid.IDlookup['Hz'], main_grid.Hz, self.Ey, 1, -1, 1, 1, self.ratio, self.is_os_sep, self.n_boundary_cells, main_grid.nthreads)
- cython_update_magnetic_os(main_grid.updatecoeffsH, main_grid.ID, 2, j_l, j_u + 1, k_l, k_u, i_l - 1, i_u, self.nwx, main_grid.IDlookup['Hy'], main_grid.Hy, self.Ez, 1, 1, -1, 0, self.ratio, self.is_os_sep, self.n_boundary_cells, main_grid.nthreads)
-
- # bottom and top
- cython_update_magnetic_os(main_grid.updatecoeffsH, main_grid.ID, 1, i_l, i_u, j_l, j_u + 1, k_l - 1, k_u, self.nwz, main_grid.IDlookup['Hy'], main_grid.Hy, self.Ex, 3, -1, 1, 1, self.ratio, self.is_os_sep, self.n_boundary_cells, main_grid.nthreads)
- cython_update_magnetic_os(main_grid.updatecoeffsH, main_grid.ID, 1, i_l, i_u + 1, j_l, j_u, k_l - 1, k_u, self.nwz, main_grid.IDlookup['Hx'], main_grid.Hx, self.Ey, 3, 1, -1, 0, self.ratio, self.is_os_sep, self.n_boundary_cells, main_grid.nthreads)
-
- def __str__(self):
-
- self.memory_estimate_basic()
-
- s = '\n'
- s += Fore.CYAN
- s += 'Sub Grid HSG\n'
- s += 'Name: {}\n'.format(self.name)
- s += 'dx, dy, dz: {}m {}m {}m\n'.format(self.dx, self.dy, self.dz)
- s += 'dt: {}s\n'.format(self.dt)
- s += 'Memory Estimate: {}\n'.format(human_size(self.memoryusage))
- s += 'Position: ({}m, {}m, {}m), ({}m, {}m, {}m)\n'.format(self.x1,
- self.y1,
- self.z1,
- self.x2,
- self.y2,
- self.z2)
- s += 'Main Grid Indices: lower left({}, {}, {}), upper right({}, {}, {})\n'.format(self.i0, self.j0, self.k0, self.i1, self.j1, self.k1)
- s += 'Total Cells: {} {} {}\n'.format(self.nx, self.ny, self.nz)
- s += 'Working Region Cells: {} {} {}\n'.format(self.nwx,
- self.nwy,
- self.nwz)
- for h in self.hertziandipoles:
- s += 'Hertizian dipole: {} {} {}\n'.format(h.xcoord,
- h.ycoord,
- h.zcoord)
- s += str([x for x in self.waveforms
- if x.ID == h.waveformID][0]) + '\n'
- for r in self.rxs:
- s += 'Receiver: {} {} {}\n'.format(r.xcoord, r.ycoord, r.zcoord)
-
- for tl in self.transmissionlines:
- s += 'Transmission Line: {} {} {}\n'.format(tl.xcoord, tl.ycoord, tl.zcoord)
- s += str([x for x in self.waveforms
- if x.ID == tl.waveformID][0]) + '\n'
- s += Style.RESET_ALL
- return s
+from .base import SubGridBase
+from ..cython.fields_updates_hsg import cython_update_is
+from ..cython.fields_updates_hsg import cython_update_magnetic_os
+from ..cython.fields_updates_hsg import cython_update_electric_os
+from ..utilities import human_size
+
+from colorama import init, Fore, Style
+init()
+
+
+class SubGridHSG(SubGridBase):
+
+ gridtype = '3DSUBGRID'
+
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ self.gridtype = SubGridHSG.gridtype
+
+ def update_magnetic_is(self, precursors):
+ """Update the subgrid nodes at the IS with the currents derived
+ from the main grid.
+ Args: nwl, nwm, nwn, face, field, inc_field, lookup_id, sign, mod, co
+ """
+ # Hz = c0Hz - c1Ey + c2Ex
+ # Hy = c0Hy - c3Ex + c1Ez
+ # Hx = c0Hx - c2Ez + c3Ey
+ # bottom and top
+ cython_update_is(self.nwx, self.nwy, self.nwz, self.updatecoeffsH, self.ID, self.n_boundary_cells, -1, self.nwx, self.nwy + 1, self.nwz, 1, self.Hy, precursors.ex_bottom, precursors.ex_top, self.IDlookup['Hy'], 1, -1, 3, self.nthreads)
+ cython_update_is(self.nwx, self.nwy, self.nwz, self.updatecoeffsH, self.ID, self.n_boundary_cells, -1, self.nwx + 1, self.nwy, self.nwz, 1, self.Hx, precursors.ey_bottom, precursors.ey_top, self.IDlookup['Hx'], -1, 1, 3, self.nthreads)
+
+ # left and right
+ cython_update_is(self.nwx, self.nwy, self.nwz, self.updatecoeffsH, self.ID, self.n_boundary_cells, -1, self.nwy, self.nwz + 1, self.nwx, 2, self.Hz, precursors.ey_left, precursors.ey_right, self.IDlookup['Hz'], 1, -1, 1, self.nthreads)
+ cython_update_is(self.nwx, self.nwy, self.nwz, self.updatecoeffsH, self.ID, self.n_boundary_cells, -1, self.nwy + 1, self.nwz, self.nwx, 2, self.Hy, precursors.ez_left, precursors.ez_right, self.IDlookup['Hy'], -1, 1, 1, self.nthreads)
+
+ # front and back
+ cython_update_is(self.nwx, self.nwy, self.nwz, self.updatecoeffsH, self.ID, self.n_boundary_cells, -1, self.nwx, self.nwz + 1, self.nwy, 3, self.Hz, precursors.ex_front, precursors.ex_back, self.IDlookup['Hz'], -1, 1, 2, self.nthreads)
+ cython_update_is(self.nwx, self.nwy, self.nwz, self.updatecoeffsH, self.ID, self.n_boundary_cells, -1, self.nwx + 1, self.nwz, self.nwy, 3, self.Hx, precursors.ez_front, precursors.ez_back, self.IDlookup['Hx'], 1, -1, 2, self.nthreads)
+
+ def update_electric_is(self, precursors):
+ # Args: nwl, nwm, nwn, face, field, inc_field, lookup_id, sign, mod, co
+
+ # Ex = c0(Ex) + c2(dHz) - c3(dHy)
+ # Ey = c0(Ey) + c3(dHx) - c1(dHz)
+ # Ez = c0(Ez) + c1(dHy) - c2(dHx)
+
+ # bottom and top
+ cython_update_is(self.nwx, self.nwy, self.nwz, self.updatecoeffsE, self.ID, self.n_boundary_cells, 0, self.nwx, self.nwy + 1, self.nwz, 1, self.Ex, precursors.hy_bottom, precursors.hy_top, self.IDlookup['Ex'], 1, -1, 3, self.nthreads)
+ cython_update_is(self.nwx, self.nwy, self.nwz, self.updatecoeffsE, self.ID, self.n_boundary_cells, 0, self.nwx + 1, self.nwy, self.nwz, 1, self.Ey, precursors.hx_bottom, precursors.hx_top, self.IDlookup['Ey'], -1, 1, 3, self.nthreads)
+
+ # left and right
+ cython_update_is(self.nwx, self.nwy, self.nwz, self.updatecoeffsE, self.ID, self.n_boundary_cells, 0, self.nwy, self.nwz + 1, self.nwx, 2, self.Ey, precursors.hz_left, precursors.hz_right, self.IDlookup['Ey'], 1, -1, 1, self.nthreads)
+ cython_update_is(self.nwx, self.nwy, self.nwz, self.updatecoeffsE, self.ID, self.n_boundary_cells, 0, self.nwy + 1, self.nwz, self.nwx, 2, self.Ez, precursors.hy_left, precursors.hy_right, self.IDlookup['Ez'], -1, 1, 1, self.nthreads)
+
+ # front and back
+ cython_update_is(self.nwx, self.nwy, self.nwz, self.updatecoeffsE, self.ID, self.n_boundary_cells, 0, self.nwx, self.nwz + 1, self.nwy, 3, self.Ex, precursors.hz_front, precursors.hz_back, self.IDlookup['Ex'], -1, 1, 2, self.nthreads)
+ cython_update_is(self.nwx, self.nwy, self.nwz, self.updatecoeffsE, self.ID, self.n_boundary_cells, 0, self.nwx + 1, self.nwz, self.nwy, 3, self.Ez, precursors.hx_front, precursors.hx_back, self.IDlookup['Ez'], 1, -1, 2, self.nthreads)
+
+ def update_electric_os(self, main_grid):
+ i_l = self.i0 - self.is_os_sep
+ i_u = self.i1 + self.is_os_sep
+ j_l = self.j0 - self.is_os_sep
+ j_u = self.j1 + self.is_os_sep
+ k_l = self.k0 - self.is_os_sep
+ k_u = self.k1 + self.is_os_sep
+
+ # Args: sub_grid, normal, l_l, l_u, m_l, m_u, n_l, n_u, nwn, lookup_id, field, inc_field, co, sign_n, sign_f
+
+ # Form of FDTD update equations for E
+ # Ex = c0(Ex) + c2(dHz) - c3(dHy)
+ # Ey = c0(Ey) + c3(dHx) - c1(dHz)
+ # Ez = c0(Ez) + c1(dHy) - c2(dHx)
+
+ cython_update_electric_os(main_grid.updatecoeffsE, main_grid.ID, 3, i_l, i_u, k_l, k_u + 1, j_l, j_u, self.nwy, main_grid.IDlookup['Ex'], main_grid.Ex, self.Hz, 2, 1, -1, 1, self.ratio, self.is_os_sep, self.n_boundary_cells, main_grid.nthreads)
+ cython_update_electric_os(main_grid.updatecoeffsE, main_grid.ID, 3, i_l, i_u + 1, k_l, k_u, j_l, j_u, self.nwy, main_grid.IDlookup['Ez'], main_grid.Ez, self.Hx, 2, -1, 1, 0, self.ratio, self.is_os_sep, self.n_boundary_cells, main_grid.nthreads)
+
+ # Left and Right
+ cython_update_electric_os(main_grid.updatecoeffsE, main_grid.ID, 2, j_l, j_u, k_l, k_u + 1, i_l, i_u, self.nwx, main_grid.IDlookup['Ey'], main_grid.Ey, self.Hz, 1, -1, 1, 1, self.ratio, self.is_os_sep, self.n_boundary_cells, main_grid.nthreads)
+ cython_update_electric_os(main_grid.updatecoeffsE, main_grid.ID, 2, j_l, j_u + 1, k_l, k_u, i_l, i_u, self.nwx, main_grid.IDlookup['Ez'], main_grid.Ez, self.Hy, 1, 1, -1, 0, self.ratio, self.is_os_sep, self.n_boundary_cells, main_grid.nthreads)
+
+ # Bottom and Top
+ cython_update_electric_os(main_grid.updatecoeffsE, main_grid.ID, 1, i_l, i_u, j_l, j_u + 1, k_l, k_u, self.nwz, main_grid.IDlookup['Ex'], main_grid.Ex, self.Hy, 3, -1, 1, 1, self.ratio, self.is_os_sep, self.n_boundary_cells, main_grid.nthreads)
+ cython_update_electric_os(main_grid.updatecoeffsE, main_grid.ID, 1, i_l, i_u + 1, j_l, j_u, k_l, k_u, self.nwz, main_grid.IDlookup['Ey'], main_grid.Ey, self.Hx, 3, 1, -1, 0, self.ratio, self.is_os_sep, self.n_boundary_cells, main_grid.nthreads)
+
+ def update_magnetic_os(self, main_grid):
+
+ i_l = self.i0 - self.is_os_sep
+ i_u = self.i1 + self.is_os_sep
+ j_l = self.j0 - self.is_os_sep
+ j_u = self.j1 + self.is_os_sep
+ k_l = self.k0 - self.is_os_sep
+ k_u = self.k1 + self.is_os_sep
+
+ # Form of FDTD update equations for H
+ # Hz = c0Hz - c1Ey + c2Ex
+ # Hy = c0Hy - c3Ex + c1Ez
+ # Hx = c0Hx - c2Ez + c3Ey
+
+ # Args: sub_grid, normal, l_l, l_u, m_l, m_u, n_l, n_u, nwn, lookup_id, field, inc_field, co, sign_n, sign_f):
+
+ # Front and back
+ cython_update_magnetic_os(main_grid.updatecoeffsH, main_grid.ID, 3, i_l, i_u, k_l, k_u + 1, j_l - 1, j_u, self.nwy, main_grid.IDlookup['Hz'], main_grid.Hz, self.Ex, 2, 1, -1, 1, self.ratio, self.is_os_sep, self.n_boundary_cells, main_grid.nthreads)
+ cython_update_magnetic_os(main_grid.updatecoeffsH, main_grid.ID, 3, i_l, i_u + 1, k_l, k_u, j_l - 1, j_u, self.nwy, main_grid.IDlookup['Hx'], main_grid.Hx, self.Ez, 2, -1, 1, 0, self.ratio, self.is_os_sep, self.n_boundary_cells, main_grid.nthreads)
+
+ # Left and Right
+ cython_update_magnetic_os(main_grid.updatecoeffsH, main_grid.ID, 2, j_l, j_u, k_l, k_u + 1, i_l - 1, i_u, self.nwx, main_grid.IDlookup['Hz'], main_grid.Hz, self.Ey, 1, -1, 1, 1, self.ratio, self.is_os_sep, self.n_boundary_cells, main_grid.nthreads)
+ cython_update_magnetic_os(main_grid.updatecoeffsH, main_grid.ID, 2, j_l, j_u + 1, k_l, k_u, i_l - 1, i_u, self.nwx, main_grid.IDlookup['Hy'], main_grid.Hy, self.Ez, 1, 1, -1, 0, self.ratio, self.is_os_sep, self.n_boundary_cells, main_grid.nthreads)
+
+ # bottom and top
+ cython_update_magnetic_os(main_grid.updatecoeffsH, main_grid.ID, 1, i_l, i_u, j_l, j_u + 1, k_l - 1, k_u, self.nwz, main_grid.IDlookup['Hy'], main_grid.Hy, self.Ex, 3, -1, 1, 1, self.ratio, self.is_os_sep, self.n_boundary_cells, main_grid.nthreads)
+ cython_update_magnetic_os(main_grid.updatecoeffsH, main_grid.ID, 1, i_l, i_u + 1, j_l, j_u, k_l - 1, k_u, self.nwz, main_grid.IDlookup['Hx'], main_grid.Hx, self.Ey, 3, 1, -1, 0, self.ratio, self.is_os_sep, self.n_boundary_cells, main_grid.nthreads)
+
+ def __str__(self):
+
+ self.memory_estimate_basic()
+
+ s = '\n'
+ s += Fore.CYAN
+ s += 'Sub Grid HSG\n'
+ s += 'Name: {}\n'.format(self.name)
+ s += 'dx, dy, dz: {}m {}m {}m\n'.format(self.dx, self.dy, self.dz)
+ s += 'dt: {}s\n'.format(self.dt)
+ s += 'Memory Estimate: {}\n'.format(human_size(self.memoryusage))
+ s += 'Position: ({}m, {}m, {}m), ({}m, {}m, {}m)\n'.format(self.x1,
+ self.y1,
+ self.z1,
+ self.x2,
+ self.y2,
+ self.z2)
+ s += 'Main Grid Indices: lower left({}, {}, {}), upper right({}, {}, {})\n'.format(self.i0, self.j0, self.k0, self.i1, self.j1, self.k1)
+ s += 'Total Cells: {} {} {}\n'.format(self.nx, self.ny, self.nz)
+ s += 'Working Region Cells: {} {} {}\n'.format(self.nwx,
+ self.nwy,
+ self.nwz)
+ for h in self.hertziandipoles:
+ s += 'Hertizian dipole: {} {} {}\n'.format(h.xcoord,
+ h.ycoord,
+ h.zcoord)
+ s += str([x for x in self.waveforms
+ if x.ID == h.waveformID][0]) + '\n'
+ for r in self.rxs:
+ s += 'Receiver: {} {} {}\n'.format(r.xcoord, r.ycoord, r.zcoord)
+
+ for tl in self.transmissionlines:
+ s += 'Transmission Line: {} {} {}\n'.format(tl.xcoord, tl.ycoord, tl.zcoord)
+ s += str([x for x in self.waveforms
+ if x.ID == tl.waveformID][0]) + '\n'
+ s += Style.RESET_ALL
+ return s
diff --git a/gprMax/subgrids/solver.py b/gprMax/subgrids/updates.py
similarity index 51%
rename from gprMax/subgrids/solver.py
rename to gprMax/subgrids/updates.py
index 41ee6138..128d876e 100644
--- a/gprMax/subgrids/solver.py
+++ b/gprMax/subgrids/updates.py
@@ -1,241 +1,167 @@
-from ..cython.fields_updates_normal import update_electric
-from ..cython.fields_updates_normal import update_magnetic
-from ..fields_outputs import store_outputs
-from ..utilities import get_terminal_width
-from ..exceptions import GeneralError
-
-from .subgrid_hsg import SubGridHSG
-from .precursor_nodes import PrecursorNodes as PrecursorNodesHSG
-from .precursor_nodes_filtered import PrecursorNodes as PrecursorNodesFilteredHSG
-
-from tqdm import tqdm
-from time import perf_counter
-
-import os
-import sys
-
-from ..updates import CPUUpdates
-
-
-
-def create_updates(G):
- """Return the solver for the given subgrids."""
- updaters = []
-
- for sg in G.subgrids:
- print(sg)
- sg_type = type(sg)
- if sg_type == SubGridHSG and sg.filter:
- precursors = PrecursorNodesFilteredHSG(G, sg)
- elif sg_type == SubGridHSG and not sg.filter:
- precursors = PrecursorNodesHSG(G, sg)
- else:
- raise GeneralError(str(sg) + ' is not a subgrid type')
-
- sgu = SubgridUpdater(sg, precursors, G)
- updaters.append(sgu)
-
- updates = SubgridUpdates(G, updaters)
- return updates
-
-class SubgridUpdates(CPUUpdates):
-
- def __init__(self, G, updaters):
- super().__init__(G)
- self.updaters = updaters
-
- def hsg_1(self):
- """Method to update the subgrids over the first phase."""
- for sg_updater in self.updaters:
- sg_updater.hsg_1()
-
- def hsg_2(self):
- """Method to update the subgrids over the second phase."""
- for sg_updater in self.updaters:
- sg_updater.hsg_2()
-
-class SubGridSolver:
- """Solver for subgridding simulations."""
-
- """Class to call the various update methods required for an HSG-Subgrid simulation.
- Multiple subgrids can be updated by adding more subgrid_updater objects to the subgrid_updater
- array.
- """
-
- def __init__(self, G, updates, hsg=True):
- """
- Args:
- G (G): Grid class instance - holds essential parameters
- describing the model.
- updates: (list): list of subgrid_updaters used for updating
- the subgrids
- hsg (bool): HSG methods for subgrids will not be called if False.
- """
- self.G = G
- self.updates = updates
- self.hsg = hsg
-
- def store_snapshots(self):
- """Store any snapshots."""
- for snap in self.G.snapshots:
- if snap.time == self.G.iteration + 1:
- snap.store(self.G)
-
- def solve(self, iterations):
- """Run timestepping."""
- tsolvestart = perf_counter()
- self.iterations = iterations
- # for time step in range(self.G.iterations):
-
- # The main grid FDTD loop
- for iteration in self.iterations:
- self.updates.grid.iteration = iteration
- self.updates.store_outputs()
- #self.updates.store_snapshots(iteration)
- self.updates.update_magnetic()
- self.updates.update_magnetic_pml()
- self.updates.update_magnetic_sources(iteration)
- self.updates.hsg_2()
- self.updates.update_electric_a()
- self.updates.update_electric_pml()
- self.updates.update_electric_sources(iteration)
- self.updates.hsg_1()
- self.updates.update_electric_b()
-
- # Keep track of the index. Required for saving output correctly
- self.G.iteration = iteration
-
- # Return the elapsed time
- tsolve = perf_counter() - tsolvestart
-
- return tsolve
-
- def write_snapshots(self, iteration):
- # Write any snapshots to file
- for i, snap in enumerate(self.G.snapshots):
- if snap.time == iteration + 1:
- snapiters = 36 * (((snap.xf - snap.xs) / snap.dx) * ((snap.yf - snap.ys) / snap.dy) * ((snap.zf - snap.zs) / snap.dz))
- pbar = tqdm(total=snapiters, leave=False, unit='byte', unit_scale=True, desc=' Writing snapshot file {} of {}, {}'.format(i + 1, len(self.G.snapshots), os.path.split(snap.filename)[1]), ncols=get_terminal_width() - 1, file=sys.stdout, disable=self.G.tqdmdisable)
-
- # Use this call to print out main grid and subgrids
- snap.write_vtk_imagedata(self.G.Ex, self.G.Ey, self.G.Ez, self.G.Hx, self.G.Hy, self.G.Hz, self.G, pbar, sub_grids=self.G.subgrids)
-
- # Use this call to print out the standard grid without subgrid
- # snap.write_vtk_imagedata(self.G.Ex, self.G.Ey, self.G.Ez, self.G.Hx, self.G.Hy, self.G.Hz, self.G, pbar)
-
- # Use this call to print out only the subgrid - use in combination with commented code in .multi_cmds/snapshots.py
- # snap.write_vtk_imagedata_fast(self.grid)
- pbar.close()
-
-
-class SubgridUpdater(CPUUpdates):
- """Class to handle updating the electric and magnetic fields of an HSG
- subgrid. The IS, OS, subgrid region and the electric/magnetic sources are updated
- using the precursor regions.
- """
-
- def __init__(self, subgrid, precursors, G):
- """
- Args:
- subgrid (SubGrid3d): Subgrid to be updated
- precursors (PrecursorNodes): Precursor nodes associated with
- the subgrid
- G (class): Grid class instance - holds essential parameters
- describing the model.
- """
- super().__init__(subgrid)
- self.precursors = precursors
- self.G = G
- self.source_iteration = 0
-
- def hsg_1(self):
- """This is the first half of the subgrid update. Takes the time step
- up to the main grid magnetic update"""
- G = self.G
- sub_grid = self.grid
- precursors = self.precursors
-
- precursors.update_electric()
-
- upper_m = int(sub_grid.ratio / 2 - 0.5)
-
- for m in range(1, upper_m + 1):
-
- # STD update, interpolate inc. field in time, apply correction
- self.store_outputs()
- self.update_electric_a()
- self.update_electric_pml()
- precursors.interpolate_magnetic_in_time(int(m + sub_grid.ratio / 2 - 0.5))
- sub_grid.update_electric_is(precursors)
- self.update_electric_b()
-
- self.update_sub_grid_electric_sources()
-
- # STD update, interpolate inc. field in time, apply correction
- self.update_magnetic()
- self.update_magnetic_pml()
- precursors.interpolate_electric_in_time(m)
- sub_grid.update_magnetic_is(precursors)
- self.update_sub_grid_magnetic_sources()
-
- self.store_outputs()
- self.update_electric_a()
- self.update_electric_pml()
- precursors.calc_exact_magnetic_in_time()
- sub_grid.update_electric_is(precursors)
- self.update_electric_b()
- self.update_sub_grid_electric_sources()
- sub_grid.update_electric_os(G)
-
- def hsg_2(self):
- """This is the first half of the subgrid update. Takes the time step
- up to the main grid electric update"""
- G = self.G
- sub_grid = self.grid
- precursors = self.precursors
-
- precursors.update_magnetic()
-
- upper_m = int(sub_grid.ratio / 2 - 0.5)
-
- for m in range(1, upper_m + 1):
-
- self.update_magnetic()
- self.update_magnetic_pml()
-
- precursors.interpolate_electric_in_time(int(m + sub_grid.ratio / 2 - 0.5))
- sub_grid.update_magnetic_is(precursors)
- self.update_sub_grid_magnetic_sources()
-
- self.store_outputs()
- self.update_electric_a()
- self.update_electric_pml()
-
- precursors.interpolate_magnetic_in_time(m)
- sub_grid.update_electric_is(precursors)
- self.update_electric_b()
-
- self.update_sub_grid_electric_sources()
-
- self.update_magnetic()
- self.update_magnetic_pml()
- precursors.calc_exact_electric_in_time()
- sub_grid.update_magnetic_is(precursors)
- self.update_sub_grid_magnetic_sources()
- sub_grid.update_magnetic_os(G)
-
- def update_sub_grid_electric_sources(self):
- """Update any electric sources in the subgrid"""
- sg = self.grid
- for source in sg.voltagesources + sg.transmissionlines + sg.hertziandipoles:
- source.update_electric(self.source_iteration, sg.updatecoeffsE, sg.ID,
- sg.Ex, sg.Ey, sg.Ez, sg)
- self.source_iteration += 1
- self.grid.iteration = self.source_iteration
-
- def update_sub_grid_magnetic_sources(self):
- """Update any magnetic sources in the subgrid"""
- sg = self.grid
- for source in sg.transmissionlines + sg.magneticdipoles:
- source.update_magnetic(self.source_iteration, sg.updatecoeffsH, sg.ID,
- sg.Hx, sg.Hy, sg.Hz, sg)
+# Copyright (C) 2015-2019: The University of Edinburgh
+# Authors: Craig Warren and Antonis Giannopoulos
+#
+# This file is part of gprMax.
+#
+# gprMax is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# gprMax is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with gprMax. If not, see .
+from ..cython.fields_updates_normal import update_electric
+from ..cython.fields_updates_normal import update_magnetic
+from ..fields_outputs import store_outputs
+from ..utilities import get_terminal_width
+from ..exceptions import GeneralError
+
+from .subgrid_hsg import SubGridHSG
+from .precursor_nodes import PrecursorNodes as PrecursorNodesHSG
+from .precursor_nodes_filtered import PrecursorNodes as PrecursorNodesFilteredHSG
+
+from tqdm import tqdm
+from time import perf_counter
+
+import os
+import sys
+
+from ..updates import CPUUpdates
+
+def create_updates(G):
+ """Return the solver for the given subgrids."""
+ updaters = []
+
+ for sg in G.subgrids:
+ print(sg)
+ sg_type = type(sg)
+ if sg_type == SubGridHSG and sg.filter:
+ precursors = PrecursorNodesFilteredHSG(G, sg)
+ elif sg_type == SubGridHSG and not sg.filter:
+ precursors = PrecursorNodesHSG(G, sg)
+ else:
+ raise GeneralError(str(sg) + ' is not a subgrid type')
+
+ sgu = SubgridUpdater(sg, precursors, G)
+ updaters.append(sgu)
+
+ updates = SubgridUpdates(G, updaters)
+ return updates
+
+class SubgridUpdates(CPUUpdates):
+
+ def __init__(self, G, updaters):
+ super().__init__(G)
+ self.updaters = updaters
+
+ def hsg_1(self):
+ """Method to update the subgrids over the first phase."""
+ for sg_updater in self.updaters:
+ sg_updater.hsg_1()
+
+ def hsg_2(self):
+ """Method to update the subgrids over the second phase."""
+ for sg_updater in self.updaters:
+ sg_updater.hsg_2()
+
+
+class SubgridUpdater(CPUUpdates):
+ """Class to handle updating the electric and magnetic fields of an HSG
+ subgrid. The IS, OS, subgrid region and the electric/magnetic sources are updated
+ using the precursor regions.
+ """
+
+ def __init__(self, subgrid, precursors, G):
+ """
+ Args:
+ subgrid (SubGrid3d): Subgrid to be updated
+ precursors (PrecursorNodes): Precursor nodes associated with
+ the subgrid - contain interpolated fields
+ G (class): Grid class instance - holds essential parameters
+ describing the model.
+ """
+ super().__init__(subgrid)
+ self.precursors = precursors
+ self.G = G
+ self.source_iteration = 0
+
+ def hsg_1(self):
+ """This is the first half of the subgrid update. Takes the time step
+ up to the main grid magnetic update"""
+ G = self.G
+ sub_grid = self.grid
+ precursors = self.precursors
+
+ precursors.update_electric()
+
+ upper_m = int(sub_grid.ratio / 2 - 0.5)
+
+ for m in range(1, upper_m + 1):
+
+ # STD update, interpolate inc. field in time, apply correction
+ self.store_outputs()
+ self.update_electric_a()
+ self.update_electric_pml()
+ precursors.interpolate_magnetic_in_time(int(m + sub_grid.ratio / 2 - 0.5))
+ sub_grid.update_electric_is(precursors)
+ self.update_electric_b()
+
+ self.update_electric_sources()
+
+ # STD update, interpolate inc. field in time, apply correction
+ self.update_magnetic()
+ self.update_magnetic_pml()
+ precursors.interpolate_electric_in_time(m)
+ sub_grid.update_magnetic_is(precursors)
+ self.update_magnetic_sources()
+
+ self.store_outputs()
+ self.update_electric_a()
+ self.update_electric_pml()
+ precursors.calc_exact_magnetic_in_time()
+ sub_grid.update_electric_is(precursors)
+ self.update_electric_b()
+ self.update_electric_sources()
+ sub_grid.update_electric_os(G)
+
+ def hsg_2(self):
+ """This is the first half of the subgrid update. Takes the time step
+ up to the main grid electric update"""
+ G = self.G
+ sub_grid = self.grid
+ precursors = self.precursors
+
+ precursors.update_magnetic()
+
+ upper_m = int(sub_grid.ratio / 2 - 0.5)
+
+ for m in range(1, upper_m + 1):
+
+ self.update_magnetic()
+ self.update_magnetic_pml()
+
+ precursors.interpolate_electric_in_time(int(m + sub_grid.ratio / 2 - 0.5))
+ sub_grid.update_magnetic_is(precursors)
+ self.update_magnetic_sources()
+
+ self.store_outputs()
+ self.update_electric_a()
+ self.update_electric_pml()
+
+ precursors.interpolate_magnetic_in_time(m)
+ sub_grid.update_electric_is(precursors)
+ self.update_electric_b()
+
+ self.update_electric_sources()
+
+ self.update_magnetic()
+ self.update_magnetic_pml()
+ precursors.calc_exact_electric_in_time()
+ sub_grid.update_magnetic_is(precursors)
+ self.update_magnetic_sources()
+ sub_grid.update_magnetic_os(G)
diff --git a/gprMax/subgrids/user_objects.py b/gprMax/subgrids/user_objects.py
index 8ed9d738..eb8d2902 100644
--- a/gprMax/subgrids/user_objects.py
+++ b/gprMax/subgrids/user_objects.py
@@ -1,205 +1,195 @@
-from .subgrid_hsg import SubGridHSG as SubGridHSGUser
-from .multi import ReferenceRx as ReferenceRxUser
-from ..exceptions import CmdInputError
-from ..cmds_multiple import UserObjectMulti
-from ..cmds_geometry.cmds_geometry import UserObjectGeometry
-from ..cmds_multiple import Rx
-from gprMax import config
-
-from copy import copy
-
-import numpy as np
-
-
-class SubGridBase(UserObjectMulti):
- """Class to allow UserObjectMulti and UserObjectGeometry to be nested in SubGrid type user objects."""
-
- def __init__(self, **kwargs):
- """Constructor."""
- super().__init__(**kwargs)
- self.children_multiple = []
- self.children_geometry = []
-
- def add(self, node):
- """Function to add other user objects. Geometry and multi only."""
- if isinstance(node, UserObjectMulti):
- self.children_multiple.append(node)
- elif isinstance(node, UserObjectGeometry):
- self.children_geometry.append(node)
- else:
- raise Exception(str(node) + ' This Object can not be added to a sub grid')
-
- def check_filters(self, grid):
- """Check the filter of other grids - Only allow filters all on or filters all off."""
- if grid.subgrids:
- f = grid.subgrids[0]
- if f != self.kwargs['filter']:
- raise CmdInputError(self.__str__() + "Filters should be on or off. Set Filter on or off for all subgrids")
-
- def set_discretisation(self, sg, grid):
- """Set the spatial discretisation."""
- sg.dx = grid.dx / sg.ratio
- sg.dy = grid.dy / sg.ratio
- sg.dz = grid.dz / sg.ratio
- sg.dl = np.array([sg.dx, sg.dy, sg.dz])
-
- def set_main_grid_indices(self, sg, grid, uip, p1, p2):
- """Set subgrid indices related to main grid placement."""
- # Main grid indices of the sub grid. These are dummy indices. They are
- # not user internal except for printing to the user
- sg.i0_u, sg.j0_u, sg.k0_u = p1
- sg.i1_u, sg.j1_u, sg.k1_u = p2
-
- # The actual sub gridded area (IS index) is 4 cells in
- sg.i0, sg.j0, sg.k0 = np.add([sg.i0_u, sg.j0_u, sg.k0_u], sg.is_os_sep)
- sg.i1, sg.j1, sg.k1 = np.subtract([sg.i1_u, sg.j1_u, sg.k1_u], sg.is_os_sep)
-
- # Main grid indices of the sub grid. These are dummy indices. They are
- # not user internal except for printing to the user
- sg.x1_u, sg.y1_u, sg.z1_u = uip.round_to_grid(p1)
- sg.x2_u, sg.y2_u, sg.z2_u = uip.round_to_grid(p2)
-
- sg.x1, sg.y1, sg.z1 = np.add([sg.x1_u, sg.y1_u, sg.z1_u], sg.is_os_sep * sg.dx)
- sg.x2, sg.y2, sg.z2 = np.subtract([sg.x2_u, sg.y2_u, sg.z2_u], sg.is_os_sep * sg.dx)
-
- def set_name(self, sg):
- sg.name = self.kwargs['id']
-
- def set_working_region_cells(self, sg):
- """Number of cells in each dimension for the working region."""
- sg.nwx = (sg.i1 - sg.i0) * sg.ratio
- sg.nwy = (sg.j1 - sg.j0) * sg.ratio
- sg.nwz = (sg.k1 - sg.k0) * sg.ratio
-
- def set_total_cells(self, sg):
- """Number of cells in each dimension for the whole region."""
- sg.nx = 2 * sg.n_boundary_cells_x + sg.nwx
- sg.ny = 2 * sg.n_boundary_cells_y + sg.nwy
- sg.nz = 2 * sg.n_boundary_cells_z + sg.nwz
-
- def set_iterations(self, sg, main):
- """Set number of iterations that will take place in the subgrid."""
- sg.iterations = main.iterations * sg.ratio
-
- def setup(self, sg, grid, uip):
- """"Common setup to both all subgrid types."""
- p1 = self.kwargs['p1']
- p2 = self.kwargs['p2']
-
- p1, p2 = uip.check_box_points(p1, p2, self.__str__())
-
- self.check_filters(grid)
-
- self.set_discretisation(sg, grid)
-
- # Set the temporal discretisation
- sg.calculate_dt()
-
- # ensure stability
- sg.round_time_step()
-
- # set the indices related to the subgrids main grid placement
- self.set_main_grid_indices(sg, grid, uip, p1, p2)
-
- """
- try:
- uip.check_box_points([sg.i0, sg.j0, sg.k0],
- [sg.i1, sg.j1, sg.k1], cmd_str)
- except CmdInputError:
- es_f = 'The subgrid should extend at least {} cells'
- es = es_f.format(sg.is_os_sep * 2)
- raise CmdInputError(cmd_str, es)
- """
-
- self.set_working_region_cells(sg)
- self.set_total_cells(sg)
- self.set_iterations(sg, grid)
- self.set_name(sg)
-
- # Copy a reference for the main grid to the sub grid
- sg.parent_grid = grid
-
- sg.timewindow = grid.timewindow
-
- # Copy a subgrid reference to self so that children.create(grid, uip) can access
- # the correct grid
- self.subgrid = sg
-
- # Copy over built in materials
- sg.materials = [copy(m) for m in grid.materials if m.numID in range(0, grid.n_built_in_materials + 1)]
- # use same number of threads
- sg.nthreads = grid.nthreads
-
- # Dont mix and match different subgrids
- for sg_made in grid.subgrids:
- if type(sg) != type(sg_made):
- raise CmdInputError(self.__str__() + ' Please only use one type of subgrid')
-
- # Reference the sub grid under the main grid to which it belongs.
- grid.subgrids.append(sg)
-
-
-class SubGridHSG(SubGridBase):
- """HSG User Object."""
- def __init__(self,
- p1=None,
- p2=None,
- ratio=3,
- ID='',
- is_os_sep=3,
- pml_separation=4,
- subgrid_pml_thickness=6,
- interpolation='linear',
- loss_mechanism=False,
- loss_factor=False,
- filter=True,
- **kwargs):
- """Constructor."""
-
- pml_separation = ratio // 2 + 2
-
- # copy over the optional parameters
- kwargs['p1'] = p1
- kwargs['p2'] = p2
- kwargs['ratio'] = ratio
- kwargs['ID'] = ID
- kwargs['is_os_sep'] = is_os_sep
- kwargs['pml_separation'] = pml_separation
- kwargs['subgrid_pml_thickness'] = subgrid_pml_thickness
- kwargs['interpolation'] = interpolation
- kwargs['filter'] = filter
- kwargs['loss_mechanism'] = loss_mechanism
- kwargs['loss_factor'] = loss_factor
-
- super().__init__(**kwargs)
- self.order = 18
- self.hash = '#subgrid_hsg'
-
- def create(self, grid, uip):
- sg = SubGridHSGUser(**self.kwargs)
- self.setup(sg, grid, uip)
- if config.is_messages():
- print(sg)
- return sg
-
-
-
-class ReferenceRx(Rx):
- """ReferenceRx User Object."""
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- self.hash = '#rx_reference'
- self.constructor = ReferenceRxUser
-
- def create(self, grid, uip):
-
- r = super().create(grid, uip)
-
- try:
- ratio = self.kwargs['ratio']
- r.ratio = ratio
- r.offset = ratio // 2
-
- except KeyError:
- raise CmdInputError("'{}' has an no ratio parameter".format(self.__str__()))
+from .subgrid_hsg import SubGridHSG as SubGridHSGUser
+from .multi import ReferenceRx as ReferenceRxUser
+from ..exceptions import CmdInputError
+from ..cmds_multiple import UserObjectMulti
+from ..cmds_geometry.cmds_geometry import UserObjectGeometry
+from ..cmds_multiple import Rx
+from gprMax import config
+
+from copy import copy
+
+import numpy as np
+
+
+class SubGridBase(UserObjectMulti):
+ """Class to allow UserObjectMulti and UserObjectGeometry to be nested in SubGrid type user objects."""
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(**kwargs)
+ self.children_multiple = []
+ self.children_geometry = []
+
+ def add(self, node):
+ """Function to add other user objects. Geometry and multi only."""
+ if isinstance(node, UserObjectMulti):
+ self.children_multiple.append(node)
+ elif isinstance(node, UserObjectGeometry):
+ self.children_geometry.append(node)
+ else:
+ raise Exception(str(node) + ' This Object can not be added to a sub grid')
+
+ def check_filters(self, grid):
+ """Check the filter of other grids - Only allow filters all on or filters all off."""
+ if grid.subgrids:
+ f = grid.subgrids[0]
+ if f != self.kwargs['filter']:
+ raise CmdInputError(self.__str__() + "Filters should be on or off. Set Filter on or off for all subgrids")
+
+ def set_discretisation(self, sg, grid):
+ """Set the spatial discretisation."""
+ sg.dx = grid.dx / sg.ratio
+ sg.dy = grid.dy / sg.ratio
+ sg.dz = grid.dz / sg.ratio
+ sg.dl = np.array([sg.dx, sg.dy, sg.dz])
+
+ def set_main_grid_indices(self, sg, grid, uip, p1, p2):
+ """Set subgrid indices related to main grid placement."""
+ # location of the IS
+ sg.i0, sg.j0, sg.k0 = p1
+ sg.i1, sg.j1, sg.k1 = p2
+
+ sg.x1, sg.y1, sg.z1 = uip.round_to_grid(p1)
+ sg.x2, sg.y2, sg.z2 = uip.round_to_grid(p2)
+
+ def set_name(self, sg):
+ sg.name = self.kwargs['id']
+
+ def set_working_region_cells(self, sg):
+ """Number of cells in each dimension for the working region."""
+ sg.nwx = (sg.i1 - sg.i0) * sg.ratio
+ sg.nwy = (sg.j1 - sg.j0) * sg.ratio
+ sg.nwz = (sg.k1 - sg.k0) * sg.ratio
+
+ def set_total_cells(self, sg):
+ """Number of cells in each dimension for the whole region."""
+ sg.nx = 2 * sg.n_boundary_cells_x + sg.nwx
+ sg.ny = 2 * sg.n_boundary_cells_y + sg.nwy
+ sg.nz = 2 * sg.n_boundary_cells_z + sg.nwz
+
+ def set_iterations(self, sg, main):
+ """Set number of iterations that will take place in the subgrid."""
+ sg.iterations = main.iterations * sg.ratio
+
+ def setup(self, sg, grid, uip):
+ """"Common setup to both all subgrid types."""
+ p1 = self.kwargs['p1']
+ p2 = self.kwargs['p2']
+
+ p1, p2 = uip.check_box_points(p1, p2, self.__str__())
+
+ self.check_filters(grid)
+
+ self.set_discretisation(sg, grid)
+
+ # Set the temporal discretisation
+ sg.calculate_dt()
+
+ # ensure stability
+ sg.round_time_step()
+
+ # set the indices related to the subgrids main grid placement
+ self.set_main_grid_indices(sg, grid, uip, p1, p2)
+
+ """
+ try:
+ uip.check_box_points([sg.i0, sg.j0, sg.k0],
+ [sg.i1, sg.j1, sg.k1], cmd_str)
+ except CmdInputError:
+ es_f = 'The subgrid should extend at least {} cells'
+ es = es_f.format(sg.is_os_sep * 2)
+ raise CmdInputError(cmd_str, es)
+ """
+
+ self.set_working_region_cells(sg)
+ self.set_total_cells(sg)
+ self.set_iterations(sg, grid)
+ self.set_name(sg)
+
+ # Copy a reference for the main grid to the sub grid
+ sg.parent_grid = grid
+
+ sg.timewindow = grid.timewindow
+
+ # Copy a subgrid reference to self so that children.create(grid, uip) can access
+ # the correct grid
+ self.subgrid = sg
+
+ # Copy over built in materials
+ sg.materials = [copy(m) for m in grid.materials if m.numID in range(0, grid.n_built_in_materials + 1)]
+ # use same number of threads
+ sg.nthreads = grid.nthreads
+
+ # Dont mix and match different subgrids
+ for sg_made in grid.subgrids:
+ if type(sg) != type(sg_made):
+ raise CmdInputError(self.__str__() + ' Please only use one type of subgrid')
+
+ # Reference the sub grid under the main grid to which it belongs.
+ grid.subgrids.append(sg)
+
+
+class SubGridHSG(SubGridBase):
+ """HSG User Object."""
+ def __init__(self,
+ p1=None,
+ p2=None,
+ ratio=3,
+ ID='',
+ is_os_sep=3,
+ pml_separation=4,
+ subgrid_pml_thickness=6,
+ interpolation='linear',
+ loss_mechanism=False,
+ loss_factor=False,
+ filter=True,
+ **kwargs):
+ """Constructor."""
+
+ pml_separation = ratio // 2 + 2
+
+ # copy over the optional parameters
+ kwargs['p1'] = p1
+ kwargs['p2'] = p2
+ kwargs['ratio'] = ratio
+ kwargs['ID'] = ID
+ kwargs['is_os_sep'] = is_os_sep
+ kwargs['pml_separation'] = pml_separation
+ kwargs['subgrid_pml_thickness'] = subgrid_pml_thickness
+ kwargs['interpolation'] = interpolation
+ kwargs['filter'] = filter
+ kwargs['loss_mechanism'] = loss_mechanism
+ kwargs['loss_factor'] = loss_factor
+
+ super().__init__(**kwargs)
+ self.order = 18
+ self.hash = '#subgrid_hsg'
+
+ def create(self, grid, uip):
+ sg = SubGridHSGUser(**self.kwargs)
+ self.setup(sg, grid, uip)
+ if config.is_messages():
+ print(sg)
+ return sg
+
+
+
+class ReferenceRx(Rx):
+ """ReferenceRx User Object."""
+
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ self.hash = '#rx_reference'
+ self.constructor = ReferenceRxUser
+
+ def create(self, grid, uip):
+
+ r = super().create(grid, uip)
+
+ try:
+ ratio = self.kwargs['ratio']
+ r.ratio = ratio
+ r.offset = ratio // 2
+
+ except KeyError:
+ raise CmdInputError("'{}' has an no ratio parameter".format(self.__str__()))
diff --git a/gprMax/templates/fields_updates_dispersive_template b/gprMax/templates/fields_updates_dispersive_template
index 88626be5..9906ced5 100644
--- a/gprMax/templates/fields_updates_dispersive_template
+++ b/gprMax/templates/fields_updates_dispersive_template
@@ -1,314 +1,314 @@
-# Copyright (C) 2015-2019: The University of Edinburgh
-# Authors: Craig Warren and Antonis Giannopoulos
-#
-# This file is part of gprMax.
-#
-# gprMax is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# gprMax is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with gprMax. If not, see .
-
-import numpy as np
-cimport numpy as np
-from cython.parallel import prange
-
-cdef extern from "complex.h" nogil:
- double creal(double complex z)
- float crealf(float complex z)
-
-
-###############################################################
-# Electric field updates - dispersive materials - multipole A #
-###############################################################
-
-{% for item in functions %}
-cpdef void {{ item.name_a }}(
- int nx,
- int ny,
- int nz,
- int nthreads,
- int maxpoles,
- {{ item.field_type }}[:, ::1] updatecoeffsE,
- {{ item.dispersive_type }}[:, ::1] updatecoeffsdispersive,
- np.uint32_t[:, :, :, ::1] ID,
- {{ item.dispersive_type }}[:, :, :, ::1] Tx,
- {{ item.dispersive_type }}[:, :, :, ::1] Ty,
- {{ item.dispersive_type }}[:, :, :, ::1] Tz,
- {{ item.field_type }}[:, :, ::1] Ex,
- {{ item.field_type }}[:, :, ::1] Ey,
- {{ item.field_type }}[:, :, ::1] Ez,
- {{ item.field_type }}[:, :, ::1] Hx,
- {{ item.field_type }}[:, :, ::1] Hy,
- {{ item.field_type }}[:, :, ::1] Hz
- ):
- """This function updates the electric field components when dispersive materials (with multiple poles) are present.
-
- Args:
- nx, ny, nz (int): Grid size in cells
- nthreads (int): Number of threads to use
- maxpoles (int): Maximum number of poles
- updatecoeffs, T, ID, E, H (memoryviews): Access to update coeffients, temporary, ID and field component arrays
- """
-
- cdef Py_ssize_t i, j, k, pole
- cdef int material
- cdef float phi = 0
-
- # Ex component
- if ny != 1 or nz != 1:
- for i in prange(0, nx, nogil=True, schedule='static', num_threads=nthreads):
- for j in range(1, ny):
- for k in range(1, nz):
- material = ID[0, i, j, k]
- phi = 0
- for pole in range(maxpoles):
- {% if 'complex' in item.dispersive_type %}
- phi = phi + {{ item.real_part }}(updatecoeffsdispersive[material, pole * 3]) * {{ item.real_part }}(Tx[pole, i, j, k])
- {% else %}
- phi = phi + updatecoeffsdispersive[material, pole * 3] * Tx[pole, i, j, k]
- {% endif %}
- Tx[pole, i, j, k] = updatecoeffsdispersive[material, 1 + (pole * 3)] * Tx[pole, i, j, k] + updatecoeffsdispersive[material, 2 + (pole * 3)] * Ex[i, j, k]
- Ex[i, j, k] = updatecoeffsE[material, 0] * Ex[i, j, k] + updatecoeffsE[material, 2] * (Hz[i, j, k] - Hz[i, j - 1, k]) - updatecoeffsE[material, 3] * (Hy[i, j, k] - Hy[i, j, k - 1]) - updatecoeffsE[material, 4] * phi
-
- # Ey component
- if nx != 1 or nz != 1:
- for i in prange(1, nx, nogil=True, schedule='static', num_threads=nthreads):
- for j in range(0, ny):
- for k in range(1, nz):
- material = ID[1, i, j, k]
- phi = 0
- for pole in range(maxpoles):
- {% if 'complex' in item.dispersive_type %}
- phi = phi + {{ item.real_part }}(updatecoeffsdispersive[material, pole * 3]) * {{ item.real_part }}(Ty[pole, i, j, k])
- {% else %}
- phi = phi + updatecoeffsdispersive[material, pole * 3] * Ty[pole, i, j, k]
- {% endif %}
- Ty[pole, i, j, k] = updatecoeffsdispersive[material, 1 + (pole * 3)] * Ty[pole, i, j, k] + updatecoeffsdispersive[material, 2 + (pole * 3)] * Ey[i, j, k]
- Ey[i, j, k] = updatecoeffsE[material, 0] * Ey[i, j, k] + updatecoeffsE[material, 3] * (Hx[i, j, k] - Hx[i, j, k - 1]) - updatecoeffsE[material, 1] * (Hz[i, j, k] - Hz[i - 1, j, k]) - updatecoeffsE[material, 4] * phi
-
- # Ez component
- if nx != 1 or ny != 1:
- for i in prange(1, nx, nogil=True, schedule='static', num_threads=nthreads):
- for j in range(1, ny):
- for k in range(0, nz):
- material = ID[2, i, j, k]
- phi = 0
- for pole in range(maxpoles):
- {% if 'complex' in item.dispersive_type %}
- phi = phi + {{ item.real_part }}(updatecoeffsdispersive[material, pole * 3]) * {{ item.real_part }}(Tz[pole, i, j, k])
- {% else %}
- phi = phi + updatecoeffsdispersive[material, pole * 3] * Tz[pole, i, j, k]
- {% endif %}
- Tz[pole, i, j, k] = updatecoeffsdispersive[material, 1 + (pole * 3)] * Tz[pole, i, j, k] + updatecoeffsdispersive[material, 2 + (pole * 3)] * Ez[i, j, k]
- Ez[i, j, k] = updatecoeffsE[material, 0] * Ez[i, j, k] + updatecoeffsE[material, 1] * (Hy[i, j, k] - Hy[i - 1, j, k]) - updatecoeffsE[material, 2] * (Hx[i, j, k] - Hx[i, j - 1, k]) - updatecoeffsE[material, 4] * phi
-{% endfor %}
-
-
-###############################################################
-# Electric field updates - dispersive materials - multipole B #
-###############################################################
-
-{% for item in functions %}
-cpdef void {{ item.name_b }}(
- int nx,
- int ny,
- int nz,
- int nthreads,
- int maxpoles,
- {{ item.dispersive_type }}[:, ::1] updatecoeffsdispersive,
- np.uint32_t[:, :, :, ::1] ID,
- {{ item.dispersive_type }}[:, :, :, ::1] Tx,
- {{ item.dispersive_type }}[:, :, :, ::1] Ty,
- {{ item.dispersive_type }}[:, :, :, ::1] Tz,
- {{ item.field_type }}[:, :, ::1] Ex,
- {{ item.field_type }}[:, :, ::1] Ey,
- {{ item.field_type }}[:, :, ::1] Ez
- ):
- """This function updates a temporary dispersive material array when disperisive materials (with multiple poles) are present.
-
- Args:
- nx, ny, nz (int): Grid size in cells
- nthreads (int): Number of threads to use
- maxpoles (int): Maximum number of poles
- updatecoeffs, T, ID, E (memoryviews): Access to update coeffients, temporary, ID and field component arrays
- """
-
- cdef Py_ssize_t i, j, k, pole
- cdef int material
-
- # Ex component
- if ny != 1 or nz != 1:
- for i in prange(0, nx, nogil=True, schedule='static', num_threads=nthreads):
- for j in range(1, ny):
- for k in range(1, nz):
- material = ID[0, i, j, k]
- for pole in range(maxpoles):
- Tx[pole, i, j, k] = Tx[pole, i, j, k] - updatecoeffsdispersive[material, 2 + (pole * 3)] * Ex[i, j, k]
-
- # Ey component
- if nx != 1 or nz != 1:
- for i in prange(1, nx, nogil=True, schedule='static', num_threads=nthreads):
- for j in range(0, ny):
- for k in range(1, nz):
- material = ID[1, i, j, k]
- for pole in range(maxpoles):
- Ty[pole, i, j, k] = Ty[pole, i, j, k] - updatecoeffsdispersive[material, 2 + (pole * 3)] * Ey[i, j, k]
-
- # Ez component
- if nx != 1 or ny != 1:
- for i in prange(1, nx, nogil=True, schedule='static', num_threads=nthreads):
- for j in range(1, ny):
- for k in range(0, nz):
- material = ID[2, i, j, k]
- for pole in range(maxpoles):
- Tz[pole, i, j, k] = Tz[pole, i, j, k] - updatecoeffsdispersive[material, 2 + (pole * 3)] * Ez[i, j, k]
-
-{% endfor %}
-
-
-#################################################################
-# Electric field updates - dispersive materials - single pole A #
-#################################################################
-
-{% for item in functions %}
-cpdef void {{ item.name_a_1 }}(
- int nx,
- int ny,
- int nz,
- int nthreads,
- int maxpoles,
- {{ item.field_type }}[:, ::1] updatecoeffsE,
- {{ item.dispersive_type }}[:, ::1] updatecoeffsdispersive,
- np.uint32_t[:, :, :, ::1] ID,
- {{ item.dispersive_type }}[:, :, :, ::1] Tx,
- {{ item.dispersive_type }}[:, :, :, ::1] Ty,
- {{ item.dispersive_type }}[:, :, :, ::1] Tz,
- {{ item.field_type }}[:, :, ::1] Ex,
- {{ item.field_type }}[:, :, ::1] Ey,
- {{ item.field_type }}[:, :, ::1] Ez,
- {{ item.field_type }}[:, :, ::1] Hx,
- {{ item.field_type }}[:, :, ::1] Hy,
- {{ item.field_type }}[:, :, ::1] Hz
- ):
- """This function updates the electric field components when dispersive materials (with 1 pole) are present.
-
- Args:
- nx, ny, nz (int): Grid size in cells
- nthreads (int): Number of threads to use
- maxpoles (int): Maximum number of poles
- updatecoeffs, T, ID, E, H (memoryviews): Access to update coeffients, temporary, ID and field component arrays
- """
-
- cdef Py_ssize_t i, j, k
- cdef int material
- cdef float phi = 0
-
- # Ex component
- if ny != 1 or nz != 1:
- for i in prange(0, nx, nogil=True, schedule='static', num_threads=nthreads):
- for j in range(1, ny):
- for k in range(1, nz):
- material = ID[0, i, j, k]
- {% if 'complex' in item.dispersive_type %}
- phi = {{ item.real_part }}(updatecoeffsdispersive[material, 0]) * {{ item.real_part }}(Tx[0, i, j, k])
- {% else %}
- phi = updatecoeffsdispersive[material, 0] * Tx[0, i, j, k]
- {% endif %}
- Tx[0, i, j, k] = updatecoeffsdispersive[material, 1] * Tx[0, i, j, k] + updatecoeffsdispersive[material, 2] * Ex[i, j, k]
- Ex[i, j, k] = updatecoeffsE[material, 0] * Ex[i, j, k] + updatecoeffsE[material, 2] * (Hz[i, j, k] - Hz[i, j - 1, k]) - updatecoeffsE[material, 3] * (Hy[i, j, k] - Hy[i, j, k - 1]) - updatecoeffsE[material, 4] * phi
-
- # Ey component
- if nx != 1 or nz != 1:
- for i in prange(1, nx, nogil=True, schedule='static', num_threads=nthreads):
- for j in range(0, ny):
- for k in range(1, nz):
- material = ID[1, i, j, k]
- {% if 'complex' in item.dispersive_type %}
- phi = {{ item.real_part }}(updatecoeffsdispersive[material, 0]) * {{ item.real_part }}(Ty[0, i, j, k])
- {% else %}
- phi = updatecoeffsdispersive[material, 0] * Ty[0, i, j, k]
- {% endif %}
- Ty[0, i, j, k] = updatecoeffsdispersive[material, 1] * Ty[0, i, j, k] + updatecoeffsdispersive[material, 2] * Ey[i, j, k]
- Ey[i, j, k] = updatecoeffsE[material, 0] * Ey[i, j, k] + updatecoeffsE[material, 3] * (Hx[i, j, k] - Hx[i, j, k - 1]) - updatecoeffsE[material, 1] * (Hz[i, j, k] - Hz[i - 1, j, k]) - updatecoeffsE[material, 4] * phi
-
- # Ez component
- if nx != 1 or ny != 1:
- for i in prange(1, nx, nogil=True, schedule='static', num_threads=nthreads):
- for j in range(1, ny):
- for k in range(0, nz):
- material = ID[2, i, j, k]
- {% if 'complex' in item.dispersive_type %}
- phi = {{ item.real_part }}(updatecoeffsdispersive[material, 0]) * {{ item.real_part }}(Tz[0, i, j, k])
- {% else %}
- phi = updatecoeffsdispersive[material, 0] * Tz[0, i, j, k]
- {% endif %}
- Tz[0, i, j, k] = updatecoeffsdispersive[material, 1] * Tz[0, i, j, k] + updatecoeffsdispersive[material, 2] * Ez[i, j, k]
- Ez[i, j, k] = updatecoeffsE[material, 0] * Ez[i, j, k] + updatecoeffsE[material, 1] * (Hy[i, j, k] - Hy[i - 1, j, k]) - updatecoeffsE[material, 2] * (Hx[i, j, k] - Hx[i, j - 1, k]) - updatecoeffsE[material, 4] * phi
-
-{% endfor %}
-
-
-#################################################################
-# Electric field updates - dispersive materials - single pole B #
-#################################################################
-
-{% for item in functions %}
-cpdef void {{ item.name_b_1 }}(
- int nx,
- int ny,
- int nz,
- int nthreads,
- int maxpoles,
- {{ item.dispersive_type }}[:, ::1] updatecoeffsdispersive,
- np.uint32_t[:, :, :, ::1] ID,
- {{ item.dispersive_type }}[:, :, :, ::1] Tx,
- {{ item.dispersive_type }}[:, :, :, ::1] Ty,
- {{ item.dispersive_type }}[:, :, :, ::1] Tz,
- {{ item.field_type }}[:, :, ::1] Ex,
- {{ item.field_type }}[:, :, ::1] Ey,
- {{ item.field_type }}[:, :, ::1] Ez
- ):
- """This function updates a temporary dispersive material array when disperisive materials (with 1 pole) are present.
-
- Args:
- nx, ny, nz (int): Grid size in cells
- nthreads (int): Number of threads to use
- maxpoles (int): Maximum number of poles
- updatecoeffs, T, ID, E (memoryviews): Access to update coeffients, temporary, ID and field component arrays
- """
-
- cdef Py_ssize_t i, j, k
- cdef int material
-
- # Ex component
- if ny != 1 or nz != 1:
- for i in prange(0, nx, nogil=True, schedule='static', num_threads=nthreads):
- for j in range(1, ny):
- for k in range(1, nz):
- material = ID[0, i, j, k]
- Tx[0, i, j, k] = Tx[0, i, j, k] - updatecoeffsdispersive[material, 2] * Ex[i, j, k]
-
- # Ey component
- if nx != 1 or nz != 1:
- for i in prange(1, nx, nogil=True, schedule='static', num_threads=nthreads):
- for j in range(0, ny):
- for k in range(1, nz):
- material = ID[1, i, j, k]
- Ty[0, i, j, k] = Ty[0, i, j, k] - updatecoeffsdispersive[material, 2] * Ey[i, j, k]
-
- # Ez component
- if nx != 1 or ny != 1:
- for i in prange(1, nx, nogil=True, schedule='static', num_threads=nthreads):
- for j in range(1, ny):
- for k in range(0, nz):
- material = ID[2, i, j, k]
- Tz[0, i, j, k] = Tz[0, i, j, k] - updatecoeffsdispersive[material, 2] * Ez[i, j, k]
-{% endfor %}
+# Copyright (C) 2015-2019: The University of Edinburgh
+# Authors: Craig Warren and Antonis Giannopoulos
+#
+# This file is part of gprMax.
+#
+# gprMax is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# gprMax is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with gprMax. If not, see .
+
+import numpy as np
+cimport numpy as np
+from cython.parallel import prange
+
+cdef extern from "complex.h" nogil:
+ double creal(double complex z)
+ float crealf(float complex z)
+
+
+###############################################################
+# Electric field updates - dispersive materials - multipole A #
+###############################################################
+
+{% for item in functions %}
+cpdef void {{ item.name_a }}(
+ int nx,
+ int ny,
+ int nz,
+ int nthreads,
+ int maxpoles,
+ {{ item.field_type }}[:, ::1] updatecoeffsE,
+ {{ item.dispersive_type }}[:, ::1] updatecoeffsdispersive,
+ np.uint32_t[:, :, :, ::1] ID,
+ {{ item.dispersive_type }}[:, :, :, ::1] Tx,
+ {{ item.dispersive_type }}[:, :, :, ::1] Ty,
+ {{ item.dispersive_type }}[:, :, :, ::1] Tz,
+ {{ item.field_type }}[:, :, ::1] Ex,
+ {{ item.field_type }}[:, :, ::1] Ey,
+ {{ item.field_type }}[:, :, ::1] Ez,
+ {{ item.field_type }}[:, :, ::1] Hx,
+ {{ item.field_type }}[:, :, ::1] Hy,
+ {{ item.field_type }}[:, :, ::1] Hz
+ ):
+ """This function updates the electric field components when dispersive materials (with multiple poles) are present.
+
+ Args:
+ nx, ny, nz (int): Grid size in cells
+ nthreads (int): Number of threads to use
+ maxpoles (int): Maximum number of poles
+ updatecoeffs, T, ID, E, H (memoryviews): Access to update coeffients, temporary, ID and field component arrays
+ """
+
+ cdef Py_ssize_t i, j, k, pole
+ cdef int material
+ cdef float phi = 0
+
+ # Ex component
+ if ny != 1 or nz != 1:
+ for i in prange(0, nx, nogil=True, schedule='static', num_threads=nthreads):
+ for j in range(1, ny):
+ for k in range(1, nz):
+ material = ID[0, i, j, k]
+ phi = 0
+ for pole in range(maxpoles):
+ {% if 'complex' in item.dispersive_type %}
+ phi = phi + {{ item.real_part }}(updatecoeffsdispersive[material, pole * 3]) * {{ item.real_part }}(Tx[pole, i, j, k])
+ {% else %}
+ phi = phi + updatecoeffsdispersive[material, pole * 3] * Tx[pole, i, j, k]
+ {% endif %}
+ Tx[pole, i, j, k] = updatecoeffsdispersive[material, 1 + (pole * 3)] * Tx[pole, i, j, k] + updatecoeffsdispersive[material, 2 + (pole * 3)] * Ex[i, j, k]
+ Ex[i, j, k] = updatecoeffsE[material, 0] * Ex[i, j, k] + updatecoeffsE[material, 2] * (Hz[i, j, k] - Hz[i, j - 1, k]) - updatecoeffsE[material, 3] * (Hy[i, j, k] - Hy[i, j, k - 1]) - updatecoeffsE[material, 4] * phi
+
+ # Ey component
+ if nx != 1 or nz != 1:
+ for i in prange(1, nx, nogil=True, schedule='static', num_threads=nthreads):
+ for j in range(0, ny):
+ for k in range(1, nz):
+ material = ID[1, i, j, k]
+ phi = 0
+ for pole in range(maxpoles):
+ {% if 'complex' in item.dispersive_type %}
+ phi = phi + {{ item.real_part }}(updatecoeffsdispersive[material, pole * 3]) * {{ item.real_part }}(Ty[pole, i, j, k])
+ {% else %}
+ phi = phi + updatecoeffsdispersive[material, pole * 3] * Ty[pole, i, j, k]
+ {% endif %}
+ Ty[pole, i, j, k] = updatecoeffsdispersive[material, 1 + (pole * 3)] * Ty[pole, i, j, k] + updatecoeffsdispersive[material, 2 + (pole * 3)] * Ey[i, j, k]
+ Ey[i, j, k] = updatecoeffsE[material, 0] * Ey[i, j, k] + updatecoeffsE[material, 3] * (Hx[i, j, k] - Hx[i, j, k - 1]) - updatecoeffsE[material, 1] * (Hz[i, j, k] - Hz[i - 1, j, k]) - updatecoeffsE[material, 4] * phi
+
+ # Ez component
+ if nx != 1 or ny != 1:
+ for i in prange(1, nx, nogil=True, schedule='static', num_threads=nthreads):
+ for j in range(1, ny):
+ for k in range(0, nz):
+ material = ID[2, i, j, k]
+ phi = 0
+ for pole in range(maxpoles):
+ {% if 'complex' in item.dispersive_type %}
+ phi = phi + {{ item.real_part }}(updatecoeffsdispersive[material, pole * 3]) * {{ item.real_part }}(Tz[pole, i, j, k])
+ {% else %}
+ phi = phi + updatecoeffsdispersive[material, pole * 3] * Tz[pole, i, j, k]
+ {% endif %}
+ Tz[pole, i, j, k] = updatecoeffsdispersive[material, 1 + (pole * 3)] * Tz[pole, i, j, k] + updatecoeffsdispersive[material, 2 + (pole * 3)] * Ez[i, j, k]
+ Ez[i, j, k] = updatecoeffsE[material, 0] * Ez[i, j, k] + updatecoeffsE[material, 1] * (Hy[i, j, k] - Hy[i - 1, j, k]) - updatecoeffsE[material, 2] * (Hx[i, j, k] - Hx[i, j - 1, k]) - updatecoeffsE[material, 4] * phi
+{% endfor %}
+
+
+###############################################################
+# Electric field updates - dispersive materials - multipole B #
+###############################################################
+
+{% for item in functions %}
+cpdef void {{ item.name_b }}(
+ int nx,
+ int ny,
+ int nz,
+ int nthreads,
+ int maxpoles,
+ {{ item.dispersive_type }}[:, ::1] updatecoeffsdispersive,
+ np.uint32_t[:, :, :, ::1] ID,
+ {{ item.dispersive_type }}[:, :, :, ::1] Tx,
+ {{ item.dispersive_type }}[:, :, :, ::1] Ty,
+ {{ item.dispersive_type }}[:, :, :, ::1] Tz,
+ {{ item.field_type }}[:, :, ::1] Ex,
+ {{ item.field_type }}[:, :, ::1] Ey,
+ {{ item.field_type }}[:, :, ::1] Ez
+ ):
+ """This function updates a temporary dispersive material array when disperisive materials (with multiple poles) are present.
+
+ Args:
+ nx, ny, nz (int): Grid size in cells
+ nthreads (int): Number of threads to use
+ maxpoles (int): Maximum number of poles
+ updatecoeffs, T, ID, E (memoryviews): Access to update coeffients, temporary, ID and field component arrays
+ """
+
+ cdef Py_ssize_t i, j, k, pole
+ cdef int material
+
+ # Ex component
+ if ny != 1 or nz != 1:
+ for i in prange(0, nx, nogil=True, schedule='static', num_threads=nthreads):
+ for j in range(1, ny):
+ for k in range(1, nz):
+ material = ID[0, i, j, k]
+ for pole in range(maxpoles):
+ Tx[pole, i, j, k] = Tx[pole, i, j, k] - updatecoeffsdispersive[material, 2 + (pole * 3)] * Ex[i, j, k]
+
+ # Ey component
+ if nx != 1 or nz != 1:
+ for i in prange(1, nx, nogil=True, schedule='static', num_threads=nthreads):
+ for j in range(0, ny):
+ for k in range(1, nz):
+ material = ID[1, i, j, k]
+ for pole in range(maxpoles):
+ Ty[pole, i, j, k] = Ty[pole, i, j, k] - updatecoeffsdispersive[material, 2 + (pole * 3)] * Ey[i, j, k]
+
+ # Ez component
+ if nx != 1 or ny != 1:
+ for i in prange(1, nx, nogil=True, schedule='static', num_threads=nthreads):
+ for j in range(1, ny):
+ for k in range(0, nz):
+ material = ID[2, i, j, k]
+ for pole in range(maxpoles):
+ Tz[pole, i, j, k] = Tz[pole, i, j, k] - updatecoeffsdispersive[material, 2 + (pole * 3)] * Ez[i, j, k]
+
+{% endfor %}
+
+
+#################################################################
+# Electric field updates - dispersive materials - single pole A #
+#################################################################
+
+{% for item in functions %}
+cpdef void {{ item.name_a_1 }}(
+ int nx,
+ int ny,
+ int nz,
+ int nthreads,
+ int maxpoles,
+ {{ item.field_type }}[:, ::1] updatecoeffsE,
+ {{ item.dispersive_type }}[:, ::1] updatecoeffsdispersive,
+ np.uint32_t[:, :, :, ::1] ID,
+ {{ item.dispersive_type }}[:, :, :, ::1] Tx,
+ {{ item.dispersive_type }}[:, :, :, ::1] Ty,
+ {{ item.dispersive_type }}[:, :, :, ::1] Tz,
+ {{ item.field_type }}[:, :, ::1] Ex,
+ {{ item.field_type }}[:, :, ::1] Ey,
+ {{ item.field_type }}[:, :, ::1] Ez,
+ {{ item.field_type }}[:, :, ::1] Hx,
+ {{ item.field_type }}[:, :, ::1] Hy,
+ {{ item.field_type }}[:, :, ::1] Hz
+ ):
+ """This function updates the electric field components when dispersive materials (with 1 pole) are present.
+
+ Args:
+ nx, ny, nz (int): Grid size in cells
+ nthreads (int): Number of threads to use
+ maxpoles (int): Maximum number of poles
+ updatecoeffs, T, ID, E, H (memoryviews): Access to update coeffients, temporary, ID and field component arrays
+ """
+
+ cdef Py_ssize_t i, j, k
+ cdef int material
+ cdef float phi = 0
+
+ # Ex component
+ if ny != 1 or nz != 1:
+ for i in prange(0, nx, nogil=True, schedule='static', num_threads=nthreads):
+ for j in range(1, ny):
+ for k in range(1, nz):
+ material = ID[0, i, j, k]
+ {% if 'complex' in item.dispersive_type %}
+ phi = {{ item.real_part }}(updatecoeffsdispersive[material, 0]) * {{ item.real_part }}(Tx[0, i, j, k])
+ {% else %}
+ phi = updatecoeffsdispersive[material, 0] * Tx[0, i, j, k]
+ {% endif %}
+ Tx[0, i, j, k] = updatecoeffsdispersive[material, 1] * Tx[0, i, j, k] + updatecoeffsdispersive[material, 2] * Ex[i, j, k]
+ Ex[i, j, k] = updatecoeffsE[material, 0] * Ex[i, j, k] + updatecoeffsE[material, 2] * (Hz[i, j, k] - Hz[i, j - 1, k]) - updatecoeffsE[material, 3] * (Hy[i, j, k] - Hy[i, j, k - 1]) - updatecoeffsE[material, 4] * phi
+
+ # Ey component
+ if nx != 1 or nz != 1:
+ for i in prange(1, nx, nogil=True, schedule='static', num_threads=nthreads):
+ for j in range(0, ny):
+ for k in range(1, nz):
+ material = ID[1, i, j, k]
+ {% if 'complex' in item.dispersive_type %}
+ phi = {{ item.real_part }}(updatecoeffsdispersive[material, 0]) * {{ item.real_part }}(Ty[0, i, j, k])
+ {% else %}
+ phi = updatecoeffsdispersive[material, 0] * Ty[0, i, j, k]
+ {% endif %}
+ Ty[0, i, j, k] = updatecoeffsdispersive[material, 1] * Ty[0, i, j, k] + updatecoeffsdispersive[material, 2] * Ey[i, j, k]
+ Ey[i, j, k] = updatecoeffsE[material, 0] * Ey[i, j, k] + updatecoeffsE[material, 3] * (Hx[i, j, k] - Hx[i, j, k - 1]) - updatecoeffsE[material, 1] * (Hz[i, j, k] - Hz[i - 1, j, k]) - updatecoeffsE[material, 4] * phi
+
+ # Ez component
+ if nx != 1 or ny != 1:
+ for i in prange(1, nx, nogil=True, schedule='static', num_threads=nthreads):
+ for j in range(1, ny):
+ for k in range(0, nz):
+ material = ID[2, i, j, k]
+ {% if 'complex' in item.dispersive_type %}
+ phi = {{ item.real_part }}(updatecoeffsdispersive[material, 0]) * {{ item.real_part }}(Tz[0, i, j, k])
+ {% else %}
+ phi = updatecoeffsdispersive[material, 0] * Tz[0, i, j, k]
+ {% endif %}
+ Tz[0, i, j, k] = updatecoeffsdispersive[material, 1] * Tz[0, i, j, k] + updatecoeffsdispersive[material, 2] * Ez[i, j, k]
+ Ez[i, j, k] = updatecoeffsE[material, 0] * Ez[i, j, k] + updatecoeffsE[material, 1] * (Hy[i, j, k] - Hy[i - 1, j, k]) - updatecoeffsE[material, 2] * (Hx[i, j, k] - Hx[i, j - 1, k]) - updatecoeffsE[material, 4] * phi
+
+{% endfor %}
+
+
+#################################################################
+# Electric field updates - dispersive materials - single pole B #
+#################################################################
+
+{% for item in functions %}
+cpdef void {{ item.name_b_1 }}(
+ int nx,
+ int ny,
+ int nz,
+ int nthreads,
+ int maxpoles,
+ {{ item.dispersive_type }}[:, ::1] updatecoeffsdispersive,
+ np.uint32_t[:, :, :, ::1] ID,
+ {{ item.dispersive_type }}[:, :, :, ::1] Tx,
+ {{ item.dispersive_type }}[:, :, :, ::1] Ty,
+ {{ item.dispersive_type }}[:, :, :, ::1] Tz,
+ {{ item.field_type }}[:, :, ::1] Ex,
+ {{ item.field_type }}[:, :, ::1] Ey,
+ {{ item.field_type }}[:, :, ::1] Ez
+ ):
+ """This function updates a temporary dispersive material array when disperisive materials (with 1 pole) are present.
+
+ Args:
+ nx, ny, nz (int): Grid size in cells
+ nthreads (int): Number of threads to use
+ maxpoles (int): Maximum number of poles
+ updatecoeffs, T, ID, E (memoryviews): Access to update coeffients, temporary, ID and field component arrays
+ """
+
+ cdef Py_ssize_t i, j, k
+ cdef int material
+
+ # Ex component
+ if ny != 1 or nz != 1:
+ for i in prange(0, nx, nogil=True, schedule='static', num_threads=nthreads):
+ for j in range(1, ny):
+ for k in range(1, nz):
+ material = ID[0, i, j, k]
+ Tx[0, i, j, k] = Tx[0, i, j, k] - updatecoeffsdispersive[material, 2] * Ex[i, j, k]
+
+ # Ey component
+ if nx != 1 or nz != 1:
+ for i in prange(1, nx, nogil=True, schedule='static', num_threads=nthreads):
+ for j in range(0, ny):
+ for k in range(1, nz):
+ material = ID[1, i, j, k]
+ Ty[0, i, j, k] = Ty[0, i, j, k] - updatecoeffsdispersive[material, 2] * Ey[i, j, k]
+
+ # Ez component
+ if nx != 1 or ny != 1:
+ for i in prange(1, nx, nogil=True, schedule='static', num_threads=nthreads):
+ for j in range(1, ny):
+ for k in range(0, nz):
+ material = ID[2, i, j, k]
+ Tz[0, i, j, k] = Tz[0, i, j, k] - updatecoeffsdispersive[material, 2] * Ez[i, j, k]
+{% endfor %}
diff --git a/gprMax/updates.py b/gprMax/updates.py
index 03e5d5bf..2ab9957b 100644
--- a/gprMax/updates.py
+++ b/gprMax/updates.py
@@ -1,185 +1,186 @@
-# Copyright (C) 2015-2019: The University of Edinburgh
-# Authors: Craig Warren and Antonis Giannopoulos
-#
-# This file is part of gprMax.
-#
-# gprMax is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# gprMax is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with gprMax. If not, see .
-from importlib import import_module
-from gprMax.fields_outputs import store_outputs
-import gprMax.config as config
-from gprMax.cython.fields_updates_normal import update_electric
-from gprMax.cython.fields_updates_normal import update_magnetic
-
-
-class CPUUpdates:
-
- def __init__(self, G):
- self.grid = G
- self.dispersive_update_a = None
- self.dispersive_update_b = None
-
- def store_outputs(self):
- # Store field component values for every receiver and transmission line
- store_outputs(self.grid)
-
- def store_snapshots(self, iteration):
- # Store any snapshots
- for snap in self.grid.snapshots:
- if snap.time == iteration + 1:
- snap.store(self.grid)
-
- def update_magnetic(self):
- # Update magnetic field components
- update_magnetic(self.grid.nx,
- self.grid.ny,
- self.grid.nz,
- config.hostinfo['ompthreads'],
- self.grid.updatecoeffsH,
- self.grid.ID,
- self.grid.Ex,
- self.grid.Ey,
- self.grid.Ez,
- self.grid.Hx,
- self.grid.Hy,
- self.grid.Hz)
-
- def update_magnetic_pml(self):
- # Update magnetic field components with the PML correction
- for pml in self.grid.pmls:
- pml.update_magnetic(self.grid)
-
- def update_magnetic_sources(self, iteration):
- # Update magnetic field components from sources
- for source in self.grid.transmissionlines + self.grid.magneticdipoles:
- source.update_magnetic(iteration,
- self.grid.updatecoeffsH,
- self.grid.ID,
- self.grid.Hx,
- self.grid.Hy,
- self.grid.Hz,
- self.grid)
-
- def update_electric_a(self):
- # Update electric field components
- # All materials are non-dispersive so do standard update
- if config.materials['maxpoles'] == 0:
- update_electric(self.grid.nx,
- self.grid.ny,
- self.grid.nz,
- config.hostinfo['ompthreads'],
- self.grid.updatecoeffsE,
- self.grid.ID,
- self.grid.Ex,
- self.grid.Ey,
- self.grid.Ez,
- self.grid.Hx,
- self.grid.Hy,
- self.grid.Hz)
-
- # If there are any dispersive materials do 1st part of dispersive update
- # (it is split into two parts as it requires present and updated electric field values).
- else:
- self.dispersive_update_a(self.grid.nx,
- self.grid.ny,
- self.grid.nz,
- config.hostinfo['ompthreads'],
- config.materials['maxpoles'],
- self.grid.updatecoeffsE,
- self.grid.updatecoeffsdispersive,
- self.grid.ID,
- self.grid.Tx,
- self.grid.Ty,
- self.grid.Tz,
- self.grid.Ex,
- self.grid.Ey,
- self.grid.Ez,
- self.grid.Hx,
- self.grid.Hy,
- self.grid.Hz)
-
- def update_electric_pml(self):
- # Update electric field components with the PML correction
- for pml in self.grid.pmls:
- pml.update_electric(self.grid)
-
- def update_electric_sources(self, iteration):
- # Update electric field components from sources (update any Hertzian dipole sources last)
- for source in self.grid.voltagesources + self.grid.transmissionlines + self.grid.hertziandipoles:
- source.update_electric(iteration, self.grid.updatecoeffsE, self.grid.ID, self.grid.Ex, self.grid.Ey, self.grid.Ez, self.grid)
-
- def update_electric_b(self):
- # If there are any dispersive materials do 2nd part of dispersive update
- # (it is split into two parts as it requires present and updated electric
- # field values). Therefore it can only be completely updated after the
- # electric field has been updated by the PML and source updates.
- if config.materials['maxpoles'] != 0:
- self.dispersive_update_b(self.grid.nx,
- self.grid.ny,
- self.grid.nz,
- config.hostinfo['ompthreads'],
- config.materials['maxpoles'],
- self.grid.updatecoeffsdispersive,
- self.grid.ID,
- self.grid.Tx,
- self.grid.Ty,
- self.grid.Tz,
- self.grid.Ex,
- self.grid.Ey,
- self.grid.Ez)
-
- def adapt_dispersive_config(self, config):
-
- if config.materials['maxpoles'] > 1:
- poles = 'multi'
-
- else:
- poles = '1'
-
- if config.precision == 'single':
- type = 'float'
-
- else:
- type = 'double'
-
- if config.materials['dispersivedtype'] == config.dtypes['complex']:
- dispersion = 'complex'
- else:
- dispersion = 'real'
-
- class Props():
- pass
-
- props = Props()
- props.poles = poles
- props.precision = type
- props.dispersion_type = dispersion
-
- return props
-
- def set_dispersive_updates(self, props):
- """Function to set dispersive update functions based on model."""
-
- update_f = 'update_electric_dispersive_{}pole_{}_{}_{}'
- disp_a = update_f.format(props.poles, 'A', props.precision, props.dispersion_type)
- disp_b = update_f.format(props.poles, 'B', props.precision, props.dispersion_type)
-
- 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
-
-
-class GPUUpdates:
- pass
+# Copyright (C) 2015-2019: The University of Edinburgh
+# Authors: Craig Warren and Antonis Giannopoulos
+#
+# This file is part of gprMax.
+#
+# gprMax is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# gprMax is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with gprMax. If not, see .
+from importlib import import_module
+from gprMax.fields_outputs import store_outputs
+import gprMax.config as config
+from gprMax.cython.fields_updates_normal import update_electric
+from gprMax.cython.fields_updates_normal import update_magnetic
+
+
+class CPUUpdates:
+
+ def __init__(self, G):
+ self.grid = G
+ self.dispersive_update_a = None
+ self.dispersive_update_b = None
+
+ def store_outputs(self):
+ # Store field component values for every receiver and transmission line
+ store_outputs(self.grid)
+
+ def store_snapshots(self, iteration):
+ # Store any snapshots
+ for snap in self.grid.snapshots:
+ if snap.time == iteration + 1:
+ snap.store(self.grid)
+
+ def update_magnetic(self):
+ # Update magnetic field components
+ update_magnetic(self.grid.nx,
+ self.grid.ny,
+ self.grid.nz,
+ config.hostinfo['ompthreads'],
+ self.grid.updatecoeffsH,
+ self.grid.ID,
+ self.grid.Ex,
+ self.grid.Ey,
+ self.grid.Ez,
+ self.grid.Hx,
+ self.grid.Hy,
+ self.grid.Hz)
+
+ def update_magnetic_pml(self):
+ # Update magnetic field components with the PML correction
+ for pml in self.grid.pmls:
+ pml.update_magnetic(self.grid)
+
+ def update_magnetic_sources(self):
+ # Update magnetic field components from sources
+ for source in self.grid.transmissionlines + self.grid.magneticdipoles:
+ source.update_magnetic(self.grid.iteration,
+ self.grid.updatecoeffsH,
+ self.grid.ID,
+ self.grid.Hx,
+ self.grid.Hy,
+ self.grid.Hz,
+ self.grid)
+
+ def update_electric_a(self):
+ # Update electric field components
+ # All materials are non-dispersive so do standard update
+ if config.materials['maxpoles'] == 0:
+ update_electric(self.grid.nx,
+ self.grid.ny,
+ self.grid.nz,
+ config.hostinfo['ompthreads'],
+ self.grid.updatecoeffsE,
+ self.grid.ID,
+ self.grid.Ex,
+ self.grid.Ey,
+ self.grid.Ez,
+ self.grid.Hx,
+ self.grid.Hy,
+ self.grid.Hz)
+
+ # If there are any dispersive materials do 1st part of dispersive update
+ # (it is split into two parts as it requires present and updated electric field values).
+ else:
+ self.dispersive_update_a(self.grid.nx,
+ self.grid.ny,
+ self.grid.nz,
+ config.hostinfo['ompthreads'],
+ config.materials['maxpoles'],
+ self.grid.updatecoeffsE,
+ self.grid.updatecoeffsdispersive,
+ self.grid.ID,
+ self.grid.Tx,
+ self.grid.Ty,
+ self.grid.Tz,
+ self.grid.Ex,
+ self.grid.Ey,
+ self.grid.Ez,
+ self.grid.Hx,
+ self.grid.Hy,
+ self.grid.Hz)
+
+ def update_electric_pml(self):
+ # Update electric field components with the PML correction
+ for pml in self.grid.pmls:
+ pml.update_electric(self.grid)
+
+ def update_electric_sources(self):
+ # Update electric field components from sources (update any Hertzian dipole sources last)
+ for source in self.grid.voltagesources + self.grid.transmissionlines + self.grid.hertziandipoles:
+ source.update_electric(self.grid.iteration, self.grid.updatecoeffsE, self.grid.ID, self.grid.Ex, self.grid.Ey, self.grid.Ez, self.grid)
+ self.grid.iteration += 1
+
+ def update_electric_b(self):
+ # If there are any dispersive materials do 2nd part of dispersive update
+ # (it is split into two parts as it requires present and updated electric
+ # field values). Therefore it can only be completely updated after the
+ # electric field has been updated by the PML and source updates.
+ if config.materials['maxpoles'] != 0:
+ self.dispersive_update_b(self.grid.nx,
+ self.grid.ny,
+ self.grid.nz,
+ config.hostinfo['ompthreads'],
+ config.materials['maxpoles'],
+ self.grid.updatecoeffsdispersive,
+ self.grid.ID,
+ self.grid.Tx,
+ self.grid.Ty,
+ self.grid.Tz,
+ self.grid.Ex,
+ self.grid.Ey,
+ self.grid.Ez)
+
+ def adapt_dispersive_config(self, config):
+
+ if config.materials['maxpoles'] > 1:
+ poles = 'multi'
+
+ else:
+ poles = '1'
+
+ if config.precision == 'single':
+ type = 'float'
+
+ else:
+ type = 'double'
+
+ if config.materials['dispersivedtype'] == config.dtypes['complex']:
+ dispersion = 'complex'
+ else:
+ dispersion = 'real'
+
+ class Props():
+ pass
+
+ props = Props()
+ props.poles = poles
+ props.precision = type
+ props.dispersion_type = dispersion
+
+ return props
+
+ def set_dispersive_updates(self, props):
+ """Function to set dispersive update functions based on model."""
+
+ update_f = 'update_electric_dispersive_{}pole_{}_{}_{}'
+ disp_a = update_f.format(props.poles, 'A', props.precision, props.dispersion_type)
+ disp_b = update_f.format(props.poles, 'B', props.precision, props.dispersion_type)
+
+ 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
+
+
+class GPUUpdates:
+ pass
diff --git a/gprMax/utilities.py b/gprMax/utilities.py
index 945a67de..2bd597ef 100644
--- a/gprMax/utilities.py
+++ b/gprMax/utilities.py
@@ -1,430 +1,430 @@
-# Copyright (C) 2015-2019: The University of Edinburgh
-# Authors: Craig Warren and Antonis Giannopoulos
-#
-# This file is part of gprMax.
-#
-# gprMax is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# gprMax is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with gprMax. If not, see .
-
-from contextlib import contextmanager
-import codecs
-import decimal as d
-import os
-import platform
-import psutil
-import re
-import subprocess
-from shutil import get_terminal_size
-import sys
-import textwrap
-from time import perf_counter
-
-from colorama import init
-from colorama import Fore
-from colorama import Style
-init()
-import numpy as np
-
-from .exceptions import GeneralError
-
-
-
-def get_terminal_width():
- """Get/set width of terminal being used.
-
- Returns:
- terminalwidth (int): Terminal width
- """
-
- terminalwidth = get_terminal_size()[0]
- if terminalwidth == 0:
- terminalwidth = 100
-
- return terminalwidth
-
-
-def logo(version):
- """Print gprMax logo, version, and licencing/copyright information.
-
- Args:
- version (str): Version number.
- """
-
- description = '\n=== Electromagnetic modelling software based on the Finite-Difference Time-Domain (FDTD) method'
- copyright = 'Copyright (C) 2015-2019: The University of Edinburgh'
- authors = 'Authors: Craig Warren and Antonis Giannopoulos'
- licenseinfo1 = 'gprMax is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n'
- licenseinfo2 = 'gprMax is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.'
- licenseinfo3 = 'You should have received a copy of the GNU General Public License along with gprMax. If not, see www.gnu.org/licenses.'
-
- logo = """ www.gprmax.com __ __
- __ _ _ __ _ __| \/ | __ ___ __
- / _` | '_ \| '__| |\/| |/ _` \ \/ /
- | (_| | |_) | | | | | | (_| |> <
- \__, | .__/|_| |_| |_|\__,_/_/\_\\
- |___/|_|
- v""" + version
-
- print('{} {}\n'.format(description, '=' * (get_terminal_width() - len(description) - 1)))
- print(Fore.CYAN + '{}\n'.format(logo))
- print(Style.RESET_ALL + textwrap.fill(copyright, width=get_terminal_width() - 1, initial_indent=' '))
- print(textwrap.fill(authors, width=get_terminal_width() - 1, initial_indent=' '))
- print()
- print(textwrap.fill(licenseinfo1, width=get_terminal_width() - 1, initial_indent=' ', subsequent_indent=' '))
- print(textwrap.fill(licenseinfo2, width=get_terminal_width() - 1, initial_indent=' ', subsequent_indent=' '))
- print(textwrap.fill(licenseinfo3, width=get_terminal_width() - 1, initial_indent=' ', subsequent_indent=' '))
-
-
-@contextmanager
-def open_path_file(path_or_file):
- """Accepts either a path as a string or a file object and returns a file
- object (http://stackoverflow.com/a/6783680).
-
- Args:
- path_or_file: path as a string or a file object.
-
- Returns:
- f (object): File object.
- """
-
- if isinstance(path_or_file, str):
- f = file_to_close = codecs.open(path_or_file, 'r', encoding='utf-8')
- else:
- f = path_or_file
- file_to_close = None
-
- try:
- yield f
- finally:
- if file_to_close:
- file_to_close.close()
-
-
-def round_value(value, decimalplaces=0):
- """Rounding function.
-
- Args:
- value (float): Number to round.
- decimalplaces (int): Number of decimal places of float to represent rounded value.
-
- Returns:
- rounded (int/float): Rounded value.
- """
-
- # Rounds to nearest integer (half values are rounded downwards)
- if decimalplaces == 0:
- rounded = int(d.Decimal(value).quantize(d.Decimal('1'), rounding=d.ROUND_HALF_DOWN))
-
- # Rounds down to nearest float represented by number of decimal places
- else:
- precision = '1.{places}'.format(places='0' * decimalplaces)
- rounded = float(d.Decimal(value).quantize(d.Decimal(precision), rounding=d.ROUND_FLOOR))
-
- return rounded
-
-
-def round32(value):
- """Rounds up to nearest multiple of 32."""
- return int(32 * np.ceil(float(value) / 32))
-
-
-def fft_power(waveform, dt):
- """Calculate a FFT of the given waveform of amplitude values;
- converted to decibels and shifted so that maximum power is 0dB
-
- Args:
- waveform (ndarray): time domain waveform
- dt (float): time step
-
- Returns:
- freqs (ndarray): frequency bins
- power (ndarray): power
- """
-
- # Calculate magnitude of frequency spectra of waveform (ignore warning from taking a log of any zero values)
- with np.errstate(divide='ignore'):
- power = 10 * np.log10(np.abs(np.fft.fft(waveform))**2)
-
- # Replace any NaNs or Infs from zero division
- power[np.invert(np.isfinite(power))] = 0
-
- # Frequency bins
- freqs = np.fft.fftfreq(power.size, d=dt)
-
- # Shift powers so that frequency with maximum power is at zero decibels
- power -= np.amax(power)
-
- return freqs, power
-
-
-def human_size(size, a_kilobyte_is_1024_bytes=False):
- """Convert a file size to human-readable form.
-
- Args:
- size (int): file size in bytes.
- a_kilobyte_is_1024_bytes (boolean) - true for multiples of 1024, false for multiples of 1000.
-
- Returns:
- Human-readable (string).
- """
-
- suffixes = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], 1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']}
-
- if size < 0:
- raise ValueError('Number must be non-negative.')
-
- multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
- for suffix in suffixes[multiple]:
- size /= multiple
- if size < multiple:
- return '{:.3g}{}'.format(size, suffix)
-
- raise ValueError('Number is too large.')
-
-
-def get_host_info():
- """Get information about the machine, CPU, RAM, and OS.
-
- Returns:
- hostinfo (dict): Manufacturer and model of machine; description of CPU
- type, speed, cores; RAM; name and version of operating system.
- """
-
- # Default to 'unknown' if any of the detection fails
- manufacturer = model = cpuID = sockets = threadspercore = 'unknown'
-
- # Windows
- if sys.platform == 'win32':
- # Manufacturer/model
- try:
- manufacturer = subprocess.check_output("wmic csproduct get vendor", shell=True, stderr=subprocess.STDOUT).decode('utf-8').strip()
- manufacturer = manufacturer.split('\n')
- if len(manufacturer) > 1:
- manufacturer = manufacturer[1]
- else:
- manufacturer = manufacturer[0]
- model = subprocess.check_output("wmic computersystem get model", shell=True, stderr=subprocess.STDOUT).decode('utf-8').strip()
- model = model.split('\n')
- if len(model) > 1:
- model = model[1]
- else:
- model = model[0]
- except subprocess.CalledProcessError:
- pass
- machineID = manufacturer + ' ' + model
-
- # CPU information
- try:
- allcpuinfo = subprocess.check_output("wmic cpu get Name", shell=True, stderr=subprocess.STDOUT).decode('utf-8').strip()
- allcpuinfo = allcpuinfo.split('\n')
- sockets = 0
- for line in allcpuinfo:
- if 'CPU' in line:
- cpuID = line.strip()
- sockets += 1
- except subprocess.CalledProcessError:
- pass
-
- # Hyperthreading
- if psutil.cpu_count(logical=False) != psutil.cpu_count(logical=True):
- hyperthreading = True
- else:
- hyperthreading = False
-
- # OS version
- if platform.machine().endswith('64'):
- osbit = ' (64-bit)'
- else:
- osbit = ' (32-bit)'
- osversion = 'Windows ' + platform.release() + osbit
-
- # Mac OS X/macOS
- elif sys.platform == 'darwin':
- # Manufacturer/model
- manufacturer = 'Apple'
- try:
- model = subprocess.check_output("sysctl -n hw.model", shell=True, stderr=subprocess.STDOUT).decode('utf-8').strip()
- except subprocess.CalledProcessError:
- pass
- machineID = manufacturer + ' ' + model
-
- # CPU information
- try:
- sockets = subprocess.check_output("sysctl -n hw.packages", shell=True, stderr=subprocess.STDOUT).decode('utf-8').strip()
- sockets = int(sockets)
- cpuID = subprocess.check_output("sysctl -n machdep.cpu.brand_string", shell=True, stderr=subprocess.STDOUT).decode('utf-8').strip()
- cpuID = ' '.join(cpuID.split())
- except subprocess.CalledProcessError:
- pass
-
- # Hyperthreading
- if psutil.cpu_count(logical=False) != psutil.cpu_count(logical=True):
- hyperthreading = True
- else:
- hyperthreading = False
-
- # OS version
- if int(platform.mac_ver()[0].split('.')[1]) < 12:
- osversion = 'Mac OS X (' + platform.mac_ver()[0] + ')'
- else:
- osversion = 'macOS (' + platform.mac_ver()[0] + ')'
-
- # Linux
- elif sys.platform == 'linux':
- # Manufacturer/model
- try:
- manufacturer = subprocess.check_output("cat /sys/class/dmi/id/sys_vendor", shell=True, stderr=subprocess.STDOUT).decode('utf-8').strip()
- model = subprocess.check_output("cat /sys/class/dmi/id/product_name", shell=True, stderr=subprocess.STDOUT).decode('utf-8').strip()
- except subprocess.CalledProcessError:
- pass
- machineID = manufacturer + ' ' + model
-
- # CPU information
- try:
- cpuIDinfo = subprocess.check_output("cat /proc/cpuinfo", shell=True, stderr=subprocess.STDOUT).decode('utf-8').strip()
- for line in cpuIDinfo.split('\n'):
- if re.search('model name', line):
- cpuID = re.sub('.*model name.*:', '', line, 1).strip()
- allcpuinfo = subprocess.check_output("lscpu", shell=True, stderr=subprocess.STDOUT).decode('utf-8').strip()
- for line in allcpuinfo.split('\n'):
- if 'Socket(s)' in line:
- sockets = int(line.strip()[-1])
- if 'Thread(s) per core' in line:
- threadspercore = int(line.strip()[-1])
- except subprocess.CalledProcessError:
- pass
-
- # Hyperthreading
- if threadspercore == 2:
- hyperthreading = True
- else:
- hyperthreading = False
-
- # OS version
- osrelease = subprocess.check_output("cat /proc/sys/kernel/osrelease", shell=True).decode('utf-8').strip()
- osversion = 'Linux (' + osrelease + ', ' + platform.linux_distribution()[0] + ')'
-
- # Dictionary of host information
- hostinfo = {}
- hostinfo['hostname'] = platform.node()
- hostinfo['machineID'] = machineID.strip()
- hostinfo['sockets'] = sockets
- hostinfo['cpuID'] = cpuID
- hostinfo['osversion'] = osversion
- hostinfo['hyperthreading'] = hyperthreading
- hostinfo['logicalcores'] = psutil.cpu_count()
- try:
- # Get number of physical CPU cores, i.e. avoid hyperthreading with OpenMP
- hostinfo['physicalcores'] = psutil.cpu_count(logical=False)
- except ValueError:
- hostinfo['physicalcores'] = hostinfo['logicalcores']
- # Handle case where cpu_count returns None on some machines
- if not hostinfo['physicalcores']:
- hostinfo['physicalcores'] = hostinfo['logicalcores']
- hostinfo['ram'] = psutil.virtual_memory().total
- hostinfo['ompthreads'] = 1
-
- return hostinfo
-
-
-class GPU(object):
- """GPU information."""
-
- def __init__(self, deviceID):
- """
- Args:
- deviceID (int): Device ID for GPU.
- """
-
- self.deviceID = deviceID
- self.name = None
- self.pcibusID = None
- self.constmem = None
- self.totalmem = None
- # Threads per block for main field updates
- self.tpb = (256, 1, 1)
- # Blocks per grid for main field updates (set in grid.py)
- self.bpg = None
-
- def get_gpu_info(self, drv):
- """Set information about GPU.
-
- Args:
- drv (object): PyCuda driver.
- """
-
- self.name = drv.Device(self.deviceID).name()
- self.pcibusID = drv.Device(self.deviceID).pci_bus_id()
- self.constmem = drv.Device(self.deviceID).total_constant_memory
- self.totalmem = drv.Device(self.deviceID).total_memory()
-
-
-def detect_check_gpus(deviceIDs):
- """Get information about Nvidia GPU(s).
-
- Args:
- deviceIDs (list): List of integers of device IDs.
-
- Returns:
- gpus (list): Detected GPU(s) object(s).
- """
-
- try:
- import pycuda.driver as drv
- except ImportError:
- raise ImportError('To use gprMax in GPU mode the pycuda package must be installed, and you must have a NVIDIA CUDA-Enabled GPU (https://developer.nvidia.com/cuda-gpus).')
- drv.init()
-
- # Check and list any CUDA-Enabled GPUs
- if drv.Device.count() == 0:
- raise GeneralError('No NVIDIA CUDA-Enabled GPUs detected (https://developer.nvidia.com/cuda-gpus)')
- elif 'CUDA_VISIBLE_DEVICES' in os.environ:
- deviceIDsavail = os.environ.get('CUDA_VISIBLE_DEVICES')
- deviceIDsavail = [int(s) for s in deviceIDsavail.split(',')]
- else:
- deviceIDsavail = range(drv.Device.count())
-
- # If no device ID is given use default of 0
- if not deviceIDs:
- deviceIDs = [0]
-
- # Check if requested device ID(s) exist
- for ID in deviceIDs:
- if ID not in deviceIDsavail:
- raise GeneralError('GPU with device ID {} does not exist'.format(ID))
-
- # Gather information about selected/detected GPUs
- gpus = []
- allgpustext = []
- for ID in deviceIDsavail:
- gpu = GPU(deviceID=ID)
- gpu.get_gpu_info(drv)
- if ID in deviceIDs:
- gpus.append(gpu)
- allgpustext.append('{} - {}, {}'.format(gpu.deviceID, gpu.name, human_size(gpu.totalmem, a_kilobyte_is_1024_bytes=True)))
-
- return gpus, allgpustext
-
-
-def timer():
- """Function to return the current process wide time in fractional seconds."""
- return perf_counter()
-
-class Printer():
-
- def __init__(self, config):
- self.printing = config.is_messages()
-
- def print(self, str):
- if self.printing:
- print(str)
+# Copyright (C) 2015-2019: The University of Edinburgh
+# Authors: Craig Warren and Antonis Giannopoulos
+#
+# This file is part of gprMax.
+#
+# gprMax is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# gprMax is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with gprMax. If not, see .
+
+from contextlib import contextmanager
+import codecs
+import decimal as d
+import os
+import platform
+import psutil
+import re
+import subprocess
+from shutil import get_terminal_size
+import sys
+import textwrap
+from time import perf_counter
+
+from colorama import init
+from colorama import Fore
+from colorama import Style
+init()
+import numpy as np
+
+from .exceptions import GeneralError
+
+
+
+def get_terminal_width():
+ """Get/set width of terminal being used.
+
+ Returns:
+ terminalwidth (int): Terminal width
+ """
+
+ terminalwidth = get_terminal_size()[0]
+ if terminalwidth == 0:
+ terminalwidth = 100
+
+ return terminalwidth
+
+
+def logo(version):
+ """Print gprMax logo, version, and licencing/copyright information.
+
+ Args:
+ version (str): Version number.
+ """
+
+ description = '\n=== Electromagnetic modelling software based on the Finite-Difference Time-Domain (FDTD) method'
+ copyright = 'Copyright (C) 2015-2019: The University of Edinburgh'
+ authors = 'Authors: Craig Warren and Antonis Giannopoulos'
+ licenseinfo1 = 'gprMax is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n'
+ licenseinfo2 = 'gprMax is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.'
+ licenseinfo3 = 'You should have received a copy of the GNU General Public License along with gprMax. If not, see www.gnu.org/licenses.'
+
+ logo = """ www.gprmax.com __ __
+ __ _ _ __ _ __| \/ | __ ___ __
+ / _` | '_ \| '__| |\/| |/ _` \ \/ /
+ | (_| | |_) | | | | | | (_| |> <
+ \__, | .__/|_| |_| |_|\__,_/_/\_\\
+ |___/|_|
+ v""" + version
+
+ print('{} {}\n'.format(description, '=' * (get_terminal_width() - len(description) - 1)))
+ print(Fore.CYAN + '{}\n'.format(logo))
+ print(Style.RESET_ALL + textwrap.fill(copyright, width=get_terminal_width() - 1, initial_indent=' '))
+ print(textwrap.fill(authors, width=get_terminal_width() - 1, initial_indent=' '))
+ print()
+ print(textwrap.fill(licenseinfo1, width=get_terminal_width() - 1, initial_indent=' ', subsequent_indent=' '))
+ print(textwrap.fill(licenseinfo2, width=get_terminal_width() - 1, initial_indent=' ', subsequent_indent=' '))
+ print(textwrap.fill(licenseinfo3, width=get_terminal_width() - 1, initial_indent=' ', subsequent_indent=' '))
+
+
+@contextmanager
+def open_path_file(path_or_file):
+ """Accepts either a path as a string or a file object and returns a file
+ object (http://stackoverflow.com/a/6783680).
+
+ Args:
+ path_or_file: path as a string or a file object.
+
+ Returns:
+ f (object): File object.
+ """
+
+ if isinstance(path_or_file, str):
+ f = file_to_close = codecs.open(path_or_file, 'r', encoding='utf-8')
+ else:
+ f = path_or_file
+ file_to_close = None
+
+ try:
+ yield f
+ finally:
+ if file_to_close:
+ file_to_close.close()
+
+
+def round_value(value, decimalplaces=0):
+ """Rounding function.
+
+ Args:
+ value (float): Number to round.
+ decimalplaces (int): Number of decimal places of float to represent rounded value.
+
+ Returns:
+ rounded (int/float): Rounded value.
+ """
+
+ # Rounds to nearest integer (half values are rounded downwards)
+ if decimalplaces == 0:
+ rounded = int(d.Decimal(value).quantize(d.Decimal('1'), rounding=d.ROUND_HALF_DOWN))
+
+ # Rounds down to nearest float represented by number of decimal places
+ else:
+ precision = '1.{places}'.format(places='0' * decimalplaces)
+ rounded = float(d.Decimal(value).quantize(d.Decimal(precision), rounding=d.ROUND_FLOOR))
+
+ return rounded
+
+
+def round32(value):
+ """Rounds up to nearest multiple of 32."""
+ return int(32 * np.ceil(float(value) / 32))
+
+
+def fft_power(waveform, dt):
+ """Calculate a FFT of the given waveform of amplitude values;
+ converted to decibels and shifted so that maximum power is 0dB
+
+ Args:
+ waveform (ndarray): time domain waveform
+ dt (float): time step
+
+ Returns:
+ freqs (ndarray): frequency bins
+ power (ndarray): power
+ """
+
+ # Calculate magnitude of frequency spectra of waveform (ignore warning from taking a log of any zero values)
+ with np.errstate(divide='ignore'):
+ power = 10 * np.log10(np.abs(np.fft.fft(waveform))**2)
+
+ # Replace any NaNs or Infs from zero division
+ power[np.invert(np.isfinite(power))] = 0
+
+ # Frequency bins
+ freqs = np.fft.fftfreq(power.size, d=dt)
+
+ # Shift powers so that frequency with maximum power is at zero decibels
+ power -= np.amax(power)
+
+ return freqs, power
+
+
+def human_size(size, a_kilobyte_is_1024_bytes=False):
+ """Convert a file size to human-readable form.
+
+ Args:
+ size (int): file size in bytes.
+ a_kilobyte_is_1024_bytes (boolean) - true for multiples of 1024, false for multiples of 1000.
+
+ Returns:
+ Human-readable (string).
+ """
+
+ suffixes = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], 1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']}
+
+ if size < 0:
+ raise ValueError('Number must be non-negative.')
+
+ multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
+ for suffix in suffixes[multiple]:
+ size /= multiple
+ if size < multiple:
+ return '{:.3g}{}'.format(size, suffix)
+
+ raise ValueError('Number is too large.')
+
+
+def get_host_info():
+ """Get information about the machine, CPU, RAM, and OS.
+
+ Returns:
+ hostinfo (dict): Manufacturer and model of machine; description of CPU
+ type, speed, cores; RAM; name and version of operating system.
+ """
+
+ # Default to 'unknown' if any of the detection fails
+ manufacturer = model = cpuID = sockets = threadspercore = 'unknown'
+
+ # Windows
+ if sys.platform == 'win32':
+ # Manufacturer/model
+ try:
+ manufacturer = subprocess.check_output("wmic csproduct get vendor", shell=True, stderr=subprocess.STDOUT).decode('utf-8').strip()
+ manufacturer = manufacturer.split('\n')
+ if len(manufacturer) > 1:
+ manufacturer = manufacturer[1]
+ else:
+ manufacturer = manufacturer[0]
+ model = subprocess.check_output("wmic computersystem get model", shell=True, stderr=subprocess.STDOUT).decode('utf-8').strip()
+ model = model.split('\n')
+ if len(model) > 1:
+ model = model[1]
+ else:
+ model = model[0]
+ except subprocess.CalledProcessError:
+ pass
+ machineID = manufacturer + ' ' + model
+
+ # CPU information
+ try:
+ allcpuinfo = subprocess.check_output("wmic cpu get Name", shell=True, stderr=subprocess.STDOUT).decode('utf-8').strip()
+ allcpuinfo = allcpuinfo.split('\n')
+ sockets = 0
+ for line in allcpuinfo:
+ if 'CPU' in line:
+ cpuID = line.strip()
+ sockets += 1
+ except subprocess.CalledProcessError:
+ pass
+
+ # Hyperthreading
+ if psutil.cpu_count(logical=False) != psutil.cpu_count(logical=True):
+ hyperthreading = True
+ else:
+ hyperthreading = False
+
+ # OS version
+ if platform.machine().endswith('64'):
+ osbit = ' (64-bit)'
+ else:
+ osbit = ' (32-bit)'
+ osversion = 'Windows ' + platform.release() + osbit
+
+ # Mac OS X/macOS
+ elif sys.platform == 'darwin':
+ # Manufacturer/model
+ manufacturer = 'Apple'
+ try:
+ model = subprocess.check_output("sysctl -n hw.model", shell=True, stderr=subprocess.STDOUT).decode('utf-8').strip()
+ except subprocess.CalledProcessError:
+ pass
+ machineID = manufacturer + ' ' + model
+
+ # CPU information
+ try:
+ sockets = subprocess.check_output("sysctl -n hw.packages", shell=True, stderr=subprocess.STDOUT).decode('utf-8').strip()
+ sockets = int(sockets)
+ cpuID = subprocess.check_output("sysctl -n machdep.cpu.brand_string", shell=True, stderr=subprocess.STDOUT).decode('utf-8').strip()
+ cpuID = ' '.join(cpuID.split())
+ except subprocess.CalledProcessError:
+ pass
+
+ # Hyperthreading
+ if psutil.cpu_count(logical=False) != psutil.cpu_count(logical=True):
+ hyperthreading = True
+ else:
+ hyperthreading = False
+
+ # OS version
+ if int(platform.mac_ver()[0].split('.')[1]) < 12:
+ osversion = 'Mac OS X (' + platform.mac_ver()[0] + ')'
+ else:
+ osversion = 'macOS (' + platform.mac_ver()[0] + ')'
+
+ # Linux
+ elif sys.platform == 'linux':
+ # Manufacturer/model
+ try:
+ manufacturer = subprocess.check_output("cat /sys/class/dmi/id/sys_vendor", shell=True, stderr=subprocess.STDOUT).decode('utf-8').strip()
+ model = subprocess.check_output("cat /sys/class/dmi/id/product_name", shell=True, stderr=subprocess.STDOUT).decode('utf-8').strip()
+ except subprocess.CalledProcessError:
+ pass
+ machineID = manufacturer + ' ' + model
+
+ # CPU information
+ try:
+ cpuIDinfo = subprocess.check_output("cat /proc/cpuinfo", shell=True, stderr=subprocess.STDOUT).decode('utf-8').strip()
+ for line in cpuIDinfo.split('\n'):
+ if re.search('model name', line):
+ cpuID = re.sub('.*model name.*:', '', line, 1).strip()
+ allcpuinfo = subprocess.check_output("lscpu", shell=True, stderr=subprocess.STDOUT).decode('utf-8').strip()
+ for line in allcpuinfo.split('\n'):
+ if 'Socket(s)' in line:
+ sockets = int(line.strip()[-1])
+ if 'Thread(s) per core' in line:
+ threadspercore = int(line.strip()[-1])
+ except subprocess.CalledProcessError:
+ pass
+
+ # Hyperthreading
+ if threadspercore == 2:
+ hyperthreading = True
+ else:
+ hyperthreading = False
+
+ # OS version
+ osrelease = subprocess.check_output("cat /proc/sys/kernel/osrelease", shell=True).decode('utf-8').strip()
+ osversion = 'Linux (' + osrelease + ', ' + platform.linux_distribution()[0] + ')'
+
+ # Dictionary of host information
+ hostinfo = {}
+ hostinfo['hostname'] = platform.node()
+ hostinfo['machineID'] = machineID.strip()
+ hostinfo['sockets'] = sockets
+ hostinfo['cpuID'] = cpuID
+ hostinfo['osversion'] = osversion
+ hostinfo['hyperthreading'] = hyperthreading
+ hostinfo['logicalcores'] = psutil.cpu_count()
+ try:
+ # Get number of physical CPU cores, i.e. avoid hyperthreading with OpenMP
+ hostinfo['physicalcores'] = psutil.cpu_count(logical=False)
+ except ValueError:
+ hostinfo['physicalcores'] = hostinfo['logicalcores']
+ # Handle case where cpu_count returns None on some machines
+ if not hostinfo['physicalcores']:
+ hostinfo['physicalcores'] = hostinfo['logicalcores']
+ hostinfo['ram'] = psutil.virtual_memory().total
+ hostinfo['ompthreads'] = 1
+
+ return hostinfo
+
+
+class GPU(object):
+ """GPU information."""
+
+ def __init__(self, deviceID):
+ """
+ Args:
+ deviceID (int): Device ID for GPU.
+ """
+
+ self.deviceID = deviceID
+ self.name = None
+ self.pcibusID = None
+ self.constmem = None
+ self.totalmem = None
+ # Threads per block for main field updates
+ self.tpb = (256, 1, 1)
+ # Blocks per grid for main field updates (set in grid.py)
+ self.bpg = None
+
+ def get_gpu_info(self, drv):
+ """Set information about GPU.
+
+ Args:
+ drv (object): PyCuda driver.
+ """
+
+ self.name = drv.Device(self.deviceID).name()
+ self.pcibusID = drv.Device(self.deviceID).pci_bus_id()
+ self.constmem = drv.Device(self.deviceID).total_constant_memory
+ self.totalmem = drv.Device(self.deviceID).total_memory()
+
+
+def detect_check_gpus(deviceIDs):
+ """Get information about Nvidia GPU(s).
+
+ Args:
+ deviceIDs (list): List of integers of device IDs.
+
+ Returns:
+ gpus (list): Detected GPU(s) object(s).
+ """
+
+ try:
+ import pycuda.driver as drv
+ except ImportError:
+ raise ImportError('To use gprMax in GPU mode the pycuda package must be installed, and you must have a NVIDIA CUDA-Enabled GPU (https://developer.nvidia.com/cuda-gpus).')
+ drv.init()
+
+ # Check and list any CUDA-Enabled GPUs
+ if drv.Device.count() == 0:
+ raise GeneralError('No NVIDIA CUDA-Enabled GPUs detected (https://developer.nvidia.com/cuda-gpus)')
+ elif 'CUDA_VISIBLE_DEVICES' in os.environ:
+ deviceIDsavail = os.environ.get('CUDA_VISIBLE_DEVICES')
+ deviceIDsavail = [int(s) for s in deviceIDsavail.split(',')]
+ else:
+ deviceIDsavail = range(drv.Device.count())
+
+ # If no device ID is given use default of 0
+ if not deviceIDs:
+ deviceIDs = [0]
+
+ # Check if requested device ID(s) exist
+ for ID in deviceIDs:
+ if ID not in deviceIDsavail:
+ raise GeneralError('GPU with device ID {} does not exist'.format(ID))
+
+ # Gather information about selected/detected GPUs
+ gpus = []
+ allgpustext = []
+ for ID in deviceIDsavail:
+ gpu = GPU(deviceID=ID)
+ gpu.get_gpu_info(drv)
+ if ID in deviceIDs:
+ gpus.append(gpu)
+ allgpustext.append('{} - {}, {}'.format(gpu.deviceID, gpu.name, human_size(gpu.totalmem, a_kilobyte_is_1024_bytes=True)))
+
+ return gpus, allgpustext
+
+
+def timer():
+ """Function to return the current process wide time in fractional seconds."""
+ return perf_counter()
+
+class Printer():
+
+ def __init__(self, config):
+ self.printing = config.is_messages()
+
+ def print(self, str):
+ if self.printing:
+ print(str)
diff --git a/setup.py b/setup.py
index dbb72b8b..e4e17861 100644
--- a/setup.py
+++ b/setup.py
@@ -1,270 +1,270 @@
-# Copyright (C) 2015-2019: The University of Edinburgh
-# Authors: Craig Warren and Antonis Giannopoulos
-#
-# This file is part of gprMax.
-#
-# gprMax is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# gprMax is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with gprMax. If not, see .
-
-try:
- from setuptools import setup, Extension
-except ImportError:
- from distutils.core import setup
- from distutils.extension import Extension
-
-try:
- import numpy as np
-except ImportError:
- raise ImportError('gprMax requires the NumPy package.')
-
-import glob
-import os
-import pathlib
-import re
-import shutil
-import sys
-from jinja2 import Environment, PackageLoader, select_autoescape
-
-
-def build_dispersive_material_templates():
- """
- Function to generate Cython .pyx files for dispersive media update.
- Jinja2 templates are used to render the various dispersive update functions.
- """
- env = Environment(
- loader=PackageLoader(__name__, 'gprMax/templates'),
- )
-
- template = env.get_template('fields_updates_dispersive_template')
-
- # Render dispersive template for different types
- r = template.render(
- functions=[
- # templates for Double precision and dispersive materials with
- # real susceptibility functions
- {
- 'name_a': 'update_electric_dispersive_multipole_A_double_real',
- 'name_b': 'update_electric_dispersive_multipole_B_double_real',
- 'name_a_1': 'update_electric_dispersive_1pole_A_double_real',
- 'name_b_1': 'update_electric_dispersive_1pole_B_double_real',
- 'field_type': 'double',
- 'dispersive_type': 'double'
- },
- # templates for Float precision and dispersive materials with
- # real susceptibility functions
- {
- 'name_a': 'update_electric_dispersive_multipole_A_float_real',
- 'name_b': 'update_electric_dispersive_multipole_B_float_real',
- 'name_a_1': 'update_electric_dispersive_1pole_A_float_real',
- 'name_b_1': 'update_electric_dispersive_1pole_B_float_real',
- 'field_type': 'float',
- 'dispersive_type': 'float'
- },
- # templates for Double precision and dispersive materials with
- # complex susceptibility functions
- {
- 'name_a': 'update_electric_dispersive_multipole_A_double_complex',
- 'name_b': 'update_electric_dispersive_multipole_B_double_complex',
- 'name_a_1': 'update_electric_dispersive_1pole_A_double_complex',
- 'name_b_1': 'update_electric_dispersive_1pole_B_double_complex',
- 'field_type': 'double',
- 'dispersive_type': 'double complex',
- # c function to take real part of complex double type
- 'real_part': 'creal'
- },
- # templates for Float precision and dispersive materials with
- # complex susceptibility functions
- {
- 'name_a': 'update_electric_dispersive_multipole_A_float_complex',
- 'name_b': 'update_electric_dispersive_multipole_B_float_complex',
- 'name_a_1': 'update_electric_dispersive_1pole_A_float_complex',
- 'name_b_1': 'update_electric_dispersive_1pole_B_float_complex',
- 'field_type': 'float',
- 'dispersive_type': 'float complex',
- # c function to take real part of complex double type
- 'real_part': 'crealf'
- }]
- )
-
- with open('gprMax/cython/fields_updates_dispersive.pyx', 'w') as f:
- f.write(r)
-
-# Generate Cython file for dispersive materials update functions
-build_dispersive_material_templates()
-
-# Importing _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).group(1)
-
-# Parse package name from init file. Importing __init__.py / gprMax will break as gprMax depends on compiled .pyx files.
-with open('gprMax/__init__.py', 'r') as fd:
- packagename = re.search(r'^__name__\s*=\s*[\'"]([^\'"]*)[\'"]',
- fd.read(), re.MULTILINE).group(1)
-
-packages = [packagename, 'tests', 'tools', 'user_libs']
-
-# Parse long_description from README.rst file.
-with open('README.rst','r') as fd:
- long_description = fd.read()
-
-# Python version
-if sys.version_info[:2] < (3, 6):
- sys.exit('\nExited: Requires Python 3.6 or newer!\n')
-
-# Process 'build' command line argument
-if 'build' in sys.argv:
- print("Running 'build_ext --inplace'")
- sys.argv.remove('build')
- sys.argv.append('build_ext')
- sys.argv.append('--inplace')
-
-# Process '--no-cython' command line argument - either Cythonize or just compile the .c files
-if '--no-cython' in sys.argv:
- USE_CYTHON = False
- sys.argv.remove('--no-cython')
-else:
- USE_CYTHON = True
-
-# Build a list of all the files that need to be Cythonized looking in gprMax directory
-cythonfiles = []
-for root, dirs, files in os.walk(os.path.join(os.getcwd(), packagename), topdown=True):
- for file in files:
- if file.endswith('.pyx'):
- cythonfiles.append(os.path.relpath(os.path.join(root, file)))
-
-# Process 'cleanall' command line argument - cleanup Cython files
-if 'cleanall' in sys.argv:
- USE_CYTHON = False
- for file in cythonfiles:
- filebase = os.path.splitext(file)[0]
- # Remove Cython C files
- if os.path.isfile(filebase + '.c'):
- try:
- os.remove(filebase + '.c')
- print('Removed: {}'.format(filebase + '.c'))
- except OSError:
- print('Could not remove: {}'.format(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')
- if libfile:
- libfile = libfile[0]
- try:
- os.remove(libfile)
- print('Removed: {}'.format(os.path.abspath(libfile)))
- except OSError:
- print('Could not remove: {}'.format(os.path.abspath(libfile)))
- # Remove build, dist, egg and __pycache__ directories
- shutil.rmtree(os.path.join(os.getcwd(), 'build'), ignore_errors=True)
- shutil.rmtree(os.path.join(os.getcwd(), 'dist'), ignore_errors=True)
- shutil.rmtree(os.path.join(os.getcwd(), 'gprMax.egg-info'), ignore_errors=True)
- for p in pathlib.Path(os.getcwd()).rglob('__pycache__'):
- shutil.rmtree(p, ignore_errors=True)
- print('Removed: {}'.format(p))
- # Now do a normal clean
- sys.argv[1] = 'clean' # this is what distutils understands
-
-# Set compiler options
-# Windows
-if sys.platform == 'win32':
- compile_args = ['/O2', '/openmp', '/w'] # No static linking as no static version of OpenMP library; /w disables warnings
- linker_args = []
- extra_objects = []
-# macOS - needs gcc (usually via HomeBrew) because the default compiler LLVM (clang) does not support OpenMP
-# - with gcc -fopenmp option implies -pthread
-elif sys.platform == 'darwin':
- gccpath = glob.glob('/usr/local/bin/gcc-[4-9]*')
- if gccpath:
- # Use newest gcc found
- os.environ['CC'] = gccpath[-1].split(os.sep)[-1]
- rpath = '/usr/local/opt/gcc/lib/gcc/' + gccpath[-1].split(os.sep)[-1][-1] + '/'
- else:
- raise('Cannot find gcc 4-9 in /usr/local/bin. gprMax requires gcc to be installed - easily done through the Homebrew package manager (http://brew.sh). Note: gcc with OpenMP support is required.')
- compile_args = ['-O3', '-w', '-fopenmp', '-march=native'] # Sometimes worth testing with '-fstrict-aliasing', '-fno-common'
- linker_args = ['-fopenmp', '-Wl,-rpath,' + rpath]
- extra_objects = []
-# Linux
-elif sys.platform == 'linux':
- compile_args = ['-O3', '-w', '-fopenmp', '-march=native']
- linker_args = ['-fopenmp']
- extra_objects = []
-
-# Build a list of all the extensions
-extensions = []
-for file in cythonfiles:
- tmp = os.path.splitext(file)
- if USE_CYTHON:
- fileext = tmp[1]
- else:
- fileext = '.c'
- extension = Extension(tmp[0].replace(os.sep, '.'),
- [tmp[0] + fileext],
- language='c',
- include_dirs=[np.get_include()],
- extra_compile_args=compile_args,
- extra_link_args=linker_args,
- extra_objects=extra_objects)
- extensions.append(extension)
-
-# Cythonize (build .c files)
-if USE_CYTHON:
- from Cython.Build import cythonize
- extensions = cythonize(extensions,
- compiler_directives={
- 'boundscheck': False,
- 'wraparound': False,
- 'initializedcheck': False,
- 'embedsignature': True,
- 'language_level': 3
- },
- annotate=False)
-
-# SetupTools Required to make package
-import setuptools
-
-setup(name=packagename,
- version=version,
- author='Craig Warren and Antonis Giannopoulos',
- url='http://www.gprmax.com',
- 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+',
- classifiers=[
- 'Environment :: Console',
- 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
- 'Operating System :: MacOS',
- 'Operating System :: Microsoft :: Windows',
- 'Operating System :: POSIX :: Linux',
- 'Programming Language :: Cython',
- 'Programming Language :: Python :: 3',
- 'Topic :: Scientific/Engineering'
- ],
- #requirements
- python_requires=">3.6",
- install_requires=[
- "colorama",
- "cython",
- "h5py",
- "jupyter",
- "matplotlib",
- "numpy",
- "psutil",
- "scipy",
- "terminaltables",
- "tqdm",
- ],
- ext_modules=extensions,
- packages=packages,
- include_package_data=True,
- include_dirs=[np.get_include()])
+# Copyright (C) 2015-2019: The University of Edinburgh
+# Authors: Craig Warren and Antonis Giannopoulos
+#
+# This file is part of gprMax.
+#
+# gprMax is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# gprMax is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with gprMax. If not, see .
+
+try:
+ from setuptools import setup, Extension
+except ImportError:
+ from distutils.core import setup
+ from distutils.extension import Extension
+
+try:
+ import numpy as np
+except ImportError:
+ raise ImportError('gprMax requires the NumPy package.')
+
+import glob
+import os
+import pathlib
+import re
+import shutil
+import sys
+from jinja2 import Environment, PackageLoader, select_autoescape
+
+
+def build_dispersive_material_templates():
+ """
+ Function to generate Cython .pyx files for dispersive media update.
+ Jinja2 templates are used to render the various dispersive update functions.
+ """
+ env = Environment(
+ loader=PackageLoader(__name__, 'gprMax/templates'),
+ )
+
+ template = env.get_template('fields_updates_dispersive_template')
+
+ # Render dispersive template for different types
+ r = template.render(
+ functions=[
+ # templates for Double precision and dispersive materials with
+ # real susceptibility functions
+ {
+ 'name_a': 'update_electric_dispersive_multipole_A_double_real',
+ 'name_b': 'update_electric_dispersive_multipole_B_double_real',
+ 'name_a_1': 'update_electric_dispersive_1pole_A_double_real',
+ 'name_b_1': 'update_electric_dispersive_1pole_B_double_real',
+ 'field_type': 'double',
+ 'dispersive_type': 'double'
+ },
+ # templates for Float precision and dispersive materials with
+ # real susceptibility functions
+ {
+ 'name_a': 'update_electric_dispersive_multipole_A_float_real',
+ 'name_b': 'update_electric_dispersive_multipole_B_float_real',
+ 'name_a_1': 'update_electric_dispersive_1pole_A_float_real',
+ 'name_b_1': 'update_electric_dispersive_1pole_B_float_real',
+ 'field_type': 'float',
+ 'dispersive_type': 'float'
+ },
+ # templates for Double precision and dispersive materials with
+ # complex susceptibility functions
+ {
+ 'name_a': 'update_electric_dispersive_multipole_A_double_complex',
+ 'name_b': 'update_electric_dispersive_multipole_B_double_complex',
+ 'name_a_1': 'update_electric_dispersive_1pole_A_double_complex',
+ 'name_b_1': 'update_electric_dispersive_1pole_B_double_complex',
+ 'field_type': 'double',
+ 'dispersive_type': 'double complex',
+ # c function to take real part of complex double type
+ 'real_part': 'creal'
+ },
+ # templates for Float precision and dispersive materials with
+ # complex susceptibility functions
+ {
+ 'name_a': 'update_electric_dispersive_multipole_A_float_complex',
+ 'name_b': 'update_electric_dispersive_multipole_B_float_complex',
+ 'name_a_1': 'update_electric_dispersive_1pole_A_float_complex',
+ 'name_b_1': 'update_electric_dispersive_1pole_B_float_complex',
+ 'field_type': 'float',
+ 'dispersive_type': 'float complex',
+ # c function to take real part of complex double type
+ 'real_part': 'crealf'
+ }]
+ )
+
+ with open('gprMax/cython/fields_updates_dispersive.pyx', 'w') as f:
+ f.write(r)
+
+# Generate Cython file for dispersive materials update functions
+build_dispersive_material_templates()
+
+# Importing _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).group(1)
+
+# Parse package name from init file. Importing __init__.py / gprMax will break as gprMax depends on compiled .pyx files.
+with open('gprMax/__init__.py', 'r') as fd:
+ packagename = re.search(r'^__name__\s*=\s*[\'"]([^\'"]*)[\'"]',
+ fd.read(), re.MULTILINE).group(1)
+
+packages = [packagename, 'tests', 'tools', 'user_libs']
+
+# Parse long_description from README.rst file.
+with open('README.rst','r') as fd:
+ long_description = fd.read()
+
+# Python version
+if sys.version_info[:2] < (3, 6):
+ sys.exit('\nExited: Requires Python 3.6 or newer!\n')
+
+# Process 'build' command line argument
+if 'build' in sys.argv:
+ print("Running 'build_ext --inplace'")
+ sys.argv.remove('build')
+ sys.argv.append('build_ext')
+ sys.argv.append('--inplace')
+
+# Process '--no-cython' command line argument - either Cythonize or just compile the .c files
+if '--no-cython' in sys.argv:
+ USE_CYTHON = False
+ sys.argv.remove('--no-cython')
+else:
+ USE_CYTHON = True
+
+# Build a list of all the files that need to be Cythonized looking in gprMax directory
+cythonfiles = []
+for root, dirs, files in os.walk(os.path.join(os.getcwd(), packagename), topdown=True):
+ for file in files:
+ if file.endswith('.pyx'):
+ cythonfiles.append(os.path.relpath(os.path.join(root, file)))
+
+# Process 'cleanall' command line argument - cleanup Cython files
+if 'cleanall' in sys.argv:
+ USE_CYTHON = False
+ for file in cythonfiles:
+ filebase = os.path.splitext(file)[0]
+ # Remove Cython C files
+ if os.path.isfile(filebase + '.c'):
+ try:
+ os.remove(filebase + '.c')
+ print('Removed: {}'.format(filebase + '.c'))
+ except OSError:
+ print('Could not remove: {}'.format(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')
+ if libfile:
+ libfile = libfile[0]
+ try:
+ os.remove(libfile)
+ print('Removed: {}'.format(os.path.abspath(libfile)))
+ except OSError:
+ print('Could not remove: {}'.format(os.path.abspath(libfile)))
+ # Remove build, dist, egg and __pycache__ directories
+ shutil.rmtree(os.path.join(os.getcwd(), 'build'), ignore_errors=True)
+ shutil.rmtree(os.path.join(os.getcwd(), 'dist'), ignore_errors=True)
+ shutil.rmtree(os.path.join(os.getcwd(), 'gprMax.egg-info'), ignore_errors=True)
+ for p in pathlib.Path(os.getcwd()).rglob('__pycache__'):
+ shutil.rmtree(p, ignore_errors=True)
+ print('Removed: {}'.format(p))
+ # Now do a normal clean
+ sys.argv[1] = 'clean' # this is what distutils understands
+
+# Set compiler options
+# Windows
+if sys.platform == 'win32':
+ compile_args = ['/O2', '/openmp', '/w'] # No static linking as no static version of OpenMP library; /w disables warnings
+ linker_args = []
+ extra_objects = []
+# macOS - needs gcc (usually via HomeBrew) because the default compiler LLVM (clang) does not support OpenMP
+# - with gcc -fopenmp option implies -pthread
+elif sys.platform == 'darwin':
+ gccpath = glob.glob('/usr/local/bin/gcc-[4-9]*')
+ if gccpath:
+ # Use newest gcc found
+ os.environ['CC'] = gccpath[-1].split(os.sep)[-1]
+ rpath = '/usr/local/opt/gcc/lib/gcc/' + gccpath[-1].split(os.sep)[-1][-1] + '/'
+ else:
+ raise('Cannot find gcc 4-9 in /usr/local/bin. gprMax requires gcc to be installed - easily done through the Homebrew package manager (http://brew.sh). Note: gcc with OpenMP support is required.')
+ compile_args = ['-O3', '-w', '-fopenmp', '-march=native'] # Sometimes worth testing with '-fstrict-aliasing', '-fno-common'
+ linker_args = ['-fopenmp', '-Wl,-rpath,' + rpath]
+ extra_objects = []
+# Linux
+elif sys.platform == 'linux':
+ compile_args = ['-O3', '-w', '-fopenmp', '-march=native']
+ linker_args = ['-fopenmp']
+ extra_objects = []
+
+# Build a list of all the extensions
+extensions = []
+for file in cythonfiles:
+ tmp = os.path.splitext(file)
+ if USE_CYTHON:
+ fileext = tmp[1]
+ else:
+ fileext = '.c'
+ extension = Extension(tmp[0].replace(os.sep, '.'),
+ [tmp[0] + fileext],
+ language='c',
+ include_dirs=[np.get_include()],
+ extra_compile_args=compile_args,
+ extra_link_args=linker_args,
+ extra_objects=extra_objects)
+ extensions.append(extension)
+
+# Cythonize (build .c files)
+if USE_CYTHON:
+ from Cython.Build import cythonize
+ extensions = cythonize(extensions,
+ compiler_directives={
+ 'boundscheck': False,
+ 'wraparound': False,
+ 'initializedcheck': False,
+ 'embedsignature': True,
+ 'language_level': 3
+ },
+ annotate=False)
+
+# SetupTools Required to make package
+import setuptools
+
+setup(name=packagename,
+ version=version,
+ author='Craig Warren and Antonis Giannopoulos',
+ url='http://www.gprmax.com',
+ 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+',
+ classifiers=[
+ 'Environment :: Console',
+ 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
+ 'Operating System :: MacOS',
+ 'Operating System :: Microsoft :: Windows',
+ 'Operating System :: POSIX :: Linux',
+ 'Programming Language :: Cython',
+ 'Programming Language :: Python :: 3',
+ 'Topic :: Scientific/Engineering'
+ ],
+ #requirements
+ python_requires=">3.6",
+ install_requires=[
+ "colorama",
+ "cython",
+ "h5py",
+ "jupyter",
+ "matplotlib",
+ "numpy",
+ "psutil",
+ "scipy",
+ "terminaltables",
+ "tqdm",
+ ],
+ ext_modules=extensions,
+ packages=packages,
+ include_package_data=True,
+ include_dirs=[np.get_include()])