Incomplete mods and updates as discussed will John. Not implemented new config yet.

这个提交包含在:
Craig Warren
2019-10-10 16:45:25 +01:00
父节点 3cad0f25b0
当前提交 c11dbd7671
共有 31 个文件被更改,包括 537 次插入574 次删除

查看文件

@@ -16,8 +16,9 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with gprMax. If not, see <http://www.gnu.org/licenses/>. # along with gprMax. If not, see <http://www.gnu.org/licenses/>.
import logging
import numpy as np import numpy as np
from tqdm import tqdm
import gprMax.config as config import gprMax.config as config
from .cmds_geometry import UserObjectGeometry from .cmds_geometry import UserObjectGeometry
@@ -28,6 +29,8 @@ from ..materials import Material
from ..utilities import round_value from ..utilities import round_value
log = logging.getLogger(__name__)
class AddGrass(UserObjectGeometry): class AddGrass(UserObjectGeometry):
"""Allows you to add grass with roots to a :class:`gprMax.cmds_geometry.fractal_box.FractalBox` in the model. """Allows you to add grass with roots to a :class:`gprMax.cmds_geometry.fractal_box.FractalBox` in the model.
@@ -68,12 +71,12 @@ class AddGrass(UserObjectGeometry):
except KeyError: except KeyError:
seed = None seed = None
# grab the correct fractal volume # Get the correct fractal volume
volumes = [volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id] volumes = [volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id]
if volumes: if volumes:
volume = volumes[0] volume = volumes[0]
else: else:
raise CmdInputError(self.__str__() + ' Cant find FractalBox {}'.format(fractal_box_id)) raise CmdInputError(self.__str__() + f' cannot find FractalBox {fractal_box_id}')
p1, p2 = uip.check_box_points(p1, p2, self.__str__()) p1, p2 = uip.check_box_points(p1, p2, self.__str__())
xs, ys, zs = p1 xs, ys, zs = p1
@@ -193,4 +196,4 @@ class AddGrass(UserObjectGeometry):
volume.fractalsurfaces.append(surface) volume.fractalsurfaces.append(surface)
if config.is_messages(): 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)) tqdm.write(f'{n_blades} blades of grass on surface from {xs * grid.dx:g}m, {ys * grid.dy:g}m, {zs * grid.dz:g}m, to {xf * grid.dx:g}m, {yf * grid.dy:g}m, {zf * grid.dz:g}m with fractal dimension {surface.dimension:g}, fractal seeding {surface.seed}, and range {limits[0]:g}m to {limits[1]:g}m, added to {surface.operatingonID}.')

查看文件

@@ -16,8 +16,9 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with gprMax. If not, see <http://www.gnu.org/licenses/>. # along with gprMax. If not, see <http://www.gnu.org/licenses/>.
import logging
import numpy as np import numpy as np
from tqdm import tqdm
import gprMax.config as config import gprMax.config as config
from .cmds_geometry import UserObjectGeometry from .cmds_geometry import UserObjectGeometry
@@ -26,6 +27,8 @@ from ..exceptions import CmdInputError
from ..fractals import FractalSurface from ..fractals import FractalSurface
from ..utilities import round_value from ..utilities import round_value
log = logging.getLogger(__name__)
class AddSurfaceRoughness(UserObjectGeometry): class AddSurfaceRoughness(UserObjectGeometry):
"""Allows you to add grass with roots to a :class:`gprMax.cmds_geometry.fractal_box.FractalBox` in the model. """Allows you to add grass with roots to a :class:`gprMax.cmds_geometry.fractal_box.FractalBox` in the model.
@@ -62,19 +65,19 @@ class AddSurfaceRoughness(UserObjectGeometry):
limits = np.array(self.kwargs['limits']) limits = np.array(self.kwargs['limits'])
fractal_box_id = self.kwargs['fractal_box_id'] fractal_box_id = self.kwargs['fractal_box_id']
except KeyError: except KeyError:
raise CmdInputError(self.__str__() + ' Incorrect parameters') raise CmdInputError(self.__str__() + ' incorrect parameters')
try: try:
seed = self.kwargs['seed'] seed = self.kwargs['seed']
except KeyError: except KeyError:
seed = None seed = None
# grab the correct fractal volume # Get the correct fractal volume
volumes = [volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id] volumes = [volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id]
if volumes: if volumes:
volume = volumes[0] volume = volumes[0]
else: else:
raise CmdInputError(self.__str__() + ' Cant find FractalBox {}'.format(fractal_box_id)) raise CmdInputError(self.__str__() + ' cannot find FractalBox {fractal_box_id}')
p1, p2 = uip.check_box_points(p1, p2, self.__str__()) p1, p2 = uip.check_box_points(p1, p2, self.__str__())
xs, ys, zs = p1 xs, ys, zs = p1
@@ -152,10 +155,10 @@ class AddSurfaceRoughness(UserObjectGeometry):
# List of existing surfaces IDs # List of existing surfaces IDs
existingsurfaceIDs = [x.surfaceID for x in volume.fractalsurfaces] existingsurfaceIDs = [x.surfaceID for x in volume.fractalsurfaces]
if surface.surfaceID in existingsurfaceIDs: if surface.surfaceID in existingsurfaceIDs:
raise CmdInputError(self.__str__() + ' has already been used on the {} surface'.format(surface.surfaceID)) raise CmdInputError(self.__str__() + f' has already been used on the {surface.surfaceID} surface')
surface.generate_fractal_surface(grid) surface.generate_fractal_surface(grid)
volume.fractalsurfaces.append(surface) volume.fractalsurfaces.append(surface)
if config.is_messages(): 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)) log.info(f'Fractal surface from {xs * grid.dx:g}m, {ys * grid.dy:g}m, {zs * grid.dz:g}m, to {xf * grid.dx:g}m, {yf * grid.dy:g}m, {zf * grid.dz:g}m with fractal dimension {surface.dimension:g}, fractal weightings {surface.weighting[0]:g}, {surface.weighting[1]:g}, fractal seeding {surface.seed}, and range {limits[0]:g}m to {limits[1]:g}m, added to {surface.operatingonID}.')

查看文件

@@ -16,7 +16,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with gprMax. If not, see <http://www.gnu.org/licenses/>. # along with gprMax. If not, see <http://www.gnu.org/licenses/>.
from tqdm import tqdm import logging
import gprMax.config as config import gprMax.config as config
from .cmds_geometry import UserObjectGeometry from .cmds_geometry import UserObjectGeometry
@@ -25,6 +25,8 @@ from ..materials import Material
from ..utilities import round_value from ..utilities import round_value
log = logging.getLogger(__name__)
class AddSurfaceWater(UserObjectGeometry): class AddSurfaceWater(UserObjectGeometry):
"""Allows you to add surface water to a :class:`gprMax.cmds_geometry.fractal_box.FractalBox` in the model. """Allows you to add surface water to a :class:`gprMax.cmds_geometry.fractal_box.FractalBox` in the model.
@@ -54,12 +56,12 @@ class AddSurfaceWater(UserObjectGeometry):
except KeyError: except KeyError:
raise CmdInputError(self.__str__() + ' requires exactly eight parameters') raise CmdInputError(self.__str__() + ' requires exactly eight parameters')
# grab the correct fractal volume # Get the correct fractal volume
volumes = [volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id] volumes = [volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id]
if volumes: if volumes:
volume = volumes[0] volume = volumes[0]
else: else:
raise CmdInputError(self.__str__() + ' Cant find FractalBox {}'.format(fractal_box_id)) raise CmdInputError(self.__str__() + f' cannot find FractalBox {fractal_box_id}')
p1, p2 = uip.check_box_points(p1, p2, self.__str__()) p1, p2 = uip.check_box_points(p1, p2, self.__str__())
xs, ys, zs = p1 xs, ys, zs = p1
@@ -143,4 +145,4 @@ class AddSurfaceWater(UserObjectGeometry):
raise CmdInputError(self.__str__() + ' requires the time step for the model to be less than the relaxation time required to model water.') 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(): 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)) log.info('Water on surface from {xs * grid.dx:g}m, {ys * grid.dy:g}m, {zs * grid.dz:g}m, to {xf * grid.dx:g}m, {yf * grid.dy:g}m, {zf * grid.dz:g}m with depth {filldepth:g}m, added to {surface.operatingonID}.')

查看文件

@@ -15,14 +15,13 @@
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with gprMax. If not, see <http://www.gnu.org/licenses/>. # along with gprMax. If not, see <http://www.gnu.org/licenses/>.
import logging
import sys import sys
import numpy as np import numpy as np
from tqdm import tqdm
import gprMax.config as config import gprMax.config as config
from .config import z0
from .config import dtypes
from .cmds_geometry.cmds_geometry import UserObjectGeometry from .cmds_geometry.cmds_geometry import UserObjectGeometry
from .exceptions import CmdInputError from .exceptions import CmdInputError
from .geometry_outputs import GeometryObjects as GeometryObjectsUser from .geometry_outputs import GeometryObjects as GeometryObjectsUser
@@ -40,18 +39,16 @@ from .subgrids.base import SubGridBase
from .utilities import round_value from .utilities import round_value
from .waveforms import Waveform as WaveformUser from .waveforms import Waveform as WaveformUser
floattype = dtypes['float_or_double']
log = logging.getLogger(__name__)
class UserObjectMulti: class UserObjectMulti:
"""Object that can occur multiple times in a model.""" """Object that can occur multiple times in a model."""
def __init__(self, **kwargs): def __init__(self, **kwargs):
"""Constructor."""
self.kwargs = kwargs self.kwargs = kwargs
self.order = None self.order = None
self.hash = '#example' self.hash = '#example'
# auto translate
self.autotranslate = True self.autotranslate = True
@@ -63,7 +60,7 @@ class UserObjectMulti:
v = ' '.join([str(el) for el in v]) v = ' '.join([str(el) for el in v])
s += str(v) + ' ' s += str(v) + ' '
return '{}: {}'.format(self.hash, s[:-1]) return f'{self.hash}: {s[:-1]}'
def create(self, grid, uip): def create(self, grid, uip):
"""Create the object and add it to the grid.""" """Create the object and add it to the grid."""
@@ -96,16 +93,15 @@ class Waveform(UserObjectMulti):
amp = self.kwargs['amp'] amp = self.kwargs['amp']
freq = self.kwargs['freq'] freq = self.kwargs['freq']
ID = self.kwargs['id'] ID = self.kwargs['id']
except KeyError: except KeyError:
raise CmdInputError(self.params_str() + ' requires exactly four parameters') raise CmdInputError(f"'{self.params_str()}' requires exactly four parameters")
if wavetype not in WaveformUser.types: if wavetype not in WaveformUser.types:
raise CmdInputError(self.__str__() + ' must have one of the following types {}'.format(','.join(WaveformUser.types))) raise CmdInputError(f"'{self.params_str()}' must have one of the following types {','.join(WaveformUser.types)}")
if freq <= 0: if freq <= 0:
raise CmdInputError(self.__str__() + ' requires an excitation frequency value of greater than zero') raise CmdInputError(f"'{self.params_str()}' requires an excitation frequency value of greater than zero")
if any(x.ID == ID for x in grid.waveforms): if any(x.ID == ID for x in grid.waveforms):
raise CmdInputError(self.__str__() + ' with ID {} already exists'.format(ID)) raise CmdInputError(f"'{self.params_str()}' with ID {ID} already exists")
w = WaveformUser() w = WaveformUser()
w.ID = ID w.ID = ID
@@ -113,8 +109,7 @@ class Waveform(UserObjectMulti):
w.amp = amp w.amp = amp
w.freq = freq w.freq = freq
if config.is_messages(): log.info(f'Waveform {w.ID} of type {w.type} with maximum amplitude scaling {w.amp:g}, frequency {w.freq:g}Hz created.')
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) grid.waveforms.append(w)
@@ -142,34 +137,32 @@ class VoltageSource(UserObjectMulti):
self.hash = '#voltage_source' self.hash = '#voltage_source'
def create(self, grid, uip): def create(self, grid, uip):
"""Create voltage source and add it to the grid."""
try: try:
p1 = self.kwargs['p1'] p1 = self.kwargs['p1']
polarisation = self.kwargs['polarisation'].lower() polarisation = self.kwargs['polarisation'].lower()
resistance = self.kwargs['resistance'] resistance = self.kwargs['resistance']
waveform_id = self.kwargs['waveform_id'] waveform_id = self.kwargs['waveform_id']
except KeyError: except KeyError:
raise CmdInputError(self.__str__() + " requires at least six parameters") raise CmdInputError(f"'{self.__str__()}' requires at least six parameters")
# Check polarity & position parameters # Check polarity & position parameters
if polarisation not in ('x', 'y', 'z'): if polarisation not in ('x', 'y', 'z'):
raise CmdInputError("'{}' polarisation must be x, y, or z".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' polarisation must be x, y, or z")
if '2D TMx' in grid.mode and (polarisation == 'y' or polarisation == 'z'): 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__())) raise CmdInputError(f"'{self.__str__()}' polarisation must be x in 2D TMx mode")
elif '2D TMy' in grid.mode and (polarisation == 'x' or polarisation == 'z'): 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__())) raise CmdInputError(f"'{self.__str__()}' polarisation must be y in 2D TMy mode")
elif '2D TMz' in grid.mode and (polarisation == 'x' or polarisation == 'y'): 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__())) raise CmdInputError(f"'{self.__str__()}' polarisation must be z in 2D TMz mode")
xcoord, ycoord, zcoord = uip.check_src_rx_point(p1, self.__str__()) xcoord, ycoord, zcoord = uip.check_src_rx_point(p1, self.__str__())
if resistance < 0: if resistance < 0:
raise CmdInputError("'{}' requires a source resistance of zero or greater".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' requires a source resistance of zero or greater")
# Check if there is a waveformID in the waveforms list # Check if there is a waveformID in the waveforms list
if not any(x.ID == waveform_id for x in grid.waveforms): 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__())) raise CmdInputError(f"'{self.__str__()}' there is no waveform with the identifier {tmp[5]}")
v = VoltageSourceUser() v = VoltageSourceUser()
v.polarisation = polarisation v.polarisation = polarisation
@@ -185,17 +178,17 @@ class VoltageSource(UserObjectMulti):
stop = self.kwargs['stop'] stop = self.kwargs['stop']
# Check source start & source remove time parameters # Check source start & source remove time parameters
if start < 0: if start < 0:
raise CmdInputError("'{}' delay of the initiation of the source should not be less than zero".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' delay of the initiation of the source should not be less than zero")
if stop < 0: if stop < 0:
raise CmdInputError("'{}' time to remove the source should not be less than zero".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' time to remove the source should not be less than zero")
if stop - start <= 0: if stop - start <= 0:
raise CmdInputError("'{}' duration of the source should not be zero or less".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' duration of the source should not be zero or less")
v.start = start v.start = start
if stop > grid.timewindow: if stop > grid.timewindow:
v.stop = grid.timewindow v.stop = grid.timewindow
else: else:
v.stop = stop v.stop = stop
startstop = ' start time {:g} secs, finish time {:g} secs '.format(v.start, v.stop) startstop = f' start time {v.start:g} secs, finish time {v.stop:g} secs '
except KeyError: except KeyError:
v.start = 0 v.start = 0
v.stop = grid.timewindow v.stop = grid.timewindow
@@ -203,8 +196,7 @@ class VoltageSource(UserObjectMulti):
v.calculate_waveform_values(grid) v.calculate_waveform_values(grid)
if config.is_messages(): log.info('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))
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) grid.voltagesources.append(v)
@@ -226,36 +218,33 @@ class HertzianDipole(UserObjectMulti):
""" """
def __init__(self, **kwargs): def __init__(self, **kwargs):
"""Constructor."""
super().__init__(**kwargs) super().__init__(**kwargs)
self.order = 2 self.order = 2
self.hash = '#hertzian_dipole' self.hash = '#hertzian_dipole'
def create(self, grid, uip): def create(self, grid, uip):
"""Create HertzianDipole and add it to the grid."""
try: try:
polarisation = self.kwargs['polarisation'].lower() polarisation = self.kwargs['polarisation'].lower()
p1 = self.kwargs['p1'] p1 = self.kwargs['p1']
waveform_id = self.kwargs['waveform_id'] waveform_id = self.kwargs['waveform_id']
except KeyError: except KeyError:
raise CmdInputError("'{}' requires at least 3 parameters".format(self.params_str())) raise CmdInputError(f"'{self.__str__()}' requires at least 3 parameters")
# Check polarity & position parameters # Check polarity & position parameters
if polarisation not in ('x', 'y', 'z'): if polarisation not in ('x', 'y', 'z'):
raise CmdInputError("'{}' polarisation must be x, y, or z".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' polarisation must be x, y, or z")
if '2D TMx' in grid.mode and (polarisation == 'y' or polarisation == 'z'): 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__())) raise CmdInputError(f"'{self.__str__()}' polarisation must be x in 2D TMx mode")
elif '2D TMy' in grid.mode and (polarisation == 'x' or polarisation == 'z'): 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__())) raise CmdInputError(f"'{self.__str__()}' polarisation must be y in 2D TMy mode")
elif '2D TMz' in grid.mode and (polarisation == 'x' or polarisation == 'y'): 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__())) raise CmdInputError(f"'{self.__str__()}' polarisation must be z in 2D TMz mode")
xcoord, ycoord, zcoord = uip.check_src_rx_point(p1, self.__str__()) xcoord, ycoord, zcoord = uip.check_src_rx_point(p1, self.__str__())
# Check if there is a waveformID in the waveforms list # Check if there is a waveformID in the waveforms list
if not any(x.ID == waveform_id for x in grid.waveforms): 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__())) raise CmdInputError(f"'{self.__str__()}' there is no waveform with the identifier {tmp[4]}')
h = HertzianDipoleUser() h = HertzianDipoleUser()
h.polarisation = polarisation h.polarisation = polarisation
@@ -282,17 +271,17 @@ class HertzianDipole(UserObjectMulti):
start = self.kwargs['start'] start = self.kwargs['start']
stop = self.kwargs['stop'] stop = self.kwargs['stop']
if start < 0: if start < 0:
raise CmdInputError("'{}' delay of the initiation of the source should not be less than zero".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' delay of the initiation of the source should not be less than zero")
if stop < 0: if stop < 0:
raise CmdInputError("'{}' time to remove the source should not be less than zero".format(self.__str__())) raise CmdInputError(f"'{self.__str__()self.__str__()}' time to remove the source should not be less than zero")
if stop - start <= 0: if stop - start <= 0:
raise CmdInputError("'{}' duration of the source should not be zero or less".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' duration of the source should not be zero or less")
h.start = start h.start = start
if stop > grid.timewindow: if stop > grid.timewindow:
h.stop = grid.timewindow h.stop = grid.timewindow
else: else:
h.stop = stop h.stop = stop
startstop = ' start time {:g} secs, finish time {:g} secs '.format(h.start, h.stop) startstop = f' start time {h.start:g} secs, finish time {h.stop:g} secs '
except KeyError: except KeyError:
h.start = 0 h.start = 0
h.stop = grid.timewindow h.stop = grid.timewindow
@@ -300,11 +289,10 @@ class HertzianDipole(UserObjectMulti):
h.calculate_waveform_values(grid) h.calculate_waveform_values(grid)
if config.is_messages():
if grid.mode == '2D': 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)) log.info(f'Hertzian dipole is a line source in 2D with polarity {h.polarisation} at {h.xcoord * grid.dx:g}m, {h.ycoord * grid.dy:g}m, {h.zcoord * grid.dz:g}m,' + startstop + f'using waveform {h.waveformID} created.')
else: 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)) log.info(f'Hertzian dipole with polarity {h.polarisation} at {h.xcoord * grid.dx:g}m, {h.ycoord * grid.dy:g}m, {h.zcoord * grid.dz:g}m,' + startstop + f'using waveform {h.waveformID} created.')
grid.hertziandipoles.append(h) grid.hertziandipoles.append(h)
@@ -326,35 +314,33 @@ class MagneticDipole(UserObjectMulti):
""" """
def __init__(self, **kwargs): def __init__(self, **kwargs):
"""Constructor."""
super().__init__(**kwargs) super().__init__(**kwargs)
self.order = 3 self.order = 3
self.hash = '#magnetic_dipole' self.hash = '#magnetic_dipole'
def create(self, grid, uip): def create(self, grid, uip):
"""Create Magnetic Dipole and add it the grid."""
try: try:
polarisation = self.kwargs['polarisation'].lower() polarisation = self.kwargs['polarisation'].lower()
p1 = self.kwargs['p1'] p1 = self.kwargs['p1']
waveform_id = self.kwargs['waveform_id'] waveform_id = self.kwargs['waveform_id']
except KeyError: except KeyError:
raise CmdInputError("'{}' requires at least five parameters".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' requires at least five parameters")
# Check polarity & position parameters # Check polarity & position parameters
if polarisation not in ('x', 'y', 'z'): if polarisation not in ('x', 'y', 'z'):
raise CmdInputError("'{}' polarisation must be x, y, or z".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' polarisation must be x, y, or z")
if '2D TMx' in grid.mode and (polarisation == 'y' or polarisation == 'z'): 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__())) raise CmdInputError(f"'{self.__str__()}' polarisation must be x in 2D TMx mode")
elif '2D TMy' in grid.mode and (polarisation == 'x' or polarisation == 'z'): 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__())) raise CmdInputError(f"'{self.__str__()}' polarisation must be y in 2D TMy mode")
elif '2D TMz' in grid.mode and (polarisation == 'x' or polarisation == 'y'): 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__())) raise CmdInputError(f"'{self.__str__()}' polarisation must be z in 2D TMz mode")
xcoord, ycoord, zcoord = uip.check_src_rx_point(p1, self.__str__()) xcoord, ycoord, zcoord = uip.check_src_rx_point(p1, self.__str__())
# Check if there is a waveformID in the waveforms list # Check if there is a waveformID in the waveforms list
if not any(x.ID == waveform_id for x in grid.waveforms): 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)) raise CmdInputError(f"'{self.__str__()}' there is no waveform with the identifier {waveform_id}")
m = MagneticDipoleUser() m = MagneticDipoleUser()
m.polarisation = polarisation m.polarisation = polarisation
@@ -372,17 +358,17 @@ class MagneticDipole(UserObjectMulti):
start = self.kwargs['start'] start = self.kwargs['start']
stop = self.kwargs['stop'] stop = self.kwargs['stop']
if start < 0: if start < 0:
raise CmdInputError("'{}' delay of the initiation of the source should not be less than zero".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' delay of the initiation of the source should not be less than zero")
if stop < 0: if stop < 0:
raise CmdInputError("'{}' time to remove the source should not be less than zero".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' time to remove the source should not be less than zero")
if stop - start <= 0: if stop - start <= 0:
raise CmdInputError("'{}' duration of the source should not be zero or less".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' duration of the source should not be zero or less")
m.start = start m.start = start
if stop > grid.timewindow: if stop > grid.timewindow:
m.stop = grid.timewindow m.stop = grid.timewindow
else: else:
m.stop = stop m.stop = stop
startstop = ' start time {:g} secs, finish time {:g} secs '.format(m.start, m.stop) startstop = f' start time {m.start:g} secs, finish time {m.stop:g} secs '
except KeyError: except KeyError:
m.start = 0 m.start = 0
m.stop = grid.timewindow m.stop = grid.timewindow
@@ -390,8 +376,7 @@ class MagneticDipole(UserObjectMulti):
m.calculate_waveform_values(grid) m.calculate_waveform_values(grid)
if config.is_messages(): log.info(f'Magnetic dipole with polarity {m.polarisation} at {m.xcoord * grid.dx:g}m, {m.ycoord * grid.dy:g}m, {m.zcoord * grid.dz:g}m,' + startstop + f'using waveform {m.waveformID} created.')
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) grid.magneticdipoles.append(m)
@@ -415,44 +400,41 @@ class TransmissionLine(UserObjectMulti):
""" """
def __init__(self, **kwargs): def __init__(self, **kwargs):
"""Constructor."""
super().__init__(**kwargs) super().__init__(**kwargs)
self.order = 4 self.order = 4
self.hash = '#transmission_line' self.hash = '#transmission_line'
def create(self, grid, uip): def create(self, grid, uip):
try: try:
polarisation = self.kwargs['polarisation'].lower() polarisation = self.kwargs['polarisation'].lower()
p1 = self.kwargs['p1'] p1 = self.kwargs['p1']
waveform_id = self.kwargs['waveform_id'] waveform_id = self.kwargs['waveform_id']
resistance = self.kwargs['resistance'] resistance = self.kwargs['resistance']
except KeyError: except KeyError:
raise CmdInputError("'{}' requires at least six parameters".format(self.params_str())) raise CmdInputError(f"'{self.params_str()}' requires at least six parameters")
# Warn about using a transmission line on GPU # Warn about using a transmission line on GPU
if grid.gpu is not None: 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__())) raise CmdInputError(f"'{self.params_str()}' A #transmission_line cannot currently be used with GPU solving. Consider using a #voltage_source instead.")
# Check polarity & position parameters # Check polarity & position parameters
if polarisation not in ('x', 'y', 'z'): if polarisation not in ('x', 'y', 'z'):
raise CmdInputError("'{}' polarisation must be x, y, or z".format(self.__str__())) raise CmdInputError(f"'{self.params_str()}' polarisation must be x, y, or z")
if '2D TMx' in grid.mode and (polarisation == 'y' or polarisation == 'z'): 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__())) raise CmdInputError(f"'{self.params_str()}' polarisation must be x in 2D TMx mode")
elif '2D TMy' in grid.mode and (polarisation == 'x' or polarisation == 'z'): 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__())) raise CmdInputError(f"'{self.params_str()}' polarisation must be y in 2D TMy mode")
elif '2D TMz' in grid.mode and (polarisation == 'x' or polarisation == 'y'): 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__())) raise CmdInputError(f"'{self.params_str()}' polarisation must be z in 2D TMz mode")
xcoord, ycoord, zcoord = uip.check_src_rx_point(p1, self.__str__()) xcoord, ycoord, zcoord = uip.check_src_rx_point(p1, self.__str__())
if resistance <= 0 or resistance >= z0: 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__())) raise CmdInputError(f"'{self.params_str()}' requires a resistance greater than zero and less than the impedance of free space, i.e. 376.73 Ohms")
# Check if there is a waveformID in the waveforms list # Check if there is a waveformID in the waveforms list
if not any(x.ID == waveform_id for x in grid.waveforms): 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__())) raise CmdInputError(f"'{self.params_str()}' there is no waveform with the identifier {tmp[5]}")
t = TransmissionLineUser(grid) t = TransmissionLineUser(grid)
t.polarisation = polarisation t.polarisation = polarisation
@@ -468,17 +450,17 @@ class TransmissionLine(UserObjectMulti):
start = self.kwargs['start'] start = self.kwargs['start']
stop = self.kwargs['stop'] stop = self.kwargs['stop']
if start < 0: if start < 0:
raise CmdInputError("'{}' delay of the initiation of the source should not be less than zero".format(self.__str__())) raise CmdInputError(f"'{self.params_str()}' delay of the initiation of the source should not be less than zero")
if stop < 0: if stop < 0:
raise CmdInputError("'{}' time to remove the source should not be less than zero".format(self.__str__())) raise CmdInputError(f"'{self.params_str()}' time to remove the source should not be less than zero")
if stop - start <= 0: if stop - start <= 0:
raise CmdInputError("'{}' duration of the source should not be zero or less".format(self.__str__())) raise CmdInputError(f"'{self.params_str()}' duration of the source should not be zero or less")
t.start = start t.start = start
if stop > grid.timewindow: if stop > grid.timewindow:
t.stop = grid.timewindow t.stop = grid.timewindow
else: else:
t.stop = stop t.stop = stop
startstop = ' start time {:g} secs, finish time {:g} secs '.format(t.start, t.stop) startstop = f' start time {t.start:g} secs, finish time {t.stop:g} secs '
except KeyError: except KeyError:
t.start = 0 t.start = 0
t.stop = grid.timewindow t.stop = grid.timewindow
@@ -487,8 +469,7 @@ class TransmissionLine(UserObjectMulti):
t.calculate_waveform_values(grid) t.calculate_waveform_values(grid)
t.calculate_incident_V_I(grid) t.calculate_incident_V_I(grid)
if config.is_messages(): log.info(f'Transmission line with polarity {t.polarisation} at {t.xcoord * grid.dx:g}m, {t.ycoord * grid.dy:g}m, {t.zcoord * grid.dz:g}m, resistance {t.resistance:.1f} Ohms,' + startstop + f'using waveform {t.waveformID} created.')
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) grid.transmissionlines.append(t)
@@ -508,7 +489,6 @@ class Rx(UserObjectMulti):
""" """
def __init__(self, **kwargs): def __init__(self, **kwargs):
"""Constructor."""
super().__init__(**kwargs) super().__init__(**kwargs)
self.order = 5 self.order = 5
self.hash = '#rx' self.hash = '#rx'
@@ -518,7 +498,7 @@ class Rx(UserObjectMulti):
try: try:
p1 = self.kwargs['p1'] p1 = self.kwargs['p1']
except KeyError: except KeyError:
raise CmdInputError("'{}' has an incorrect number of parameters".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' has an incorrect number of parameters")
p = uip.check_src_rx_point(p1, self.__str__()) p = uip.check_src_rx_point(p1, self.__str__())
@@ -537,17 +517,16 @@ class Rx(UserObjectMulti):
# Check and add field output names # Check and add field output names
for field in outputs: for field in outputs:
if field in allowableoutputs: if field in allowableoutputs:
r.outputs[field] = np.zeros(grid.iterations, dtype=floattype) r.outputs[field] = np.zeros(grid.iterations, dtype=config.dtypes['float_or_double'])
else: else:
raise CmdInputError("{} contains an output type that is not allowable. Allowable outputs in current context are {}".format(self.__str__(), allowableoutputs)) raise CmdInputError(f"'{self.__str__()}' contains an output type that is not allowable. Allowable outputs in current context are {allowableoutputs}")
# If no ID or outputs are specified, use default # If no ID or outputs are specified, use default
except KeyError: except KeyError:
r.ID = r.__class__.__name__ + '(' + str(r.xcoord) + ',' + str(r.ycoord) + ',' + str(r.zcoord) + ')' r.ID = r.__class__.__name__ + '(' + str(r.xcoord) + ',' + str(r.ycoord) + ',' + str(r.zcoord) + ')'
for key in RxUser.defaultoutputs: for key in RxUser.defaultoutputs:
r.outputs[key] = np.zeros(grid.iterations, dtype=floattype) r.outputs[key] = np.zeros(grid.iterations, dtype=config.dtypes['float_or_double'])
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))) log.info(f"Receiver at {r.xcoord * grid.dx:g}m, {r.ycoord * grid.dy:g}m, {r.zcoord * grid.dz:g}m with output component(s) {', '.join(r.outputs)} created.")
grid.rxs.append(r) grid.rxs.append(r)
@@ -566,47 +545,43 @@ class RxArray(UserObjectMulti):
""" """
def __init__(self, **kwargs): def __init__(self, **kwargs):
"""Constructor."""
super().__init__(**kwargs) super().__init__(**kwargs)
self.order = 6 self.order = 6
self.hash = '#rx_array' self.hash = '#rx_array'
def create(self, grid, uip): def create(self, grid, uip):
try: try:
p1 = self.kwargs['p1'] p1 = self.kwargs['p1']
p2 = self.kwargs['p2'] p2 = self.kwargs['p2']
dl = self.kwargs['dl'] dl = self.kwargs['dl']
except KeyError: except KeyError:
raise CmdInputError("'{}' requires exactly 9 parameters".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' requires exactly 9 parameters")
xs, ys, zs = uip.check_src_rx_point(p1, self.__str__(), 'lower') xs, ys, zs = uip.check_src_rx_point(p1, self.__str__(), 'lower')
xf, yf, zf = uip.check_src_rx_point(p2, self.__str__(), 'upper') xf, yf, zf = uip.check_src_rx_point(p2, self.__str__(), 'upper')
dx, dy, dz = uip.discretise_point(dl) dx, dy, dz = uip.discretise_point(dl)
if xs > xf or ys > yf or zs > zf: if xs > xf or ys > yf or zs > zf:
raise CmdInputError("'{}' the lower coordinates should be less than the upper coordinates".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' the lower coordinates should be less than the upper coordinates")
if dx < 0 or dy < 0 or dz < 0: if dx < 0 or dy < 0 or dz < 0:
raise CmdInputError("'{}' the step size should not be less than zero".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' the step size should not be less than zero")
if dx < 1: if dx < 1:
if dx == 0: if dx == 0:
dx = 1 dx = 1
else: else:
raise CmdInputError("'{}' the step size should not be less than the spatial discretisation".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' the step size should not be less than the spatial discretisation")
if dy < 1: if dy < 1:
if dy == 0: if dy == 0:
dy = 1 dy = 1
else: else:
raise CmdInputError("'{}' the step size should not be less than the spatial discretisation".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' the step size should not be less than the spatial discretisation")
if dz < 1: if dz < 1:
if dz == 0: if dz == 0:
dz = 1 dz = 1
else: else:
raise CmdInputError("'{}' the step size should not be less than the spatial discretisation".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' the step size should not be less than the spatial discretisation")
if config.is_messages(): log.info(f'Receiver array {xs * grid.dx:g}m, {ys * grid.dy:g}m, {zs * grid.dz:g}m, to {xf * grid.dx:g}m, {yf * grid.dy:g}m, {zf * grid.dz:g}m with steps {dx * grid.dx:g}m, {dy * grid.dy:g}m, {dz * grid.dz:g}m')
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 x in range(xs, xf + 1, dx):
for y in range(ys, yf + 1, dy): for y in range(ys, yf + 1, dy):
@@ -620,9 +595,8 @@ class RxArray(UserObjectMulti):
r.zcoordorigin = z r.zcoordorigin = z
r.ID = r.__class__.__name__ + '(' + str(x) + ',' + str(y) + ',' + str(z) + ')' r.ID = r.__class__.__name__ + '(' + str(x) + ',' + str(y) + ',' + str(z) + ')'
for key in RxUser.defaultoutputs: for key in RxUser.defaultoutputs:
r.outputs[key] = np.zeros(grid.iterations, dtype=floattype) r.outputs[key] = np.zeros(grid.iterations, dtype=config.dtypes['float_or_double'])
if config.is_messages(): log.info(f" Receiver at {r.xcoord * grid.dx:g}m, {r.ycoord * grid.dy:g}m, {r.zcoord * grid.dz:g}m with output component(s) {', '.join(r.outputs)} created.")
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) grid.rxs.append(r)
@@ -644,22 +618,20 @@ class Snapshot(UserObjectMulti):
""" """
def __init__(self, **kwargs): def __init__(self, **kwargs):
"""Constructor."""
super().__init__(**kwargs) super().__init__(**kwargs)
self.order = 19 self.order = 19
self.hash = '#snapshot' self.hash = '#snapshot'
def create(self, grid, uip): def create(self, grid, uip):
if isinstance(grid, SubGridBase): if isinstance(grid, SubGridBase):
raise CmdInputError("'{}' Do not add Snapshots to Subgrids.".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' do not add Snapshots to Subgrids.")
try: try:
p1 = self.kwargs['p1'] p1 = self.kwargs['p1']
p2 = self.kwargs['p2'] p2 = self.kwargs['p2']
dl = self.kwargs['dl'] dl = self.kwargs['dl']
filename = self.kwargs['filename'] filename = self.kwargs['filename']
except KeyError: except KeyError:
raise CmdInputError("'{}' requires exactly 11 parameters".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' requires exactly 11 parameters")
p1, p2 = uip.check_box_points(p1, p2, self.__str__()) p1, p2 = uip.check_box_points(p1, p2, self.__str__())
xs, ys, zs = p1 xs, ys, zs = p1
@@ -674,18 +646,18 @@ class Snapshot(UserObjectMulti):
try: try:
time = self.kwargs['time'] time = self.kwargs['time']
except KeyError: except KeyError:
raise CmdInputError("'{}' requires exactly 5 parameters".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' requires exactly 5 parameters")
if time > 0: if time > 0:
iterations = round_value((time / grid.dt)) + 1 iterations = round_value((time / grid.dt)) + 1
else: else:
raise CmdInputError("'{}' time value must be greater than zero".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' time value must be greater than zero")
if dx < 0 or dy < 0 or dz < 0: if dx < 0 or dy < 0 or dz < 0:
raise CmdInputError("'{}' the step size should not be less than zero".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' the step size should not be less than zero")
if dx < 1 or dy < 1 or dz < 1: 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__())) raise CmdInputError(f"'{self.__str__()}' the step size should not be less than the spatial discretisation")
if iterations <= 0 or iterations > grid.iterations: if iterations <= 0 or iterations > grid.iterations:
raise CmdInputError("'{}' time value is not valid".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' time value is not valid")
# Replace with old style snapshots if there are subgrids # Replace with old style snapshots if there are subgrids
#if grid.subgrids: #if grid.subgrids:
@@ -694,8 +666,7 @@ class Snapshot(UserObjectMulti):
#else: #else:
s = SnapshotUser(xs, ys, zs, xf, yf, zf, dx, dy, dz, iterations, filename) s = SnapshotUser(xs, ys, zs, xf, yf, zf, dx, dy, dz, iterations, filename)
if config.is_messages(): log.info(f'Snapshot from {xs * grid.dx:g}m, {ys * grid.dy:g}m, {zs * grid.dz:g}m, to {xf * grid.dx:g}m, {yf * grid.dy:g}m, {zf * grid.dz:g}m, discretisation {dx * grid.dx:g}m, {dy * grid.dy:g}m, {dz * grid.dz:g}m, at {s.time * grid.dt:g} secs with filename {s.basefilename} created.')
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) grid.snapshots.append(s)
@@ -714,7 +685,6 @@ class Material(UserObjectMulti):
""" """
def __init__(self, **kwargs): def __init__(self, **kwargs):
"""Constructor."""
super().__init__(**kwargs) super().__init__(**kwargs)
self.order = 8 self.order = 8
self.hash = '#material' self.hash = '#material'
@@ -727,22 +697,22 @@ class Material(UserObjectMulti):
sm = self.kwargs['sm'] sm = self.kwargs['sm']
material_id = self.kwargs['id'] material_id = self.kwargs['id']
except KeyError: except KeyError:
raise CmdInputError('{} requires exactly five parameters'.format(self.params_str())) raise CmdInputError(f"'{self.params_str()}' requires exactly five parameters")
if er < 1: if er < 1:
raise CmdInputError('{} requires a positive value of one or greater for static (DC) permittivity'.format(self.__str__())) raise CmdInputError(f"'{self.params_str()}' requires a positive value of one or greater for static (DC) permittivity")
if se != 'inf': if se != 'inf':
se = float(se) se = float(se)
if se < 0: if se < 0:
raise CmdInputError('{} requires a positive value for conductivity'.format(self.__str__())) raise CmdInputError(f"'{self.params_str()}' requires a positive value for conductivity")
else: else:
se = float('inf') se = float('inf')
if mr < 1: if mr < 1:
raise CmdInputError('{} requires a positive value of one or greater for permeability'.format(self.__str__())) raise CmdInputError(f"'{self.params_str()}' requires a positive value of one or greater for permeability")
if sm < 0: if sm < 0:
raise CmdInputError('{} requires a positive value for magnetic conductivity'.format(self.__str__())) raise CmdInputError(f"'{self.params_str()}' requires a positive value for magnetic conductivity")
if any(x.ID == material_id for x in grid.materials): if any(x.ID == material_id for x in grid.materials):
raise CmdInputError('{} with ID {} already exists'.format(material_id).format(self.__str__())) raise CmdInputError(f"'{self.params_str()}' with ID {material_id} already exists")
# Create a new instance of the Material class material (start index after pec & free_space) # Create a new instance of the Material class material (start index after pec & free_space)
m = MaterialUser(len(grid.materials), material_id) m = MaterialUser(len(grid.materials), material_id)
@@ -755,8 +725,7 @@ class Material(UserObjectMulti):
if m.se == float('inf'): if m.se == float('inf'):
m.averagable = False m.averagable = False
if config.is_messages(): log.info(f'Material {m.ID} with eps_r={m.er:g}, sigma={m.se:g} S/m; mu_r={m.mr:g}, sigma*={m.sm:g} Ohm/m created.')
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 # Append the new material object to the materials list
grid.materials.append(m) grid.materials.append(m)
@@ -776,31 +745,28 @@ class AddDebyeDispersion(UserObjectMulti):
""" """
def __init__(self, **kwargs): def __init__(self, **kwargs):
"""Constructor."""
super().__init__(**kwargs) super().__init__(**kwargs)
self.order = 9 self.order = 9
self.hash = '#add_dispersion_debye' self.hash = '#add_dispersion_debye'
def create(self, grid, uip): def create(self, grid, uip):
try: try:
poles = self.kwargs['n_poles'] poles = self.kwargs['n_poles']
er_delta = self.kwargs['er_delta'] er_delta = self.kwargs['er_delta']
tau = self.kwargs['tau'] tau = self.kwargs['tau']
material_ids = self.kwargs['material_ids'] material_ids = self.kwargs['material_ids']
except KeyError: except KeyError:
raise CmdInputError(self.__str__() + ' requires at least four parameters') raise CmdInputError(f"'{self.params_str()}' requires at least four parameters")
if poles < 0: if poles < 0:
raise CmdInputError(self.__str__() + ' requires a positive value for number of poles') raise CmdInputError(f"'{self.params_str()}' requires a positive value for number of poles")
# Look up requested materials in existing list of material instances # 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] materials = [y for x in material_ids for y in grid.materials if y.ID == x]
if len(materials) != len(material_ids): if len(materials) != len(material_ids):
notfound = [x for x in material_ids if x not in materials] notfound = [x for x in material_ids if x not in materials]
raise CmdInputError(self.__str__() + ' material(s) {} do not exist'.format(notfound)) raise CmdInputError(f"'{self.params_str()}' material(s) {notfound} do not exist")
for material in materials: for material in materials:
material.type = 'debye' material.type = 'debye'
@@ -812,12 +778,11 @@ class AddDebyeDispersion(UserObjectMulti):
material.deltaer.append(er_delta[i]) material.deltaer.append(er_delta[i])
material.tau.append(tau[i]) material.tau.append(tau[i])
else: else:
raise CmdInputError(self.__str__() + ' requires positive values for the permittivity difference.') raise CmdInputError(f"'{self.params_str()}' requires positive values for the permittivity difference.")
if material.poles > MaterialUser.maxpoles: if material.poles > MaterialUser.maxpoles:
MaterialUser.maxpoles = material.poles MaterialUser.maxpoles = material.poles
if config.is_messages(): log.info(f"Debye disperion added to {material.ID} with delta_eps_r={', '.join('%4.2f' % deltaer for deltaer in material.deltaer)}, and tau={', '.join('%4.3e' % tau for tau in material.tau)} secs created.")
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): class AddLorentzDispersion(UserObjectMulti):
@@ -836,13 +801,11 @@ class AddLorentzDispersion(UserObjectMulti):
""" """
def __init__(self, **kwargs): def __init__(self, **kwargs):
"""Constructor."""
super().__init__(**kwargs) super().__init__(**kwargs)
self.order = 10 self.order = 10
self.hash = '#add_dispersion_lorentz' self.hash = '#add_dispersion_lorentz'
def create(self, grid, uip): def create(self, grid, uip):
try: try:
poles = self.kwargs['n_poles'] poles = self.kwargs['n_poles']
er_delta = self.kwargs['er_delta'] er_delta = self.kwargs['er_delta']
@@ -850,17 +813,17 @@ class AddLorentzDispersion(UserObjectMulti):
alpha = self.kwargs['delta'] alpha = self.kwargs['delta']
material_ids = self.kwargs['material_ids'] material_ids = self.kwargs['material_ids']
except KeyError: except KeyError:
raise CmdInputError(self.__str__() + ' requires at least five parameters') raise CmdInputError(f"'{self.params_str()}' requires at least five parameters")
if poles < 0: if poles < 0:
raise CmdInputError(self.__str__() + ' requires a positive value for number of poles') raise CmdInputError(f"'{self.params_str()}' requires a positive value for number of poles")
# Look up requested materials in existing list of material instances # 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] materials = [y for x in material_ids for y in grid.materials if y.ID == x]
if len(materials) != len(material_ids): if len(materials) != len(material_ids):
notfound = [x for x in material_ids if x not in materials] notfound = [x for x in material_ids if x not in materials]
raise CmdInputError(self.__str__() + ' material(s) {} do not exist'.format(notfound)) raise CmdInputError(f"'{self.params_str()}' material(s) {notfound} do not exist")
for material in materials: for material in materials:
material.type = 'lorentz' material.type = 'lorentz'
@@ -872,12 +835,11 @@ class AddLorentzDispersion(UserObjectMulti):
material.tau.append(tau[i]) material.tau.append(tau[i])
material.alpha.append(alpha[i]) material.alpha.append(alpha[i])
else: 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.') raise CmdInputError(f"'{self.params_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: if material.poles > MaterialUser.maxpoles:
MaterialUser.maxpoles = material.poles MaterialUser.maxpoles = material.poles
if config.is_messages(): log.info(f"Lorentz disperion added to {material.ID} with delta_eps_r={', '.join('%4.2f' % deltaer for deltaer in material.deltaer)}, omega={', '.join('%4.3e' % tau for tau in material.tau)} secs, and gamma={', '.join('%4.3e' % alpha for alpha in material.alpha)} created.")
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): class AddDrudeDispersion(UserObjectMulti):
@@ -894,29 +856,28 @@ class AddDrudeDispersion(UserObjectMulti):
""" """
def __init__(self, **kwargs): def __init__(self, **kwargs):
"""Constructor."""
super().__init__(**kwargs) super().__init__(**kwargs)
self.order = 11 self.order = 11
self.hash = '#add_dispersion_Drude' self.hash = '#add_dispersion_Drude'
def create(self, grid, uip): def create(self, grid, uip):
try: try:
poles = self.kwargs['n_poles'] poles = self.kwargs['n_poles']
tau = self.kwargs['tau'] tau = self.kwargs['tau']
alpha = self.kwargs['alpha'] alpha = self.kwargs['alpha']
material_ids = self.kwargs['material_ids'] material_ids = self.kwargs['material_ids']
except KeyError: except KeyError:
raise CmdInputError(self.__str__() + ' requires at least four parameters') raise CmdInputError(f"'{self.params_str()}' requires at least four parameters")
if poles < 0: if poles < 0:
raise CmdInputError(self.__str__() + ' requires a positive value for number of poles') raise CmdInputError(f"'{self.params_str()}' requires a positive value for number of poles")
# Look up requested materials in existing list of material instances # 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] materials = [y for x in material_ids for y in grid.materials if y.ID == x]
if len(materials) != len(material_ids): if len(materials) != len(material_ids):
notfound = [x for x in material_ids if x not in materials] notfound = [x for x in material_ids if x not in materials]
raise CmdInputError(self.__str__() + ' material(s) {} do not exist'.format(notfound)) raise CmdInputError(f"'{self.params_str()}' material(s) {notfound} do not exist")
for material in materials: for material in materials:
material.type = 'drude' material.type = 'drude'
@@ -927,12 +888,11 @@ class AddDrudeDispersion(UserObjectMulti):
material.tau.append(tau[i]) material.tau.append(tau[i])
material.alpha.append(alpha[i]) material.alpha.append(alpha[i])
else: else:
raise CmdInputError(self.__str__() + ' requires positive values for the frequencies, and associated times that are greater than the time step for the model.') raise CmdInputError(f"'{self.params_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: if material.poles > MaterialUser.maxpoles:
MaterialUser.maxpoles = material.poles MaterialUser.maxpoles = material.poles
if config.is_messages(): log.info(f"Drude disperion added to {material.ID} with omega={', '.join('%4.3e' % tau for tau in material.tau)} secs, and gamma={', '.join('%4.3e' % alpha for alpha in material.alpha)} secs created.")
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): class SoilPeplinski(UserObjectMulti):
@@ -953,13 +913,11 @@ class SoilPeplinski(UserObjectMulti):
""" """
def __init__(self, **kwargs): def __init__(self, **kwargs):
"""Constructor."""
super().__init__(**kwargs) super().__init__(**kwargs)
self.order = 12 self.order = 12
self.hash = '#soil_peplinski' self.hash = '#soil_peplinski'
def create(self, grid, uip): def create(self, grid, uip):
try: try:
sand_fraction = self.kwargs['sand_fraction'] sand_fraction = self.kwargs['sand_fraction']
clay_fraction = self.kwargs['clay_fraction'] clay_fraction = self.kwargs['clay_fraction']
@@ -968,30 +926,28 @@ class SoilPeplinski(UserObjectMulti):
water_fraction_lower = self.kwargs['water_fraction_lower'] water_fraction_lower = self.kwargs['water_fraction_lower']
water_fraction_upper = self.kwargs['water_fraction_upper'] water_fraction_upper = self.kwargs['water_fraction_upper']
ID = self.kwargs['id'] ID = self.kwargs['id']
except KeyError: except KeyError:
raise CmdInputError(self.__str__() + ' requires at exactly seven parameters') raise CmdInputError(f"'{self.params_str()}' requires at exactly seven parameters")
if sand_fraction < 0: if sand_fraction < 0:
raise CmdInputError(self.__str__() + ' requires a positive value for the sand fraction') raise CmdInputError(f"'{self.params_str()}' requires a positive value for the sand fraction")
if clay_fraction < 0: if clay_fraction < 0:
raise CmdInputError(self.__str__() + ' requires a positive value for the clay fraction') raise CmdInputError(f"'{self.params_str()}' requires a positive value for the clay fraction")
if bulk_density < 0: if bulk_density < 0:
raise CmdInputError(self.__str__() + ' requires a positive value for the bulk density') raise CmdInputError(f"'{self.params_str()}' requires a positive value for the bulk density")
if sand_density < 0: if sand_density < 0:
raise CmdInputError(self.__str__() + ' requires a positive value for the sand particle density') raise CmdInputError(f"'{self.params_str()}' requires a positive value for the sand particle density")
if water_fraction_lower < 0: if water_fraction_lower < 0:
raise CmdInputError(self.__str__() + ' requires a positive value for the lower limit of the water volumetric fraction') raise CmdInputError(f"'{self.params_str()}' requires a positive value for the lower limit of the water volumetric fraction")
if water_fraction_upper < 0: if water_fraction_upper < 0:
raise CmdInputError(self.__str__() + ' requires a positive value for the upper limit of the water volumetric fraction') raise CmdInputError(f"'{self.params_str()}' requires a positive value for the upper limit of the water volumetric fraction")
if any(x.ID == ID for x in grid.mixingmodels): if any(x.ID == ID for x in grid.mixingmodels):
raise CmdInputError(self.__str__() + ' with ID {} already exists'.format(ID)) raise CmdInputError(f"'{self.params_str()}' with ID {ID} already exists")
# Create a new instance of the Material class material (start index after pec & free_space) # 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)) s = PeplinskiSoilUser(ID, sand_fraction, clay_fraction, bulk_density, sand_density, (water_fraction_lower, water_fraction_upper))
if config.is_messages(): log.info(f'Mixing model (Peplinski) used to create {s.ID} with sand fraction {s.S:g}, clay fraction {s.C:g}, bulk density {s.rb:g}g/cm3, sand particle density {s.rs:g}g/cm3, and water volumetric fraction {s.mu[0]:g} to {s.mu[1]:g} created.')
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 # Append the new material object to the materials list
grid.mixingmodels.append(s) grid.mixingmodels.append(s)
@@ -1014,7 +970,6 @@ class GeometryView(UserObjectMulti):
""" """
def __init__(self, **kwargs): def __init__(self, **kwargs):
"""Constructor."""
super().__init__(**kwargs) super().__init__(**kwargs)
self.order = 18 self.order = 18
self.hash = '#geometry_view' self.hash = '#geometry_view'
@@ -1026,9 +981,9 @@ class GeometryView(UserObjectMulti):
self.kwargs['multi_grid'] self.kwargs['multi_grid']
# there is no voxel output for multi grid output # there is no voxel output for multi grid output
if isinstance(grid, SubGridBase): if isinstance(grid, SubGridBase):
raise CmdInputError("'{}' Do not add multi_grid output to subgrid user object. Please add to Scene".format(self.__str__())) raise CmdInputError(f"'{self.params_str()}' do not add multi_grid output to subgrid user object. Please add to Scene")
if output_type == 'n': if output_type == 'n':
raise CmdInputError("'{}' Voxel output type (n) is not supported for multigrid output :(".format(self.__str__())) raise CmdInputError(f"'{self.params_str()}' voxel output type (n) is not supported for multigrid output.")
# Change constructor to the multi grid output # Change constructor to the multi grid output
from .geometry_outputs import GeometryViewFineMultiGrid as GeometryViewUser from .geometry_outputs import GeometryViewFineMultiGrid as GeometryViewUser
self.multi_grid = True self.multi_grid = True
@@ -1046,7 +1001,7 @@ class GeometryView(UserObjectMulti):
output_type = self.kwargs['output_type'].lower() output_type = self.kwargs['output_type'].lower()
filename = self.kwargs['filename'] filename = self.kwargs['filename']
except KeyError: except KeyError:
raise CmdInputError("'{}' requires exactly eleven parameters".format(self.__str__())) raise CmdInputError(f"'{self.params_str()}' requires exactly eleven parameters")
GeometryViewUser = self.geometry_view_constructor(grid, output_type) GeometryViewUser = self.geometry_view_constructor(grid, output_type)
@@ -1057,15 +1012,15 @@ class GeometryView(UserObjectMulti):
dx, dy, dz = uip.discretise_point(dl) dx, dy, dz = uip.discretise_point(dl)
if dx < 0 or dy < 0 or dz < 0: if dx < 0 or dy < 0 or dz < 0:
raise CmdInputError("'{}' the step size should not be less than zero".format(self.__str__())) raise CmdInputError(f"'{self.params_str()}' the step size should not be less than zero")
if dx > grid.nx or dy > grid.ny or dz > grid.nz: 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__())) raise CmdInputError(f"'{self.params_str()}' the step size should be less than the domain size")
if dx < 1 or dy < 1 or dz < 1: 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__())) raise CmdInputError(f"'{self.params_str()}' the step size should not be less than the spatial discretisation")
if output_type != 'n' and output_type != 'f': if output_type != 'n' and output_type != 'f':
raise CmdInputError("'{}' requires type to be either n (normal) or f (fine)".format(self.__str__())) raise CmdInputError(f"'{self.params_str()}' requires type to be either n (normal) or f (fine)")
if output_type == 'f' and (dx * grid.dx != grid.dx or dy * grid.dy != grid.dy or dz * grid.dz != grid.dz): if output_type == 'f' and (dx * grid.dx != grid.dx or dy * grid.dy != grid.dy or dz * grid.dz != grid.dz):
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__())) raise CmdInputError(f"'{self.params_str()}' requires the spatial discretisation for the geometry view to be the same as the model for geometry view of type f (fine)")
# Set type of geometry file # Set type of geometry file
if output_type == 'n': if output_type == 'n':
@@ -1075,8 +1030,7 @@ class GeometryView(UserObjectMulti):
g = GeometryViewUser(xs, ys, zs, xf, yf, zf, dx, dy, dz, filename, fileext, grid) g = GeometryViewUser(xs, ys, zs, xf, yf, zf, dx, dy, dz, filename, fileext, grid)
if config.is_messages(): log.info(f'Geometry view from {xs * grid.dx:g}m, {ys * grid.dy:g}m, {zs * grid.dz:g}m, to {xf * grid.dx:g}m, {yf * grid.dy:g}m, {zf * grid.dz:g}m, discretisation {dx * grid.dx:g}m, {dy * grid.dy:g}m, {dz * grid.dz:g}m, multi_grid={self.multi_grid}, grid={grid.name}, with filename base {g.basefilename} created.')
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 # Append the new GeometryView object to the geometry views list
grid.geometryviews.append(g) grid.geometryviews.append(g)
@@ -1095,7 +1049,6 @@ class GeometryObjectsWrite(UserObjectMulti):
""" """
def __init__(self, **kwargs): def __init__(self, **kwargs):
"""Constructor."""
super().__init__(**kwargs) super().__init__(**kwargs)
self.order = 14 self.order = 14
self.hash = '#geometry_objects_write' self.hash = '#geometry_objects_write'
@@ -1106,7 +1059,7 @@ class GeometryObjectsWrite(UserObjectMulti):
p2 = self.kwargs['p2'] p2 = self.kwargs['p2']
filename = self.kwargs['filename'] filename = self.kwargs['filename']
except KeyError: except KeyError:
raise CmdInputError("'{}' requires exactly seven parameters".format(self.__str__())) raise CmdInputError(f"'{self.params_str()}' requires exactly seven parameters")
p1, p2 = uip.check_box_points(p1, p2, self.__str__()) p1, p2 = uip.check_box_points(p1, p2, self.__str__())
x0, y0, z0 = p1 x0, y0, z0 = p1
@@ -1114,8 +1067,7 @@ class GeometryObjectsWrite(UserObjectMulti):
g = GeometryObjectsUser(x0, y0, z0, x1, y1, z1, filename) g = GeometryObjectsUser(x0, y0, z0, x1, y1, z1, filename)
if config.is_messages(): log.info(f'Geometry objects in the volume from {p1[0] * grid.dx:g}m, {p1[1] * grid.dy:g}m, {p1[2] * grid.dz:g}m, to {p2[0] * grid.dx:g}m, {p2[1] * grid.dy:g}m, {p2[2] * grid.dz:g}m, will be written to {g.filename}, with materials written to {g.materialsfilename}')
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 # Append the new GeometryView object to the geometry objects to write list
grid.geometryobjectswrite.append(g) grid.geometryobjectswrite.append(g)
@@ -1153,16 +1105,14 @@ class PMLCFS(UserObjectMulti):
count = 0 count = 0
def __init__(self, **kwargs): def __init__(self, **kwargs):
"""Constructor."""
super().__init__(**kwargs) super().__init__(**kwargs)
self.order = 15 self.order = 15
self.hash = '#pml_cfs' self.hash = '#pml_cfs'
PMLCFS.count += 1 PMLCFS.count += 1
if PMLCFS.count == 2: if PMLCFS.count == 2:
raise CmdInputError(self.__str__() + ' can only be used up to two times, for up to a 2nd order PML') raise CmdInputError(f"'{self.params_str()}' can only be used up to two times, for up to a 2nd order PML")
def create(self, grid, uip): def create(self, grid, uip):
try: try:
alphascalingprofile = self.kwargs['alphascalingprofile'] alphascalingprofile = self.kwargs['alphascalingprofile']
alphascalingdirection = self.kwargs['alphascalingdirection'] alphascalingdirection = self.kwargs['alphascalingdirection']
@@ -1176,18 +1126,17 @@ class PMLCFS(UserObjectMulti):
sigmascalingdirection = self.kwargs['sigmascalingdirection'] sigmascalingdirection = self.kwargs['sigmascalingdirection']
sigmamin = self.kwargs['sigmamin'] sigmamin = self.kwargs['sigmamin']
sigmamax = self.kwargs['sigmamax'] sigmamax = self.kwargs['sigmamax']
except KeyError: except KeyError:
raise CmdInputError(self.__str__() + ' requires exactly twelve parameters') raise CmdInputError(f"'{self.params_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(): 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()))) raise CmdInputError(f"'{self.params_str()}' must have scaling type {','.join(CFSParameter.scalingprofiles.keys())}")
if alphascalingdirection not in CFSParameter.scalingdirections or kappascalingdirection not in CFSParameter.scalingdirections or sigmascalingdirection not in CFSParameter.scalingdirections: 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))) raise CmdInputError(f"'{self.params_str()}' must have scaling type {','.join(CFSParameter.scalingdirections)}")
if float(alphamin) < 0 or float(alphamax) < 0 or float(kappamin) < 0 or float(kappamax) < 0 or float(sigmamin) < 0: 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') raise CmdInputError(f"'{self.params_str()}' minimum and maximum scaling values must be greater than zero")
if float(kappamin) < 1: if float(kappamin) < 1:
raise CmdInputError(self.__str__() + ' minimum scaling value for kappa must be greater than or equal to one') raise CmdInputError(f"'{self.params_str()}' minimum scaling value for kappa must be greater than or equal to one")
cfsalpha = CFSParameter() cfsalpha = CFSParameter()
cfsalpha.ID = 'alpha' cfsalpha.ID = 'alpha'
@@ -1215,8 +1164,7 @@ class PMLCFS(UserObjectMulti):
cfs.kappa = cfskappa cfs.kappa = cfskappa
cfs.sigma = cfssigma cfs.sigma = cfssigma
if config.is_messages(): log.info(f'PML CFS parameters: alpha (scaling: {cfsalpha.scalingprofile}, scaling direction: {cfsalpha.scalingdirection}, min: {cfsalpha.min:g}, max: {cfsalpha.max:g}), kappa (scaling: {cfskappa.scalingprofile}, scaling direction: {cfskappa.scalingdirection}, min: {cfskappa.min:g}, max: {cfskappa.max:g}), sigma (scaling: {cfssigma.scalingprofile}, scaling direction: {cfssigma.scalingdirection}, min: {cfssigma.min:g}, max: {cfssigma.max:g}) created.')
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) grid.cfs.append(cfs)
@@ -1234,7 +1182,7 @@ class Subgrid(UserObjectMulti):
elif isinstance(node, UserObjectGeometry): elif isinstance(node, UserObjectGeometry):
self.children_geometry.append(node) self.children_geometry.append(node)
else: else:
raise Exception('This Object is Unknown to gprMax') raise GeneralError('This object is unknown to gprMax')
class SubgridHSG(UserObjectMulti): class SubgridHSG(UserObjectMulti):

查看文件

@@ -18,6 +18,7 @@
import decimal as d import decimal as d
import inspect import inspect
import logging
import os import os
import sys import sys
@@ -26,17 +27,16 @@ from colorama import Fore
from colorama import Style from colorama import Style
init() init()
import numpy as np import numpy as np
from scipy.constants import c
from scipy import interpolate from scipy import interpolate
import gprMax.config as config import gprMax.config as config
from .config import c
from .config import dtypes
from .config import hostinfo
from .exceptions import CmdInputError from .exceptions import CmdInputError
from .waveforms import Waveform from .waveforms import Waveform
from .utilities import round_value from .utilities import round_value
floattype = dtypes['float_or_double']
log = logging.getLogger(__name__)
class Properties: class Properties:
@@ -82,7 +82,7 @@ class Domain(UserObjectSingle):
self.kwargs['p1'][1], self.kwargs['p1'][1],
self.kwargs['p1'][2]) self.kwargs['p1'][2])
except KeyError: except KeyError:
print('error message') log.warning('error message')
return s return s
@@ -90,40 +90,35 @@ class Domain(UserObjectSingle):
try: try:
G.nx, G.ny, G.nz = uip.discretise_point(self.kwargs['p1']) G.nx, G.ny, G.nz = uip.discretise_point(self.kwargs['p1'])
except KeyError: except KeyError:
raise CmdInputError(self.__str__ + ' Please specify a point') raise CmdInputError(f"'{self.params_str()}' please specify a point")
if G.nx == 0 or G.ny == 0 or G.nz == 0: if G.nx == 0 or G.ny == 0 or G.nz == 0:
raise CmdInputError(self.__str__ + ' requires at least one cell in every dimension') raise CmdInputError(f"'{self.params_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 log.info(f'Domain size: {self.kwargs['p1'][0]:g} x {self.kwargs['p1'][1]:g} x {self.kwargs['p1'][2]:g}m ({G.nx:d} x {G.ny:d} x {G.nz:d} = {(G.nx * G.ny * G.nz):g} cells)')
# Calculate time step at CFL limit; switch off appropriate PMLs for 2D
if G.nx == 1: if G.nx == 1:
G.dt = 1 / (c * np.sqrt((1 / G.dy) * (1 / G.dy) + (1 / G.dz) * (1 / G.dz))) G.calculate_dt()
G.mode = '2D TMx' G.mode = '2D TMx'
G.pmlthickness['x0'] = 0 G.pmlthickness['x0'] = 0
G.pmlthickness['xmax'] = 0 G.pmlthickness['xmax'] = 0
elif G.ny == 1: elif G.ny == 1:
G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dz) * (1 / G.dz))) G.calculate_dt()
G.mode = '2D TMy' G.mode = '2D TMy'
G.pmlthickness['y0'] = 0 G.pmlthickness['y0'] = 0
G.pmlthickness['ymax'] = 0 G.pmlthickness['ymax'] = 0
elif G.nz == 1: elif G.nz == 1:
G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dy) * (1 / G.dy))) G.calculate_dt()
G.mode = '2D TMz' G.mode = '2D TMz'
G.pmlthickness['z0'] = 0 G.pmlthickness['z0'] = 0
G.pmlthickness['zmax'] = 0 G.pmlthickness['zmax'] = 0
else: 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.calculate_dt()
G.mode = '3D' G.mode = '3D'
# Round down time step to nearest float with precision one less than hardware maximum. log.info(f'Mode: {G.mode}')
# Avoids inadvertently exceeding the CFL due to binary representation of floating point number. log.info(f'Time step (at CFL limit): {G.dt:g} secs')
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 # Number of threads (OpenMP) to use
if sys.platform == 'darwin': if sys.platform == 'darwin':
@@ -134,7 +129,7 @@ class Domain(UserObjectSingle):
# os.environ['OMP_DISPLAY_ENV'] = 'TRUE' # Prints OMP version and environment variables (useful for debug) # 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) # Catch bug with Windows Subsystem for Linux (https://github.com/Microsoft/BashOnWindows/issues/785)
if 'Microsoft' in hostinfo['osversion']: if 'Microsoft' in config.hostinfo['osversion']:
os.environ['KMP_AFFINITY'] = 'disabled' os.environ['KMP_AFFINITY'] = 'disabled'
del os.environ['OMP_PLACES'] del os.environ['OMP_PLACES']
del os.environ['OMP_PROC_BIND'] del os.environ['OMP_PROC_BIND']
@@ -143,13 +138,12 @@ class Domain(UserObjectSingle):
G.nthreads = int(os.environ.get('OMP_NUM_THREADS')) G.nthreads = int(os.environ.get('OMP_NUM_THREADS'))
else: else:
# Set number of threads to number of physical CPU cores # Set number of threads to number of physical CPU cores
G.nthreads = hostinfo['physicalcores'] G.nthreads = config.hostinfo['physicalcores']
os.environ['OMP_NUM_THREADS'] = str(G.nthreads) os.environ['OMP_NUM_THREADS'] = str(G.nthreads)
if config.is_messages(): log.info(f'Number of CPU (OpenMP) threads: {G.nthreads}')
print('Number of CPU (OpenMP) threads: {}'.format(G.nthreads)) if G.nthreads > config.hostinfo['physicalcores']:
if G.nthreads > hostinfo['physicalcores']: log.warning(Fore.RED + f'You have specified more threads ({G.nthreads}) than available physical CPU cores ({config.hostinfo['physicalcores']}). This may lead to degraded performance.' + Style.RESET_ALL)
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 Discretisation(UserObjectSingle): class Discretisation(UserObjectSingle):
@@ -169,7 +163,7 @@ class Discretisation(UserObjectSingle):
self.kwargs['p1'][1], self.kwargs['p1'][1],
self.kwargs['p1'][2]) self.kwargs['p1'][2])
except KeyError: except KeyError:
print('error message') log.info('error message')
return s return s
@@ -188,7 +182,7 @@ class Discretisation(UserObjectSingle):
raise CmdInputError('Discretisation requires the z-direction spatial step to be greater than zero') raise CmdInputError('Discretisation requires the z-direction spatial step to be greater than zero')
if config.is_messages(): if config.is_messages():
print('Spatial discretisation: {:g} x {:g} x {:g}m'.format(*G.dl)) log.info('Spatial discretisation: {:g} x {:g} x {:g}m'.format(*G.dl))
class TimeWindow(UserObjectSingle): class TimeWindow(UserObjectSingle):
@@ -211,7 +205,7 @@ class TimeWindow(UserObjectSingle):
try: try:
s = '#time_window: {}'.format(self.kwargs['iterations']) s = '#time_window: {}'.format(self.kwargs['iterations'])
except KeyError: except KeyError:
print('time window error') log.info('time window error')
return s return s
@@ -242,7 +236,7 @@ class TimeWindow(UserObjectSingle):
raise CmdInputError('TimeWindow: Specify a time or number of iterations') raise CmdInputError('TimeWindow: Specify a time or number of iterations')
if config.is_messages(): if config.is_messages():
print('Time window: {:g} secs ({} iterations)'.format(G.timewindow, G.iterations)) log.info('Time window: {:g} secs ({} iterations)'.format(G.timewindow, G.iterations))
class Messages(UserObjectSingle): class Messages(UserObjectSingle):
@@ -260,7 +254,7 @@ class Messages(UserObjectSingle):
try: try:
s = '#messages: {}'.format(self.kwargs['yn']) s = '#messages: {}'.format(self.kwargs['yn'])
except KeyError: except KeyError:
print('messages problem') log.info('messages problem')
def create(self, G, uip): def create(self, G, uip):
try: try:
@@ -296,7 +290,7 @@ class Title(UserObjectSingle):
pass pass
if config.is_messages(): if config.is_messages():
print('Model title: {}'.format(G.title)) log.info('Model title: {}'.format(G.title))
class NumThreads(UserObjectSingle): class NumThreads(UserObjectSingle):
@@ -332,14 +326,14 @@ class NumThreads(UserObjectSingle):
os.environ['OMP_NUM_THREADS'] = str(G.nthreads) os.environ['OMP_NUM_THREADS'] = str(G.nthreads)
if config.is_messages(): if config.is_messages():
print('Number of CPU (OpenMP) threads: {}'.format(G.nthreads)) log.info('Number of CPU (OpenMP) threads: {}'.format(G.nthreads))
if G.nthreads > hostinfo['physicalcores']: if G.nthreads > config.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) log.info(Fore.RED + 'WARNING: You have specified more threads ({}) than available physical CPU cores ({}). This may lead to degraded performance.'.format(G.nthreads, config.hostinfo['physicalcores']) + Style.RESET_ALL)
# Print information about any GPU in use # Print information about any GPU in use
if config.is_messages(): if config.is_messages():
if G.gpu is not None: if G.gpu is not None:
print('GPU solving using: {} - {}'.format(G.gpu.deviceID, G.gpu.name)) log.info('GPU solving using: {} - {}'.format(G.gpu.deviceID, G.gpu.name))
class TimeStepStabilityFactor(UserObjectSingle): class TimeStepStabilityFactor(UserObjectSingle):
@@ -369,7 +363,7 @@ class TimeStepStabilityFactor(UserObjectSingle):
raise CmdInputError(self.__str__() + ' requires the value of the time step stability factor to be between zero and one') raise CmdInputError(self.__str__() + ' requires the value of the time step stability factor to be between zero and one')
G.dt = G.dt * f G.dt = G.dt * f
if config.is_messages(): if config.is_messages():
print('Time step (modified): {:g} secs'.format(G.dt)) log.info('Time step (modified): {:g} secs'.format(G.dt))
class PMLCells(UserObjectSingle): class PMLCells(UserObjectSingle):
@@ -442,7 +436,7 @@ class SrcSteps(UserObjectSingle):
raise CmdInputError('#src_steps: requires exactly three parameters') raise CmdInputError('#src_steps: requires exactly three parameters')
if config.is_messages(): 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)) log.info('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): class RxSteps(UserObjectSingle):
@@ -463,7 +457,7 @@ class RxSteps(UserObjectSingle):
raise CmdInputError('#rx_steps: requires exactly three parameters') raise CmdInputError('#rx_steps: requires exactly three parameters')
if config.is_messages(): 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)) log.info('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): class ExcitationFile(UserObjectSingle):
@@ -498,7 +492,7 @@ class ExcitationFile(UserObjectSingle):
excitationfile = os.path.abspath(os.path.join(G.inputdirectory, excitationfile)) excitationfile = os.path.abspath(os.path.join(G.inputdirectory, excitationfile))
if config.is_messages(): if config.is_messages():
print('\nExcitation file: {}'.format(excitationfile)) log.info('\nExcitation file: {}'.format(excitationfile))
# Get waveform names # Get waveform names
with open(excitationfile, 'r') as f: with open(excitationfile, 'r') as f:
@@ -538,7 +532,7 @@ class ExcitationFile(UserObjectSingle):
w.userfunc = interpolate.interp1d(waveformtime, singlewaveformvalues, **kwargs) w.userfunc = interpolate.interp1d(waveformtime, singlewaveformvalues, **kwargs)
if config.is_messages(): 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'])) log.info('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) G.waveforms.append(w)

查看文件

@@ -27,9 +27,3 @@ ctypedef fused float_or_double:
ctypedef fused float_or_double_complex: ctypedef fused float_or_double_complex:
float complex float complex
double complex double complex
ctypedef fused real_or_complex:
float
double
float complex
double complex

查看文件

@@ -16,9 +16,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with gprMax. If not, see <http://www.gnu.org/licenses/>. # along with gprMax. If not, see <http://www.gnu.org/licenses/>.
import os
from pathlib import Path from pathlib import Path
import sys
from colorama import init from colorama import init
from colorama import Fore from colorama import Fore
@@ -27,31 +25,37 @@ init()
import cython import cython
import numpy as np import numpy as np
from scipy.constants import c from scipy.constants import c
from scipy.constants import mu_0 as m0
from scipy.constants import epsilon_0 as e0 from scipy.constants import epsilon_0 as e0
from scipy.constants import mu_0 as m0
from .utilities import get_host_info from .utilities import get_host_info
from .utilities import get_terminal_width from .utilities import get_terminal_width
# Impedance of free space (Ohms) # General settings for the simulation
z0 = np.sqrt(m0 / e0)
# General setting for the simulation
# inputfilepath: path to inputfile location # inputfilepath: path to inputfile location
# outputfilepath: path to outputfile location # outputfilepath: path to outputfile location
# messages: whether to print all messages as output to stdout or not # messages: whether to print all messages as output to stdout or not
# progressbars: whether to show progress bars on stdoout or not # progressbars: whether to show progress bars on stdoout or not
# mode: 2D TMx, 2D TMy, 2D TMz, or 3D # mode: 2D TMx, 2D TMy, 2D TMz, or 3D
# cpu, cuda, opencl: solver type # cpu, cuda, opencl: solver type
# precision: data type for electromagnetic field output (single/double)
# autotranslate: auto translate objects with main grid coordinates # autotranslate: auto translate objects with main grid coordinates
# to their equivalent local grid coordinate within the subgrid. If this option is off # 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. # users must specify sub-grid object point within the global subgrid space.
general = {'inputfilepath': 'gprMax', 'outputfilepath': 'gprMax', 'messages': True, # z0: impedance of free space (Ohms)
'progressbars': True, 'mode': '3D', 'cpu': True, 'cuda': False, 'opencl': False, 'autotranslate': False} general = {'messages': True,
'progressbars': True,
'mode': '3D',
'cpu': True,
'cuda': False,
'opencl': False,
'precision': 'single',
'autotranslate': False,
'z0': np.sqrt(m0 / e0)}
def is_messages(): def is_messages():
"""Function to return messages.""" """Return messages."""
return general['messages'] return general['messages']
# Store information about host machine # Store information about host machine
@@ -64,37 +68,30 @@ hostinfo = get_host_info()
# on the memory of the GPU. If True this will slow performance significantly # on the memory of the GPU. If True this will slow performance significantly
cuda = {'gpus': None, 'snapsgpu2cpu': False} cuda = {'gpus': None, 'snapsgpu2cpu': False}
# Numerical dispersion analysis parameters # Data type (precision) for electromagnetic field output
# 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) # 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) # Rigid arrays use 8-bit integers (the smallest available type to store true/false)
# Fractal and dispersive coefficient arrays use complex numbers (complex) # Fractal arrays use complex numbers
# which are represented as two floats # Dispersive coefficient arrays use either float or complex numbers
# Main field arrays use floats (float_or_double) and complex numbers (complex) # Main field arrays use floats
# Precision of float_or_double and complex: single or double for numpy and C (CUDA) arrays
precision = 'double'
if precision == 'single': if general['precision'] == 'single':
dtypes = {'float_or_double': np.float32, 'complex': np.complex64, dtypes = {'float_or_double': np.float32,
'cython_float_or_double': cython.float, 'cython_complex': cython.floatcomplex, 'complex': np.complex64,
'C_float_or_double': 'float', 'C_complex': 'pycuda::complex<float>'} 'cython_float_or_double': cython.float,
elif precision == 'double': 'cython_complex': cython.floatcomplex,
dtypes = {'float_or_double': np.float64, 'complex': np.complex128, 'C_float_or_double': 'float',
'cython_float_or_double': cython.double, 'cython_complex': cython.doublecomplex, 'C_complex': 'pycuda::complex<float>'}
'C_float_or_double': 'double', 'C_complex': 'pycuda::complex<double>'} elif general['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<double>'}
class ModelConfig(): class ModelConfig:
"""Configuration parameters for a model. """Configuration parameters for a model.
N.B. Multiple models can exist within a simulation N.B. Multiple models can exist within a simulation
""" """
@@ -107,8 +104,8 @@ class ModelConfig():
""" """
self.sim_config = sim_config self.sim_config = sim_config
self.reuse_geometry = False
self.i = i # Indexed from 0 self.i = i # Indexed from 0
self.reuse_geometry = False
if not sim_config.single_model: if not sim_config.single_model:
self.appendmodelnumber = str(self.i + 1) # Indexed from 1 self.appendmodelnumber = str(self.i + 1) # Indexed from 1
@@ -128,16 +125,33 @@ class ModelConfig():
self.inputfilestr = inputfilestr_f.format(self.i + 1, self.sim_config.model_end, self.sim_config.input_file_path) self.inputfilestr = inputfilestr_f.format(self.i + 1, self.sim_config.model_end, self.sim_config.input_file_path)
self.next_model = Fore.GREEN + '{} {}\n'.format(self.inputfilestr, '-' * (get_terminal_width() - 1 - len(self.inputfilestr))) + Style.RESET_ALL self.next_model = Fore.GREEN + '{} {}\n'.format(self.inputfilestr, '-' * (get_terminal_width() - 1 - len(self.inputfilestr))) + Style.RESET_ALL
# 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
self.numdispersion = {'highestfreqthres': 40,
'maxnumericaldisp': 2,
'mingridsampling': 3}
# General information to configure materials
# maxpoles: Maximum number of dispersive material poles in a model
# dispersivedtype: Data type for dispersive materials
# dispersiveCdtype: Data type for dispersive materials in Cython
self.materials = {'maxpoles': 0,
'dispersivedtype': None,
'dispersiveCdtype': None}
def get_scene(self): def get_scene(self):
if self.sim_config.scenes: if self.sim_config.scenes:
return self.sim_config.scenes[self.i] return self.sim_config.scenes[self.i]
else: return None else: return None
def get_usernamespace(self): def get_usernamespace(self):
return {'c': c, return {'c': c, # Speed of light in free space (m/s)
'e0': e0, 'e0': e0, # Permittivity of free space (F/m)
'm0': m0, 'm0': m0, # Permeability of free space (H/m)
'z0': z0, 'z0': general['z0'], # Impedance of free space (Ohms)
'number_model_runs': self.sim_config.model_end + 1, 'number_model_runs': self.sim_config.model_end + 1,
'current_model_run': self.i + 1, 'current_model_run': self.i + 1,
'inputfile': self.sim_config.input_file_path.resolve()} 'inputfile': self.sim_config.input_file_path.resolve()}
@@ -155,16 +169,10 @@ class SimulationConfig:
""" """
self.args = args self.args = args
self.n_models = args.n self.general = general
self.inputfile = args.inputfile self.hostinfo = hostinfo
self.gpu = args.gpu self.cuda = cuda
self.mpi = args.mpi self.dtypes = dtypes
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 enters via CLI # Subgrid parameter may not exist if user enters via CLI
try: try:
@@ -191,7 +199,7 @@ class SimulationConfig:
self.single_model = False self.single_model = False
def set_model_start_end(self): def set_model_start_end(self):
# Set range for number of models to run (internally 0 index) """Set range for number of models to run (internally 0 index)."""
if self.args.task: if self.args.task:
# Job array feeds args.n number of single tasks # Job array feeds args.n number of single tasks
modelstart = self.args.task - 1 modelstart = self.args.task - 1
@@ -206,23 +214,20 @@ class SimulationConfig:
self.model_start = modelstart self.model_start = modelstart
self.model_end = modelend self.model_end = modelend
def set_precision(self):
pass
def set_input_file_path(self): def set_input_file_path(self):
# If the API is in use an id for the simulation must be provided. """If the API is in use an id for the simulation must be provided."""
if self.args.inputfile is None: if self.args.inputfile is None:
self.input_file_path = Path(self.args.outputfile) self.input_file_path = Path(self.args.outputfile)
else: else:
self.input_file_path = Path(self.args.inputfile) self.input_file_path = Path(self.args.inputfile)
def set_output_file_path(self): def set_output_file_path(self):
# Output file path can be provided by the user. If they havent provided one """Output file path can be provided by the user. If they havent provided one
# use the inputfile file path instead use the inputfile file path instead."""
try: try:
self.output_file_path = Path(self.args.outputfile) self.output_file_path = Path(self.args.outputfile)
except AttributeError: except AttributeError:
self.output_file_path = Path(self.args.inputfile) self.output_file_path = Path(self.input_file_path)
class SimulationConfigMPI(SimulationConfig): class SimulationConfigMPI(SimulationConfig):

查看文件

@@ -18,15 +18,17 @@
import datetime import datetime
from ._version import __version__, codename
from .config import create_model_config from .config import create_model_config
from .model_build_run import ModelBuildRun from .model_build_run import ModelBuildRun
from .solvers import create_solver from .solvers import create_solver
from .solvers import create_G from .solvers import create_G
from .utilities import get_terminal_width from .utilities import get_terminal_width
from .utilities import logo
from .utilities import timer from .utilities import timer
class Context(): class Context:
"""Generic context for the model to run in. Sub-class with specific contexts """Generic context for the model to run in. Sub-class with specific contexts
e.g. an MPI context. e.g. an MPI context.
""" """
@@ -43,18 +45,23 @@ class Context():
self.tsimstart = 1 self.tsimstart = 1
def run(self): def run(self):
"""Function to run the simulation in the correct context.""" """Run the simulation in the correct context."""
self.print_logo_copyright()
self.tsimstart = timer() self.tsimstart = timer()
self._run() self._run()
self.tsimend = timer() self.tsimend = timer()
def print_logo_copyright(self):
"""Print gprMax logo, version, and copyright/licencing information."""
logo(__version__ + ' (' + codename + ')')
def print_time_report(self): def print_time_report(self):
"""Function to print the total simulation time based on context.""" """Print the total simulation time based on context."""
s = self.make_time_report(sim_time) s = self.make_time_report()
print(s) log.info(s)
def make_time_report(self): def make_time_report(self):
"""Function to generate a string for the total simulation time.""" """Generate a string for the total simulation time."""
pass pass
@@ -63,6 +70,9 @@ class NoMPIContext(Context):
is parallelised using either OpenMP (CPU) or CUDA (GPU). is parallelised using either OpenMP (CPU) or CUDA (GPU).
""" """
def __init__(self):
super().__init__()
def _run(self): def _run(self):
"""Specialise how the models are farmed out.""" """Specialise how the models are farmed out."""
@@ -83,7 +93,7 @@ class NoMPIContext(Context):
solver = create_solver(G, self.sim_config) solver = create_solver(G, self.sim_config)
if not self.sim_config.geometry_only: if not self.sim_config.geometry_only:
model.run_model(solver) model.solve(solver)
def make_time_report(self): def make_time_report(self):
"""Function to specialise the time reporting for the standard Simulation """Function to specialise the time reporting for the standard Simulation
@@ -91,9 +101,8 @@ class NoMPIContext(Context):
""" """
sim_time = datetime.timedelta(seconds=self.tsimend - self.tsimstart) sim_time = datetime.timedelta(seconds=self.tsimend - self.tsimstart)
s = '\n=== Simulation on {} completed in [HH:MM:SS]: {}' s = f'\n=== Simulation on {self.simconfig.hostinfo['hostname']} completed in [HH:MM:SS]: {sim_time}'
s = s.format(self.simconfig.hostinfo['hostname'], sim_time) return f'{s} {'=' * (get_terminal_width() - 1 - len(s))}\n'
return '{} {}\n'.format(s, '=' * (get_terminal_width() - 1 - len(s)))
class MPIContext(Context): class MPIContext(Context):
@@ -116,6 +125,9 @@ class MPIContext(Context):
class MPINoSpawnContext(Context): class MPINoSpawnContext(Context):
def __init__(self):
super().__init__()
def _run(self): def _run(self):
pass pass
@@ -133,9 +145,9 @@ def create_context(sim_config):
context (Context): Context for the model to run in. context (Context): Context for the model to run in.
""" """
if sim_config.mpi: if sim_config.args.mpi:
context = MPIContext(sim_config) context = MPIContext(sim_config)
elif sim_config.mpi_no_spawn: elif sim_config.args.mpi_no_spawn:
context = MPINoSpawnContext(sim_config) context = MPINoSpawnContext(sim_config)
else: else:
context = NoMPIContext(sim_config) context = NoMPIContext(sim_config)

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2017: The University of Edinburgh # Copyright (C) 2015-2019: The University of Edinburgh
# Authors: Craig Warren and Antonis Giannopoulos # Authors: Craig Warren and Antonis Giannopoulos
# #
# This file is part of gprMax. # This file is part of gprMax.
@@ -15,10 +15,13 @@
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with gprMax. If not, see <http://www.gnu.org/licenses/>. # along with gprMax. If not, see <http://www.gnu.org/licenses/>.
import numpy as np import numpy as np
cimport numpy as np cimport numpy as np
from cython.parallel import prange from cython.parallel import prange
cpdef void cython_update_electric_os( cpdef void cython_update_electric_os(
np.float64_t[:, :] updatecoeffsE, np.float64_t[:, :] updatecoeffsE,
np.uint32_t[:, :, :, :] ID, np.uint32_t[:, :, :, :] ID,

查看文件

@@ -1,4 +1,4 @@
cdef float phi# Copyright (C) 2015-2019: The University of Edinburgh # Copyright (C) 2015-2019: The University of Edinburgh
# Authors: Craig Warren and Antonis Giannopoulos # Authors: Craig Warren and Antonis Giannopoulos
# #
# This file is part of gprMax. # This file is part of gprMax.

查看文件

@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with gprMax. If not, see <http://www.gnu.org/licenses/>. # along with gprMax. If not, see <http://www.gnu.org/licenses/>.
import numpy as np
cimport numpy as np cimport numpy as np

查看文件

@@ -18,8 +18,8 @@
import numpy as np import numpy as np
cimport numpy as np cimport numpy as np
np.seterr(divide='raise')
from gprMax.utilities import round_value
from gprMax.cython.yee_cell_setget_rigid cimport set_rigid_Ex from gprMax.cython.yee_cell_setget_rigid cimport set_rigid_Ex
from gprMax.cython.yee_cell_setget_rigid cimport set_rigid_Ey from gprMax.cython.yee_cell_setget_rigid cimport set_rigid_Ey
from gprMax.cython.yee_cell_setget_rigid cimport set_rigid_Ez from gprMax.cython.yee_cell_setget_rigid cimport set_rigid_Ez
@@ -30,8 +30,7 @@ from gprMax.cython.yee_cell_setget_rigid cimport set_rigid_E
from gprMax.cython.yee_cell_setget_rigid cimport unset_rigid_E from gprMax.cython.yee_cell_setget_rigid cimport unset_rigid_E
from gprMax.cython.yee_cell_setget_rigid cimport set_rigid_H from gprMax.cython.yee_cell_setget_rigid cimport set_rigid_H
from gprMax.cython.yee_cell_setget_rigid cimport unset_rigid_H from gprMax.cython.yee_cell_setget_rigid cimport unset_rigid_H
from gprMax.utilities import round_value
np.seterr(divide='raise')
cpdef bint are_clockwise( cpdef bint are_clockwise(

查看文件

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

查看文件

@@ -1,19 +1,19 @@
cdef float_or_double# Copyright (C) 2015-2019: The University of Edinburgh # Copyright (C) 2015-2019: The University of Edinburgh
# Authors: Craig Warren and Antonis Giannopoulos # Authors: Craig Warren and Antonis Giannopoulos
# #
# This file is part of gprMax. # This file is part of gprMax.
# #
# gprMax is free software: you can redistribute it and/or modify # gprMax is free software: you can redistribute it and/or modify
# it under the terms of the GNU GenRAl Public License as published by # it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or # the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version. # (at your option) any later version.
# #
# gprMax is distributed in the hope that it will be useful, # gprMax is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU GenRAl Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU GenRAl Public License # You should have received a copy of the GNU General Public License
# along with gprMax. If not, see <http://www.gnu.org/licenses/>. # along with gprMax. If not, see <http://www.gnu.org/licenses/>.
import numpy as np import numpy as np

查看文件

@@ -16,8 +16,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with gprMax. If not, see <http://www.gnu.org/licenses/>. # along with gprMax. If not, see <http://www.gnu.org/licenses/>.
cimport numpy as np
from gprMax.config cimport float_or_double from gprMax.config cimport float_or_double

查看文件

@@ -19,13 +19,13 @@
import numpy as np import numpy as np
cimport numpy as np cimport numpy as np
from gprMax.materials import Material
from gprMax.cython.yee_cell_setget_rigid cimport get_rigid_Ex from gprMax.cython.yee_cell_setget_rigid cimport get_rigid_Ex
from gprMax.cython.yee_cell_setget_rigid cimport get_rigid_Ey from gprMax.cython.yee_cell_setget_rigid cimport get_rigid_Ey
from gprMax.cython.yee_cell_setget_rigid cimport get_rigid_Ez from gprMax.cython.yee_cell_setget_rigid cimport get_rigid_Ez
from gprMax.cython.yee_cell_setget_rigid cimport get_rigid_Hx from gprMax.cython.yee_cell_setget_rigid cimport get_rigid_Hx
from gprMax.cython.yee_cell_setget_rigid cimport get_rigid_Hy from gprMax.cython.yee_cell_setget_rigid cimport get_rigid_Hy
from gprMax.cython.yee_cell_setget_rigid cimport get_rigid_Hz from gprMax.cython.yee_cell_setget_rigid cimport get_rigid_Hz
from gprMax.materials import Material
cpdef void create_electric_average(int i, int j, int k, int numID1, int numID2, int numID3, int numID4, int componentID, G): cpdef void create_electric_average(int i, int j, int k, int numID1, int numID2, int numID3, int numID4, int componentID, G):

查看文件

@@ -19,8 +19,9 @@
import numpy as np import numpy as np
cimport numpy as np cimport numpy as np
# Get and set functions for the rigid electric component array. The rigid array is 4D with the 1st dimension holding # Get and set functions for the rigid electric component array. The rigid array
# the 12 electric edge components of a cell - Ex1, Ex2, Ex3, Ex4, Ey1, Ey2, Ey3, Ey4, Ez1, Ez2, Ez3, Ez4 # is 4D with the 1st dimension holding the 12 electric edge components of a
# cell - Ex1, Ex2, Ex3, Ex4, Ey1, Ey2, Ey3, Ey4, Ez1, Ez2, Ez3, Ez4
cdef bint get_rigid_Ex(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidE) cdef bint get_rigid_Ex(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidE)
cdef bint get_rigid_Ey(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidE) cdef bint get_rigid_Ey(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidE)
cdef bint get_rigid_Ez(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidE) cdef bint get_rigid_Ez(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidE)
@@ -30,8 +31,9 @@ cdef void set_rigid_Ez(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidE)
cdef void set_rigid_E(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidE) cdef void set_rigid_E(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidE)
cdef void unset_rigid_E(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidE) cdef void unset_rigid_E(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidE)
# Get and set functions for the rigid magnetic component array. The rigid array is 4D with the 1st dimension holding # Get and set functions for the rigid magnetic component array. The rigid array
# the 6 magnetic edge components - Hx1, Hx2, Hy1, Hy2, Hz1, Hz2 # is 4D with the 1st dimension holding the 6 magnetic edge components - Hx1,
# Hx2, Hy1, Hy2, Hz1, Hz2
cdef bint get_rigid_Hx(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidH) cdef bint get_rigid_Hx(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidH)
cdef bint get_rigid_Hy(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidH) cdef bint get_rigid_Hy(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidH)
cdef bint get_rigid_Hz(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidH) cdef bint get_rigid_Hz(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidH)
@@ -40,5 +42,3 @@ cdef void set_rigid_Hy(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidH)
cdef void set_rigid_Hz(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidH) cdef void set_rigid_Hz(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidH)
cdef void set_rigid_H(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidH) cdef void set_rigid_H(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidH)
cdef void unset_rigid_H(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidH) cdef void unset_rigid_H(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidH)

查看文件

@@ -19,8 +19,10 @@
import numpy as np import numpy as np
cimport numpy as np cimport numpy as np
# Get and set functions for the rigid electric component array. The rigid array is 4D with the 1st dimension holding
# the 12 electric edge components of a cell - Ex1, Ex2, Ex3, Ex4, Ey1, Ey2, Ey3, Ey4, Ez1, Ez2, Ez3, Ez4 # Get and set functions for the rigid electric component array. The rigid array
# is 4D with the 1st dimension holding the 12 electric edge components of a
# cell - Ex1, Ex2, Ex3, Ex4, Ey1, Ey2, Ey3, Ey4, Ez1, Ez2, Ez3, Ez4
cdef bint get_rigid_Ex(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidE): cdef bint get_rigid_Ex(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidE):
cdef bint result cdef bint result
result = False result = False
@@ -102,8 +104,9 @@ cdef void set_rigid_E(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidE):
cdef void unset_rigid_E(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidE): cdef void unset_rigid_E(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidE):
rigidE[:, i, j, k] = False rigidE[:, i, j, k] = False
# Get and set functions for the rigid magnetic component array. The rigid array is 4D with the 1st dimension holding # Get and set functions for the rigid magnetic component array. The rigid array
# the 6 magnetic edge components - Hx1, Hx2, Hy1, Hy2, Hz1, Hz2 # is 4D with the 1st dimension holding the 6 magnetic edge components - Hx1,
# Hx2, Hy1, Hy2, Hz1, Hz2
cdef bint get_rigid_Hx(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidH): cdef bint get_rigid_Hx(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidH):
cdef bint result cdef bint result
result = False result = False
@@ -154,4 +157,3 @@ cdef void set_rigid_H(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidH):
cdef void unset_rigid_H(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidH): cdef void unset_rigid_H(int i, int j, int k, np.int8_t[:, :, :, ::1] rigidH):
rigidH[:, i, j, k] = False rigidH[:, i, j, k] = False

查看文件

@@ -17,11 +17,14 @@
# along with gprMax. If not, see <http://www.gnu.org/licenses/>. # along with gprMax. If not, see <http://www.gnu.org/licenses/>.
import argparse import argparse
import logging
from .config import create_simulation_config from .config import create_simulation_config
from .contexts import create_context from .contexts import create_context
logging.basicConfig(level=logging.INFO)
def run( def run(
scenes=None, scenes=None,
inputfile=None, inputfile=None,
@@ -137,3 +140,4 @@ def run_main(args):
sim_config = create_simulation_config(args) sim_config = create_simulation_config(args)
context = create_context(sim_config) context = create_context(sim_config)
context.run() context.run()
context.print_time_report()

查看文件

@@ -36,80 +36,16 @@ from .utilities import human_size
from .utilities import round_value from .utilities import round_value
class Grid(object): class FDTDGrid:
"""Generic grid/mesh.""" """Holds attributes associated with entire grid. A convenient way for
accessing regularly used parameters.
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 entire grid. A convenient
way for accessing regularly used parameters.
""" """
def __init__(self): def __init__(self):
self.title = '' self.title = ''
self.memoryusage = 0 self.memoryusage = 0
self.name = 'Main' self.name = 'Main'
self.mode = '' # 2D TMx, 2D TMy, 2D TMz, or 3D
self.gpu = None self.gpu = None
self.outputdirectory = '' self.outputdirectory = ''
@@ -150,6 +86,50 @@ class FDTDGrid(Grid):
self.snapshots = [] self.snapshots = []
self.subgrids = [] self.subgrids = []
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 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
def initialise_geometry_arrays(self): def initialise_geometry_arrays(self):
"""Initialise an array for volumetric material IDs (solid); """Initialise an array for volumetric material IDs (solid);
@@ -173,9 +153,8 @@ class FDTDGrid(Grid):
self.Hy = 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']) self.Hz = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=config.dtypes['float_or_double'])
def initialise_grids(self): def initialise_grids(self):
"""Function to call the initialisation of all grids.""" """Initialise all grids."""
for g in [self] + self.subgrids: for g in [self] + self.subgrids:
g.initialise_geometry_arrays() g.initialise_geometry_arrays()
g.initialise_field_arrays() g.initialise_field_arrays()
@@ -227,32 +206,75 @@ class FDTDGrid(Grid):
self.memoryusage = int(stdoverhead + fieldarrays + solidarray + rigidarrays + pmlarrays) self.memoryusage = int(stdoverhead + fieldarrays + solidarray + rigidarrays + pmlarrays)
def memory_check(self, snapsmemsize=0): def memory_check(self):
"""Check if the required amount of memory (RAM) is available on the host and GPU if specified. """Check if the required amount of memory (RAM) is available to build
and/or run model on the host.
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']: 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))) raise GeneralError(f'Memory (RAM) required ~{human_size(self.memoryusage)} exceeds {human_size(config.hostinfo['ram'], a_kilobyte_is_1024_bytes=True)} detected!\n')
# Check if model can be run on specified GPU if required def tmx(self):
if config.cuda['gpus'] is not None: """Add PEC boundaries to invariant direction in 2D TMx mode.
if self.memoryusage - snapsmemsize > config.cuda['gpus'].totalmem: N.B. 2D modes are a single cell slice of 3D grid.
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)) """
# Ey & Ez components
self.ID[1, 0, :, :] = 0
self.ID[1, 1, :, :] = 0
self.ID[2, 0, :, :] = 0
self.ID[2, 1, :, :] = 0
# If the required memory without the snapshots will fit on the GPU then transfer and store snaphots on host def tmy(self):
if snapsmemsize != 0 and self.memoryusage - snapsmemsize < config.cuda['gpus'].totalmem: """Add PEC boundaries to invariant direction in 2D TMy mode.
config.cuda['snapsgpu2cpu'] = True N.B. 2D modes are a single cell slice of 3D grid.
"""
# Ex & Ez components
self.ID[0, :, 0, :] = 0
self.ID[0, :, 1, :] = 0
self.ID[2, :, 0, :] = 0
self.ID[2, :, 1, :] = 0
def gpu_set_blocks_per_grid(self): def tmz(self):
"""Add PEC boundaries to invariant direction in 2D TMz mode.
N.B. 2D modes are a single cell slice of 3D grid.
"""
# Ex & Ey components
self.ID[0, :, :, 0] = 0
self.ID[0, :, :, 1] = 0
self.ID[1, :, :, 0] = 0
self.ID[1, :, :, 1] = 0
def reset_fields(self):
"""Clear arrays for field components and PMLs."""
# 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):
"""Calculate time step at the CFL limit."""
self.dt = (1 / (c * np.sqrt(
(1 / self.dx) * (1 / self.dx) +
(1 / self.dy) * (1 / self.dy) +
(1 / self.dz) * (1 / self.dz))))
# 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)
class CUDAGrid(FDTDGrid):
"""Additional grid methods for solving on GPU using CUDA."""
def set_blocks_per_grid(self):
"""Set the blocks per grid size used for updating the electric and magnetic field arrays on a GPU.""" """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) 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): def initialise_arrays(self):
"""Initialise standard field arrays on GPU.""" """Initialise geometry and field arrays on GPU."""
import pycuda.gpuarray as gpuarray import pycuda.gpuarray as gpuarray
@@ -264,7 +286,7 @@ class FDTDGrid(Grid):
self.Hy_gpu = gpuarray.to_gpu(self.Hy) self.Hy_gpu = gpuarray.to_gpu(self.Hy)
self.Hz_gpu = gpuarray.to_gpu(self.Hz) self.Hz_gpu = gpuarray.to_gpu(self.Hz)
def gpu_initialise_dispersive_arrays(self): def initialise_dispersive_arrays(self):
"""Initialise dispersive material coefficient arrays on GPU.""" """Initialise dispersive material coefficient arrays on GPU."""
import pycuda.gpuarray as gpuarray import pycuda.gpuarray as gpuarray
@@ -274,46 +296,19 @@ class FDTDGrid(Grid):
self.Tz_gpu = gpuarray.to_gpu(self.Tz) self.Tz_gpu = gpuarray.to_gpu(self.Tz)
self.updatecoeffsdispersive_gpu = gpuarray.to_gpu(self.updatecoeffsdispersive) self.updatecoeffsdispersive_gpu = gpuarray.to_gpu(self.updatecoeffsdispersive)
# Add PEC boundaries to invariant direction in 2D modes def memory_check(self, snapsmemsize=0):
# N.B. 2D modes are a single cell slice of 3D grid """Check if model can be run on specified GPU."""
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(self): super().memory_check()
# Ex & Ez components
self.ID[0, :, 0, :] = 0
self.ID[0, :, 1, :] = 0
self.ID[2, :, 0, :] = 0
self.ID[2, :, 1, :] = 0
def tmz(self): if config.cuda['gpus'] is not None:
# Ex & Ey components if self.memoryusage - snapsmemsize > config.cuda['gpus'].totalmem:
self.ID[0, :, :, 0] = 0 raise GeneralError(f'Memory (RAM) required ~{human_size(self.memoryusage)} exceeds {human_size(config.cuda['gpus'].totalmem, a_kilobyte_is_1024_bytes=True} detected on specified {config.cuda['gpus'].deviceID} - {config.cuda['gpus'].name} GPU!\n')
self.ID[0, :, :, 1] = 0
self.ID[1, :, :, 0] = 0
self.ID[1, :, :, 1] = 0
# If the required memory for the model without the snapshots will
def reset_fields(self): # fit on the GPU then transfer and store snaphots on host
# Clear arrays for field components if snapsmemsize != 0 and self.memoryusage - snapsmemsize < config.cuda['gpus'].totalmem:
self.initialise_field_arrays() config.cuda['snapsgpu2cpu'] = True
# 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): def dispersion_analysis(G):
@@ -487,7 +482,3 @@ def Iz(x, y, z, Hx, Hy, Hz, G):
Iz = G.dx * (Hx[x, y - 1, z] - Hx[x, y, z]) + G.dy * (Hy[x, y, z] - Hy[x - 1, y, z]) Iz = G.dx * (Hx[x, y - 1, z] - Hx[x, y, z]) + G.dy * (Hy[x, y, z] - Hy[x - 1, y, z])
return Iz return Iz
class GPUGrid(FDTDGrid):
pass

查看文件

@@ -308,7 +308,7 @@ def parse_hash_commands(model_config, G, scene):
# Write a file containing the input commands after Python or include # Write a file containing the input commands after Python or include
# file commands have been processed # file commands have been processed
if sim_config.write_processed: if sim_config.args.write_processed:
write_processed_file(processedlines, model_config.appendmodelnumber, G) write_processed_file(processedlines, model_config.appendmodelnumber, G)
user_objs = get_user_objects(processedlines, check=True) user_objs = get_user_objects(processedlines, check=True)

查看文件

@@ -19,6 +19,7 @@
import datetime import datetime
from importlib import import_module from importlib import import_module
import itertools import itertools
import logging
import os import os
import psutil import psutil
import sys import sys
@@ -74,6 +75,8 @@ from .utilities import timer
from .utilities import Printer from .utilities import Printer
log = logging.getLogger(__name__)
class ModelBuildRun: class ModelBuildRun:
"""Builds and runs (solves) a model.""" """Builds and runs (solves) a model."""
@@ -159,7 +162,7 @@ class ModelBuildRun:
gb.grid.initialise_std_update_coeff_arrays() gb.grid.initialise_std_update_coeff_arrays()
# Set datatype for dispersive arrays if there are any dispersive materials. # Set datatype for dispersive arrays if there are any dispersive materials.
if config.materials['maxpoles'] != 0: if self.model_config.materials['maxpoles'] != 0:
drudelorentz = any([m for m in G.materials if 'drude' in m.type or 'lorentz' in m.type]) drudelorentz = any([m for m in G.materials if 'drude' in m.type or 'lorentz' in m.type])
if drudelorentz: if drudelorentz:
config.materials['dispersivedtype'] = config.dtypes['complex'] config.materials['dispersivedtype'] = config.dtypes['complex']
@@ -206,8 +209,8 @@ class ModelBuildRun:
G = self.G G = self.G
# Reset iteration number # Reset iteration number
G.iteration = 0 G.iteration = 0
self.model_config.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.model_config.inputfilestr = f'\n--- Model {self.model_config.appendmodelnumber}/{self.sim_config.model_end}, input file (not re-processed, i.e. geometry fixed): {self.sim_config.input_file_path}'
self.printer.print(Fore.GREEN + '{} {}'.format(self.model_config.inputfilestr, '-' * (get_terminal_width() - 1 - len(self.model_config.inputfilestr))) + Style.RESET_ALL) log.info(Fore.GREEN + f'{self.model_config.inputfilestr} {'-' * (get_terminal_width() - 1 - len(self.model_config.inputfilestr))}' + Style.RESET_ALL)
for grid in [G] + G.subgrids: for grid in [G] + G.subgrids:
grid.reset_fields() grid.reset_fields()
@@ -232,52 +235,52 @@ class ModelBuildRun:
# Check and set output directory and filename # Check and set output directory and filename
try: try:
os.mkdir(self.G.outputdirectory) os.mkdir(self.G.outputdirectory)
self.printer.print('\nCreated output directory: {}'.format(self.G.outputdirectory)) log.info(f'\nCreated output directory: {self.G.outputdirectory}')
except FileExistsError: except FileExistsError:
pass pass
# Modify the output path (hack) # Modify the output path (hack)
self.model_config.output_file_path_ext = Path(self.G.outputdirectory, self.model_config.output_file_path_ext) self.model_config.output_file_path_ext = Path(self.G.outputdirectory, self.model_config.output_file_path_ext)
def run_model(self, solver): def write_output_data(self):
G = self.G """Write output data, i.e. field data for receivers and snapshots
self.create_output_directory() to file(s).
self.printer.print('Output file: {}\n'.format(self.model_config.output_file_path_ext)) """
# Run solver G = self.G
tsolve = self.solve(solver)
# Write an output file in HDF5 format # Write an output file in HDF5 format
write_hdf5_outputfile(self.model_config.output_file_path_ext, G) write_hdf5_outputfile(self.model_config.output_file_path_ext, G)
# Write any snapshots to file # Write any snapshots to file
if G.snapshots: if G.snapshots:
# Create directory and construct filename from user-supplied name and model run number # Create directory and construct filename from user-supplied name
# and model run number
snapshotdir = self.model_config.snapshot_dir snapshotdir = self.model_config.snapshot_dir
if not os.path.exists(snapshotdir): if not os.path.exists(snapshotdir):
os.mkdir(snapshotdir) os.mkdir(snapshotdir)
self.printer.print('') log.info('')
for i, snap in enumerate(G.snapshots): for i, snap in enumerate(G.snapshots):
fn = snapshotdir / Path(self.model_config.output_file_path.stem + '_' + snap.basefilename) fn = snapshotdir / Path(self.model_config.output_file_path.stem + '_' + snap.basefilename)
snap.filename = fn.with_suffix('.vti') snap.filename = fn.with_suffix('.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']) 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) snap.write_vtk_imagedata(pbar, G)
pbar.close() pbar.close()
self.printer.print('') log.info('')
def print_resource_info(self):
"""Print resource information on runtime and memory usage."""
memGPU = '' memGPU = ''
if config.cuda['gpus']: if config.cuda['gpus']:
memGPU = ' host + ~{} GPU'.format(human_size(self.solver.get_memsolve())) memGPU = f' host + ~{human_size(self.solver.get_memsolve())} GPU'
self.printer.print('\nMemory (RAM) used: ~{}{}'.format(human_size(self.p.memory_full_info().uss), memGPU)) log.info(f'\nMemory (RAM) used: ~{human_size(self.p.memory_full_info().uss)}{memGPU}')
self.printer.print('Solving time [HH:MM:SS]: {}'.format(datetime.timedelta(seconds=tsolve))) log.info(f'Solving time [HH:MM:SS]: {datetime.timedelta(seconds=tsolve)}')
return tsolve
def solve(self, solver): def solve(self, solver):
""" """
Solving using FDTD method on CPU. Parallelised using Cython (OpenMP) for Solve using FDTD method.
electric and magnetic field updates, and PML updates.
Args: Args:
solver (Solver): solver object. solver (Solver): solver object.
@@ -294,7 +297,17 @@ class ModelBuildRun:
else: else:
iterator = range(0, G.iterations) iterator = range(0, G.iterations)
tsolve = solver.solve(iterator) self.create_output_directory()
log.info(f'Output file: {self.model_config.output_file_path_ext}\n')
# Run solver
tsolve = self.solve(solver)
# Write output data, i.e. field data for receivers and snapshots to file(s)
self.write_output_data
# Print resource information on runtime and memory usage
self.print_resource_info
return tsolve return tsolve
@@ -318,7 +331,7 @@ class GridBuilder:
def build_components(self): def build_components(self):
# Build the model, i.e. set the material properties (ID) for every edge # Build the model, i.e. set the material properties (ID) for every edge
# of every Yee cell # of every Yee cell
self.printer.print('') log.info('')
pbar = tqdm(total=2, desc='Building {} Grid'.format(self.grid.name), ncols=get_terminal_width() - 1, file=sys.stdout, disable=not config.general['progressbars']) 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) build_electric_components(self.grid.solid, self.grid.rigidE, self.grid.ID, self.grid)
pbar.update() pbar.update()
@@ -348,5 +361,5 @@ class GridBuilder:
materialstable.outer_border = False materialstable.outer_border = False
materialstable.justify_columns[0] = 'right' materialstable.justify_columns[0] = 'right'
self.printer.print('\n{} Grid Materials:'.format(self.grid.name)) log.info(f'\n{self.grid.name} Grid Materials:')
self.printer.print(materialstable.table) log.info(materialstable.table)

查看文件

@@ -81,7 +81,7 @@ class CFS(object):
# Calculation of the maximum value of sigma from http://dx.doi.org/10.1109/8.546249 # Calculation of the maximum value of sigma from http://dx.doi.org/10.1109/8.546249
m = CFSParameter.scalingprofiles[self.sigma.scalingprofile] m = CFSParameter.scalingprofiles[self.sigma.scalingprofile]
self.sigma.max = (0.8 * (m + 1)) / (config.z0 * d * np.sqrt(er * mr)) self.sigma.max = (0.8 * (m + 1)) / (config.general['z0'] * d * np.sqrt(er * mr))
def scaling_polynomial(self, order, Evalues, Hvalues): def scaling_polynomial(self, order, Evalues, Hvalues):
"""Applies the polynomial to be used for the scaling profile for """Applies the polynomial to be used for the scaling profile for

查看文件

@@ -18,10 +18,10 @@
import gprMax.config as config import gprMax.config as config
from .grid import FDTDGrid from .grid import FDTDGrid
from .grid import GPUGrid from .grid import CUDAGrid
from .subgrids.updates import create_updates as create_subgrid_updates from .subgrids.updates import create_updates as create_subgrid_updates
from .updates import CPUUpdates from .updates import CPUUpdates
from .updates import GPUUpdates from .updates import CUDAUpdates
def create_G(sim_config): def create_G(sim_config):
@@ -34,8 +34,8 @@ def create_G(sim_config):
G (Grid): Holds essential parameters describing the model. G (Grid): Holds essential parameters describing the model.
""" """
if sim_config.gpu: if sim_config.general['cuda']:
G = GPUGrid() G = CUDAGrid()
elif sim_config.subgrid: elif sim_config.subgrid:
G = FDTDGrid() G = FDTDGrid()
else: else:
@@ -56,7 +56,7 @@ def create_solver(G, sim_config):
""" """
if sim_config.gpu: if sim_config.gpu:
updates = GPUUpdates(G) updates = CUDAUpdates(G)
solver = Solver(updates) solver = Solver(updates)
elif sim_config.subgrid: elif sim_config.subgrid:
updates = create_subgrid_updates(G) updates = create_subgrid_updates(G)

查看文件

@@ -63,19 +63,9 @@ class SubGridBase(FDTDGrid):
self.interpolation = kwargs['interpolation'] self.interpolation = kwargs['interpolation']
def calculate_dt(self):
"""Calculate dt at the CFL limit."""
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): def main_grid_index_to_subgrid_index(self, i, j, k):
"""Calculate local subgrid index from global main grid index.""" """Calculate local subgrid index from global main grid index."""
i_s = self.n_boundary_cells_x + (i - self.i0) * self.ratio i_s = self.n_boundary_cells_x + (i - self.i0) * self.ratio
j_s = self.n_boundary_cells_y + (j - self.j0) * 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 k_s = self.n_boundary_cells_z + (k - self.k0) * self.ratio
return (i_s, j_s, k_s) return (i_s, j_s, k_s)
def initialise_geometry_arrays(self):
super().initialise_geometry_arrays()

查看文件

@@ -27,10 +27,6 @@ class ReferenceRx(Rx):
position as the coarse grid. position as the coarse grid.
""" """
def __init__(self):
"""Constructor."""
super().__init__()
def get_field(self, str_id, field): def get_field(self, str_id, field):
"""Return the field value at the equivalent coarse yee cell. """Return the field value at the equivalent coarse yee cell.

查看文件

@@ -28,7 +28,7 @@ def calculate_weighting_coefficients(x1, x):
return (c1, c2) return (c1, c2)
class PrecusorNodesBase(object): class PrecusorNodesBase:
def __init__(self, fdtd_grid, sub_grid): def __init__(self, fdtd_grid, sub_grid):
self.G = fdtd_grid self.G = fdtd_grid

查看文件

@@ -18,6 +18,7 @@
import numpy as np import numpy as np
cimport numpy as np cimport numpy as np
from cython.parallel import prange from cython.parallel import prange
cdef extern from "complex.h" nogil: cdef extern from "complex.h" nogil:

查看文件

@@ -17,6 +17,8 @@
# along with gprMax. If not, see <http://www.gnu.org/licenses/>. # along with gprMax. If not, see <http://www.gnu.org/licenses/>.
from importlib import import_module from importlib import import_module
import logging
import sys
import gprMax.config as config import gprMax.config as config
from .cuda.fields_updates import kernel_template_fields from .cuda.fields_updates import kernel_template_fields
@@ -34,6 +36,8 @@ from .sources import gpu_initialise_src_arrays
from .utilities import timer from .utilities import timer
log = logging.getLogger(__name__)
class CPUUpdates: class CPUUpdates:
"""Defines update functions for CPU-based solver.""" """Defines update functions for CPU-based solver."""
@@ -226,7 +230,7 @@ class CPUUpdates:
pass pass
class GPUUpdates: class CUDAUpdates:
"""Defines update functions for GPU-based (CUDA) solver.""" """Defines update functions for GPU-based (CUDA) solver."""
def __init__(self, G): def __init__(self, G):
@@ -235,6 +239,10 @@ class GPUUpdates:
G (FDTDGrid): FDTD grid object G (FDTDGrid): FDTD grid object
""" """
self.grid = G
self.dispersive_update_a = None
self.dispersive_update_b = None
import pycuda.driver as drv import pycuda.driver as drv
from pycuda.compiler import SourceModule from pycuda.compiler import SourceModule
drv.init() drv.init()
@@ -249,10 +257,6 @@ class GPUUpdates:
self.dev = drv.Device(self.grid.gpu.deviceID) self.dev = drv.Device(self.grid.gpu.deviceID)
self.ctx = dev.make_context() self.ctx = dev.make_context()
self.grid = G
self.dispersive_update_a = None
self.dispersive_update_b = None
# Initialise arrays on GPU, prepare kernels, and get kernel functions # Initialise arrays on GPU, prepare kernels, and get kernel functions
self.set_field_kernels() self.set_field_kernels()
self.set_pml_kernels() self.set_pml_kernels()
@@ -261,7 +265,9 @@ class GPUUpdates:
self.set_snapshot_kernel() self.set_snapshot_kernel()
def set_field_kernels(self): def set_field_kernels(self):
# Electric and magnetic field updates - prepare kernels, and get kernel functions """Electric and magnetic field updates - prepare kernels, and
get kernel functions.
"""
if config.materials['maxpoles'] > 0: if config.materials['maxpoles'] > 0:
kernels_fields = SourceModule(kernels_template_fields.substitute(REAL=cudafloattype, COMPLEX=cudacomplextype, N_updatecoeffsE=self.grid.updatecoeffsE.size, N_updatecoeffsH=self.grid.updatecoeffsH.size, NY_MATCOEFFS=self.grid.updatecoeffsE.shape[1], NY_MATDISPCOEFFS=self.grid.updatecoeffsdispersive.shape[1], NX_FIELDS=self.grid.nx + 1, NY_FIELDS=self.grid.ny + 1, NZ_FIELDS=self.grid.nz + 1, NX_ID=self.grid.ID.shape[1], NY_ID=self.grid.ID.shape[2], NZ_ID=self.grid.ID.shape[3], NX_T=self.grid.Tx.shape[1], NY_T=self.grid.Tx.shape[2], NZ_T=self.grid.Tx.shape[3]), options=self.compiler_opts) kernels_fields = SourceModule(kernels_template_fields.substitute(REAL=cudafloattype, COMPLEX=cudacomplextype, N_updatecoeffsE=self.grid.updatecoeffsE.size, N_updatecoeffsH=self.grid.updatecoeffsH.size, NY_MATCOEFFS=self.grid.updatecoeffsE.shape[1], NY_MATDISPCOEFFS=self.grid.updatecoeffsdispersive.shape[1], NX_FIELDS=self.grid.nx + 1, NY_FIELDS=self.grid.ny + 1, NZ_FIELDS=self.grid.nz + 1, NX_ID=self.grid.ID.shape[1], NY_ID=self.grid.ID.shape[2], NZ_ID=self.grid.ID.shape[3], NX_T=self.grid.Tx.shape[1], NY_T=self.grid.Tx.shape[2], NZ_T=self.grid.Tx.shape[3]), options=self.compiler_opts)
else: # Set to one any substitutions for dispersive materials else: # Set to one any substitutions for dispersive materials
@@ -269,7 +275,7 @@ class GPUUpdates:
self.update_electric = kernels_fields.get_function("update_electric") self.update_electric = kernels_fields.get_function("update_electric")
self.update_magnetic = kernels_fields.get_function("update_magnetic") self.update_magnetic = kernels_fields.get_function("update_magnetic")
if self.grid.updatecoeffsE.nbytes + self.grid.updatecoeffsH.nbytes > self.grid.gpu.constmem: if self.grid.updatecoeffsE.nbytes + self.grid.updatecoeffsH.nbytes > self.grid.gpu.constmem:
raise GeneralError('Too many materials in the model to fit onto constant memory of size {} on {} - {} GPU'.format(human_size(self.grid.gpu.constmem), self.grid.gpu.deviceID, self.grid.gpu.name)) raise GeneralError(log.exception(f'Too many materials in the model to fit onto constant memory of size {human_size(self.grid.gpu.constmem)} on {self.grid.gpu.deviceID} - {self.grid.gpu.name} GPU'))
self.copy_mat_coeffs() self.copy_mat_coeffs()
# Electric and magnetic field updates - dispersive materials - get kernel functions and initialise array on GPU # Electric and magnetic field updates - dispersive materials - get kernel functions and initialise array on GPU
@@ -283,8 +289,8 @@ class GPUUpdates:
self.grid.gpu_initialise_arrays() self.grid.gpu_initialise_arrays()
def set_pml_kernels(self): def set_pml_kernels(self):
"""PMLS - prepare kernels and get kernel functions."""
if self.grid.pmls: if self.grid.pmls:
# PMLS - prepare kernels and get kernel functions
pmlmodulelectric = 'gprMax.cuda.pml_updates_electric_' + self.grid.pmlformulation pmlmodulelectric = 'gprMax.cuda.pml_updates_electric_' + self.grid.pmlformulation
kernelelectricfunc = getattr(import_module(pmlmodulelectric), 'kernels_template_pml_electric_' + self.grid.pmlformulation) kernelelectricfunc = getattr(import_module(pmlmodulelectric), 'kernels_template_pml_electric_' + self.grid.pmlformulation)
pmlmodulemagnetic = 'gprMax.cuda.pml_updates_magnetic_' + self.grid.pmlformulation pmlmodulemagnetic = 'gprMax.cuda.pml_updates_magnetic_' + self.grid.pmlformulation
@@ -299,14 +305,18 @@ class GPUUpdates:
pml.gpu_set_blocks_per_grid(self.grid) pml.gpu_set_blocks_per_grid(self.grid)
def set_rx_kernel(self): def set_rx_kernel(self):
# Receivers - initialise arrays on GPU, prepare kernel and get kernel function """Receivers - initialise arrays on GPU, prepare kernel and
get kernel function.
"""
if self.grid.rxs: if self.grid.rxs:
rxcoords_gpu, rxs_gpu = gpu_initialise_rx_arrays(self.grid) rxcoords_gpu, rxs_gpu = gpu_initialise_rx_arrays(self.grid)
kernel_store_outputs = SourceModule(kernel_template_store_outputs.substitute(REAL=cudafloattype, NY_RXCOORDS=3, NX_RXS=6, NY_RXS=self.grid.iterations, NZ_RXS=len(self.grid.rxs), NX_FIELDS=self.grid.nx + 1, NY_FIELDS=self.grid.ny + 1, NZ_FIELDS=self.grid.nz + 1), options=self.compiler_opts) kernel_store_outputs = SourceModule(kernel_template_store_outputs.substitute(REAL=cudafloattype, NY_RXCOORDS=3, NX_RXS=6, NY_RXS=self.grid.iterations, NZ_RXS=len(self.grid.rxs), NX_FIELDS=self.grid.nx + 1, NY_FIELDS=self.grid.ny + 1, NZ_FIELDS=self.grid.nz + 1), options=self.compiler_opts)
self.store_outputs = kernel_store_outputs.get_function("store_outputs") self.store_outputs = kernel_store_outputs.get_function("store_outputs")
def set_src_kernels(self): def set_src_kernels(self):
# Sources - initialise arrays on GPU, prepare kernel and get kernel function """Sources - initialise arrays on GPU, prepare kernel and
get kernel function.
"""
if self.grid.voltagesources + self.grid.hertziandipoles + self.grid.magneticdipoles: if self.grid.voltagesources + self.grid.hertziandipoles + self.grid.magneticdipoles:
kernels_sources = SourceModule(kernels_template_sources.substitute(REAL=cudafloattype, N_updatecoeffsE=self.grid.updatecoeffsE.size, N_updatecoeffsH=self.grid.updatecoeffsH.size, NY_MATCOEFFS=self.grid.updatecoeffsE.shape[1], NY_SRCINFO=4, NY_SRCWAVES=self.grid.iterations, NX_FIELDS=self.grid.nx + 1, NY_FIELDS=self.grid.ny + 1, NZ_FIELDS=self.grid.nz + 1, NX_ID=self.grid.ID.shape[1], NY_ID=self.grid.ID.shape[2], NZ_ID=self.grid.ID.shape[3]), options=self.compiler_opts) kernels_sources = SourceModule(kernels_template_sources.substitute(REAL=cudafloattype, N_updatecoeffsE=self.grid.updatecoeffsE.size, N_updatecoeffsH=self.grid.updatecoeffsH.size, NY_MATCOEFFS=self.grid.updatecoeffsE.shape[1], NY_SRCINFO=4, NY_SRCWAVES=self.grid.iterations, NX_FIELDS=self.grid.nx + 1, NY_FIELDS=self.grid.ny + 1, NZ_FIELDS=self.grid.nz + 1, NX_ID=self.grid.ID.shape[1], NY_ID=self.grid.ID.shape[2], NZ_ID=self.grid.ID.shape[3]), options=self.compiler_opts)
self.copy_mat_coeffs() self.copy_mat_coeffs()
@@ -321,14 +331,18 @@ class GPUUpdates:
self.update_voltage_source_gpu = kernels_sources.get_function("update_voltage_source") self.update_voltage_source_gpu = kernels_sources.get_function("update_voltage_source")
def set_snapshot_kernel(self): def set_snapshot_kernel(self):
"""Snapshots - initialise arrays on GPU, prepare kernel and
get kernel function.
"""
if self.grid.snapshots: if self.grid.snapshots:
# Snapshots - initialise arrays on GPU, prepare kernel and get kernel function
self.snapEx_gpu, self.snapEy_gpu, self.snapEz_gpu, self.snapHx_gpu, self.snapHy_gpu, self.snapHz_gpu = gpu_initialise_snapshot_array(self.grid) self.snapEx_gpu, self.snapEy_gpu, self.snapEz_gpu, self.snapHx_gpu, self.snapHy_gpu, self.snapHz_gpu = gpu_initialise_snapshot_array(self.grid)
kernel_store_snapshot = SourceModule(kernel_template_store_snapshot.substitute(REAL=cudafloattype, NX_SNAPS=Snapshot.nx_max, NY_SNAPS=Snapshot.ny_max, NZ_SNAPS=Snapshot.nz_max, NX_FIELDS=self.grid.nx + 1, NY_FIELDS=self.grid.ny + 1, NZ_FIELDS=self.grid.nz + 1), options=self.compiler_opts) kernel_store_snapshot = SourceModule(kernel_template_store_snapshot.substitute(REAL=cudafloattype, NX_SNAPS=Snapshot.nx_max, NY_SNAPS=Snapshot.ny_max, NZ_SNAPS=Snapshot.nz_max, NX_FIELDS=self.grid.nx + 1, NY_FIELDS=self.grid.ny + 1, NZ_FIELDS=self.grid.nz + 1), options=self.compiler_opts)
self.store_snapshot_gpu = kernel_store_snapshot.get_function("store_snapshot") self.store_snapshot_gpu = kernel_store_snapshot.get_function("store_snapshot")
def copy_mat_coeffs(self): def copy_mat_coeffs(self):
# Copy material coefficient arrays to constant memory of GPU (must be <64KB) """Copy material coefficient arrays to constant memory of GPU
(must be <64KB).
"""
updatecoeffsE = kernels_sources.get_global('updatecoeffsE')[0] updatecoeffsE = kernels_sources.get_global('updatecoeffsE')[0]
updatecoeffsH = kernels_sources.get_global('updatecoeffsH')[0] updatecoeffsH = kernels_sources.get_global('updatecoeffsH')[0]
self.drv.memcpy_htod(updatecoeffsE, self.grid.updatecoeffsE) self.drv.memcpy_htod(updatecoeffsE, self.grid.updatecoeffsE)
@@ -396,7 +410,6 @@ class GPUUpdates:
def update_magnetic(self): def update_magnetic(self):
"""Update magnetic field components.""" """Update magnetic field components."""
self.update_magnetic(np.int32(self.grid.nx), self.update_magnetic(np.int32(self.grid.nx),
np.int32(self.grid.ny), np.int32(self.grid.ny),
np.int32(self.grid.nz), np.int32(self.grid.nz),
@@ -412,13 +425,11 @@ class GPUUpdates:
def update_magnetic_pml(self): def update_magnetic_pml(self):
"""Update magnetic field components with the PML correction.""" """Update magnetic field components with the PML correction."""
for pml in self.grid.pmls: for pml in self.grid.pmls:
pml.gpu_update_magnetic(self.grid) pml.gpu_update_magnetic(self.grid)
def update_magnetic_sources(self): def update_magnetic_sources(self):
"""Update magnetic field components from sources.""" """Update magnetic field components from sources."""
if self.grid.magneticdipoles: if self.grid.magneticdipoles:
self.update_magnetic_dipole_gpu(np.int32(len(self.grid.magneticdipoles)), self.update_magnetic_dipole_gpu(np.int32(len(self.grid.magneticdipoles)),
np.int32(self.grid.iteration), np.int32(self.grid.iteration),
@@ -437,7 +448,6 @@ class GPUUpdates:
def update_electric_a(self): def update_electric_a(self):
"""Update electric field components.""" """Update electric field components."""
# All materials are non-dispersive so do standard update. # All materials are non-dispersive so do standard update.
if config.materials['maxpoles'] == 0: if config.materials['maxpoles'] == 0:
self.update_electric(np.int32(self.grid.nx), self.update_electric(np.int32(self.grid.nx),
@@ -476,7 +486,6 @@ class GPUUpdates:
def update_electric_pml(self): def update_electric_pml(self):
"""Update electric field components with the PML correction.""" """Update electric field components with the PML correction."""
for pml in self.grid.pmls: for pml in self.grid.pmls:
pml.gpu_update_electric(self.grid) pml.gpu_update_electric(self.grid)
@@ -484,7 +493,6 @@ class GPUUpdates:
"""Update electric field components from sources - """Update electric field components from sources -
update any Hertzian dipole sources last. update any Hertzian dipole sources last.
""" """
if self.grid.voltagesources: if self.grid.voltagesources:
self.update_voltage_source_gpu(np.int32(len(self.grid.voltagesources)), self.update_voltage_source_gpu(np.int32(len(self.grid.voltagesources)),
np.int32(self.grid.iteration), np.int32(self.grid.iteration),
@@ -525,7 +533,6 @@ class GPUUpdates:
field values. Therefore it can only be completely updated after the field values. Therefore it can only be completely updated after the
electric field has been updated by the PML and source updates. electric field has been updated by the PML and source updates.
""" """
if config.materials['maxpoles'] != 0: if config.materials['maxpoles'] != 0:
self.dispersive_update_b(np.int32(self.grid.nx), self.dispersive_update_b(np.int32(self.grid.nx),
np.int32(self.grid.ny), np.int32(self.grid.ny),
@@ -543,11 +550,14 @@ class GPUUpdates:
grid=self.grid.bpg) grid=self.grid.bpg)
def time_start(self): def time_start(self):
"""Start event timers used to calculate solving time for model."""
self.iterstart = self.drv.Event() self.iterstart = self.drv.Event()
self.iterend = self.drv.Event() self.iterend = self.drv.Event()
self.iterstart.record() self.iterstart.record()
self.iterstart.synchronize()
def calculate_tsolve(self): def calculate_tsolve(self):
"""Calculate solving time for model."""
self.iterend.record() self.iterend.record()
self.iterend.synchronize() self.iterend.synchronize()
tsolve = self.iterstart.time_till(self.iterend) * 1e-3 tsolve = self.iterstart.time_till(self.iterend) * 1e-3
@@ -555,6 +565,7 @@ class GPUUpdates:
return tsolve return tsolve
def finalise(self): def finalise(self):
"""Copy data from GPU back to CPU to save to file(s)."""
# Copy output from receivers array back to correct receiver objects # Copy output from receivers array back to correct receiver objects
if self.grid.rxs: if self.grid.rxs:
gpu_get_rx_array(self.rxs_gpu.get(), gpu_get_rx_array(self.rxs_gpu.get(),
@@ -574,6 +585,7 @@ class GPUUpdates:
snap) snap)
def cleanup(self): def cleanup(self):
"""Cleanup GPU context."""
# Remove context from top of stack and delete # Remove context from top of stack and delete
self.ctx.pop() self.ctx.pop()
del self.ctx del self.ctx

查看文件

@@ -27,7 +27,7 @@ import subprocess
from shutil import get_terminal_size from shutil import get_terminal_size
import sys import sys
import textwrap import textwrap
from time import perf_counter from time import process_time
from colorama import init from colorama import init
from colorama import Fore from colorama import Fore
@@ -38,7 +38,6 @@ import numpy as np
from .exceptions import GeneralError from .exceptions import GeneralError
def get_terminal_width(): def get_terminal_width():
"""Get/set width of terminal being used. """Get/set width of terminal being used.
@@ -75,14 +74,14 @@ def logo(version):
|___/|_| |___/|_|
v""" + version v""" + version
print('{} {}\n'.format(description, '=' * (get_terminal_width() - len(description) - 1))) log.info(f'{description} {'=' * (get_terminal_width() - len(description) - 1)}\n')
print(Fore.CYAN + '{}\n'.format(logo)) log.info(Fore.CYAN + f'{logo}\n')
print(Style.RESET_ALL + textwrap.fill(copyright, width=get_terminal_width() - 1, initial_indent=' ')) log.info(Style.RESET_ALL + textwrap.fill(copyright, width=get_terminal_width() - 1, initial_indent=' '))
print(textwrap.fill(authors, width=get_terminal_width() - 1, initial_indent=' ')) log.info(textwrap.fill(authors, width=get_terminal_width() - 1, initial_indent=' '))
print() log.info('')
print(textwrap.fill(licenseinfo1, width=get_terminal_width() - 1, initial_indent=' ', subsequent_indent=' ')) log.info(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=' ')) log.info(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=' ')) log.info(textwrap.fill(licenseinfo3, width=get_terminal_width() - 1, initial_indent=' ', subsequent_indent=' '))
@contextmanager @contextmanager
@@ -401,7 +400,7 @@ def detect_check_gpus(deviceIDs):
# Check if requested device ID(s) exist # Check if requested device ID(s) exist
for ID in deviceIDs: for ID in deviceIDs:
if ID not in deviceIDsavail: if ID not in deviceIDsavail:
raise GeneralError('GPU with device ID {} does not exist'.format(ID)) raise GeneralError(f'GPU with device ID {ID} does not exist')
# Gather information about selected/detected GPUs # Gather information about selected/detected GPUs
gpus = [] gpus = []
@@ -411,22 +410,11 @@ def detect_check_gpus(deviceIDs):
gpu.get_gpu_info(drv) gpu.get_gpu_info(drv)
if ID in deviceIDs: if ID in deviceIDs:
gpus.append(gpu) gpus.append(gpu)
allgpustext.append('{} - {}, {}'.format(gpu.deviceID, gpu.name, human_size(gpu.totalmem, a_kilobyte_is_1024_bytes=True))) allgpustext.append(f'{gpu.deviceID} - {gpu.name}, {human_size(gpu.totalmem, a_kilobyte_is_1024_bytes=True)}')
return gpus, allgpustext return gpus, allgpustext
def timer(): def timer():
"""Function to return the current process wide time in fractional seconds.""" """Function to return the current process wide time in fractional seconds."""
return perf_counter() return process_time()
class Printer():
"""Printing information messages."""
def __init__(self, config):
self.printing = config.is_messages()
def print(self, str):
if self.printing:
print(str)

查看文件

@@ -33,14 +33,18 @@ import pathlib
import re import re
import shutil import shutil
import sys import sys
from jinja2 import Environment, PackageLoader, select_autoescape
from jinja2 import Environment
from jinja2 import PackageLoader
from jinja2 import select_autoescape
def build_dispersive_material_templates(): 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.
""" """
Function to generate Cython .pyx files for dispersive media update.
Jinja2 templates are used to render the various dispersive update functions.
"""
env = Environment( env = Environment(
loader=PackageLoader(__name__, 'gprMax/templates'), loader=PackageLoader(__name__, 'gprMax/templates'),
) )
@@ -152,25 +156,25 @@ if 'cleanall' in sys.argv:
if os.path.isfile(filebase + '.c'): if os.path.isfile(filebase + '.c'):
try: try:
os.remove(filebase + '.c') os.remove(filebase + '.c')
print('Removed: {}'.format(filebase + '.c')) print(f'Removed: {filebase} + ".c"')
except OSError: except OSError:
print('Could not remove: {}'.format(filebase + '.c')) print(f'Could not remove: {filebase} + ".c"')
# Remove compiled Cython modules # Remove compiled Cython modules
libfile = glob.glob(os.path.join(os.getcwd(), os.path.splitext(file)[0]) + '*.pyd') + glob.glob(os.path.join(os.getcwd(), os.path.splitext(file)[0]) + '*.so') libfile = glob.glob(os.path.join(os.getcwd(), os.path.splitext(file)[0]) + '*.pyd') + glob.glob(os.path.join(os.getcwd(), os.path.splitext(file)[0]) + '*.so')
if libfile: if libfile:
libfile = libfile[0] libfile = libfile[0]
try: try:
os.remove(libfile) os.remove(libfile)
print('Removed: {}'.format(os.path.abspath(libfile))) print(f'Removed: {os.path.abspath(libfile)}')
except OSError: except OSError:
print('Could not remove: {}'.format(os.path.abspath(libfile))) print(f'Could not remove: {os.path.abspath(libfile)}')
# Remove build, dist, egg and __pycache__ directories # 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(), 'build'), ignore_errors=True)
shutil.rmtree(os.path.join(os.getcwd(), 'dist'), 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) shutil.rmtree(os.path.join(os.getcwd(), 'gprMax.egg-info'), ignore_errors=True)
for p in pathlib.Path(os.getcwd()).rglob('__pycache__'): for p in pathlib.Path(os.getcwd()).rglob('__pycache__'):
shutil.rmtree(p, ignore_errors=True) shutil.rmtree(p, ignore_errors=True)
print('Removed: {}'.format(p)) print(f'Removed: {p}')
# Now do a normal clean # Now do a normal clean
sys.argv[1] = 'clean' # this is what distutils understands sys.argv[1] = 'clean' # this is what distutils understands