你已经派生过 gprMax
镜像自地址
https://gitee.com/sunhf/gprMax.git
已同步 2025-08-07 23:14:03 +08:00
Incomplete mods and updates as discussed will John. Not implemented new config yet.
这个提交包含在:
@@ -16,8 +16,9 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with gprMax. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
|
||||
import numpy as np
|
||||
from tqdm import tqdm
|
||||
|
||||
import gprMax.config as config
|
||||
from .cmds_geometry import UserObjectGeometry
|
||||
@@ -28,6 +29,8 @@ from ..materials import Material
|
||||
from ..utilities import round_value
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class AddGrass(UserObjectGeometry):
|
||||
"""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:
|
||||
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]
|
||||
if volumes:
|
||||
volume = volumes[0]
|
||||
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__())
|
||||
xs, ys, zs = p1
|
||||
@@ -193,4 +196,4 @@ class AddGrass(UserObjectGeometry):
|
||||
volume.fractalsurfaces.append(surface)
|
||||
|
||||
if config.is_messages():
|
||||
tqdm.write('{} blades of grass on surface from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m with fractal dimension {:g}, fractal seeding {}, and range {:g}m to {:g}m, added to {}.'.format(n_blades, xs * grid.dx, ys * grid.dy, zs * grid.dz, xf * grid.dx, yf * grid.dy, zf * grid.dz, surface.dimension, surface.seed, limits[0], limits[1], surface.operatingonID))
|
||||
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
|
||||
# along with gprMax. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
|
||||
import numpy as np
|
||||
from tqdm import tqdm
|
||||
|
||||
import gprMax.config as config
|
||||
from .cmds_geometry import UserObjectGeometry
|
||||
@@ -26,6 +27,8 @@ from ..exceptions import CmdInputError
|
||||
from ..fractals import FractalSurface
|
||||
from ..utilities import round_value
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AddSurfaceRoughness(UserObjectGeometry):
|
||||
"""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'])
|
||||
fractal_box_id = self.kwargs['fractal_box_id']
|
||||
except KeyError:
|
||||
raise CmdInputError(self.__str__() + ' Incorrect parameters')
|
||||
raise CmdInputError(self.__str__() + ' incorrect parameters')
|
||||
|
||||
try:
|
||||
seed = self.kwargs['seed']
|
||||
except KeyError:
|
||||
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]
|
||||
if volumes:
|
||||
volume = volumes[0]
|
||||
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__())
|
||||
xs, ys, zs = p1
|
||||
@@ -152,10 +155,10 @@ class AddSurfaceRoughness(UserObjectGeometry):
|
||||
# List of existing surfaces IDs
|
||||
existingsurfaceIDs = [x.surfaceID for x in volume.fractalsurfaces]
|
||||
if surface.surfaceID in existingsurfaceIDs:
|
||||
raise CmdInputError(self.__str__() + ' has already been used on the {} surface'.format(surface.surfaceID))
|
||||
raise CmdInputError(self.__str__() + f' has already been used on the {surface.surfaceID} surface')
|
||||
|
||||
surface.generate_fractal_surface(grid)
|
||||
volume.fractalsurfaces.append(surface)
|
||||
|
||||
if config.is_messages():
|
||||
tqdm.write('Fractal surface from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m with fractal dimension {:g}, fractal weightings {:g}, {:g}, fractal seeding {}, and range {:g}m to {:g}m, added to {}.'.format(xs * grid.dx, ys * grid.dy, zs * grid.dz, xf * grid.dx, yf * grid.dy, zf * grid.dz, surface.dimension, surface.weighting[0], surface.weighting[1], surface.seed, limits[0], limits[1], surface.operatingonID))
|
||||
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
|
||||
# along with gprMax. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from tqdm import tqdm
|
||||
import logging
|
||||
|
||||
import gprMax.config as config
|
||||
from .cmds_geometry import UserObjectGeometry
|
||||
@@ -25,6 +25,8 @@ from ..materials import Material
|
||||
from ..utilities import round_value
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class AddSurfaceWater(UserObjectGeometry):
|
||||
"""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:
|
||||
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]
|
||||
if volumes:
|
||||
volume = volumes[0]
|
||||
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__())
|
||||
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.')
|
||||
|
||||
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
|
||||
# along with gprMax. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
from tqdm import tqdm
|
||||
|
||||
import gprMax.config as config
|
||||
from .config import z0
|
||||
from .config import dtypes
|
||||
|
||||
from .cmds_geometry.cmds_geometry import UserObjectGeometry
|
||||
from .exceptions import CmdInputError
|
||||
from .geometry_outputs import GeometryObjects as GeometryObjectsUser
|
||||
@@ -40,18 +39,16 @@ from .subgrids.base import SubGridBase
|
||||
from .utilities import round_value
|
||||
from .waveforms import Waveform as WaveformUser
|
||||
|
||||
floattype = dtypes['float_or_double']
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class UserObjectMulti:
|
||||
"""Object that can occur multiple times in a model."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Constructor."""
|
||||
self.kwargs = kwargs
|
||||
self.order = None
|
||||
self.hash = '#example'
|
||||
# auto translate
|
||||
self.autotranslate = True
|
||||
|
||||
|
||||
@@ -63,7 +60,7 @@ class UserObjectMulti:
|
||||
v = ' '.join([str(el) for el in v])
|
||||
s += str(v) + ' '
|
||||
|
||||
return '{}: {}'.format(self.hash, s[:-1])
|
||||
return f'{self.hash}: {s[:-1]}'
|
||||
|
||||
def create(self, grid, uip):
|
||||
"""Create the object and add it to the grid."""
|
||||
@@ -96,16 +93,15 @@ class Waveform(UserObjectMulti):
|
||||
amp = self.kwargs['amp']
|
||||
freq = self.kwargs['freq']
|
||||
ID = self.kwargs['id']
|
||||
|
||||
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:
|
||||
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:
|
||||
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):
|
||||
raise CmdInputError(self.__str__() + ' with ID {} already exists'.format(ID))
|
||||
raise CmdInputError(f"'{self.params_str()}' with ID {ID} already exists")
|
||||
|
||||
w = WaveformUser()
|
||||
w.ID = ID
|
||||
@@ -113,8 +109,7 @@ class Waveform(UserObjectMulti):
|
||||
w.amp = amp
|
||||
w.freq = freq
|
||||
|
||||
if config.is_messages():
|
||||
print('Waveform {} of type {} with maximum amplitude scaling {:g}, frequency {:g}Hz created.'.format(w.ID, w.type, w.amp, w.freq))
|
||||
log.info(f'Waveform {w.ID} of type {w.type} with maximum amplitude scaling {w.amp:g}, frequency {w.freq:g}Hz created.')
|
||||
|
||||
grid.waveforms.append(w)
|
||||
|
||||
@@ -142,34 +137,32 @@ class VoltageSource(UserObjectMulti):
|
||||
self.hash = '#voltage_source'
|
||||
|
||||
def create(self, grid, uip):
|
||||
"""Create voltage source and add it to the grid."""
|
||||
try:
|
||||
p1 = self.kwargs['p1']
|
||||
polarisation = self.kwargs['polarisation'].lower()
|
||||
resistance = self.kwargs['resistance']
|
||||
waveform_id = self.kwargs['waveform_id']
|
||||
|
||||
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
|
||||
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'):
|
||||
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'):
|
||||
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'):
|
||||
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__())
|
||||
|
||||
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
|
||||
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.polarisation = polarisation
|
||||
@@ -185,17 +178,17 @@ class VoltageSource(UserObjectMulti):
|
||||
stop = self.kwargs['stop']
|
||||
# Check source start & source remove time parameters
|
||||
if start < 0:
|
||||
raise CmdInputError("'{}' delay of the initiation of the source should not be less than zero".format(self.__str__()))
|
||||
raise CmdInputError(f"'{self.__str__()}' delay of the initiation of the source should not be less than zero")
|
||||
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:
|
||||
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
|
||||
if stop > grid.timewindow:
|
||||
v.stop = grid.timewindow
|
||||
else:
|
||||
v.stop = stop
|
||||
startstop = ' start time {:g} secs, finish time {:g} secs '.format(v.start, v.stop)
|
||||
startstop = f' start time {v.start:g} secs, finish time {v.stop:g} secs '
|
||||
except KeyError:
|
||||
v.start = 0
|
||||
v.stop = grid.timewindow
|
||||
@@ -203,8 +196,7 @@ class VoltageSource(UserObjectMulti):
|
||||
|
||||
v.calculate_waveform_values(grid)
|
||||
|
||||
if config.is_messages():
|
||||
print('Voltage source with polarity {} at {:g}m, {:g}m, {:g}m, resistance {:.1f} Ohms,'.format(v.polarisation, v.xcoord * grid.dx, v.ycoord * grid.dy, v.zcoord * grid.dz, v.resistance) + startstop + 'using waveform {} created.'.format(v.waveformID))
|
||||
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))
|
||||
|
||||
grid.voltagesources.append(v)
|
||||
|
||||
@@ -226,36 +218,33 @@ class HertzianDipole(UserObjectMulti):
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Constructor."""
|
||||
super().__init__(**kwargs)
|
||||
self.order = 2
|
||||
self.hash = '#hertzian_dipole'
|
||||
|
||||
def create(self, grid, uip):
|
||||
"""Create HertzianDipole and add it to the grid."""
|
||||
try:
|
||||
polarisation = self.kwargs['polarisation'].lower()
|
||||
p1 = self.kwargs['p1']
|
||||
waveform_id = self.kwargs['waveform_id']
|
||||
|
||||
except KeyError:
|
||||
raise CmdInputError("'{}' requires at least 3 parameters".format(self.params_str()))
|
||||
raise CmdInputError(f"'{self.__str__()}' requires at least 3 parameters")
|
||||
|
||||
# Check polarity & position parameters
|
||||
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'):
|
||||
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'):
|
||||
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'):
|
||||
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__())
|
||||
|
||||
# Check if there is a waveformID in the waveforms list
|
||||
if not any(x.ID == waveform_id for x in grid.waveforms):
|
||||
raise CmdInputError("'{}' there is no waveform with the identifier {}'.format(tmp[4]".format(self.__str__()))
|
||||
raise CmdInputError(f"'{self.__str__()}' there is no waveform with the identifier {tmp[4]}')
|
||||
|
||||
h = HertzianDipoleUser()
|
||||
h.polarisation = polarisation
|
||||
@@ -282,17 +271,17 @@ class HertzianDipole(UserObjectMulti):
|
||||
start = self.kwargs['start']
|
||||
stop = self.kwargs['stop']
|
||||
if start < 0:
|
||||
raise CmdInputError("'{}' delay of the initiation of the source should not be less than zero".format(self.__str__()))
|
||||
raise CmdInputError(f"'{self.__str__()}' delay of the initiation of the source should not be less than zero")
|
||||
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:
|
||||
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
|
||||
if stop > grid.timewindow:
|
||||
h.stop = grid.timewindow
|
||||
else:
|
||||
h.stop = stop
|
||||
startstop = ' start time {:g} secs, finish time {:g} secs '.format(h.start, h.stop)
|
||||
startstop = f' start time {h.start:g} secs, finish time {h.stop:g} secs '
|
||||
except KeyError:
|
||||
h.start = 0
|
||||
h.stop = grid.timewindow
|
||||
@@ -300,11 +289,10 @@ class HertzianDipole(UserObjectMulti):
|
||||
|
||||
h.calculate_waveform_values(grid)
|
||||
|
||||
if config.is_messages():
|
||||
if grid.mode == '2D':
|
||||
print('Hertzian dipole is a line source in 2D with polarity {} at {:g}m, {:g}m, {:g}m,'.format(h.polarisation, h.xcoord * grid.dx, h.ycoord * grid.dy, h.zcoord * grid.dz) + startstop + 'using waveform {} created.'.format(h.waveformID))
|
||||
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:
|
||||
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)
|
||||
|
||||
@@ -326,35 +314,33 @@ class MagneticDipole(UserObjectMulti):
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Constructor."""
|
||||
super().__init__(**kwargs)
|
||||
self.order = 3
|
||||
self.hash = '#magnetic_dipole'
|
||||
|
||||
def create(self, grid, uip):
|
||||
"""Create Magnetic Dipole and add it the grid."""
|
||||
try:
|
||||
polarisation = self.kwargs['polarisation'].lower()
|
||||
p1 = self.kwargs['p1']
|
||||
waveform_id = self.kwargs['waveform_id']
|
||||
except KeyError:
|
||||
raise CmdInputError("'{}' requires at least five parameters".format(self.__str__()))
|
||||
raise CmdInputError(f"'{self.__str__()}' requires at least five parameters")
|
||||
|
||||
# Check polarity & position parameters
|
||||
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'):
|
||||
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'):
|
||||
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'):
|
||||
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__())
|
||||
|
||||
# Check if there is a waveformID in the waveforms list
|
||||
if not any(x.ID == waveform_id for x in grid.waveforms):
|
||||
raise CmdInputError("'{}' there is no waveform with the identifier {}".format(self.__str__(), waveform_id))
|
||||
raise CmdInputError(f"'{self.__str__()}' there is no waveform with the identifier {waveform_id}")
|
||||
|
||||
m = MagneticDipoleUser()
|
||||
m.polarisation = polarisation
|
||||
@@ -372,17 +358,17 @@ class MagneticDipole(UserObjectMulti):
|
||||
start = self.kwargs['start']
|
||||
stop = self.kwargs['stop']
|
||||
if start < 0:
|
||||
raise CmdInputError("'{}' delay of the initiation of the source should not be less than zero".format(self.__str__()))
|
||||
raise CmdInputError(f"'{self.__str__()}' delay of the initiation of the source should not be less than zero")
|
||||
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:
|
||||
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
|
||||
if stop > grid.timewindow:
|
||||
m.stop = grid.timewindow
|
||||
else:
|
||||
m.stop = stop
|
||||
startstop = ' start time {:g} secs, finish time {:g} secs '.format(m.start, m.stop)
|
||||
startstop = f' start time {m.start:g} secs, finish time {m.stop:g} secs '
|
||||
except KeyError:
|
||||
m.start = 0
|
||||
m.stop = grid.timewindow
|
||||
@@ -390,8 +376,7 @@ class MagneticDipole(UserObjectMulti):
|
||||
|
||||
m.calculate_waveform_values(grid)
|
||||
|
||||
if config.is_messages():
|
||||
print('Magnetic dipole with polarity {} at {:g}m, {:g}m, {:g}m,'.format(m.polarisation, m.xcoord * grid.dx, m.ycoord * grid.dy, m.zcoord * grid.dz) + startstop + 'using waveform {} created.'.format(m.waveformID))
|
||||
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.')
|
||||
|
||||
grid.magneticdipoles.append(m)
|
||||
|
||||
@@ -415,44 +400,41 @@ class TransmissionLine(UserObjectMulti):
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Constructor."""
|
||||
super().__init__(**kwargs)
|
||||
self.order = 4
|
||||
self.hash = '#transmission_line'
|
||||
|
||||
def create(self, grid, uip):
|
||||
|
||||
try:
|
||||
polarisation = self.kwargs['polarisation'].lower()
|
||||
p1 = self.kwargs['p1']
|
||||
waveform_id = self.kwargs['waveform_id']
|
||||
resistance = self.kwargs['resistance']
|
||||
|
||||
except KeyError:
|
||||
raise CmdInputError("'{}' requires at least six parameters".format(self.params_str()))
|
||||
raise CmdInputError(f"'{self.params_str()}' requires at least six parameters")
|
||||
|
||||
# Warn about using a transmission line on GPU
|
||||
if grid.gpu is not None:
|
||||
raise CmdInputError("'{}' A #transmission_line cannot currently be used with GPU solving. Consider using a #voltage_source instead.".format(self.__str__()))
|
||||
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
|
||||
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'):
|
||||
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'):
|
||||
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'):
|
||||
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__())
|
||||
|
||||
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
|
||||
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.polarisation = polarisation
|
||||
@@ -468,17 +450,17 @@ class TransmissionLine(UserObjectMulti):
|
||||
start = self.kwargs['start']
|
||||
stop = self.kwargs['stop']
|
||||
if start < 0:
|
||||
raise CmdInputError("'{}' delay of the initiation of the source should not be less than zero".format(self.__str__()))
|
||||
raise CmdInputError(f"'{self.params_str()}' delay of the initiation of the source should not be less than zero")
|
||||
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:
|
||||
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
|
||||
if stop > grid.timewindow:
|
||||
t.stop = grid.timewindow
|
||||
else:
|
||||
t.stop = stop
|
||||
startstop = ' start time {:g} secs, finish time {:g} secs '.format(t.start, t.stop)
|
||||
startstop = f' start time {t.start:g} secs, finish time {t.stop:g} secs '
|
||||
except KeyError:
|
||||
t.start = 0
|
||||
t.stop = grid.timewindow
|
||||
@@ -487,8 +469,7 @@ class TransmissionLine(UserObjectMulti):
|
||||
t.calculate_waveform_values(grid)
|
||||
t.calculate_incident_V_I(grid)
|
||||
|
||||
if config.is_messages():
|
||||
print('Transmission line with polarity {} at {:g}m, {:g}m, {:g}m, resistance {:.1f} Ohms,'.format(t.polarisation, t.xcoord * grid.dx, t.ycoord * grid.dy, t.zcoord * grid.dz, t.resistance) + startstop + 'using waveform {} created.'.format(t.waveformID))
|
||||
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.')
|
||||
|
||||
grid.transmissionlines.append(t)
|
||||
|
||||
@@ -508,7 +489,6 @@ class Rx(UserObjectMulti):
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Constructor."""
|
||||
super().__init__(**kwargs)
|
||||
self.order = 5
|
||||
self.hash = '#rx'
|
||||
@@ -518,7 +498,7 @@ class Rx(UserObjectMulti):
|
||||
try:
|
||||
p1 = self.kwargs['p1']
|
||||
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__())
|
||||
|
||||
@@ -537,17 +517,16 @@ class Rx(UserObjectMulti):
|
||||
# Check and add field output names
|
||||
for field in outputs:
|
||||
if field in allowableoutputs:
|
||||
r.outputs[field] = np.zeros(grid.iterations, dtype=floattype)
|
||||
r.outputs[field] = np.zeros(grid.iterations, dtype=config.dtypes['float_or_double'])
|
||||
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
|
||||
except KeyError:
|
||||
r.ID = r.__class__.__name__ + '(' + str(r.xcoord) + ',' + str(r.ycoord) + ',' + str(r.zcoord) + ')'
|
||||
for key in RxUser.defaultoutputs:
|
||||
r.outputs[key] = np.zeros(grid.iterations, dtype=floattype)
|
||||
if config.is_messages():
|
||||
print('Receiver at {:g}m, {:g}m, {:g}m with output component(s) {} created.'.format(r.xcoord * grid.dx, r.ycoord * grid.dy, r.zcoord * grid.dz, ', '.join(r.outputs)))
|
||||
r.outputs[key] = np.zeros(grid.iterations, dtype=config.dtypes['float_or_double'])
|
||||
|
||||
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)
|
||||
|
||||
@@ -566,47 +545,43 @@ class RxArray(UserObjectMulti):
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Constructor."""
|
||||
super().__init__(**kwargs)
|
||||
self.order = 6
|
||||
self.hash = '#rx_array'
|
||||
|
||||
def create(self, grid, uip):
|
||||
|
||||
try:
|
||||
p1 = self.kwargs['p1']
|
||||
p2 = self.kwargs['p2']
|
||||
dl = self.kwargs['dl']
|
||||
|
||||
except KeyError:
|
||||
raise CmdInputError("'{}' requires exactly 9 parameters".format(self.__str__()))
|
||||
raise CmdInputError(f"'{self.__str__()}' requires exactly 9 parameters")
|
||||
|
||||
xs, ys, zs = uip.check_src_rx_point(p1, self.__str__(), 'lower')
|
||||
xf, yf, zf = uip.check_src_rx_point(p2, self.__str__(), 'upper')
|
||||
dx, dy, dz = uip.discretise_point(dl)
|
||||
|
||||
if xs > xf or ys > yf or zs > zf:
|
||||
raise CmdInputError("'{}' the lower coordinates should be less than the upper coordinates".format(self.__str__()))
|
||||
raise CmdInputError(f"'{self.__str__()}' the lower coordinates should be less than the upper coordinates")
|
||||
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 == 0:
|
||||
dx = 1
|
||||
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 == 0:
|
||||
dy = 1
|
||||
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 == 0:
|
||||
dz = 1
|
||||
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():
|
||||
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))
|
||||
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')
|
||||
|
||||
for x in range(xs, xf + 1, dx):
|
||||
for y in range(ys, yf + 1, dy):
|
||||
@@ -620,9 +595,8 @@ class RxArray(UserObjectMulti):
|
||||
r.zcoordorigin = z
|
||||
r.ID = r.__class__.__name__ + '(' + str(x) + ',' + str(y) + ',' + str(z) + ')'
|
||||
for key in RxUser.defaultoutputs:
|
||||
r.outputs[key] = np.zeros(grid.iterations, dtype=floattype)
|
||||
if config.is_messages():
|
||||
print(' Receiver at {:g}m, {:g}m, {:g}m with output component(s) {} created.'.format(r.xcoord * grid.dx, r.ycoord * grid.dy, r.zcoord * grid.dz, ', '.join(r.outputs)))
|
||||
r.outputs[key] = np.zeros(grid.iterations, dtype=config.dtypes['float_or_double'])
|
||||
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)
|
||||
|
||||
|
||||
@@ -644,22 +618,20 @@ class Snapshot(UserObjectMulti):
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Constructor."""
|
||||
super().__init__(**kwargs)
|
||||
self.order = 19
|
||||
self.hash = '#snapshot'
|
||||
|
||||
def create(self, grid, uip):
|
||||
|
||||
if isinstance(grid, SubGridBase):
|
||||
raise CmdInputError("'{}' Do not add Snapshots to Subgrids.".format(self.__str__()))
|
||||
raise CmdInputError(f"'{self.__str__()}' do not add Snapshots to Subgrids.")
|
||||
try:
|
||||
p1 = self.kwargs['p1']
|
||||
p2 = self.kwargs['p2']
|
||||
dl = self.kwargs['dl']
|
||||
filename = self.kwargs['filename']
|
||||
except KeyError:
|
||||
raise CmdInputError("'{}' requires exactly 11 parameters".format(self.__str__()))
|
||||
raise CmdInputError(f"'{self.__str__()}' requires exactly 11 parameters")
|
||||
|
||||
p1, p2 = uip.check_box_points(p1, p2, self.__str__())
|
||||
xs, ys, zs = p1
|
||||
@@ -674,18 +646,18 @@ class Snapshot(UserObjectMulti):
|
||||
try:
|
||||
time = self.kwargs['time']
|
||||
except KeyError:
|
||||
raise CmdInputError("'{}' requires exactly 5 parameters".format(self.__str__()))
|
||||
raise CmdInputError(f"'{self.__str__()}' requires exactly 5 parameters")
|
||||
if time > 0:
|
||||
iterations = round_value((time / grid.dt)) + 1
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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
|
||||
#if grid.subgrids:
|
||||
@@ -694,8 +666,7 @@ class Snapshot(UserObjectMulti):
|
||||
#else:
|
||||
s = SnapshotUser(xs, ys, zs, xf, yf, zf, dx, dy, dz, iterations, filename)
|
||||
|
||||
if config.is_messages():
|
||||
print('Snapshot from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m, discretisation {:g}m, {:g}m, {:g}m, at {:g} secs with filename {} created.'.format(xs * grid.dx, ys * grid.dy, zs * grid.dz, xf * grid.dx, yf * grid.dy, zf * grid.dz, dx * grid.dx, dy * grid.dy, dz * grid.dz, s.time * grid.dt, s.basefilename))
|
||||
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.')
|
||||
|
||||
grid.snapshots.append(s)
|
||||
|
||||
@@ -714,7 +685,6 @@ class Material(UserObjectMulti):
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Constructor."""
|
||||
super().__init__(**kwargs)
|
||||
self.order = 8
|
||||
self.hash = '#material'
|
||||
@@ -727,22 +697,22 @@ class Material(UserObjectMulti):
|
||||
sm = self.kwargs['sm']
|
||||
material_id = self.kwargs['id']
|
||||
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:
|
||||
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':
|
||||
se = float(se)
|
||||
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:
|
||||
se = float('inf')
|
||||
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:
|
||||
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):
|
||||
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)
|
||||
m = MaterialUser(len(grid.materials), material_id)
|
||||
@@ -755,8 +725,7 @@ class Material(UserObjectMulti):
|
||||
if m.se == float('inf'):
|
||||
m.averagable = False
|
||||
|
||||
if config.is_messages():
|
||||
tqdm.write('Material {} with eps_r={:g}, sigma={:g} S/m; mu_r={:g}, sigma*={:g} Ohm/m created.'.format(m.ID, m.er, m.se, m.mr, m.sm))
|
||||
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.')
|
||||
|
||||
# Append the new material object to the materials list
|
||||
grid.materials.append(m)
|
||||
@@ -776,31 +745,28 @@ class AddDebyeDispersion(UserObjectMulti):
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Constructor."""
|
||||
super().__init__(**kwargs)
|
||||
self.order = 9
|
||||
self.hash = '#add_dispersion_debye'
|
||||
|
||||
def create(self, grid, uip):
|
||||
|
||||
try:
|
||||
poles = self.kwargs['n_poles']
|
||||
er_delta = self.kwargs['er_delta']
|
||||
tau = self.kwargs['tau']
|
||||
material_ids = self.kwargs['material_ids']
|
||||
|
||||
except KeyError:
|
||||
raise CmdInputError(self.__str__() + ' requires at least four parameters')
|
||||
raise CmdInputError(f"'{self.params_str()}' requires at least four parameters")
|
||||
|
||||
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
|
||||
materials = [y for x in material_ids for y in grid.materials if y.ID == x]
|
||||
|
||||
if len(materials) != len(material_ids):
|
||||
notfound = [x for x in material_ids if x not in materials]
|
||||
raise CmdInputError(self.__str__() + ' material(s) {} do not exist'.format(notfound))
|
||||
raise CmdInputError(f"'{self.params_str()}' material(s) {notfound} do not exist")
|
||||
|
||||
for material in materials:
|
||||
material.type = 'debye'
|
||||
@@ -812,12 +778,11 @@ class AddDebyeDispersion(UserObjectMulti):
|
||||
material.deltaer.append(er_delta[i])
|
||||
material.tau.append(tau[i])
|
||||
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:
|
||||
MaterialUser.maxpoles = material.poles
|
||||
|
||||
if config.is_messages():
|
||||
tqdm.write('Debye disperion added to {} with delta_eps_r={}, and tau={} secs created.'.format(material.ID, ', '.join('%4.2f' % deltaer for deltaer in material.deltaer), ', '.join('%4.3e' % tau for tau in material.tau)))
|
||||
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.")
|
||||
|
||||
|
||||
class AddLorentzDispersion(UserObjectMulti):
|
||||
@@ -836,13 +801,11 @@ class AddLorentzDispersion(UserObjectMulti):
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Constructor."""
|
||||
super().__init__(**kwargs)
|
||||
self.order = 10
|
||||
self.hash = '#add_dispersion_lorentz'
|
||||
|
||||
def create(self, grid, uip):
|
||||
|
||||
try:
|
||||
poles = self.kwargs['n_poles']
|
||||
er_delta = self.kwargs['er_delta']
|
||||
@@ -850,17 +813,17 @@ class AddLorentzDispersion(UserObjectMulti):
|
||||
alpha = self.kwargs['delta']
|
||||
material_ids = self.kwargs['material_ids']
|
||||
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:
|
||||
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
|
||||
materials = [y for x in material_ids for y in grid.materials if y.ID == x]
|
||||
|
||||
if len(materials) != len(material_ids):
|
||||
notfound = [x for x in material_ids if x not in materials]
|
||||
raise CmdInputError(self.__str__() + ' material(s) {} do not exist'.format(notfound))
|
||||
raise CmdInputError(f"'{self.params_str()}' material(s) {notfound} do not exist")
|
||||
|
||||
for material in materials:
|
||||
material.type = 'lorentz'
|
||||
@@ -872,12 +835,11 @@ class AddLorentzDispersion(UserObjectMulti):
|
||||
material.tau.append(tau[i])
|
||||
material.alpha.append(alpha[i])
|
||||
else:
|
||||
raise CmdInputError(self.__str__() + ' requires positive values for the permittivity difference and frequencies, and associated times that are greater than the time step for the model.')
|
||||
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:
|
||||
MaterialUser.maxpoles = material.poles
|
||||
|
||||
if config.is_messages():
|
||||
tqdm.write('Lorentz disperion added to {} with delta_eps_r={}, omega={} secs, and gamma={} created.'.format(material.ID, ', '.join('%4.2f' % deltaer for deltaer in material.deltaer), ', '.join('%4.3e' % tau for tau in material.tau), ', '.join('%4.3e' % alpha for alpha in material.alpha)))
|
||||
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.")
|
||||
|
||||
|
||||
class AddDrudeDispersion(UserObjectMulti):
|
||||
@@ -894,29 +856,28 @@ class AddDrudeDispersion(UserObjectMulti):
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Constructor."""
|
||||
super().__init__(**kwargs)
|
||||
self.order = 11
|
||||
self.hash = '#add_dispersion_Drude'
|
||||
|
||||
def create(self, grid, uip):
|
||||
|
||||
try:
|
||||
poles = self.kwargs['n_poles']
|
||||
tau = self.kwargs['tau']
|
||||
alpha = self.kwargs['alpha']
|
||||
material_ids = self.kwargs['material_ids']
|
||||
except KeyError:
|
||||
raise CmdInputError(self.__str__() + ' requires at least four parameters')
|
||||
raise CmdInputError(f"'{self.params_str()}' requires at least four parameters")
|
||||
|
||||
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
|
||||
materials = [y for x in material_ids for y in grid.materials if y.ID == x]
|
||||
|
||||
if len(materials) != len(material_ids):
|
||||
notfound = [x for x in material_ids if x not in materials]
|
||||
raise CmdInputError(self.__str__() + ' material(s) {} do not exist'.format(notfound))
|
||||
raise CmdInputError(f"'{self.params_str()}' material(s) {notfound} do not exist")
|
||||
|
||||
for material in materials:
|
||||
material.type = 'drude'
|
||||
@@ -927,12 +888,11 @@ class AddDrudeDispersion(UserObjectMulti):
|
||||
material.tau.append(tau[i])
|
||||
material.alpha.append(alpha[i])
|
||||
else:
|
||||
raise CmdInputError(self.__str__() + ' requires positive values for the frequencies, and associated times that are greater than the time step for the model.')
|
||||
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:
|
||||
MaterialUser.maxpoles = material.poles
|
||||
|
||||
if config.is_messages():
|
||||
tqdm.write('Drude disperion added to {} with omega={} secs, and gamma={} secs created.'.format(material.ID, ', '.join('%4.3e' % tau for tau in material.tau), ', '.join('%4.3e' % alpha for alpha in material.alpha)))
|
||||
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.")
|
||||
|
||||
|
||||
class SoilPeplinski(UserObjectMulti):
|
||||
@@ -953,13 +913,11 @@ class SoilPeplinski(UserObjectMulti):
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Constructor."""
|
||||
super().__init__(**kwargs)
|
||||
self.order = 12
|
||||
self.hash = '#soil_peplinski'
|
||||
|
||||
def create(self, grid, uip):
|
||||
|
||||
try:
|
||||
sand_fraction = self.kwargs['sand_fraction']
|
||||
clay_fraction = self.kwargs['clay_fraction']
|
||||
@@ -968,30 +926,28 @@ class SoilPeplinski(UserObjectMulti):
|
||||
water_fraction_lower = self.kwargs['water_fraction_lower']
|
||||
water_fraction_upper = self.kwargs['water_fraction_upper']
|
||||
ID = self.kwargs['id']
|
||||
|
||||
except KeyError:
|
||||
raise CmdInputError(self.__str__() + ' requires at exactly seven parameters')
|
||||
raise CmdInputError(f"'{self.params_str()}' requires at exactly seven parameters")
|
||||
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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):
|
||||
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)
|
||||
s = PeplinskiSoilUser(ID, sand_fraction, clay_fraction, bulk_density, sand_density, (water_fraction_lower, water_fraction_upper))
|
||||
|
||||
if config.is_messages():
|
||||
print('Mixing model (Peplinski) used to create {} with sand fraction {:g}, clay fraction {:g}, bulk density {:g}g/cm3, sand particle density {:g}g/cm3, and water volumetric fraction {:g} to {:g} created.'.format(s.ID, s.S, s.C, s.rb, s.rs, s.mu[0], s.mu[1]))
|
||||
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.')
|
||||
|
||||
# Append the new material object to the materials list
|
||||
grid.mixingmodels.append(s)
|
||||
@@ -1014,7 +970,6 @@ class GeometryView(UserObjectMulti):
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Constructor."""
|
||||
super().__init__(**kwargs)
|
||||
self.order = 18
|
||||
self.hash = '#geometry_view'
|
||||
@@ -1026,9 +981,9 @@ class GeometryView(UserObjectMulti):
|
||||
self.kwargs['multi_grid']
|
||||
# there is no voxel output for multi grid output
|
||||
if isinstance(grid, SubGridBase):
|
||||
raise CmdInputError("'{}' Do not add multi_grid output to subgrid user object. Please add to Scene".format(self.__str__()))
|
||||
raise CmdInputError(f"'{self.params_str()}' do not add multi_grid output to subgrid user object. Please add to Scene")
|
||||
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
|
||||
from .geometry_outputs import GeometryViewFineMultiGrid as GeometryViewUser
|
||||
self.multi_grid = True
|
||||
@@ -1046,7 +1001,7 @@ class GeometryView(UserObjectMulti):
|
||||
output_type = self.kwargs['output_type'].lower()
|
||||
filename = self.kwargs['filename']
|
||||
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)
|
||||
|
||||
@@ -1057,15 +1012,15 @@ class GeometryView(UserObjectMulti):
|
||||
dx, dy, dz = uip.discretise_point(dl)
|
||||
|
||||
if dx < 0 or dy < 0 or dz < 0:
|
||||
raise CmdInputError("'{}' the step size should not be less than zero".format(self.__str__()))
|
||||
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:
|
||||
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:
|
||||
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':
|
||||
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):
|
||||
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
|
||||
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)
|
||||
|
||||
if config.is_messages():
|
||||
print('Geometry view from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m, discretisation {:g}m, {:g}m, {:g}m, multi_grid {}, grid={}, with filename base {} created.'.format(xs * grid.dx, ys * grid.dy, zs * grid.dz, xf * grid.dx, yf * grid.dy, zf * grid.dz, dx * grid.dx, dy * grid.dy, dz * grid.dz, self.multi_grid, grid.name, g.basefilename))
|
||||
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.')
|
||||
|
||||
# Append the new GeometryView object to the geometry views list
|
||||
grid.geometryviews.append(g)
|
||||
@@ -1095,7 +1049,6 @@ class GeometryObjectsWrite(UserObjectMulti):
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Constructor."""
|
||||
super().__init__(**kwargs)
|
||||
self.order = 14
|
||||
self.hash = '#geometry_objects_write'
|
||||
@@ -1106,7 +1059,7 @@ class GeometryObjectsWrite(UserObjectMulti):
|
||||
p2 = self.kwargs['p2']
|
||||
filename = self.kwargs['filename']
|
||||
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__())
|
||||
x0, y0, z0 = p1
|
||||
@@ -1114,8 +1067,7 @@ class GeometryObjectsWrite(UserObjectMulti):
|
||||
|
||||
g = GeometryObjectsUser(x0, y0, z0, x1, y1, z1, filename)
|
||||
|
||||
if config.is_messages():
|
||||
print('Geometry objects in the volume from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m, will be written to {}, with materials written to {}'.format(p1[0] * grid.dx, p1[1] * grid.dy, p1[2] * grid.dz, p2[0] * grid.dx, p2[1] * grid.dy, p2[2] * grid.dz, g.filename, g.materialsfilename))
|
||||
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}')
|
||||
|
||||
# Append the new GeometryView object to the geometry objects to write list
|
||||
grid.geometryobjectswrite.append(g)
|
||||
@@ -1153,16 +1105,14 @@ class PMLCFS(UserObjectMulti):
|
||||
count = 0
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Constructor."""
|
||||
super().__init__(**kwargs)
|
||||
self.order = 15
|
||||
self.hash = '#pml_cfs'
|
||||
PMLCFS.count += 1
|
||||
if PMLCFS.count == 2:
|
||||
raise CmdInputError(self.__str__() + ' can only be used up to two times, for up to a 2nd order PML')
|
||||
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):
|
||||
|
||||
try:
|
||||
alphascalingprofile = self.kwargs['alphascalingprofile']
|
||||
alphascalingdirection = self.kwargs['alphascalingdirection']
|
||||
@@ -1176,18 +1126,17 @@ class PMLCFS(UserObjectMulti):
|
||||
sigmascalingdirection = self.kwargs['sigmascalingdirection']
|
||||
sigmamin = self.kwargs['sigmamin']
|
||||
sigmamax = self.kwargs['sigmamax']
|
||||
|
||||
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():
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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.ID = 'alpha'
|
||||
@@ -1215,8 +1164,7 @@ class PMLCFS(UserObjectMulti):
|
||||
cfs.kappa = cfskappa
|
||||
cfs.sigma = cfssigma
|
||||
|
||||
if config.is_messages():
|
||||
print('PML CFS parameters: alpha (scaling: {}, scaling direction: {}, min: {:g}, max: {:g}), kappa (scaling: {}, scaling direction: {}, min: {:g}, max: {:g}), sigma (scaling: {}, scaling direction: {}, min: {:g}, max: {}) created.'.format(cfsalpha.scalingprofile, cfsalpha.scalingdirection, cfsalpha.min, cfsalpha.max, cfskappa.scalingprofile, cfskappa.scalingdirection, cfskappa.min, cfskappa.max, cfssigma.scalingprofile, cfssigma.scalingdirection, cfssigma.min, cfssigma.max))
|
||||
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.')
|
||||
|
||||
grid.cfs.append(cfs)
|
||||
|
||||
@@ -1234,7 +1182,7 @@ class Subgrid(UserObjectMulti):
|
||||
elif isinstance(node, UserObjectGeometry):
|
||||
self.children_geometry.append(node)
|
||||
else:
|
||||
raise Exception('This Object is Unknown to gprMax')
|
||||
raise GeneralError('This object is unknown to gprMax')
|
||||
|
||||
|
||||
class SubgridHSG(UserObjectMulti):
|
||||
|
@@ -18,6 +18,7 @@
|
||||
|
||||
import decimal as d
|
||||
import inspect
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
@@ -26,17 +27,16 @@ from colorama import Fore
|
||||
from colorama import Style
|
||||
init()
|
||||
import numpy as np
|
||||
from scipy.constants import c
|
||||
from scipy import interpolate
|
||||
|
||||
import gprMax.config as config
|
||||
from .config import c
|
||||
from .config import dtypes
|
||||
from .config import hostinfo
|
||||
from .exceptions import CmdInputError
|
||||
from .waveforms import Waveform
|
||||
from .utilities import round_value
|
||||
|
||||
floattype = dtypes['float_or_double']
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Properties:
|
||||
@@ -82,7 +82,7 @@ class Domain(UserObjectSingle):
|
||||
self.kwargs['p1'][1],
|
||||
self.kwargs['p1'][2])
|
||||
except KeyError:
|
||||
print('error message')
|
||||
log.warning('error message')
|
||||
|
||||
return s
|
||||
|
||||
@@ -90,40 +90,35 @@ class Domain(UserObjectSingle):
|
||||
try:
|
||||
G.nx, G.ny, G.nz = uip.discretise_point(self.kwargs['p1'])
|
||||
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:
|
||||
raise CmdInputError(self.__str__ + ' requires at least one cell in every dimension')
|
||||
if config.is_messages():
|
||||
print('Domain size: {:g} x {:g} x {:g}m ({:d} x {:d} x {:d} = {:g} cells)'.format(self.kwargs['p1'][0], self.kwargs['p1'][1], self.kwargs['p1'][2], G.nx, G.ny, G.nz, (G.nx * G.ny * G.nz)))
|
||||
raise CmdInputError(f"'{self.params_str()}' requires at least one cell in every dimension")
|
||||
|
||||
# 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:
|
||||
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.pmlthickness['x0'] = 0
|
||||
G.pmlthickness['xmax'] = 0
|
||||
elif G.ny == 1:
|
||||
G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dz) * (1 / G.dz)))
|
||||
G.calculate_dt()
|
||||
G.mode = '2D TMy'
|
||||
G.pmlthickness['y0'] = 0
|
||||
G.pmlthickness['ymax'] = 0
|
||||
elif G.nz == 1:
|
||||
G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dy) * (1 / G.dy)))
|
||||
G.calculate_dt()
|
||||
G.mode = '2D TMz'
|
||||
G.pmlthickness['z0'] = 0
|
||||
G.pmlthickness['zmax'] = 0
|
||||
else:
|
||||
G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dy) * (1 / G.dy) + (1 / G.dz) * (1 / G.dz)))
|
||||
G.calculate_dt()
|
||||
G.mode = '3D'
|
||||
|
||||
# Round down time step to nearest float with precision one less than hardware maximum.
|
||||
# Avoids inadvertently exceeding the CFL due to binary representation of floating point number.
|
||||
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))
|
||||
log.info(f'Mode: {G.mode}')
|
||||
log.info(f'Time step (at CFL limit): {G.dt:g} secs')
|
||||
|
||||
# Number of threads (OpenMP) to use
|
||||
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)
|
||||
|
||||
# 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'
|
||||
del os.environ['OMP_PLACES']
|
||||
del os.environ['OMP_PROC_BIND']
|
||||
@@ -143,13 +138,12 @@ class Domain(UserObjectSingle):
|
||||
G.nthreads = int(os.environ.get('OMP_NUM_THREADS'))
|
||||
else:
|
||||
# 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)
|
||||
|
||||
if config.is_messages():
|
||||
print('Number of CPU (OpenMP) threads: {}'.format(G.nthreads))
|
||||
if G.nthreads > hostinfo['physicalcores']:
|
||||
print(Fore.RED + 'WARNING: You have specified more threads ({}) than available physical CPU cores ({}). This may lead to degraded performance.'.format(G.nthreads, hostinfo['physicalcores']) + Style.RESET_ALL)
|
||||
log.info(f'Number of CPU (OpenMP) threads: {G.nthreads}')
|
||||
if G.nthreads > config.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)
|
||||
|
||||
|
||||
class Discretisation(UserObjectSingle):
|
||||
@@ -169,7 +163,7 @@ class Discretisation(UserObjectSingle):
|
||||
self.kwargs['p1'][1],
|
||||
self.kwargs['p1'][2])
|
||||
except KeyError:
|
||||
print('error message')
|
||||
log.info('error message')
|
||||
|
||||
return s
|
||||
|
||||
@@ -188,7 +182,7 @@ class Discretisation(UserObjectSingle):
|
||||
raise CmdInputError('Discretisation requires the z-direction spatial step to be greater than zero')
|
||||
|
||||
if config.is_messages():
|
||||
print('Spatial discretisation: {:g} x {:g} x {:g}m'.format(*G.dl))
|
||||
log.info('Spatial discretisation: {:g} x {:g} x {:g}m'.format(*G.dl))
|
||||
|
||||
|
||||
class TimeWindow(UserObjectSingle):
|
||||
@@ -211,7 +205,7 @@ class TimeWindow(UserObjectSingle):
|
||||
try:
|
||||
s = '#time_window: {}'.format(self.kwargs['iterations'])
|
||||
except KeyError:
|
||||
print('time window error')
|
||||
log.info('time window error')
|
||||
|
||||
return s
|
||||
|
||||
@@ -242,7 +236,7 @@ class TimeWindow(UserObjectSingle):
|
||||
raise CmdInputError('TimeWindow: Specify a time or number of iterations')
|
||||
|
||||
if config.is_messages():
|
||||
print('Time window: {:g} secs ({} iterations)'.format(G.timewindow, G.iterations))
|
||||
log.info('Time window: {:g} secs ({} iterations)'.format(G.timewindow, G.iterations))
|
||||
|
||||
|
||||
class Messages(UserObjectSingle):
|
||||
@@ -260,7 +254,7 @@ class Messages(UserObjectSingle):
|
||||
try:
|
||||
s = '#messages: {}'.format(self.kwargs['yn'])
|
||||
except KeyError:
|
||||
print('messages problem')
|
||||
log.info('messages problem')
|
||||
|
||||
def create(self, G, uip):
|
||||
try:
|
||||
@@ -296,7 +290,7 @@ class Title(UserObjectSingle):
|
||||
pass
|
||||
|
||||
if config.is_messages():
|
||||
print('Model title: {}'.format(G.title))
|
||||
log.info('Model title: {}'.format(G.title))
|
||||
|
||||
|
||||
class NumThreads(UserObjectSingle):
|
||||
@@ -332,14 +326,14 @@ class NumThreads(UserObjectSingle):
|
||||
os.environ['OMP_NUM_THREADS'] = str(G.nthreads)
|
||||
|
||||
if config.is_messages():
|
||||
print('Number of CPU (OpenMP) threads: {}'.format(G.nthreads))
|
||||
if G.nthreads > hostinfo['physicalcores']:
|
||||
print(Fore.RED + 'WARNING: You have specified more threads ({}) than available physical CPU cores ({}). This may lead to degraded performance.'.format(G.nthreads, hostinfo['physicalcores']) + Style.RESET_ALL)
|
||||
log.info('Number of CPU (OpenMP) threads: {}'.format(G.nthreads))
|
||||
if G.nthreads > config.hostinfo['physicalcores']:
|
||||
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
|
||||
if config.is_messages():
|
||||
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):
|
||||
@@ -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')
|
||||
G.dt = G.dt * f
|
||||
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):
|
||||
@@ -442,7 +436,7 @@ class SrcSteps(UserObjectSingle):
|
||||
raise CmdInputError('#src_steps: requires exactly three parameters')
|
||||
|
||||
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):
|
||||
@@ -463,7 +457,7 @@ class RxSteps(UserObjectSingle):
|
||||
raise CmdInputError('#rx_steps: requires exactly three parameters')
|
||||
|
||||
if config.is_messages():
|
||||
print('All receivers will step {:g}m, {:g}m, {:g}m for each model run.'.format(G.rxsteps[0] * G.dx, G.rxsteps[1] * G.dy, G.rxsteps[2] * G.dz))
|
||||
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):
|
||||
@@ -498,7 +492,7 @@ class ExcitationFile(UserObjectSingle):
|
||||
excitationfile = os.path.abspath(os.path.join(G.inputdirectory, excitationfile))
|
||||
|
||||
if config.is_messages():
|
||||
print('\nExcitation file: {}'.format(excitationfile))
|
||||
log.info('\nExcitation file: {}'.format(excitationfile))
|
||||
|
||||
# Get waveform names
|
||||
with open(excitationfile, 'r') as f:
|
||||
@@ -538,7 +532,7 @@ class ExcitationFile(UserObjectSingle):
|
||||
w.userfunc = interpolate.interp1d(waveformtime, singlewaveformvalues, **kwargs)
|
||||
|
||||
if config.is_messages():
|
||||
print('User waveform {} created using {} and, if required, interpolation parameters (kind: {}, fill value: {}).'.format(w.ID, timestr, kwargs['kind'], kwargs['fill_value']))
|
||||
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)
|
||||
|
||||
|
@@ -27,9 +27,3 @@ ctypedef fused float_or_double:
|
||||
ctypedef fused float_or_double_complex:
|
||||
float complex
|
||||
double complex
|
||||
|
||||
ctypedef fused real_or_complex:
|
||||
float
|
||||
double
|
||||
float complex
|
||||
double complex
|
||||
|
123
gprMax/config.py
123
gprMax/config.py
@@ -16,9 +16,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with gprMax. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
from colorama import init
|
||||
from colorama import Fore
|
||||
@@ -27,31 +25,37 @@ init()
|
||||
import cython
|
||||
import numpy as np
|
||||
from scipy.constants import c
|
||||
from scipy.constants import mu_0 as m0
|
||||
from scipy.constants import epsilon_0 as e0
|
||||
from scipy.constants import mu_0 as m0
|
||||
|
||||
from .utilities import get_host_info
|
||||
from .utilities import get_terminal_width
|
||||
|
||||
|
||||
# Impedance of free space (Ohms)
|
||||
z0 = np.sqrt(m0 / e0)
|
||||
|
||||
# General setting for the simulation
|
||||
# General settings for the simulation
|
||||
# inputfilepath: path to inputfile location
|
||||
# outputfilepath: path to outputfile location
|
||||
# messages: whether to print all messages as output to stdout or not
|
||||
# progressbars: whether to show progress bars on stdoout or not
|
||||
# mode: 2D TMx, 2D TMy, 2D TMz, or 3D
|
||||
# cpu, cuda, opencl: solver type
|
||||
# precision: data type for electromagnetic field output (single/double)
|
||||
# autotranslate: auto translate objects with main grid coordinates
|
||||
# to their equivalent local grid coordinate within the subgrid. If this option is off
|
||||
# users must specify sub-grid object point within the global subgrid space.
|
||||
general = {'inputfilepath': 'gprMax', 'outputfilepath': 'gprMax', 'messages': True,
|
||||
'progressbars': True, 'mode': '3D', 'cpu': True, 'cuda': False, 'opencl': False, 'autotranslate': False}
|
||||
# z0: impedance of free space (Ohms)
|
||||
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():
|
||||
"""Function to return messages."""
|
||||
"""Return messages."""
|
||||
return general['messages']
|
||||
|
||||
# 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
|
||||
cuda = {'gpus': None, 'snapsgpu2cpu': False}
|
||||
|
||||
# Numerical dispersion analysis parameters
|
||||
# highestfreqthres: threshold (dB) down from maximum power (0dB) of main frequency used
|
||||
# to calculate highest frequency for numerical dispersion analysis
|
||||
# maxnumericaldisp: maximum allowable percentage physical phase-velocity phase error
|
||||
# mingridsampling: minimum grid sampling of smallest wavelength for physical wave propagation
|
||||
numdispersion = {'highestfreqthres': 40, 'maxnumericaldisp': 2, 'mingridsampling': 3}
|
||||
|
||||
# Materials
|
||||
# maxpoles: Maximum number of dispersive material poles in a model
|
||||
materials = {'maxpoles': 0, 'dispersivedtype': None, 'dispersiveCdtype': None}
|
||||
|
||||
# Data types
|
||||
# Data type (precision) for electromagnetic field output
|
||||
# Solid and ID arrays use 32-bit integers (0 to 4294967295)
|
||||
# Rigid arrays use 8-bit integers (the smallest available type to store true/false)
|
||||
# Fractal and dispersive coefficient arrays use complex numbers (complex)
|
||||
# which are represented as two floats
|
||||
# Main field arrays use floats (float_or_double) and complex numbers (complex)
|
||||
# Precision of float_or_double and complex: single or double for numpy and C (CUDA) arrays
|
||||
precision = 'double'
|
||||
# Fractal arrays use complex numbers
|
||||
# Dispersive coefficient arrays use either float or complex numbers
|
||||
# Main field arrays use floats
|
||||
|
||||
if precision == 'single':
|
||||
dtypes = {'float_or_double': np.float32, 'complex': np.complex64,
|
||||
'cython_float_or_double': cython.float, 'cython_complex': cython.floatcomplex,
|
||||
'C_float_or_double': 'float', 'C_complex': 'pycuda::complex<float>'}
|
||||
elif precision == 'double':
|
||||
dtypes = {'float_or_double': np.float64, 'complex': np.complex128,
|
||||
'cython_float_or_double': cython.double, 'cython_complex': cython.doublecomplex,
|
||||
'C_float_or_double': 'double', 'C_complex': 'pycuda::complex<double>'}
|
||||
if general['precision'] == 'single':
|
||||
dtypes = {'float_or_double': np.float32,
|
||||
'complex': np.complex64,
|
||||
'cython_float_or_double': cython.float,
|
||||
'cython_complex': cython.floatcomplex,
|
||||
'C_float_or_double': 'float',
|
||||
'C_complex': 'pycuda::complex<float>'}
|
||||
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.
|
||||
N.B. Multiple models can exist within a simulation
|
||||
"""
|
||||
@@ -107,8 +104,8 @@ class ModelConfig():
|
||||
"""
|
||||
|
||||
self.sim_config = sim_config
|
||||
self.reuse_geometry = False
|
||||
self.i = i # Indexed from 0
|
||||
self.reuse_geometry = False
|
||||
|
||||
if not sim_config.single_model:
|
||||
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.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):
|
||||
if self.sim_config.scenes:
|
||||
return self.sim_config.scenes[self.i]
|
||||
else: return None
|
||||
|
||||
def get_usernamespace(self):
|
||||
return {'c': c,
|
||||
'e0': e0,
|
||||
'm0': m0,
|
||||
'z0': z0,
|
||||
return {'c': c, # Speed of light in free space (m/s)
|
||||
'e0': e0, # Permittivity of free space (F/m)
|
||||
'm0': m0, # Permeability of free space (H/m)
|
||||
'z0': general['z0'], # Impedance of free space (Ohms)
|
||||
'number_model_runs': self.sim_config.model_end + 1,
|
||||
'current_model_run': self.i + 1,
|
||||
'inputfile': self.sim_config.input_file_path.resolve()}
|
||||
@@ -155,16 +169,10 @@ class SimulationConfig:
|
||||
"""
|
||||
|
||||
self.args = args
|
||||
self.n_models = args.n
|
||||
self.inputfile = args.inputfile
|
||||
self.gpu = args.gpu
|
||||
self.mpi = args.mpi
|
||||
self.mpi_no_spawn = args.mpi_no_spawn
|
||||
self.general = {}
|
||||
self.general['messages'] = general['messages']
|
||||
self.geometry_fixed = args.geometry_fixed
|
||||
self.geometry_only = args.geometry_only
|
||||
self.write_processed = args.write_processed
|
||||
self.general = general
|
||||
self.hostinfo = hostinfo
|
||||
self.cuda = cuda
|
||||
self.dtypes = dtypes
|
||||
|
||||
# Subgrid parameter may not exist if user enters via CLI
|
||||
try:
|
||||
@@ -191,7 +199,7 @@ class SimulationConfig:
|
||||
self.single_model = False
|
||||
|
||||
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:
|
||||
# Job array feeds args.n number of single tasks
|
||||
modelstart = self.args.task - 1
|
||||
@@ -206,23 +214,20 @@ class SimulationConfig:
|
||||
self.model_start = modelstart
|
||||
self.model_end = modelend
|
||||
|
||||
def set_precision(self):
|
||||
pass
|
||||
|
||||
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:
|
||||
self.input_file_path = Path(self.args.outputfile)
|
||||
else:
|
||||
self.input_file_path = Path(self.args.inputfile)
|
||||
|
||||
def set_output_file_path(self):
|
||||
# Output file path can be provided by the user. If they havent provided one
|
||||
# use the inputfile file path instead
|
||||
"""Output file path can be provided by the user. If they havent provided one
|
||||
use the inputfile file path instead."""
|
||||
try:
|
||||
self.output_file_path = Path(self.args.outputfile)
|
||||
except AttributeError:
|
||||
self.output_file_path = Path(self.args.inputfile)
|
||||
self.output_file_path = Path(self.input_file_path)
|
||||
|
||||
|
||||
class SimulationConfigMPI(SimulationConfig):
|
||||
|
@@ -18,15 +18,17 @@
|
||||
|
||||
import datetime
|
||||
|
||||
from ._version import __version__, codename
|
||||
from .config import create_model_config
|
||||
from .model_build_run import ModelBuildRun
|
||||
from .solvers import create_solver
|
||||
from .solvers import create_G
|
||||
from .utilities import get_terminal_width
|
||||
from .utilities import logo
|
||||
from .utilities import timer
|
||||
|
||||
|
||||
class Context():
|
||||
class Context:
|
||||
"""Generic context for the model to run in. Sub-class with specific contexts
|
||||
e.g. an MPI context.
|
||||
"""
|
||||
@@ -43,18 +45,23 @@ class Context():
|
||||
self.tsimstart = 1
|
||||
|
||||
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._run()
|
||||
self.tsimend = timer()
|
||||
|
||||
def print_logo_copyright(self):
|
||||
"""Print gprMax logo, version, and copyright/licencing information."""
|
||||
logo(__version__ + ' (' + codename + ')')
|
||||
|
||||
def print_time_report(self):
|
||||
"""Function to print the total simulation time based on context."""
|
||||
s = self.make_time_report(sim_time)
|
||||
print(s)
|
||||
"""Print the total simulation time based on context."""
|
||||
s = self.make_time_report()
|
||||
log.info(s)
|
||||
|
||||
def make_time_report(self):
|
||||
"""Function to generate a string for the total simulation time."""
|
||||
"""Generate a string for the total simulation time."""
|
||||
pass
|
||||
|
||||
|
||||
@@ -63,6 +70,9 @@ class NoMPIContext(Context):
|
||||
is parallelised using either OpenMP (CPU) or CUDA (GPU).
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def _run(self):
|
||||
"""Specialise how the models are farmed out."""
|
||||
|
||||
@@ -83,7 +93,7 @@ class NoMPIContext(Context):
|
||||
solver = create_solver(G, self.sim_config)
|
||||
|
||||
if not self.sim_config.geometry_only:
|
||||
model.run_model(solver)
|
||||
model.solve(solver)
|
||||
|
||||
def make_time_report(self):
|
||||
"""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)
|
||||
s = '\n=== Simulation on {} completed in [HH:MM:SS]: {}'
|
||||
s = s.format(self.simconfig.hostinfo['hostname'], sim_time)
|
||||
return '{} {}\n'.format(s, '=' * (get_terminal_width() - 1 - len(s)))
|
||||
s = f'\n=== Simulation on {self.simconfig.hostinfo['hostname']} completed in [HH:MM:SS]: {sim_time}'
|
||||
return f'{s} {'=' * (get_terminal_width() - 1 - len(s))}\n'
|
||||
|
||||
|
||||
class MPIContext(Context):
|
||||
@@ -116,6 +125,9 @@ class MPIContext(Context):
|
||||
|
||||
class MPINoSpawnContext(Context):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def _run(self):
|
||||
pass
|
||||
|
||||
@@ -133,9 +145,9 @@ def create_context(sim_config):
|
||||
context (Context): Context for the model to run in.
|
||||
"""
|
||||
|
||||
if sim_config.mpi:
|
||||
if sim_config.args.mpi:
|
||||
context = MPIContext(sim_config)
|
||||
elif sim_config.mpi_no_spawn:
|
||||
elif sim_config.args.mpi_no_spawn:
|
||||
context = MPINoSpawnContext(sim_config)
|
||||
else:
|
||||
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
|
||||
#
|
||||
# This file is part of gprMax.
|
||||
@@ -15,10 +15,13 @@
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with gprMax. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import numpy as np
|
||||
cimport numpy as np
|
||||
|
||||
from cython.parallel import prange
|
||||
|
||||
|
||||
cpdef void cython_update_electric_os(
|
||||
np.float64_t[:, :] updatecoeffsE,
|
||||
np.uint32_t[:, :, :, :] ID,
|
||||
|
@@ -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
|
||||
#
|
||||
# This file is part of gprMax.
|
||||
|
@@ -16,6 +16,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with gprMax. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import numpy as np
|
||||
cimport numpy as np
|
||||
|
||||
|
||||
|
@@ -18,8 +18,8 @@
|
||||
|
||||
import 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_Ey
|
||||
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 set_rigid_H
|
||||
from gprMax.cython.yee_cell_setget_rigid cimport unset_rigid_H
|
||||
|
||||
np.seterr(divide='raise')
|
||||
from gprMax.utilities import round_value
|
||||
|
||||
|
||||
cpdef bint are_clockwise(
|
||||
|
@@ -4,16 +4,16 @@
|
||||
# This file is part of gprMax.
|
||||
#
|
||||
# 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
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# gprMax is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU 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/>.
|
||||
|
||||
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
|
||||
#
|
||||
# This file is part of gprMax.
|
||||
#
|
||||
# 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
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# gprMax is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU 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/>.
|
||||
|
||||
import numpy as np
|
||||
|
@@ -16,8 +16,6 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with gprMax. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
cimport numpy as np
|
||||
|
||||
from gprMax.config cimport float_or_double
|
||||
|
||||
|
||||
|
@@ -19,13 +19,13 @@
|
||||
import 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_Ey
|
||||
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_Hy
|
||||
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):
|
||||
|
@@ -19,8 +19,9 @@
|
||||
import 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_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)
|
||||
@@ -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 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
|
||||
# the 6 magnetic edge components - Hx1, Hx2, Hy1, Hy2, Hz1, Hz2
|
||||
# Get and set functions for the rigid magnetic component array. The rigid array
|
||||
# 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_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)
|
||||
@@ -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_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
|
||||
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 result
|
||||
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):
|
||||
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
|
||||
# the 6 magnetic edge components - Hx1, Hx2, Hy1, Hy2, Hz1, Hz2
|
||||
# Get and set functions for the rigid magnetic component array. The rigid array
|
||||
# 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 result
|
||||
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):
|
||||
rigidH[:, i, j, k] = False
|
||||
|
||||
|
@@ -17,11 +17,14 @@
|
||||
# along with gprMax. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from .config import create_simulation_config
|
||||
from .contexts import create_context
|
||||
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
def run(
|
||||
scenes=None,
|
||||
inputfile=None,
|
||||
@@ -137,3 +140,4 @@ def run_main(args):
|
||||
sim_config = create_simulation_config(args)
|
||||
context = create_context(sim_config)
|
||||
context.run()
|
||||
context.print_time_report()
|
||||
|
251
gprMax/grid.py
251
gprMax/grid.py
@@ -36,80 +36,16 @@ from .utilities import human_size
|
||||
from .utilities import round_value
|
||||
|
||||
|
||||
class Grid(object):
|
||||
"""Generic grid/mesh."""
|
||||
|
||||
def __init__(self, grid):
|
||||
self.nx = grid.shape[0]
|
||||
self.ny = grid.shape[1]
|
||||
self.nz = grid.shape[2]
|
||||
self.dx = 1
|
||||
self.dy = 1
|
||||
self.dz = 1
|
||||
self.i_max = self.nx - 1
|
||||
self.j_max = self.ny - 1
|
||||
self.k_max = self.nz - 1
|
||||
self.grid = grid
|
||||
|
||||
def n_edges(self):
|
||||
i = self.nx
|
||||
j = self.ny
|
||||
k = self.nz
|
||||
e = (i * j * (k - 1)) + (j * k * (i - 1)) + (i * k * (j - 1))
|
||||
return e
|
||||
|
||||
def n_nodes(self):
|
||||
return self.nx * self.ny * self.nz
|
||||
|
||||
def n_cells(self):
|
||||
return (self.nx - 1) * (self.ny - 1) * (self.nz - 1)
|
||||
|
||||
def get(self, i, j, k):
|
||||
return self.grid[i, j, k]
|
||||
|
||||
def within_bounds(self, p):
|
||||
if p[0] < 0 or p[0] > self.nx:
|
||||
raise ValueError('x')
|
||||
if p[1] < 0 or p[1] > self.ny:
|
||||
raise ValueError('y')
|
||||
if p[2] < 0 or p[2] > self.nz:
|
||||
raise ValueError('z')
|
||||
|
||||
def discretise_point(self, p):
|
||||
x = round_value(float(p[0]) / self.dx)
|
||||
y = round_value(float(p[1]) / self.dy)
|
||||
z = round_value(float(p[2]) / self.dz)
|
||||
return (x, y, z)
|
||||
|
||||
def round_to_grid(self, p):
|
||||
p = self.discretise_point(p)
|
||||
p_r = (p[0] * self.dx,
|
||||
p[1] * self.dy,
|
||||
p[2] * self.dz)
|
||||
return p_r
|
||||
|
||||
|
||||
def within_pml(self, p):
|
||||
if (p[0] < self.pmlthickness['x0'] or
|
||||
p[0] > self.nx - self.pmlthickness['xmax'] or
|
||||
p[1] < self.pmlthickness['y0'] or
|
||||
p[1] > self.ny - self.pmlthickness['ymax'] or
|
||||
p[2] < self.pmlthickness['z0'] or
|
||||
p[2] > self.nz - self.pmlthickness['zmax']):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class FDTDGrid(Grid):
|
||||
"""Holds attributes associated with entire grid. A convenient
|
||||
way for accessing regularly used parameters.
|
||||
class FDTDGrid:
|
||||
"""Holds attributes associated with entire grid. A convenient way for
|
||||
accessing regularly used parameters.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.title = ''
|
||||
self.memoryusage = 0
|
||||
self.name = 'Main'
|
||||
self.mode = '' # 2D TMx, 2D TMy, 2D TMz, or 3D
|
||||
self.gpu = None
|
||||
self.outputdirectory = ''
|
||||
|
||||
@@ -150,6 +86,50 @@ class FDTDGrid(Grid):
|
||||
self.snapshots = []
|
||||
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):
|
||||
"""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.Hz = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=config.dtypes['float_or_double'])
|
||||
|
||||
|
||||
def initialise_grids(self):
|
||||
"""Function to call the initialisation of all grids."""
|
||||
"""Initialise all grids."""
|
||||
for g in [self] + self.subgrids:
|
||||
g.initialise_geometry_arrays()
|
||||
g.initialise_field_arrays()
|
||||
@@ -227,32 +206,75 @@ class FDTDGrid(Grid):
|
||||
|
||||
self.memoryusage = int(stdoverhead + fieldarrays + solidarray + rigidarrays + pmlarrays)
|
||||
|
||||
def memory_check(self, snapsmemsize=0):
|
||||
"""Check if the required amount of memory (RAM) is available on the host and GPU if specified.
|
||||
|
||||
Args:
|
||||
snapsmemsize (int): amount of memory (bytes) required to store all requested snapshots
|
||||
def memory_check(self):
|
||||
"""Check if the required amount of memory (RAM) is available to build
|
||||
and/or run model on the host.
|
||||
"""
|
||||
|
||||
# Check if model can be built and/or run on host
|
||||
if self.memoryusage > config.hostinfo['ram']:
|
||||
raise GeneralError('Memory (RAM) required ~{} exceeds {} detected!\n'.format(human_size(self.memoryusage), human_size(config.hostinfo['ram'], a_kilobyte_is_1024_bytes=True)))
|
||||
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
|
||||
if config.cuda['gpus'] is not None:
|
||||
if self.memoryusage - snapsmemsize > config.cuda['gpus'].totalmem:
|
||||
raise GeneralError('Memory (RAM) required ~{} exceeds {} detected on specified {} - {} GPU!\n'.format(human_size(self.memoryusage), human_size(config.cuda['gpus'].totalmem, a_kilobyte_is_1024_bytes=True), config.cuda['gpus'].deviceID, config.cuda['gpus'].name))
|
||||
def tmx(self):
|
||||
"""Add PEC boundaries to invariant direction in 2D TMx mode.
|
||||
N.B. 2D modes are a single cell slice of 3D grid.
|
||||
"""
|
||||
# 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
|
||||
if snapsmemsize != 0 and self.memoryusage - snapsmemsize < config.cuda['gpus'].totalmem:
|
||||
config.cuda['snapsgpu2cpu'] = True
|
||||
def tmy(self):
|
||||
"""Add PEC boundaries to invariant direction in 2D TMy mode.
|
||||
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."""
|
||||
|
||||
config.cuda['gpus'].bpg = (int(np.ceil(((self.nx + 1) * (self.ny + 1) * (self.nz + 1)) / config.cuda['gpus'].tpb[0])), 1, 1)
|
||||
|
||||
def gpu_initialise_arrays(self):
|
||||
"""Initialise standard field arrays on GPU."""
|
||||
def initialise_arrays(self):
|
||||
"""Initialise geometry and field arrays on GPU."""
|
||||
|
||||
import pycuda.gpuarray as gpuarray
|
||||
|
||||
@@ -264,7 +286,7 @@ class FDTDGrid(Grid):
|
||||
self.Hy_gpu = gpuarray.to_gpu(self.Hy)
|
||||
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."""
|
||||
|
||||
import pycuda.gpuarray as gpuarray
|
||||
@@ -274,46 +296,19 @@ class FDTDGrid(Grid):
|
||||
self.Tz_gpu = gpuarray.to_gpu(self.Tz)
|
||||
self.updatecoeffsdispersive_gpu = gpuarray.to_gpu(self.updatecoeffsdispersive)
|
||||
|
||||
# Add PEC boundaries to invariant direction in 2D modes
|
||||
# N.B. 2D modes are a single cell slice of 3D grid
|
||||
def tmx(self):
|
||||
# Ey & Ez components
|
||||
self.ID[1, 0, :, :] = 0
|
||||
self.ID[1, 1, :, :] = 0
|
||||
self.ID[2, 0, :, :] = 0
|
||||
self.ID[2, 1, :, :] = 0
|
||||
def memory_check(self, snapsmemsize=0):
|
||||
"""Check if model can be run on specified GPU."""
|
||||
|
||||
def tmy(self):
|
||||
# Ex & Ez components
|
||||
self.ID[0, :, 0, :] = 0
|
||||
self.ID[0, :, 1, :] = 0
|
||||
self.ID[2, :, 0, :] = 0
|
||||
self.ID[2, :, 1, :] = 0
|
||||
super().memory_check()
|
||||
|
||||
def tmz(self):
|
||||
# Ex & Ey components
|
||||
self.ID[0, :, :, 0] = 0
|
||||
self.ID[0, :, :, 1] = 0
|
||||
self.ID[1, :, :, 0] = 0
|
||||
self.ID[1, :, :, 1] = 0
|
||||
if config.cuda['gpus'] is not None:
|
||||
if self.memoryusage - snapsmemsize > config.cuda['gpus'].totalmem:
|
||||
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')
|
||||
|
||||
|
||||
def reset_fields(self):
|
||||
# Clear arrays for field components
|
||||
self.initialise_field_arrays()
|
||||
|
||||
# Clear arrays for fields in PML
|
||||
for pml in self.pmls:
|
||||
pml.initialise_field_arrays()
|
||||
|
||||
def calculate_dt(self):
|
||||
self.dt = 1 / (c * np.sqrt((1 / self.dx) * (1 / self.dx) + (1 / self.dy) * (1 / self.dy) + (1 / self.dz) * (1 / self.dz)))
|
||||
|
||||
def round_time_step(self):
|
||||
# Round down time step to nearest float with precision one less than hardware maximum. Avoids inadvertently exceeding the CFL due to binary representation of floating point number.
|
||||
# Round down time step to nearest float with precision one less than hardware maximum.
|
||||
# Avoids inadvertently exceeding the CFL due to binary representation of floating point number.
|
||||
self.dt = round_value(self.dt, decimalplaces=d.getcontext().prec - 1)
|
||||
# If the required memory for the model without the snapshots will
|
||||
# fit on the GPU then transfer and store snaphots on host
|
||||
if snapsmemsize != 0 and self.memoryusage - snapsmemsize < config.cuda['gpus'].totalmem:
|
||||
config.cuda['snapsgpu2cpu'] = True
|
||||
|
||||
|
||||
def 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])
|
||||
|
||||
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
|
||||
# file commands have been processed
|
||||
if sim_config.write_processed:
|
||||
if sim_config.args.write_processed:
|
||||
write_processed_file(processedlines, model_config.appendmodelnumber, G)
|
||||
|
||||
user_objs = get_user_objects(processedlines, check=True)
|
||||
|
@@ -19,6 +19,7 @@
|
||||
import datetime
|
||||
from importlib import import_module
|
||||
import itertools
|
||||
import logging
|
||||
import os
|
||||
import psutil
|
||||
import sys
|
||||
@@ -74,6 +75,8 @@ from .utilities import timer
|
||||
from .utilities import Printer
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class ModelBuildRun:
|
||||
"""Builds and runs (solves) a model."""
|
||||
|
||||
@@ -159,7 +162,7 @@ class ModelBuildRun:
|
||||
gb.grid.initialise_std_update_coeff_arrays()
|
||||
|
||||
# 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])
|
||||
if drudelorentz:
|
||||
config.materials['dispersivedtype'] = config.dtypes['complex']
|
||||
@@ -206,8 +209,8 @@ class ModelBuildRun:
|
||||
G = self.G
|
||||
# Reset iteration number
|
||||
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.printer.print(Fore.GREEN + '{} {}'.format(self.model_config.inputfilestr, '-' * (get_terminal_width() - 1 - len(self.model_config.inputfilestr))) + Style.RESET_ALL)
|
||||
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}'
|
||||
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:
|
||||
grid.reset_fields()
|
||||
|
||||
@@ -232,52 +235,52 @@ class ModelBuildRun:
|
||||
# Check and set output directory and filename
|
||||
try:
|
||||
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:
|
||||
pass
|
||||
# Modify the output path (hack)
|
||||
self.model_config.output_file_path_ext = Path(self.G.outputdirectory, self.model_config.output_file_path_ext)
|
||||
|
||||
def run_model(self, solver):
|
||||
G = self.G
|
||||
self.create_output_directory()
|
||||
self.printer.print('Output file: {}\n'.format(self.model_config.output_file_path_ext))
|
||||
def write_output_data(self):
|
||||
"""Write output data, i.e. field data for receivers and snapshots
|
||||
to file(s).
|
||||
"""
|
||||
|
||||
# Run solver
|
||||
tsolve = self.solve(solver)
|
||||
G = self.G
|
||||
|
||||
# Write an output file in HDF5 format
|
||||
write_hdf5_outputfile(self.model_config.output_file_path_ext, G)
|
||||
|
||||
# Write any snapshots to file
|
||||
if G.snapshots:
|
||||
# Create directory and construct filename from user-supplied name and model run number
|
||||
# Create directory and construct filename from user-supplied name
|
||||
# and model run number
|
||||
snapshotdir = self.model_config.snapshot_dir
|
||||
if not os.path.exists(snapshotdir):
|
||||
os.mkdir(snapshotdir)
|
||||
|
||||
self.printer.print('')
|
||||
log.info('')
|
||||
for i, snap in enumerate(G.snapshots):
|
||||
fn = snapshotdir / Path(self.model_config.output_file_path.stem + '_' + snap.basefilename)
|
||||
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'])
|
||||
snap.write_vtk_imagedata(pbar, G)
|
||||
pbar.close()
|
||||
self.printer.print('')
|
||||
log.info('')
|
||||
|
||||
def print_resource_info(self):
|
||||
"""Print resource information on runtime and memory usage."""
|
||||
|
||||
memGPU = ''
|
||||
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))
|
||||
self.printer.print('Solving time [HH:MM:SS]: {}'.format(datetime.timedelta(seconds=tsolve)))
|
||||
|
||||
return tsolve
|
||||
log.info(f'\nMemory (RAM) used: ~{human_size(self.p.memory_full_info().uss)}{memGPU}')
|
||||
log.info(f'Solving time [HH:MM:SS]: {datetime.timedelta(seconds=tsolve)}')
|
||||
|
||||
def solve(self, solver):
|
||||
"""
|
||||
Solving using FDTD method on CPU. Parallelised using Cython (OpenMP) for
|
||||
electric and magnetic field updates, and PML updates.
|
||||
Solve using FDTD method.
|
||||
|
||||
Args:
|
||||
solver (Solver): solver object.
|
||||
@@ -294,7 +297,17 @@ class ModelBuildRun:
|
||||
else:
|
||||
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
|
||||
|
||||
@@ -318,7 +331,7 @@ class GridBuilder:
|
||||
def build_components(self):
|
||||
# Build the model, i.e. set the material properties (ID) for every edge
|
||||
# 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'])
|
||||
build_electric_components(self.grid.solid, self.grid.rigidE, self.grid.ID, self.grid)
|
||||
pbar.update()
|
||||
@@ -348,5 +361,5 @@ class GridBuilder:
|
||||
materialstable.outer_border = False
|
||||
materialstable.justify_columns[0] = 'right'
|
||||
|
||||
self.printer.print('\n{} Grid Materials:'.format(self.grid.name))
|
||||
self.printer.print(materialstable.table)
|
||||
log.info(f'\n{self.grid.name} Grid Materials:')
|
||||
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
|
||||
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):
|
||||
"""Applies the polynomial to be used for the scaling profile for
|
||||
|
@@ -18,10 +18,10 @@
|
||||
|
||||
import gprMax.config as config
|
||||
from .grid import FDTDGrid
|
||||
from .grid import GPUGrid
|
||||
from .grid import CUDAGrid
|
||||
from .subgrids.updates import create_updates as create_subgrid_updates
|
||||
from .updates import CPUUpdates
|
||||
from .updates import GPUUpdates
|
||||
from .updates import CUDAUpdates
|
||||
|
||||
|
||||
def create_G(sim_config):
|
||||
@@ -34,8 +34,8 @@ def create_G(sim_config):
|
||||
G (Grid): Holds essential parameters describing the model.
|
||||
"""
|
||||
|
||||
if sim_config.gpu:
|
||||
G = GPUGrid()
|
||||
if sim_config.general['cuda']:
|
||||
G = CUDAGrid()
|
||||
elif sim_config.subgrid:
|
||||
G = FDTDGrid()
|
||||
else:
|
||||
@@ -56,7 +56,7 @@ def create_solver(G, sim_config):
|
||||
"""
|
||||
|
||||
if sim_config.gpu:
|
||||
updates = GPUUpdates(G)
|
||||
updates = CUDAUpdates(G)
|
||||
solver = Solver(updates)
|
||||
elif sim_config.subgrid:
|
||||
updates = create_subgrid_updates(G)
|
||||
|
@@ -63,19 +63,9 @@ class SubGridBase(FDTDGrid):
|
||||
|
||||
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):
|
||||
"""Calculate local subgrid index from global main grid index."""
|
||||
i_s = self.n_boundary_cells_x + (i - self.i0) * self.ratio
|
||||
j_s = self.n_boundary_cells_y + (j - self.j0) * self.ratio
|
||||
k_s = self.n_boundary_cells_z + (k - self.k0) * self.ratio
|
||||
return (i_s, j_s, k_s)
|
||||
|
||||
def initialise_geometry_arrays(self):
|
||||
super().initialise_geometry_arrays()
|
||||
|
@@ -27,10 +27,6 @@ class ReferenceRx(Rx):
|
||||
position as the coarse grid.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Constructor."""
|
||||
super().__init__()
|
||||
|
||||
def get_field(self, str_id, field):
|
||||
"""Return the field value at the equivalent coarse yee cell.
|
||||
|
||||
|
@@ -28,7 +28,7 @@ def calculate_weighting_coefficients(x1, x):
|
||||
return (c1, c2)
|
||||
|
||||
|
||||
class PrecusorNodesBase(object):
|
||||
class PrecusorNodesBase:
|
||||
|
||||
def __init__(self, fdtd_grid, sub_grid):
|
||||
self.G = fdtd_grid
|
||||
|
@@ -18,6 +18,7 @@
|
||||
|
||||
import numpy as np
|
||||
cimport numpy as np
|
||||
|
||||
from cython.parallel import prange
|
||||
|
||||
cdef extern from "complex.h" nogil:
|
||||
|
@@ -17,6 +17,8 @@
|
||||
# along with gprMax. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from importlib import import_module
|
||||
import logging
|
||||
import sys
|
||||
|
||||
import gprMax.config as config
|
||||
from .cuda.fields_updates import kernel_template_fields
|
||||
@@ -34,6 +36,8 @@ from .sources import gpu_initialise_src_arrays
|
||||
from .utilities import timer
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class CPUUpdates:
|
||||
"""Defines update functions for CPU-based solver."""
|
||||
|
||||
@@ -226,7 +230,7 @@ class CPUUpdates:
|
||||
pass
|
||||
|
||||
|
||||
class GPUUpdates:
|
||||
class CUDAUpdates:
|
||||
"""Defines update functions for GPU-based (CUDA) solver."""
|
||||
|
||||
def __init__(self, G):
|
||||
@@ -235,6 +239,10 @@ class GPUUpdates:
|
||||
G (FDTDGrid): FDTD grid object
|
||||
"""
|
||||
|
||||
self.grid = G
|
||||
self.dispersive_update_a = None
|
||||
self.dispersive_update_b = None
|
||||
|
||||
import pycuda.driver as drv
|
||||
from pycuda.compiler import SourceModule
|
||||
drv.init()
|
||||
@@ -249,10 +257,6 @@ class GPUUpdates:
|
||||
self.dev = drv.Device(self.grid.gpu.deviceID)
|
||||
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
|
||||
self.set_field_kernels()
|
||||
self.set_pml_kernels()
|
||||
@@ -261,7 +265,9 @@ class GPUUpdates:
|
||||
self.set_snapshot_kernel()
|
||||
|
||||
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:
|
||||
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
|
||||
@@ -269,7 +275,7 @@ class GPUUpdates:
|
||||
self.update_electric = kernels_fields.get_function("update_electric")
|
||||
self.update_magnetic = kernels_fields.get_function("update_magnetic")
|
||||
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()
|
||||
|
||||
# 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()
|
||||
|
||||
def set_pml_kernels(self):
|
||||
"""PMLS - prepare kernels and get kernel functions."""
|
||||
if self.grid.pmls:
|
||||
# PMLS - prepare kernels and get kernel functions
|
||||
pmlmodulelectric = 'gprMax.cuda.pml_updates_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
|
||||
@@ -299,14 +305,18 @@ class GPUUpdates:
|
||||
pml.gpu_set_blocks_per_grid(self.grid)
|
||||
|
||||
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:
|
||||
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)
|
||||
self.store_outputs = kernel_store_outputs.get_function("store_outputs")
|
||||
|
||||
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:
|
||||
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()
|
||||
@@ -321,14 +331,18 @@ class GPUUpdates:
|
||||
self.update_voltage_source_gpu = kernels_sources.get_function("update_voltage_source")
|
||||
|
||||
def set_snapshot_kernel(self):
|
||||
"""Snapshots - initialise arrays on GPU, prepare kernel and
|
||||
get kernel function.
|
||||
"""
|
||||
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)
|
||||
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")
|
||||
|
||||
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]
|
||||
updatecoeffsH = kernels_sources.get_global('updatecoeffsH')[0]
|
||||
self.drv.memcpy_htod(updatecoeffsE, self.grid.updatecoeffsE)
|
||||
@@ -396,7 +410,6 @@ class GPUUpdates:
|
||||
|
||||
def update_magnetic(self):
|
||||
"""Update magnetic field components."""
|
||||
|
||||
self.update_magnetic(np.int32(self.grid.nx),
|
||||
np.int32(self.grid.ny),
|
||||
np.int32(self.grid.nz),
|
||||
@@ -412,13 +425,11 @@ class GPUUpdates:
|
||||
|
||||
def update_magnetic_pml(self):
|
||||
"""Update magnetic field components with the PML correction."""
|
||||
|
||||
for pml in self.grid.pmls:
|
||||
pml.gpu_update_magnetic(self.grid)
|
||||
|
||||
def update_magnetic_sources(self):
|
||||
"""Update magnetic field components from sources."""
|
||||
|
||||
if self.grid.magneticdipoles:
|
||||
self.update_magnetic_dipole_gpu(np.int32(len(self.grid.magneticdipoles)),
|
||||
np.int32(self.grid.iteration),
|
||||
@@ -437,7 +448,6 @@ class GPUUpdates:
|
||||
|
||||
def update_electric_a(self):
|
||||
"""Update electric field components."""
|
||||
|
||||
# All materials are non-dispersive so do standard update.
|
||||
if config.materials['maxpoles'] == 0:
|
||||
self.update_electric(np.int32(self.grid.nx),
|
||||
@@ -476,7 +486,6 @@ class GPUUpdates:
|
||||
|
||||
def update_electric_pml(self):
|
||||
"""Update electric field components with the PML correction."""
|
||||
|
||||
for pml in self.grid.pmls:
|
||||
pml.gpu_update_electric(self.grid)
|
||||
|
||||
@@ -484,7 +493,6 @@ class GPUUpdates:
|
||||
"""Update electric field components from sources -
|
||||
update any Hertzian dipole sources last.
|
||||
"""
|
||||
|
||||
if self.grid.voltagesources:
|
||||
self.update_voltage_source_gpu(np.int32(len(self.grid.voltagesources)),
|
||||
np.int32(self.grid.iteration),
|
||||
@@ -525,7 +533,6 @@ class GPUUpdates:
|
||||
field values. Therefore it can only be completely updated after the
|
||||
electric field has been updated by the PML and source updates.
|
||||
"""
|
||||
|
||||
if config.materials['maxpoles'] != 0:
|
||||
self.dispersive_update_b(np.int32(self.grid.nx),
|
||||
np.int32(self.grid.ny),
|
||||
@@ -543,11 +550,14 @@ class GPUUpdates:
|
||||
grid=self.grid.bpg)
|
||||
|
||||
def time_start(self):
|
||||
"""Start event timers used to calculate solving time for model."""
|
||||
self.iterstart = self.drv.Event()
|
||||
self.iterend = self.drv.Event()
|
||||
self.iterstart.record()
|
||||
self.iterstart.synchronize()
|
||||
|
||||
def calculate_tsolve(self):
|
||||
"""Calculate solving time for model."""
|
||||
self.iterend.record()
|
||||
self.iterend.synchronize()
|
||||
tsolve = self.iterstart.time_till(self.iterend) * 1e-3
|
||||
@@ -555,6 +565,7 @@ class GPUUpdates:
|
||||
return tsolve
|
||||
|
||||
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
|
||||
if self.grid.rxs:
|
||||
gpu_get_rx_array(self.rxs_gpu.get(),
|
||||
@@ -574,6 +585,7 @@ class GPUUpdates:
|
||||
snap)
|
||||
|
||||
def cleanup(self):
|
||||
"""Cleanup GPU context."""
|
||||
# Remove context from top of stack and delete
|
||||
self.ctx.pop()
|
||||
del self.ctx
|
||||
|
@@ -27,7 +27,7 @@ import subprocess
|
||||
from shutil import get_terminal_size
|
||||
import sys
|
||||
import textwrap
|
||||
from time import perf_counter
|
||||
from time import process_time
|
||||
|
||||
from colorama import init
|
||||
from colorama import Fore
|
||||
@@ -38,7 +38,6 @@ import numpy as np
|
||||
from .exceptions import GeneralError
|
||||
|
||||
|
||||
|
||||
def get_terminal_width():
|
||||
"""Get/set width of terminal being used.
|
||||
|
||||
@@ -75,14 +74,14 @@ def logo(version):
|
||||
|___/|_|
|
||||
v""" + version
|
||||
|
||||
print('{} {}\n'.format(description, '=' * (get_terminal_width() - len(description) - 1)))
|
||||
print(Fore.CYAN + '{}\n'.format(logo))
|
||||
print(Style.RESET_ALL + textwrap.fill(copyright, width=get_terminal_width() - 1, initial_indent=' '))
|
||||
print(textwrap.fill(authors, width=get_terminal_width() - 1, initial_indent=' '))
|
||||
print()
|
||||
print(textwrap.fill(licenseinfo1, width=get_terminal_width() - 1, initial_indent=' ', subsequent_indent=' '))
|
||||
print(textwrap.fill(licenseinfo2, width=get_terminal_width() - 1, initial_indent=' ', subsequent_indent=' '))
|
||||
print(textwrap.fill(licenseinfo3, width=get_terminal_width() - 1, initial_indent=' ', subsequent_indent=' '))
|
||||
log.info(f'{description} {'=' * (get_terminal_width() - len(description) - 1)}\n')
|
||||
log.info(Fore.CYAN + f'{logo}\n')
|
||||
log.info(Style.RESET_ALL + textwrap.fill(copyright, width=get_terminal_width() - 1, initial_indent=' '))
|
||||
log.info(textwrap.fill(authors, width=get_terminal_width() - 1, initial_indent=' '))
|
||||
log.info('')
|
||||
log.info(textwrap.fill(licenseinfo1, width=get_terminal_width() - 1, initial_indent=' ', subsequent_indent=' '))
|
||||
log.info(textwrap.fill(licenseinfo2, width=get_terminal_width() - 1, initial_indent=' ', subsequent_indent=' '))
|
||||
log.info(textwrap.fill(licenseinfo3, width=get_terminal_width() - 1, initial_indent=' ', subsequent_indent=' '))
|
||||
|
||||
|
||||
@contextmanager
|
||||
@@ -401,7 +400,7 @@ def detect_check_gpus(deviceIDs):
|
||||
# Check if requested device ID(s) exist
|
||||
for ID in deviceIDs:
|
||||
if ID not in deviceIDsavail:
|
||||
raise GeneralError('GPU with device ID {} does not exist'.format(ID))
|
||||
raise GeneralError(f'GPU with device ID {ID} does not exist')
|
||||
|
||||
# Gather information about selected/detected GPUs
|
||||
gpus = []
|
||||
@@ -411,22 +410,11 @@ def detect_check_gpus(deviceIDs):
|
||||
gpu.get_gpu_info(drv)
|
||||
if ID in deviceIDs:
|
||||
gpus.append(gpu)
|
||||
allgpustext.append('{} - {}, {}'.format(gpu.deviceID, gpu.name, human_size(gpu.totalmem, a_kilobyte_is_1024_bytes=True)))
|
||||
allgpustext.append(f'{gpu.deviceID} - {gpu.name}, {human_size(gpu.totalmem, a_kilobyte_is_1024_bytes=True)}')
|
||||
|
||||
return gpus, allgpustext
|
||||
|
||||
|
||||
def timer():
|
||||
"""Function to return the current process wide time in fractional seconds."""
|
||||
return perf_counter()
|
||||
|
||||
|
||||
class Printer():
|
||||
"""Printing information messages."""
|
||||
|
||||
def __init__(self, config):
|
||||
self.printing = config.is_messages()
|
||||
|
||||
def print(self, str):
|
||||
if self.printing:
|
||||
print(str)
|
||||
return process_time()
|
||||
|
22
setup.py
22
setup.py
@@ -33,14 +33,18 @@ import pathlib
|
||||
import re
|
||||
import shutil
|
||||
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():
|
||||
"""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(
|
||||
loader=PackageLoader(__name__, 'gprMax/templates'),
|
||||
)
|
||||
@@ -152,25 +156,25 @@ if 'cleanall' in sys.argv:
|
||||
if os.path.isfile(filebase + '.c'):
|
||||
try:
|
||||
os.remove(filebase + '.c')
|
||||
print('Removed: {}'.format(filebase + '.c'))
|
||||
print(f'Removed: {filebase} + ".c"')
|
||||
except OSError:
|
||||
print('Could not remove: {}'.format(filebase + '.c'))
|
||||
print(f'Could not remove: {filebase} + ".c"')
|
||||
# Remove compiled Cython modules
|
||||
libfile = glob.glob(os.path.join(os.getcwd(), os.path.splitext(file)[0]) + '*.pyd') + glob.glob(os.path.join(os.getcwd(), os.path.splitext(file)[0]) + '*.so')
|
||||
if libfile:
|
||||
libfile = libfile[0]
|
||||
try:
|
||||
os.remove(libfile)
|
||||
print('Removed: {}'.format(os.path.abspath(libfile)))
|
||||
print(f'Removed: {os.path.abspath(libfile)}')
|
||||
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
|
||||
shutil.rmtree(os.path.join(os.getcwd(), 'build'), ignore_errors=True)
|
||||
shutil.rmtree(os.path.join(os.getcwd(), 'dist'), ignore_errors=True)
|
||||
shutil.rmtree(os.path.join(os.getcwd(), 'gprMax.egg-info'), ignore_errors=True)
|
||||
for p in pathlib.Path(os.getcwd()).rglob('__pycache__'):
|
||||
shutil.rmtree(p, ignore_errors=True)
|
||||
print('Removed: {}'.format(p))
|
||||
print(f'Removed: {p}')
|
||||
# Now do a normal clean
|
||||
sys.argv[1] = 'clean' # this is what distutils understands
|
||||
|
||||
|
在新工单中引用
屏蔽一个用户