你已经派生过 gprMax
镜像自地址
https://gitee.com/sunhf/gprMax.git
已同步 2025-08-07 04:56:51 +08:00
Tidy up of logging and initial MPI task farm work.
这个提交包含在:
@@ -11,7 +11,6 @@ from ._version import __version__
|
||||
from .cmds_single_use import Discretisation
|
||||
from .cmds_single_use import Domain
|
||||
from .cmds_single_use import TimeWindow
|
||||
from .cmds_single_use import Messages
|
||||
from .cmds_single_use import Title
|
||||
from .cmds_single_use import NumThreads
|
||||
from .cmds_single_use import TimeStepStabilityFactor
|
||||
|
@@ -28,7 +28,7 @@ from ..fractals import Grass
|
||||
from ..materials import Material
|
||||
from ..utilities import round_value
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AddGrass(UserObjectGeometry):
|
||||
@@ -193,4 +193,4 @@ class AddGrass(UserObjectGeometry):
|
||||
|
||||
volume.fractalsurfaces.append(surface)
|
||||
|
||||
log.info(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}.')
|
||||
logger.info(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}.')
|
||||
|
@@ -27,7 +27,7 @@ from ..exceptions import CmdInputError
|
||||
from ..fractals import FractalSurface
|
||||
from ..utilities import round_value
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AddSurfaceRoughness(UserObjectGeometry):
|
||||
@@ -158,4 +158,4 @@ class AddSurfaceRoughness(UserObjectGeometry):
|
||||
surface.generate_fractal_surface(grid)
|
||||
volume.fractalsurfaces.append(surface)
|
||||
|
||||
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}.')
|
||||
logger.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}.')
|
||||
|
@@ -24,7 +24,7 @@ from ..exceptions import CmdInputError
|
||||
from ..materials import Material
|
||||
from ..utilities import round_value
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AddSurfaceWater(UserObjectGeometry):
|
||||
@@ -142,4 +142,4 @@ class AddSurfaceWater(UserObjectGeometry):
|
||||
if testwater:
|
||||
raise CmdInputError(self.__str__() + ' requires the time step for the model to be less than the relaxation time required to model water.')
|
||||
|
||||
log.info(f'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}.')
|
||||
logger.info(f'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}.')
|
||||
|
@@ -26,7 +26,7 @@ from ..cython.geometry_primitives import build_box
|
||||
from ..exceptions import CmdInputError
|
||||
from ..materials import Material
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Box(UserObjectGeometry):
|
||||
@@ -116,4 +116,4 @@ class Box(UserObjectGeometry):
|
||||
build_box(xs, xf, ys, yf, zs, zf, numID, numIDx, numIDy, numIDz, averaging, grid.solid, grid.rigidE, grid.rigidH, grid.ID)
|
||||
|
||||
dielectricsmoothing = 'on' if averaging else 'off'
|
||||
log.info(f"Box 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 of material(s) {', '.join(materialsrequested)} created, dielectric smoothing is {dielectricsmoothing}.")
|
||||
logger.info(f"Box 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 of material(s) {', '.join(materialsrequested)} created, dielectric smoothing is {dielectricsmoothing}.")
|
||||
|
@@ -18,7 +18,7 @@
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UserObjectGeometry:
|
||||
@@ -45,4 +45,4 @@ class UserObjectGeometry:
|
||||
|
||||
def create(self, grid, uip):
|
||||
"""Create the object and add it to the grid."""
|
||||
log.debug('This method is incomplete')
|
||||
logger.debug('This method is incomplete')
|
||||
|
@@ -25,7 +25,7 @@ from ..cython.geometry_primitives import build_cylinder
|
||||
from ..exceptions import CmdInputError
|
||||
from ..materials import Material
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Cylinder(UserObjectGeometry):
|
||||
@@ -121,4 +121,4 @@ class Cylinder(UserObjectGeometry):
|
||||
build_cylinder(x1, y1, z1, x2, y2, z2, r, grid.dx, grid.dy, grid.dz, numID, numIDx, numIDy, numIDz, averaging, grid.solid, grid.rigidE, grid.rigidH, grid.ID)
|
||||
|
||||
dielectricsmoothing = 'on' if averaging else 'off'
|
||||
log.info(f"Cylinder with face centres {x1:g}m, {y1:g}m, {z1:g}m and {x2:g}m, {y2:g}m, {z2:g}m, with radius {r:g}m, of material(s) {', '.join(materialsrequested)} created, dielectric smoothing is {dielectricsmoothing}.")
|
||||
logger.info(f"Cylinder with face centres {x1:g}m, {y1:g}m, {z1:g}m and {x2:g}m, {y2:g}m, {z2:g}m, with radius {r:g}m, of material(s) {', '.join(materialsrequested)} created, dielectric smoothing is {dielectricsmoothing}.")
|
||||
|
@@ -26,7 +26,7 @@ from ..cython.geometry_primitives import build_cylindrical_sector
|
||||
from ..exceptions import CmdInputError
|
||||
from ..materials import Material
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CylindricalSector(UserObjectGeometry):
|
||||
@@ -171,6 +171,6 @@ class CylindricalSector(UserObjectGeometry):
|
||||
|
||||
if thickness > 0:
|
||||
dielectricsmoothing = 'on' if averaging else 'off'
|
||||
log.info(f"Cylindrical sector with centre {ctr1:g}m, {ctr2:g}m, radius {r:g}m, starting angle {(sectorstartangle / (2 * np.pi)) * 360:.1f} degrees, sector angle {(sectorangle / (2 * np.pi)) * 360:.1f} degrees, thickness {thickness:g}m, of material(s) {', '.join(materialsrequested)} created, dielectric smoothing is {dielectricsmoothing}.")
|
||||
logger.info(f"Cylindrical sector with centre {ctr1:g}m, {ctr2:g}m, radius {r:g}m, starting angle {(sectorstartangle / (2 * np.pi)) * 360:.1f} degrees, sector angle {(sectorangle / (2 * np.pi)) * 360:.1f} degrees, thickness {thickness:g}m, of material(s) {', '.join(materialsrequested)} created, dielectric smoothing is {dielectricsmoothing}.")
|
||||
else:
|
||||
log.info(f"Cylindrical sector with centre {ctr1:g}m, {ctr2:g}m, radius {r:g}m, starting angle {(sectorstartangle / (2 * np.pi)) * 360:.1f} degrees, sector angle {(sectorangle / (2 * np.pi)) * 360:.1f} degrees, of material(s) {', '.join(materialsrequested)} created.")
|
||||
logger.info(f"Cylindrical sector with centre {ctr1:g}m, {ctr2:g}m, radius {r:g}m, starting angle {(sectorstartangle / (2 * np.pi)) * 360:.1f} degrees, sector angle {(sectorangle / (2 * np.pi)) * 360:.1f} degrees, of material(s) {', '.join(materialsrequested)} created.")
|
||||
|
@@ -25,7 +25,7 @@ from ..cython.geometry_primitives import build_edge_y
|
||||
from ..cython.geometry_primitives import build_edge_z
|
||||
from ..exceptions import CmdInputError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Edge(UserObjectGeometry):
|
||||
@@ -86,4 +86,4 @@ class Edge(UserObjectGeometry):
|
||||
for k in range(zs, zf):
|
||||
build_edge_z(xs, ys, k, material.numID, grid.rigidE, grid.rigidH, grid.ID)
|
||||
|
||||
log.info(f'Edge 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 of material {material_id} created.')
|
||||
logger.info(f'Edge 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 of material {material_id} created.')
|
||||
|
@@ -25,7 +25,7 @@ from .cmds_geometry import UserObjectGeometry
|
||||
from ..exceptions import CmdInputError
|
||||
from ..fractals import FractalVolume
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FractalBox(UserObjectGeometry):
|
||||
@@ -121,6 +121,6 @@ class FractalBox(UserObjectGeometry):
|
||||
volume.mixingmodel = mixingmodel
|
||||
|
||||
dielectricsmoothing = 'on' if volume.averaging else 'off'
|
||||
log.info(f'Fractal box {volume.ID} 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 {volume.operatingonID}, fractal dimension {volume.dimension:g}, fractal weightings {volume.weighting[0]:g}, {volume.weighting[1]:g}, {volume.weighting[2]:g}, fractal seeding {volume.seed}, with {volume.nbins} material(s) created, dielectric smoothing is {dielectricsmoothing}.')
|
||||
logger.info(f'Fractal box {volume.ID} 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 {volume.operatingonID}, fractal dimension {volume.dimension:g}, fractal weightings {volume.weighting[0]:g}, {volume.weighting[1]:g}, {volume.weighting[2]:g}, fractal seeding {volume.seed}, with {volume.nbins} material(s) created, dielectric smoothing is {dielectricsmoothing}.')
|
||||
|
||||
grid.fractalvolumes.append(volume)
|
||||
|
@@ -28,7 +28,7 @@ from ..exceptions import CmdInputError
|
||||
from ..hash_cmds_file import get_user_objects
|
||||
from ..utilities import round_value
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GeometryObjectsRead(UserObjectGeometry):
|
||||
@@ -113,8 +113,8 @@ class GeometryObjectsRead(UserObjectGeometry):
|
||||
G.rigidE[:, xs:xs + rigidE.shape[1], ys:ys + rigidE.shape[2], zs:zs + rigidE.shape[3]] = rigidE
|
||||
G.rigidH[:, xs:xs + rigidH.shape[1], ys:ys + rigidH.shape[2], zs:zs + rigidH.shape[3]] = rigidH
|
||||
G.ID[:, xs:xs + ID.shape[1], ys:ys + ID.shape[2], zs:zs + ID.shape[3]] = ID + numexistmaterials
|
||||
log.info(f'Geometry objects from file {geofile} inserted at {xs * G.dx:g}m, {ys * G.dy:g}m, {zs * G.dz:g}m, with corresponding materials file {matfile}.')
|
||||
logger.info(f'Geometry objects from file {geofile} inserted at {xs * G.dx:g}m, {ys * G.dy:g}m, {zs * G.dz:g}m, with corresponding materials file {matfile}.')
|
||||
except KeyError:
|
||||
averaging = False
|
||||
build_voxels_from_array(xs, ys, zs, numexistmaterials, averaging, data, G.solid, G.rigidE, G.rigidH, G.ID)
|
||||
log.info(f'Geometry objects from file (voxels only){geofile} inserted at {xs * G.dx:g}m, {ys * G.dy:g}m, {zs * G.dz:g}m, with corresponding materials file {matfile}.')
|
||||
logger.info(f'Geometry objects from file (voxels only){geofile} inserted at {xs * G.dx:g}m, {ys * G.dy:g}m, {zs * G.dz:g}m, with corresponding materials file {matfile}.')
|
||||
|
@@ -25,7 +25,7 @@ from ..cython.geometry_primitives import build_face_xz
|
||||
from ..cython.geometry_primitives import build_face_xy
|
||||
from ..exceptions import CmdInputError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Plate(UserObjectGeometry):
|
||||
@@ -134,4 +134,4 @@ class Plate(UserObjectGeometry):
|
||||
for j in range(ys, yf):
|
||||
build_face_xy(i, j, zs, numIDx, numIDy, grid.rigidE, grid.rigidH, grid.ID)
|
||||
|
||||
log.info(f"Plate 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 of material(s) {', '.join(materialsrequested)} created.")
|
||||
logger.info(f"Plate 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 of material(s) {', '.join(materialsrequested)} created.")
|
||||
|
@@ -26,7 +26,7 @@ from ..cython.geometry_primitives import build_sphere
|
||||
from ..exceptions import CmdInputError
|
||||
from ..materials import Material
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Sphere(UserObjectGeometry):
|
||||
@@ -115,4 +115,4 @@ class Sphere(UserObjectGeometry):
|
||||
build_sphere(xc, yc, zc, r, grid.dx, grid.dy, grid.dz, numID, numIDx, numIDy, numIDz, averaging, grid.solid, grid.rigidE, grid.rigidH, grid.ID)
|
||||
|
||||
dielectricsmoothing = 'on' if averaging else 'off'
|
||||
log.info(f"Sphere with centre {xc * grid.dx:g}m, {yc * grid.dy:g}m, {zc * grid.dz:g}m, radius {r:g}m, of material(s) {', '.join(materialsrequested)} created, dielectric smoothing is {dielectricsmoothing}.")
|
||||
logger.info(f"Sphere with centre {xc * grid.dx:g}m, {yc * grid.dy:g}m, {zc * grid.dz:g}m, radius {r:g}m, of material(s) {', '.join(materialsrequested)} created, dielectric smoothing is {dielectricsmoothing}.")
|
||||
|
@@ -26,7 +26,7 @@ from ..cython.geometry_primitives import build_triangle
|
||||
from ..exceptions import CmdInputError
|
||||
from ..materials import Material
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Triangle(UserObjectGeometry):
|
||||
@@ -156,6 +156,6 @@ class Triangle(UserObjectGeometry):
|
||||
|
||||
if thickness > 0:
|
||||
dielectricsmoothing = 'on' if averaging else 'off'
|
||||
log.info(f"Triangle with coordinates {x1:g}m {y1:g}m {z1:g}m, {x2:g}m {y2:g}m {z2:g}m, {x3:g}m {y3:g}m {z3:g}m and thickness {thickness:g}m of material(s) {', '.join(materialsrequested)} created, dielectric smoothing is {dielectricsmoothing}.")
|
||||
logger.info(f"Triangle with coordinates {x1:g}m {y1:g}m {z1:g}m, {x2:g}m {y2:g}m {z2:g}m, {x3:g}m {y3:g}m {z3:g}m and thickness {thickness:g}m of material(s) {', '.join(materialsrequested)} created, dielectric smoothing is {dielectricsmoothing}.")
|
||||
else:
|
||||
log.info(f"Triangle with coordinates {x1:g}m {y1:g}m {z1:g}m, {x2:g}m {y2:g}m {z2:g}m, {x3:g}m {y3:g}m {z3:g}m of material(s) {', '.join(materialsrequested)} created.")
|
||||
logger.info(f"Triangle with coordinates {x1:g}m {y1:g}m {z1:g}m, {x2:g}m {y2:g}m {z2:g}m, {x3:g}m {y3:g}m {z3:g}m of material(s) {', '.join(materialsrequested)} created.")
|
||||
|
@@ -39,7 +39,7 @@ from .subgrids.base import SubGridBase
|
||||
from .utilities import round_value
|
||||
from .waveforms import Waveform as WaveformUser
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UserObjectMulti:
|
||||
@@ -84,6 +84,7 @@ class Waveform(UserObjectMulti):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.order = 1
|
||||
self.hash = '#waveform'
|
||||
|
||||
def create(self, grid, uip):
|
||||
@@ -108,7 +109,7 @@ class Waveform(UserObjectMulti):
|
||||
w.amp = amp
|
||||
w.freq = freq
|
||||
|
||||
log.info(f'Waveform {w.ID} of type {w.type} with maximum amplitude scaling {w.amp:g}, frequency {w.freq:g}Hz created.')
|
||||
logger.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)
|
||||
|
||||
@@ -132,6 +133,7 @@ class VoltageSource(UserObjectMulti):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.order = 2
|
||||
self.hash = '#voltage_source'
|
||||
|
||||
def create(self, grid, uip):
|
||||
@@ -194,7 +196,7 @@ class VoltageSource(UserObjectMulti):
|
||||
|
||||
v.calculate_waveform_values(grid)
|
||||
|
||||
log.info(f'Voltage source with polarity {v.polarisation} at {v.xcoord * grid.dx:g}m, {v.ycoord * grid.dy:g}m, {v.zcoord * grid.dz:g}m, resistance {v.resistance:.1f} Ohms,' + startstop + f'using waveform {v.waveformID} created.')
|
||||
logger.info(f'Voltage source with polarity {v.polarisation} at {v.xcoord * grid.dx:g}m, {v.ycoord * grid.dy:g}m, {v.zcoord * grid.dz:g}m, resistance {v.resistance:.1f} Ohms,' + startstop + f'using waveform {v.waveformID} created.')
|
||||
|
||||
grid.voltagesources.append(v)
|
||||
|
||||
@@ -217,6 +219,7 @@ class HertzianDipole(UserObjectMulti):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.order = 3
|
||||
self.hash = '#hertzian_dipole'
|
||||
|
||||
def create(self, grid, uip):
|
||||
@@ -287,9 +290,9 @@ class HertzianDipole(UserObjectMulti):
|
||||
h.calculate_waveform_values(grid)
|
||||
|
||||
if grid.mode == '2D':
|
||||
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.')
|
||||
logger.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:
|
||||
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.')
|
||||
logger.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)
|
||||
|
||||
@@ -312,6 +315,7 @@ class MagneticDipole(UserObjectMulti):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.order = 4
|
||||
self.hash = '#magnetic_dipole'
|
||||
|
||||
def create(self, grid, uip):
|
||||
@@ -372,7 +376,7 @@ class MagneticDipole(UserObjectMulti):
|
||||
|
||||
m.calculate_waveform_values(grid)
|
||||
|
||||
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.')
|
||||
logger.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)
|
||||
|
||||
@@ -397,6 +401,7 @@ class TransmissionLine(UserObjectMulti):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.order = 5
|
||||
self.hash = '#transmission_line'
|
||||
|
||||
def create(self, grid, uip):
|
||||
@@ -464,7 +469,7 @@ class TransmissionLine(UserObjectMulti):
|
||||
t.calculate_waveform_values(grid)
|
||||
t.calculate_incident_V_I(grid)
|
||||
|
||||
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.')
|
||||
logger.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)
|
||||
|
||||
@@ -485,6 +490,7 @@ class Rx(UserObjectMulti):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.order = 6
|
||||
self.hash = '#rx'
|
||||
self.constructor = RxUser
|
||||
|
||||
@@ -492,7 +498,7 @@ class Rx(UserObjectMulti):
|
||||
try:
|
||||
p1 = self.kwargs['p1']
|
||||
except KeyError:
|
||||
log.exception(f'KeyError with {self.params_str()}')
|
||||
logger.exception(f'KeyError with {self.params_str()}')
|
||||
|
||||
p = uip.check_src_rx_point(p1, self.params_str())
|
||||
|
||||
@@ -519,7 +525,7 @@ class Rx(UserObjectMulti):
|
||||
else:
|
||||
raise CmdInputError(f"'{self.params_str()}' contains an output type that is not allowable. Allowable outputs in current context are {allowableoutputs}")
|
||||
|
||||
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.")
|
||||
logger.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)
|
||||
|
||||
@@ -539,6 +545,7 @@ class RxArray(UserObjectMulti):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.order = 7
|
||||
self.hash = '#rx_array'
|
||||
|
||||
def create(self, grid, uip):
|
||||
@@ -573,7 +580,7 @@ class RxArray(UserObjectMulti):
|
||||
else:
|
||||
raise CmdInputError(f"'{self.params_str()}' the step size should not be less than the spatial discretisation")
|
||||
|
||||
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')
|
||||
logger.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):
|
||||
@@ -588,7 +595,7 @@ class RxArray(UserObjectMulti):
|
||||
r.ID = r.__class__.__name__ + '(' + str(x) + ',' + str(y) + ',' + str(z) + ')'
|
||||
for key in RxUser.defaultoutputs:
|
||||
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.")
|
||||
logger.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)
|
||||
|
||||
|
||||
@@ -611,6 +618,7 @@ class Snapshot(UserObjectMulti):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.order = 8
|
||||
self.hash = '#snapshot'
|
||||
|
||||
def create(self, grid, uip):
|
||||
@@ -657,7 +665,7 @@ class Snapshot(UserObjectMulti):
|
||||
#else:
|
||||
s = SnapshotUser(xs, ys, zs, xf, yf, zf, dx, dy, dz, iterations, filename)
|
||||
|
||||
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.filename} created.')
|
||||
logger.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.filename} created.')
|
||||
|
||||
grid.snapshots.append(s)
|
||||
|
||||
@@ -677,6 +685,7 @@ class Material(UserObjectMulti):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.order = 9
|
||||
self.hash = '#material'
|
||||
|
||||
def create(self, grid, uip):
|
||||
@@ -715,7 +724,7 @@ class Material(UserObjectMulti):
|
||||
if m.se == float('inf'):
|
||||
m.averagable = False
|
||||
|
||||
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.')
|
||||
logger.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)
|
||||
@@ -736,6 +745,7 @@ class AddDebyeDispersion(UserObjectMulti):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.order = 10
|
||||
self.hash = '#add_dispersion_debye'
|
||||
|
||||
def create(self, grid, uip):
|
||||
@@ -768,7 +778,7 @@ class AddDebyeDispersion(UserObjectMulti):
|
||||
disp_material.averagable = False
|
||||
for i in range(0, poles):
|
||||
if tau[i] > 0:
|
||||
log.debug('Not checking if relaxation times are greater than time-step')
|
||||
logger.debug('Not checking if relaxation times are greater than time-step')
|
||||
disp_material.deltaer.append(er_delta[i])
|
||||
disp_material.tau.append(tau[i])
|
||||
else:
|
||||
@@ -779,7 +789,7 @@ class AddDebyeDispersion(UserObjectMulti):
|
||||
# Replace original material with newly created DispersiveMaterial
|
||||
grid.materials = [disp_material if mat.numID==material.numID else mat for mat in grid.materials]
|
||||
|
||||
log.info(f"Debye disperion added to {disp_material.ID} with delta_eps_r={', '.join('%4.2f' % deltaer for deltaer in disp_material.deltaer)}, and tau={', '.join('%4.3e' % tau for tau in disp_material.tau)} secs created.")
|
||||
logger.info(f"Debye disperion added to {disp_material.ID} with delta_eps_r={', '.join('%4.2f' % deltaer for deltaer in disp_material.deltaer)}, and tau={', '.join('%4.3e' % tau for tau in disp_material.tau)} secs created.")
|
||||
|
||||
|
||||
class AddLorentzDispersion(UserObjectMulti):
|
||||
@@ -799,6 +809,7 @@ class AddLorentzDispersion(UserObjectMulti):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.order = 11
|
||||
self.hash = '#add_dispersion_lorentz'
|
||||
|
||||
def create(self, grid, uip):
|
||||
@@ -843,7 +854,7 @@ class AddLorentzDispersion(UserObjectMulti):
|
||||
# Replace original material with newly created DispersiveMaterial
|
||||
grid.materials = [disp_material if mat.numID==material.numID else mat for mat in grid.materials]
|
||||
|
||||
log.info(f"Lorentz disperion added to {disp_material.ID} with delta_eps_r={', '.join('%4.2f' % deltaer for deltaer in disp_material.deltaer)}, omega={', '.join('%4.3e' % tau for tau in disp_material.tau)} secs, and gamma={', '.join('%4.3e' % alpha for alpha in disp_material.alpha)} created.")
|
||||
logger.info(f"Lorentz disperion added to {disp_material.ID} with delta_eps_r={', '.join('%4.2f' % deltaer for deltaer in disp_material.deltaer)}, omega={', '.join('%4.3e' % tau for tau in disp_material.tau)} secs, and gamma={', '.join('%4.3e' % alpha for alpha in disp_material.alpha)} created.")
|
||||
|
||||
|
||||
class AddDrudeDispersion(UserObjectMulti):
|
||||
@@ -861,6 +872,7 @@ class AddDrudeDispersion(UserObjectMulti):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.order = 12
|
||||
self.hash = '#add_dispersion_drude'
|
||||
|
||||
def create(self, grid, uip):
|
||||
@@ -903,7 +915,7 @@ class AddDrudeDispersion(UserObjectMulti):
|
||||
# Replace original material with newly created DispersiveMaterial
|
||||
grid.materials = [disp_material if mat.numID==material.numID else mat for mat in grid.materials]
|
||||
|
||||
log.info(f"Drude disperion added to {disp_material.ID} with omega={', '.join('%4.3e' % tau for tau in disp_material.tau)} secs, and gamma={', '.join('%4.3e' % alpha for alpha in disp_material.alpha)} secs created.")
|
||||
logger.info(f"Drude disperion added to {disp_material.ID} with omega={', '.join('%4.3e' % tau for tau in disp_material.tau)} secs, and gamma={', '.join('%4.3e' % alpha for alpha in disp_material.alpha)} secs created.")
|
||||
|
||||
|
||||
class SoilPeplinski(UserObjectMulti):
|
||||
@@ -925,6 +937,7 @@ class SoilPeplinski(UserObjectMulti):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.order = 13
|
||||
self.hash = '#soil_peplinski'
|
||||
|
||||
def create(self, grid, uip):
|
||||
@@ -957,7 +970,7 @@ class SoilPeplinski(UserObjectMulti):
|
||||
# Create a new instance of the Material class material (start index after pec & free_space)
|
||||
s = PeplinskiSoilUser(ID, sand_fraction, clay_fraction, bulk_density, sand_density, (water_fraction_lower, water_fraction_upper))
|
||||
|
||||
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.')
|
||||
logger.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)
|
||||
@@ -981,6 +994,7 @@ class GeometryView(UserObjectMulti):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.order = 14
|
||||
self.hash = '#geometry_view'
|
||||
self.multi_grid = False
|
||||
|
||||
@@ -1039,7 +1053,7 @@ class GeometryView(UserObjectMulti):
|
||||
|
||||
g = GeometryViewUser(xs, ys, zs, xf, yf, zf, dx, dy, dz, filename, fileext, grid)
|
||||
|
||||
log.info(f'Geometry view from {xs * grid.dx:g}m, {ys * grid.dy:g}m, {zs * grid.dz:g}m, to {xf * grid.dx:g}m, {yf * grid.dy:g}m, {zf * grid.dz:g}m, discretisation {dx * grid.dx:g}m, {dy * grid.dy:g}m, {dz * grid.dz:g}m, multi_grid={self.multi_grid}, grid={grid.name}, with filename base {g.filename} created.')
|
||||
logger.info(f'Geometry view from {xs * grid.dx:g}m, {ys * grid.dy:g}m, {zs * grid.dz:g}m, to {xf * grid.dx:g}m, {yf * grid.dy:g}m, {zf * grid.dz:g}m, discretisation {dx * grid.dx:g}m, {dy * grid.dy:g}m, {dz * grid.dz:g}m, multi_grid={self.multi_grid}, grid={grid.name}, with filename base {g.filename} created.')
|
||||
|
||||
# Append the new GeometryView object to the geometry views list
|
||||
grid.geometryviews.append(g)
|
||||
@@ -1059,6 +1073,7 @@ class GeometryObjectsWrite(UserObjectMulti):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.order = 15
|
||||
self.hash = '#geometry_objects_write'
|
||||
|
||||
def create(self, grid, uip):
|
||||
@@ -1075,7 +1090,7 @@ class GeometryObjectsWrite(UserObjectMulti):
|
||||
|
||||
g = GeometryObjectsUser(x0, y0, z0, x1, y1, z1, filename)
|
||||
|
||||
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}')
|
||||
logger.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)
|
||||
@@ -1114,6 +1129,7 @@ class PMLCFS(UserObjectMulti):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.order = 16
|
||||
self.hash = '#pml_cfs'
|
||||
PMLCFS.count += 1
|
||||
if PMLCFS.count == 2:
|
||||
@@ -1171,7 +1187,7 @@ class PMLCFS(UserObjectMulti):
|
||||
cfs.kappa = cfskappa
|
||||
cfs.sigma = cfssigma
|
||||
|
||||
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.')
|
||||
logger.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)
|
||||
|
||||
@@ -1198,4 +1214,4 @@ class SubgridHSG(UserObjectMulti):
|
||||
"""
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
log.debug('Is this required?')
|
||||
logger.debug('Is this required?')
|
||||
|
@@ -33,7 +33,7 @@ from .exceptions import CmdInputError
|
||||
from .waveforms import Waveform
|
||||
from .utilities import round_value
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Properties:
|
||||
@@ -59,32 +59,6 @@ class UserObjectSingle:
|
||||
pass
|
||||
|
||||
|
||||
class Messages(UserObjectSingle):
|
||||
"""Allows you to control the amount of information displayed on screen
|
||||
when gprMax is run
|
||||
|
||||
:param yn: Whether information should be displayed.
|
||||
:type yn: bool, optional
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.order = 0
|
||||
|
||||
def create(self, G, uip):
|
||||
try:
|
||||
yn = self.kwargs['yn']
|
||||
except KeyError:
|
||||
raise CmdInputError(self.__str__() + ' requires exactly one parameter')
|
||||
|
||||
if yn.lower() == 'y':
|
||||
config.sim_config.general['messages'] = True
|
||||
elif yn.lower() == 'n':
|
||||
config.sim_config.general['messages'] = False
|
||||
else:
|
||||
raise CmdInputError(self.__str__() + ' requires input values of either y or n')
|
||||
|
||||
|
||||
class Title(UserObjectSingle):
|
||||
"""Allows you to include a title for your model.
|
||||
|
||||
@@ -100,7 +74,7 @@ class Title(UserObjectSingle):
|
||||
try:
|
||||
title = self.kwargs['name']
|
||||
G.title = title
|
||||
log.info(f'Model title: {G.title}')
|
||||
logger.info(f'Model title: {G.title}')
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
@@ -125,7 +99,7 @@ class Domain(UserObjectSingle):
|
||||
if G.nx == 0 or G.ny == 0 or G.nz == 0:
|
||||
raise CmdInputError(f"'{self.params_str()}' requires at least one cell in every dimension")
|
||||
|
||||
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)")
|
||||
logger.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:
|
||||
@@ -144,8 +118,8 @@ class Domain(UserObjectSingle):
|
||||
G.mode = '3D'
|
||||
G.calculate_dt()
|
||||
|
||||
log.info(f'Mode: {G.mode}')
|
||||
log.info(f'Time step (at CFL limit): {G.dt:g} secs')
|
||||
logger.info(f'Mode: {G.mode}')
|
||||
logger.info(f'Time step (at CFL limit): {G.dt:g} secs')
|
||||
|
||||
|
||||
class Discretisation(UserObjectSingle):
|
||||
@@ -174,7 +148,7 @@ class Discretisation(UserObjectSingle):
|
||||
if G.dl[2] <= 0:
|
||||
raise CmdInputError(f"'{self.params_str()}' discretisation requires the z-direction spatial step to be greater than zero")
|
||||
|
||||
log.info(f'Spatial discretisation: {G.dl[0]:g} x {G.dl[1]:g} x {G.dl[2]:g}m')
|
||||
logger.info(f'Spatial discretisation: {G.dl[0]:g} x {G.dl[1]:g} x {G.dl[2]:g}m')
|
||||
|
||||
|
||||
class TimeWindow(UserObjectSingle):
|
||||
@@ -214,7 +188,7 @@ class TimeWindow(UserObjectSingle):
|
||||
if not G.timewindow:
|
||||
raise CmdInputError(f"'{self.params_str()}' specify a time or number of iterations")
|
||||
|
||||
log.info(f'Time window: {G.timewindow:g} secs ({G.iterations} iterations)')
|
||||
logger.info(f'Time window: {G.timewindow:g} secs ({G.iterations} iterations)')
|
||||
|
||||
|
||||
class NumThreads(UserObjectSingle):
|
||||
@@ -261,7 +235,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
|
||||
|
||||
log.info(f'Time step (modified): {G.dt:g} secs')
|
||||
logger.info(f'Time step (modified): {G.dt:g} secs')
|
||||
|
||||
|
||||
class PMLCells(UserObjectSingle):
|
||||
@@ -333,7 +307,7 @@ class SrcSteps(UserObjectSingle):
|
||||
except KeyError:
|
||||
raise CmdInputError('#src_steps: requires exactly three parameters')
|
||||
|
||||
log.info(f'Simple sources will step {G.srcsteps[0] * G.dx:g}m, {G.srcsteps[1] * G.dy:g}m, {G.srcsteps[2] * G.dz:g}m for each model run.')
|
||||
logger.info(f'Simple sources will step {G.srcsteps[0] * G.dx:g}m, {G.srcsteps[1] * G.dy:g}m, {G.srcsteps[2] * G.dz:g}m for each model run.')
|
||||
|
||||
|
||||
class RxSteps(UserObjectSingle):
|
||||
@@ -354,7 +328,7 @@ class RxSteps(UserObjectSingle):
|
||||
except KeyError:
|
||||
raise CmdInputError('#rx_steps: requires exactly three parameters')
|
||||
|
||||
log.info(f'All receivers will step {G.rxsteps[0] * G.dx:g}m, {G.rxsteps[1] * G.dy:g}m, {G.rxsteps[2] * G.dz:g}m for each model run.')
|
||||
logger.info(f'All receivers will step {G.rxsteps[0] * G.dx:g}m, {G.rxsteps[1] * G.dy:g}m, {G.rxsteps[2] * G.dz:g}m for each model run.')
|
||||
|
||||
|
||||
class ExcitationFile(UserObjectSingle):
|
||||
@@ -394,7 +368,7 @@ class ExcitationFile(UserObjectSingle):
|
||||
if not excitationfile.exists():
|
||||
excitationfile = Path(config.sim_config.input_file_path.parent, excitationfile)
|
||||
|
||||
log.info(f'Excitation file: {excitationfile}')
|
||||
logger.info(f'Excitation file: {excitationfile}')
|
||||
|
||||
# Get waveform names
|
||||
with open(excitationfile, 'r') as f:
|
||||
@@ -436,7 +410,7 @@ class ExcitationFile(UserObjectSingle):
|
||||
# Interpolate waveform values
|
||||
w.userfunc = interpolate.interp1d(waveformtime, singlewaveformvalues, **kwargs)
|
||||
|
||||
log.info(f"User waveform {w.ID} created using {timestr} and, if required, interpolation parameters (kind: {kwargs['kind']}, fill value: {kwargs['fill_value']}).")
|
||||
logger.info(f"User waveform {w.ID} created using {timestr} and, if required, interpolation parameters (kind: {kwargs['kind']}, fill value: {kwargs['fill_value']}).")
|
||||
|
||||
G.waveforms.append(w)
|
||||
|
||||
|
@@ -35,8 +35,7 @@ from .utilities import detect_check_gpus
|
||||
from .utilities import get_host_info
|
||||
from .utilities import get_terminal_width
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Single instance of SimConfig to hold simulation configuration parameters.
|
||||
sim_config = None
|
||||
@@ -185,12 +184,12 @@ class SimulationConfig:
|
||||
# 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
|
||||
# cpu, cuda, opencl: solver type
|
||||
# subgrid: whether the simulation uses sub-grids
|
||||
# precision: data type for electromagnetic field output (single/double)
|
||||
self.general = {'messages': True,
|
||||
|
||||
self.general = {'log_level': logging.WARNING,
|
||||
'progressbars': True,
|
||||
'cpu': True,
|
||||
'cuda': False,
|
||||
@@ -224,6 +223,10 @@ class SimulationConfig:
|
||||
if any(isinstance(element, list) for element in self.args.gpu):
|
||||
self.args.gpu = [val for sublist in self.args.gpu for val in sublist]
|
||||
|
||||
# If no deviceID is given default to 0
|
||||
if not self.args.gpu:
|
||||
self.args.gpu = [0]
|
||||
|
||||
self.cuda['gpus'] = detect_check_gpus(self.args.gpu)
|
||||
|
||||
# Subgrid parameter may not exist if user enters via CLI
|
||||
@@ -243,20 +246,21 @@ class SimulationConfig:
|
||||
self.scenes = []
|
||||
|
||||
# Set more complex parameters
|
||||
self.set_precision()
|
||||
self.get_byteorder()
|
||||
self.set_input_file_path()
|
||||
self.set_model_start_end()
|
||||
self.set_single_model()
|
||||
|
||||
def is_messages(self):
|
||||
return self.general['messages']
|
||||
self._set_precision()
|
||||
self._get_byteorder()
|
||||
self._set_input_file_path()
|
||||
self._set_model_start_end()
|
||||
self._set_single_model()
|
||||
|
||||
def set_model_gpu(self):
|
||||
"""Specify single GPU object for model."""
|
||||
return self.cuda['gpus'][0]
|
||||
"""Specify single GPU object for model.
|
||||
Uses first GPU deviceID if list of deviceID given."""
|
||||
|
||||
def set_precision(self):
|
||||
for gpu in self.cuda['gpus']:
|
||||
if gpu.deviceID == self.args.gpu[0]:
|
||||
return gpu
|
||||
|
||||
def _set_precision(self):
|
||||
"""Data type (precision) for electromagnetic field output.
|
||||
|
||||
Solid and ID arrays use 32-bit integers (0 to 4294967295)
|
||||
@@ -283,19 +287,19 @@ class SimulationConfig:
|
||||
'C_complex': 'pycuda::complex<double>',
|
||||
'vtk_float': 'Float64'}
|
||||
|
||||
def get_byteorder(self):
|
||||
def _get_byteorder(self):
|
||||
"""Check the byte order of system to use for VTK files, i.e. geometry
|
||||
views and snapshots.
|
||||
"""
|
||||
self.vtk_byteorder = 'LittleEndian' if sys.byteorder == 'little' else 'BigEndian'
|
||||
|
||||
def set_single_model(self):
|
||||
def _set_single_model(self):
|
||||
if self.model_start == 0 and self.model_end == 1:
|
||||
self.single_model = True
|
||||
else:
|
||||
self.single_model = False
|
||||
|
||||
def set_model_start_end(self):
|
||||
def _set_model_start_end(self):
|
||||
"""Set range for number of models to run (internally 0 index)."""
|
||||
if self.args.task:
|
||||
# Job array feeds args.n number of single tasks
|
||||
@@ -311,7 +315,7 @@ class SimulationConfig:
|
||||
self.model_start = modelstart
|
||||
self.model_end = modelend
|
||||
|
||||
def set_input_file_path(self):
|
||||
def _set_input_file_path(self):
|
||||
"""Set input file path for CLI or API."""
|
||||
# API
|
||||
if self.args.inputfile is None:
|
||||
@@ -329,11 +333,11 @@ class SimulationConfigMPI(SimulationConfig):
|
||||
def __init__(self, args):
|
||||
super().__init__(args)
|
||||
|
||||
def set_model_start_end(self):
|
||||
def _set_model_start_end(self):
|
||||
# Set range for number of models to run
|
||||
self.model_start = self.args.restart if self.args.restart else 1
|
||||
self.model_end = self.modelstart + self.args.n
|
||||
self.model_end = self.model_start + self.args.n
|
||||
|
||||
def set_gpus(self):
|
||||
def set_model_gpu(self):
|
||||
"""Leave list of GPU object(s) as multi-object list."""
|
||||
pass
|
||||
|
@@ -50,10 +50,33 @@ class Context:
|
||||
if config.sim_config.general['cuda']:
|
||||
self.print_gpu_info()
|
||||
self.tsimstart = timer()
|
||||
# Clear list of model configs. It can be retained when gprMax is
|
||||
# called in a loop, and want to avoid this.
|
||||
config.model_configs = []
|
||||
self._run()
|
||||
self.tsimend = timer()
|
||||
self.print_time_report()
|
||||
|
||||
def _run_model(self, i):
|
||||
"""Process for running a single model."""
|
||||
|
||||
config.model_num = i
|
||||
write_model_config()
|
||||
|
||||
# Always create a grid for the first model. The next model to run
|
||||
# only gets a new grid if the geometry is not re-used.
|
||||
if i != 0 and config.sim_config.args.geometry_fixed:
|
||||
config.get_model_config().reuse_geometry = True
|
||||
else:
|
||||
G = create_G()
|
||||
|
||||
model = ModelBuildRun(G)
|
||||
model.build()
|
||||
solver = create_solver(G)
|
||||
|
||||
if not config.sim_config.args.geometry_only:
|
||||
model.solve(solver)
|
||||
|
||||
def print_logo_copyright(self):
|
||||
"""Print gprMax logo, version, and copyright/licencing information."""
|
||||
logo(__version__ + ' (' + codename + ')')
|
||||
@@ -82,29 +105,9 @@ class NoMPIContext(Context):
|
||||
"""
|
||||
|
||||
def _run(self):
|
||||
"""Specialise how the models are farmed out."""
|
||||
|
||||
# Clear list of model configs. It can be retained with gprMax is
|
||||
# called in a loop, and want to avoid this.
|
||||
config.model_configs = []
|
||||
|
||||
"""Specialise how models are run."""
|
||||
for i in self.model_range:
|
||||
config.model_num = i
|
||||
write_model_config()
|
||||
|
||||
# Always create a grid for the first model. The next model to run
|
||||
# only gets a new grid if the geometry is not re-used.
|
||||
if i != 0 and config.sim_config.args.geometry_fixed:
|
||||
config.get_model_config().reuse_geometry = True
|
||||
else:
|
||||
G = create_G()
|
||||
|
||||
model = ModelBuildRun(G)
|
||||
model.build()
|
||||
solver = create_solver(G)
|
||||
|
||||
if not config.sim_config.args.geometry_only:
|
||||
model.solve(solver)
|
||||
self._run_model(i)
|
||||
|
||||
|
||||
class MPIContext(Context):
|
||||
@@ -116,9 +119,27 @@ class MPIContext(Context):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
from mpi4py import MPI
|
||||
from gprMax.mpi import MPIExecutor
|
||||
|
||||
self.comm = MPI.COMM_WORLD
|
||||
self.rank = self.comm.rank
|
||||
self.MPIExecutor = MPIExecutor
|
||||
|
||||
def _run(self):
|
||||
pass
|
||||
"""Specialise how the models are run."""
|
||||
|
||||
# compile jobs
|
||||
jobs = []
|
||||
for i in range(config.sim_config.args.n):
|
||||
jobs.append({'i': i})
|
||||
|
||||
# Execute jobs
|
||||
log.info(f'Starting execution of {config.sim_config.args.n} gprMax model runs.')
|
||||
with self.MPIExecutor(self._run_model, comm=self.comm) as executor:
|
||||
if executor is not None:
|
||||
results = executor.submit(jobs)
|
||||
log.info('Results: %s' % str(results))
|
||||
log.info('Finished.')
|
||||
|
||||
|
||||
def create_context():
|
||||
|
@@ -22,7 +22,7 @@ from colorama import init
|
||||
from colorama import Fore
|
||||
init()
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GeneralError(ValueError):
|
||||
@@ -30,7 +30,7 @@ class GeneralError(ValueError):
|
||||
def __init__(self, message, *args):
|
||||
self.message = message
|
||||
super(GeneralError, self).__init__(message, *args)
|
||||
log.exception(Fore.RED)
|
||||
logger.exception(Fore.RED)
|
||||
|
||||
|
||||
class CmdInputError(Exception):
|
||||
@@ -41,4 +41,4 @@ class CmdInputError(Exception):
|
||||
# def __init__(self, message, *args):
|
||||
# self.message = message
|
||||
# super(CmdInputError, self).__init__(message, *args)
|
||||
# log.exception(Fore.RED)
|
||||
# logger.exception(Fore.RED)
|
||||
|
@@ -24,7 +24,7 @@ import h5py
|
||||
|
||||
from ._version import __version__
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def store_outputs(G):
|
||||
@@ -126,7 +126,7 @@ def write_hdf5_outputfile(outputfile, G):
|
||||
write_grid(grp, sg, is_subgrid=True)
|
||||
|
||||
if G.rxs or sg_rxs:
|
||||
log.info(f'Written output file: {outputfile.name}')
|
||||
logger.info(f'Written output file: {outputfile.name}')
|
||||
|
||||
|
||||
def write_grid(basegrp, G, is_subgrid=False):
|
||||
|
@@ -31,7 +31,7 @@ from .cython.geometry_outputs import define_normal_geometry
|
||||
from .cython.geometry_outputs import define_fine_geometry
|
||||
from .utilities import round_value
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GeometryView:
|
||||
@@ -482,7 +482,7 @@ class GeometryViewFineMultiGrid:
|
||||
f.write('</Piece>\n</PolyData>\n<AppendedData encoding="raw">\n_'.encode('utf-8'))
|
||||
|
||||
# Write points
|
||||
log.info('\nWriting points main grid')
|
||||
logger.info('\nWriting points main grid')
|
||||
datasize = np.dtype(np.float32).itemsize * self.vtk_numpoints * self.vtk_numpoint_components
|
||||
f.write(pack('I', datasize))
|
||||
for i in range(0, G.nx + 1):
|
||||
@@ -491,7 +491,7 @@ class GeometryViewFineMultiGrid:
|
||||
f.write(pack('fff', i * G.dx, j * G.dy, k * G.dz))
|
||||
|
||||
for sg_v in self.sg_views:
|
||||
log.info('Writing points subgrid')
|
||||
logger.info('Writing points subgrid')
|
||||
sg_v.write_points(f, G)
|
||||
|
||||
n_x_lines = self.nx * (self.ny + 1) * (self.nz + 1)
|
||||
@@ -506,7 +506,7 @@ class GeometryViewFineMultiGrid:
|
||||
z_lines = np.zeros((n_z_lines, 2), dtype=np.uint32)
|
||||
z_materials = np.zeros((n_z_lines), dtype=np.uint32)
|
||||
|
||||
log.info('Calculate connectivity main grid')
|
||||
logger.info('Calculate connectivity main grid')
|
||||
label = 0
|
||||
counter_x = 0
|
||||
counter_y = 0
|
||||
@@ -538,7 +538,7 @@ class GeometryViewFineMultiGrid:
|
||||
|
||||
label = label + 1
|
||||
|
||||
log.info('Calculate connectivity subgrids')
|
||||
logger.info('Calculate connectivity subgrids')
|
||||
for sg_v in self.sg_views:
|
||||
sg_v.populate_connectivity_and_materials(label)
|
||||
# use the last subgrids label for the next view
|
||||
|
@@ -21,11 +21,9 @@ import logging
|
||||
|
||||
from .config_parser import write_simulation_config
|
||||
from .contexts import create_context
|
||||
from .utilities import setup_logging
|
||||
|
||||
# Configure logging
|
||||
log = logging.getLogger(__name__)
|
||||
# logging.basicConfig(level=logging.DEBUG, format='%(module)s %(lineno)d %(message)s')
|
||||
logging.basicConfig(level=logging.INFO, format='%(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def run(
|
||||
@@ -36,8 +34,6 @@ def run(
|
||||
task=None,
|
||||
restart=None,
|
||||
mpi=False,
|
||||
mpi_no_spawn=False,
|
||||
mpicomm=None,
|
||||
gpu=None,
|
||||
subgrid=None,
|
||||
autotranslate=False,
|
||||
@@ -45,7 +41,7 @@ def run(
|
||||
geometry_fixed=False,
|
||||
write_processed=False,
|
||||
):
|
||||
"""This is the main function for gprMax when entering as application
|
||||
"""This is the main function for gprMax when entering using application
|
||||
programming interface (API). Run the simulation for the
|
||||
given list of scenes.
|
||||
|
||||
@@ -75,13 +71,13 @@ def run(
|
||||
from A-scan 45 when creating a B-scan with 60 traces.
|
||||
:type restart: int, optional
|
||||
|
||||
:param mpi: number of Message Passing Interface (MPI) tasks,
|
||||
i.e. master + workers, for MPI task farm. This option is most
|
||||
usefully combined with n to allow individual models to be farmed
|
||||
out using a MPI task farm, e.g. to create a B-scan with 60 traces
|
||||
and use MPI to farm out each trace1. For further details see the
|
||||
parallel performance section of the User Guide.
|
||||
:type mpi: int, optional
|
||||
:param mpi: flag to use Message Passing Interface (MPI) task farm. This
|
||||
option is most usefully combined with n to allow individual
|
||||
models to be farmed out using a MPI task farm, e.g. to create a
|
||||
B-scan with 60 traces and use MPI to farm out each trace.
|
||||
For further details see the parallel performance section of the
|
||||
User Guide.
|
||||
:type mpi: bool, optional
|
||||
|
||||
:param gpu: flag to use NVIDIA GPU or list of NVIDIA GPU device ID(s) for
|
||||
specific GPU card(s).
|
||||
@@ -122,7 +118,6 @@ def run(
|
||||
args.task = task
|
||||
args.restart = restart
|
||||
args.mpi = mpi
|
||||
args.mpicomm = mpicomm
|
||||
args.gpu = gpu
|
||||
args.subgrid = subgrid
|
||||
args.autotranslate = autotranslate
|
||||
@@ -133,29 +128,47 @@ def run(
|
||||
try:
|
||||
run_main(args)
|
||||
except Exception:
|
||||
log.exception('Error from main API function', exc_info=True)
|
||||
logger.exception('Error from main API function', exc_info=True)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function for gprMax when entering from the command line interface (CLI)."""
|
||||
"""Main function for gprMax when entering using the command line interface (CLI)."""
|
||||
|
||||
# Parse command line arguments
|
||||
parser = argparse.ArgumentParser(prog='gprMax', formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('inputfile', help='path to, and name of inputfile or file object')
|
||||
parser.add_argument('-n', default=1, type=int, help='number of times to run the input file, e.g. to create a B-scan')
|
||||
parser.add_argument('-task', type=int, help='task identifier (model number) for job array on Open Grid Scheduler/Grid Engine (http://gridscheduler.sourceforge.net/index.html)')
|
||||
parser.add_argument('-restart', type=int, help='model number to restart from, e.g. when creating B-scan')
|
||||
parser.add_argument('-mpi', type=int, help='number of MPI tasks, i.e. master + workers')
|
||||
parser.add_argument('-gpu', type=int, action='append', nargs='*', help='flag to use Nvidia GPU or option to give list of device ID(s)')
|
||||
parser.add_argument('--geometry-only', action='store_true', default=False, help='flag to only build model and produce geometry file(s)')
|
||||
parser.add_argument('--geometry-fixed', action='store_true', default=False, help='flag to not reprocess model geometry, e.g. for B-scans where the geometry is fixed')
|
||||
parser.add_argument('--write-processed', action='store_true', default=False, help='flag to write an input file after any Python code and include commands in the original input file have been processed')
|
||||
parser.add_argument('inputfile',
|
||||
help='relative or absolute path to inputfile')
|
||||
parser.add_argument('-n', default=1, type=int,
|
||||
help='number of times to run the input file, e.g. to create a B-scan')
|
||||
parser.add_argument('-task', type=int,
|
||||
help='task identifier (model number) for job array on '
|
||||
'Open Grid Scheduler/Grid Engine (http://gridscheduler.sourceforge.net/index.html)')
|
||||
parser.add_argument('-r', '--restart', type=int,
|
||||
help='model number to restart from, e.g. when creating B-scan')
|
||||
parser.add_argument('-mpi', action='store_true', default=False,
|
||||
help='flag to enable MPI task farming')
|
||||
parser.add_argument('-gpu', type=int, action='append', nargs='*',
|
||||
help='flag to use Nvidia GPU or option to give list of device ID(s)')
|
||||
parser.add_argument('--geometry-only', action='store_true', default=False,
|
||||
help='flag to only build model and produce geometry file(s)')
|
||||
parser.add_argument('--geometry-fixed', action='store_true', default=False,
|
||||
help='flag to not reprocess model geometry, e.g. for B-scans where the geometry is fixed')
|
||||
parser.add_argument('--write-processed', action='store_true', default=False,
|
||||
help='flag to write an input file after any Python code and include commands '
|
||||
'in the original input file have been processed')
|
||||
parser.add_argument('-l', '--logfile', action='store_true', default=False,
|
||||
help='flag to enable writing to a log file')
|
||||
parser.add_argument('-v', '--verbose', action='store_true', default=False,
|
||||
help="flag to increase output")
|
||||
args = parser.parse_args()
|
||||
|
||||
setup_logging()
|
||||
|
||||
try:
|
||||
run_main(args)
|
||||
except Exception:
|
||||
log.exception('Error from main CLI function', exc_info=True)
|
||||
logger.exception('Error from main CLI function', exc_info=True)
|
||||
|
||||
|
||||
def run_main(args):
|
||||
"""Called by either run (API) or main (CLI).
|
||||
|
@@ -28,7 +28,7 @@ from .hash_cmds_geometry import process_geometrycmds
|
||||
from .hash_cmds_multiuse import process_multicmds
|
||||
from .hash_cmds_singleuse import process_singlecmds
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def process_python_include_code(inputfile, usernamespace):
|
||||
@@ -62,7 +62,7 @@ def process_python_include_code(inputfile, usernamespace):
|
||||
|
||||
# Process any Python code
|
||||
if(inputlines[x].startswith('#python:')):
|
||||
log.warning('#python blocks are deprecated and will be removed in the next release of gprMax. Please convert your model to use our Python API instead.\n')
|
||||
logger.warning('#python blocks are deprecated and will be removed in the next release of gprMax. Please convert your model to use our Python API instead.\n')
|
||||
# String to hold Python code to be executed
|
||||
pythoncode = ''
|
||||
x += 1
|
||||
@@ -99,7 +99,7 @@ def process_python_include_code(inputfile, usernamespace):
|
||||
|
||||
# Print any generated output that is not commands
|
||||
if pythonout:
|
||||
log.info(f'Python messages (from stdout/stderr): {pythonout}\n')
|
||||
logger.info(f'Python messages (from stdout/stderr): {pythonout}\n')
|
||||
|
||||
# Add any other commands to list
|
||||
elif(inputlines[x].startswith('#')):
|
||||
@@ -157,7 +157,7 @@ def process_include_files(hashcmds, inputfile):
|
||||
x += 1
|
||||
|
||||
return processedincludecmds
|
||||
|
||||
logger
|
||||
|
||||
def write_processed_file(processedlines, G):
|
||||
"""Writes an input file after any Python code and include commands
|
||||
@@ -176,7 +176,7 @@ def write_processed_file(processedlines, G):
|
||||
for item in processedlines:
|
||||
f.write(f'{item}')
|
||||
|
||||
log.info(f'Written input commands, after processing any Python code and include commands, to file: {processedfile}\n')
|
||||
logger.info(f'Written input commands, after processing any Python code and include commands, to file: {processedfile}\n')
|
||||
|
||||
|
||||
def check_cmd_names(processedlines, checkessential=True):
|
||||
@@ -306,7 +306,7 @@ def parse_hash_commands(scene, G):
|
||||
for key, value in sorted(usernamespace.items()):
|
||||
if key != '__builtins__':
|
||||
uservars += f'{key}: {value}, '
|
||||
log.info(f'Constants/variables used/available for Python scripting: {{{uservars[:-2]}}}\n')
|
||||
logger.info(f'Constants/variables used/available for Python scripting: {{{uservars[:-2]}}}\n')
|
||||
|
||||
# Write a file containing the input commands after Python or include
|
||||
# file commands have been processed
|
||||
|
@@ -16,7 +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/>.
|
||||
|
||||
from .cmds_single_use import Messages
|
||||
from .cmds_single_use import Title
|
||||
from .cmds_single_use import NumThreads
|
||||
from .cmds_single_use import Discretisation
|
||||
@@ -46,15 +45,6 @@ def process_singlecmds(singlecmds):
|
||||
scene_objects = []
|
||||
|
||||
# Check validity of command parameters in order needed
|
||||
cmd = '#messages'
|
||||
if singlecmds[cmd] is not None:
|
||||
tmp = singlecmds[cmd].split()
|
||||
if len(tmp) != 1:
|
||||
raise CmdInputError(cmd + ' requires exactly one parameter')
|
||||
|
||||
messages = Messages(yn=str(tmp[0]))
|
||||
scene_objects.append(messages)
|
||||
|
||||
cmd = '#title'
|
||||
if singlecmds[cmd] is not None:
|
||||
title = Title(name=str(singlecmds[cmd]))
|
||||
@@ -141,7 +131,7 @@ def process_singlecmds(singlecmds):
|
||||
tmp = singlecmds[cmd].split()
|
||||
if len(tmp) != 3:
|
||||
raise CmdInputError(cmd + ' requires exactly three parameters')
|
||||
|
||||
|
||||
p1 = (float(tmp[0]), float(tmp[1]), float(tmp[2]))
|
||||
src_steps = SrcSteps(p1=p1)
|
||||
scene_objects.append(src_steps)
|
||||
|
@@ -50,7 +50,7 @@ from .utilities import human_size
|
||||
from .utilities import mem_check_all
|
||||
from .utilities import set_omp_threads
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ModelBuildRun:
|
||||
@@ -78,7 +78,7 @@ class ModelBuildRun:
|
||||
# Normal model reading/building process; bypassed if geometry information to be reused
|
||||
self.build_geometry() if not config.get_model_config().reuse_geometry else self.reuse_geometry()
|
||||
|
||||
log.info(f'\nOutput directory: {config.get_model_config().output_file_path.parent.resolve()}')
|
||||
logger.info(f'\nOutput directory: {config.get_model_config().output_file_path.parent.resolve()}')
|
||||
|
||||
# Adjust position of simple sources and receivers if required
|
||||
if G.srcsteps[0] != 0 or G.srcsteps[1] != 0 or G.srcsteps[2] != 0:
|
||||
@@ -110,9 +110,9 @@ class ModelBuildRun:
|
||||
|
||||
# Write files for any geometry views and geometry object outputs
|
||||
if not (G.geometryviews or G.geometryobjectswrite) and config.sim_config.args.geometry_only:
|
||||
log.warning(Fore.RED + f'\nNo geometry views or geometry objects found.' + Style.RESET_ALL)
|
||||
logger.warning(Fore.RED + f'\nNo geometry views or geometry objects found.' + Style.RESET_ALL)
|
||||
for i, geometryview in enumerate(G.geometryviews):
|
||||
log.info('')
|
||||
logger.info('')
|
||||
geometryview.set_filename()
|
||||
pbar = tqdm(total=geometryview.datawritesize, unit='byte', unit_scale=True,
|
||||
desc=f'Writing geometry view file {i + 1}/{len(G.geometryviews)}, {geometryview.filename.name}',
|
||||
@@ -121,7 +121,7 @@ class ModelBuildRun:
|
||||
geometryview.write_vtk(G, pbar)
|
||||
pbar.close()
|
||||
for i, geometryobject in enumerate(G.geometryobjectswrite):
|
||||
log.info('')
|
||||
logger.info('')
|
||||
pbar = tqdm(total=geometryobject.datawritesize, unit='byte', unit_scale=True,
|
||||
desc=f'Writing geometry object file {i + 1}/{len(G.geometryobjectswrite)}, {geometryobject.filename.name}',
|
||||
ncols=get_terminal_width() - 1, file=sys.stdout,
|
||||
@@ -132,13 +132,13 @@ class ModelBuildRun:
|
||||
def build_geometry(self):
|
||||
G = self.G
|
||||
|
||||
log.info(config.get_model_config().inputfilestr)
|
||||
logger.info(config.get_model_config().inputfilestr)
|
||||
|
||||
scene = self.build_scene()
|
||||
|
||||
# Print info on any subgrids
|
||||
for grid in G.subgrids:
|
||||
log.info(grid)
|
||||
logger.info(grid)
|
||||
|
||||
# Combine available grids
|
||||
grids = [G] + G.subgrids
|
||||
@@ -154,12 +154,12 @@ class ModelBuildRun:
|
||||
|
||||
# Check memory requirements
|
||||
total_mem, mem_strs = mem_check_all(grids)
|
||||
log.info(f'\nMemory required: {" + ".join(mem_strs)} + ~{human_size(config.get_model_config().mem_overhead)} overhead = {human_size(total_mem)}')
|
||||
logger.info(f'\nMemory required: {" + ".join(mem_strs)} + ~{human_size(config.get_model_config().mem_overhead)} overhead = {human_size(total_mem)}')
|
||||
|
||||
# Build grids
|
||||
gridbuilders = [GridBuilder(grid) for grid in grids]
|
||||
for gb in gridbuilders:
|
||||
log.info(print_pml_info(gb.grid))
|
||||
logger.info(print_pml_info(gb.grid))
|
||||
if not all(value == 0 for value in gb.grid.pmlthickness.values()):
|
||||
gb.build_pmls()
|
||||
gb.build_components()
|
||||
@@ -175,21 +175,21 @@ class ModelBuildRun:
|
||||
# Check to see if numerical dispersion might be a problem
|
||||
results = dispersion_analysis(gb.grid)
|
||||
if results['error']:
|
||||
log.warning(Fore.RED + f"\nNumerical dispersion analysis [{gb.grid.name}] not carried out as {results['error']}" + Style.RESET_ALL)
|
||||
logger.warning(Fore.RED + f"\nNumerical dispersion analysis [{gb.grid.name}] not carried out as {results['error']}" + Style.RESET_ALL)
|
||||
elif results['N'] < config.get_model_config().numdispersion['mingridsampling']:
|
||||
raise GeneralError(f"\nNon-physical wave propagation in [{gb.grid.name}] detected. Material '{results['material'].ID}' has wavelength sampled by {results['N']} cells, less than required minimum for physical wave propagation. Maximum significant frequency estimated as {results['maxfreq']:g}Hz")
|
||||
elif (results['deltavp'] and np.abs(results['deltavp']) >
|
||||
config.get_model_config().numdispersion['maxnumericaldisp']):
|
||||
log.warning(Fore.RED + f"\n[{gb.grid.name}] has potentially significant numerical dispersion. Estimated largest physical phase-velocity error is {results['deltavp']:.2f}% in material '{results['material'].ID}' whose wavelength sampled by {results['N']} cells. Maximum significant frequency estimated as {results['maxfreq']:g}Hz" + Style.RESET_ALL)
|
||||
logger.warning(Fore.RED + f"\n[{gb.grid.name}] has potentially significant numerical dispersion. Estimated largest physical phase-velocity error is {results['deltavp']:.2f}% in material '{results['material'].ID}' whose wavelength sampled by {results['N']} cells. Maximum significant frequency estimated as {results['maxfreq']:g}Hz" + Style.RESET_ALL)
|
||||
elif results['deltavp']:
|
||||
log.info(f"\nNumerical dispersion analysis [{gb.grid.name}]: estimated largest physical phase-velocity error is {results['deltavp']:.2f}% in material '{results['material'].ID}' whose wavelength sampled by {results['N']} cells. Maximum significant frequency estimated as {results['maxfreq']:g}Hz")
|
||||
logger.info(f"\nNumerical dispersion analysis [{gb.grid.name}]: estimated largest physical phase-velocity error is {results['deltavp']:.2f}% in material '{results['material'].ID}' whose wavelength sampled by {results['N']} cells. Maximum significant frequency estimated as {results['maxfreq']:g}Hz")
|
||||
|
||||
def reuse_geometry(self):
|
||||
# Reset iteration number
|
||||
self.G.iteration = 0
|
||||
s = f'\n--- Model {config.get_model_config().appendmodelnumber}/{config.sim_config.model_end}, input file (not re-processed, i.e. geometry fixed): {config.sim_config.input_file_path}'
|
||||
config.get_model_config().inputfilestr = Fore.GREEN + f"{s} {'-' * (get_terminal_width() - 1 - len(s))}\n" + Style.RESET_ALL
|
||||
log.info(config.get_model_config().inputfilestr)
|
||||
logger.info(config.get_model_config().inputfilestr)
|
||||
for grid in [self.G] + self.G.subgrids:
|
||||
grid.reset_fields()
|
||||
|
||||
@@ -223,7 +223,7 @@ class ModelBuildRun:
|
||||
snapshotdir = config.get_model_config().snapshot_file_path
|
||||
snapshotdir.mkdir(exist_ok=True)
|
||||
|
||||
log.info('')
|
||||
logger.info('')
|
||||
for i, snap in enumerate(self.G.snapshots):
|
||||
fn = snapshotdir / Path(snap.filename)
|
||||
snap.filename = fn.with_suffix('.vti')
|
||||
@@ -231,7 +231,7 @@ class ModelBuildRun:
|
||||
unit_scale=True, desc=f'Writing snapshot file {i + 1} of {len(self.G.snapshots)}, {snap.filename.name}', ncols=get_terminal_width() - 1, file=sys.stdout, disable=not config.sim_config.general['progressbars'])
|
||||
snap.write_vtk_imagedata(pbar, self.G)
|
||||
pbar.close()
|
||||
log.info('')
|
||||
logger.info('')
|
||||
|
||||
def print_resource_info(self, tsolve, memsolve):
|
||||
"""Print resource information on runtime and memory usage.
|
||||
@@ -245,8 +245,8 @@ class ModelBuildRun:
|
||||
if config.sim_config.general['cuda']:
|
||||
mem_str = f' host + ~{human_size(memsolve)} GPU'
|
||||
|
||||
log.info(f'\nMemory used: ~{human_size(self.p.memory_full_info().uss)}{mem_str}')
|
||||
log.info(f'Solving time [HH:MM:SS]: {datetime.timedelta(seconds=tsolve)}')
|
||||
logger.info(f'\nMemory used: ~{human_size(self.p.memory_full_info().uss)}{mem_str}')
|
||||
logger.info(f'Solving time [HH:MM:SS]: {datetime.timedelta(seconds=tsolve)}')
|
||||
|
||||
def solve(self, solver):
|
||||
"""Solve using FDTD method.
|
||||
@@ -260,15 +260,15 @@ class ModelBuildRun:
|
||||
|
||||
# Check number of OpenMP threads
|
||||
if config.sim_config.general['cpu']:
|
||||
log.info(f'CPU (OpenMP) threads for solving: {config.get_model_config().ompthreads}\n')
|
||||
logger.info(f'CPU (OpenMP) threads for solving: {config.get_model_config().ompthreads}\n')
|
||||
if config.get_model_config().ompthreads > config.sim_config.hostinfo['physicalcores']:
|
||||
log.warning(Fore.RED + f"You have specified more threads ({config.get_model_config().ompthreads}) than available physical CPU cores ({config.sim_config.hostinfo['physicalcores']}). This may lead to degraded performance." + Style.RESET_ALL)
|
||||
logger.warning(Fore.RED + f"You have specified more threads ({config.get_model_config().ompthreads}) than available physical CPU cores ({config.sim_config.hostinfo['physicalcores']}). This may lead to degraded performance." + Style.RESET_ALL)
|
||||
# Print information about any GPU in use
|
||||
elif config.sim_config.general['cuda']:
|
||||
log.info(f"GPU for solving: {config.get_model_config().cuda['gpu'].deviceID} - {config.get_model_config().cuda['gpu'].name}\n")
|
||||
logger.info(f"GPU for solving: {config.get_model_config().cuda['gpu'].deviceID} - {config.get_model_config().cuda['gpu'].name}\n")
|
||||
|
||||
# Prepare iterator
|
||||
if config.sim_config.is_messages():
|
||||
if config.sim_config.general['progressbars']:
|
||||
iterator = tqdm(range(self.G.iterations), desc=f'Running model {config.model_num + 1}/{config.sim_config.model_end}', ncols=get_terminal_width() - 1, file=sys.stdout, disable=not config.sim_config.general['progressbars'])
|
||||
else:
|
||||
iterator = range(self.G.iterations)
|
||||
@@ -303,7 +303,7 @@ class GridBuilder:
|
||||
def build_components(self):
|
||||
# Build the model, i.e. set the material properties (ID) for every edge
|
||||
# of every Yee cell
|
||||
log.info('')
|
||||
logger.info('')
|
||||
pbar = tqdm(total=2, desc=f'Building Yee cells [{self.grid.name}]',
|
||||
ncols=get_terminal_width() - 1, file=sys.stdout,
|
||||
disable=not config.sim_config.general['progressbars'])
|
||||
@@ -335,5 +335,5 @@ class GridBuilder:
|
||||
materialstable.outer_border = False
|
||||
materialstable.justify_columns[0] = 'right'
|
||||
|
||||
log.info(f'\nMaterials [{self.grid.name}]:')
|
||||
log.info(materialstable.table)
|
||||
logger.info(f'\nMaterials [{self.grid.name}]:')
|
||||
logger.info(materialstable.table)
|
||||
|
463
gprMax/mpi.py
普通文件
463
gprMax/mpi.py
普通文件
@@ -0,0 +1,463 @@
|
||||
# Copyright (C) 2015-2020: The University of Edinburgh
|
||||
# Authors: Tobias Schruff
|
||||
#
|
||||
# This file is part of gprMax.
|
||||
#
|
||||
# gprMax is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# gprMax is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with gprMax. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from enum import IntEnum
|
||||
import logging
|
||||
import time
|
||||
|
||||
from mpi4py import MPI
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
"""
|
||||
MPI communication tags.
|
||||
READY
|
||||
Send by worker to master to signal that it is ready to receive a new job.
|
||||
START
|
||||
Send by master to worker together with a job dict to initiate work.
|
||||
DONE
|
||||
Send by worker to master together with the results of the current job.
|
||||
EXIT
|
||||
Send by master to worker to initiate worker shutdown and then
|
||||
send back to master to signal shutdown has completed.
|
||||
"""
|
||||
Tags = IntEnum('Tags', 'READY START DONE EXIT')
|
||||
|
||||
|
||||
class MPIExecutor(object):
|
||||
"""A generic parallel executor based on MPI.
|
||||
This executor can be used to run generic jobs on multiple
|
||||
processes based on a master/worker pattern with MPI being used for
|
||||
communication between the master and the workers.
|
||||
Examples
|
||||
--------
|
||||
A basic example of how to use the `MPIExecutor` to run
|
||||
`gprMax` models in parallel is given below.
|
||||
>>> from mpi4py import MPI
|
||||
>>> from gprMax.mpi import MPIExecutor
|
||||
>>> from gprMax.model_build_run import run_model
|
||||
>>> # choose an MPI.Intracomm for communication (MPI.COMM_WORLD by default)
|
||||
>>> comm = MPI.COMM_WORLD
|
||||
>>> # choose a target function
|
||||
>>> func = run_model
|
||||
>>> # define job parameters
|
||||
>>> inputfile = 'some_input_file.in'
|
||||
>>> n_traces = 10
|
||||
>>> jobs = []
|
||||
>>> # create jobs
|
||||
>>> for i in range(n_traces):
|
||||
>>> jobs.append({
|
||||
>>> 'inputfile': inputfile,
|
||||
>>> 'currentmodelrun': i + 1,
|
||||
>>> 'modelend': n_traces,
|
||||
>>> 'numbermodelruns': n_traces
|
||||
>>> })
|
||||
>>> gpr = MPIExecutor(func, comm=comm)
|
||||
>>> # send the workers to their work loop
|
||||
>>> gpr.start()
|
||||
>>> if gpr.is_master():
|
||||
>>> results = gpr.submit(jobs)
|
||||
>>> print('Results:', results)
|
||||
>>> # make the workers exit their work loop
|
||||
>>> # and join the main loop again
|
||||
>>> gpr.join()
|
||||
A slightly more concise way is to use the context manager
|
||||
interface of `MPIExecutor` that automatically takes care
|
||||
of calling `start()` and `join()` at the beginning and end
|
||||
of the execution, respectively.
|
||||
>>> with MPIExecutor(func, comm=comm) as executor:
|
||||
>>> # executor will be None on all ranks except for the master
|
||||
>>> if executor is not None:
|
||||
>>> results = executor.submit(jobs)
|
||||
>>> print('Results:', results)
|
||||
Limitations
|
||||
-----------
|
||||
Because some popular MPI implementations (especially on HPC machines) do not
|
||||
support concurrent MPI calls from multiple threads yet, the `MPIExecutor` does
|
||||
not use a separate thread in the master to do the communication between the
|
||||
master and the workers. Hence, the lowest thread level of MPI_THREAD_SINGLE
|
||||
(no multi-threading) is enough.
|
||||
However, this imposes some slight limitations on the usage since it is not
|
||||
possible to interact with the workers during a call to `submit()` until
|
||||
`submit()` returns.
|
||||
In particular, it is not possible to handle exceptions that occur on workers
|
||||
in the main loop. Instead all exceptions that occur on workers are caught and
|
||||
logged and the worker returns None instead of the actual result of the worker
|
||||
function. A second limitation is that it is not possible to terminate workers.
|
||||
If you need an MPI executor that supports custom exception handling, you should
|
||||
use a multi-threading implementation such as the `MPICommExecutor` in
|
||||
`mpi4py.futures`. Below is a brief example of how to use it with the example
|
||||
given above.
|
||||
>>> from mpi4py.futures import MPICommExecutor
|
||||
>>> from concurrent.futures import as_completed
|
||||
>>> # define comm, func, and jobs like above
|
||||
>>> with MPICommExecutor(comm, root=0) as executor:
|
||||
>>> if executor is not None:
|
||||
>>> futures = [executor.submit(func, **job) for job in jobs]
|
||||
>>> for future in as_completed(futures):
|
||||
>>> try:
|
||||
>>> print(future.result())
|
||||
>>> except Exception as e:
|
||||
>>> # custom exception handling for exceptions
|
||||
>>> # raised in the worker
|
||||
>>> print(e)
|
||||
>>> comm.Abort()
|
||||
"""
|
||||
|
||||
def __init__(self, func, master=0, comm=None):
|
||||
"""Initializes a new executor instance.
|
||||
Parameters
|
||||
----------
|
||||
func: callable
|
||||
The worker function. Jobs will be passed as keyword arguments,
|
||||
so `func` must support this. This is usually the case, but
|
||||
can be a problem when builtin functions are used, e.g. `abs()`.
|
||||
master: int
|
||||
The rank of the master. Must be in `comm`. All other
|
||||
ranks in `comm` will be treated as workers.
|
||||
comm: MPI.Intracomm
|
||||
The MPI communicator used for communication between the
|
||||
master and workers.
|
||||
"""
|
||||
if comm is None:
|
||||
self.comm = MPI.COMM_WORLD
|
||||
elif not comm.Is_intra():
|
||||
raise TypeError('MPI.Intracomm expected')
|
||||
else:
|
||||
self.comm = comm
|
||||
|
||||
self.rank = self.comm.rank
|
||||
self.size = self.comm.size
|
||||
if self.size < 2:
|
||||
raise RuntimeError('MPIExecutor must run with at least 2 processes')
|
||||
|
||||
self._up = False
|
||||
|
||||
master = int(master)
|
||||
if master < 0:
|
||||
raise ValueError('master rank must be non-negative')
|
||||
elif master >= self.size:
|
||||
raise ValueError('master not in comm')
|
||||
else:
|
||||
self.master = master
|
||||
|
||||
# the worker ranks
|
||||
self.workers = tuple(set(range(self.size)) - {self.master})
|
||||
# the worker function
|
||||
if not callable(func):
|
||||
raise TypeError('func must be a callable')
|
||||
self.func = func
|
||||
# holds the state of workers on the master
|
||||
self.busy = [False] * len(self.workers)
|
||||
|
||||
_log.debug(f'MPIExecutor on comm: {self.comm.name}, Master: {self.master}, Workers: {self.workers}')
|
||||
if self.is_master():
|
||||
_log.debug('*** MASTER ***')
|
||||
else:
|
||||
_log.debug('*** WORKER ***')
|
||||
|
||||
def __enter__(self):
|
||||
"""Context manager enter.
|
||||
Only the master returns an executor,
|
||||
all other ranks return None.
|
||||
"""
|
||||
self.start()
|
||||
if self.is_master():
|
||||
return self
|
||||
return None
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""Context manager exit.
|
||||
"""
|
||||
if exc_type is not None:
|
||||
_log.exception(exc_val)
|
||||
return False
|
||||
|
||||
# no exception handling necessary
|
||||
# since we catch everything in __guarded_work
|
||||
# exc_type should always be None
|
||||
self.join()
|
||||
return True
|
||||
|
||||
def is_idle(self):
|
||||
"""Returns a bool indicating whether the executor is idle.
|
||||
The executor is considered to be not idle if *any* worker
|
||||
process is busy with a job. That means, it is idle only
|
||||
if *all* workers are idle.
|
||||
Note: This member must not be called on a worker.
|
||||
"""
|
||||
assert self.is_master()
|
||||
return not any(self.busy)
|
||||
|
||||
def is_master(self):
|
||||
"""Returns a bool indicating whether `self` is the master.
|
||||
"""
|
||||
return self.rank == self.master
|
||||
|
||||
def is_worker(self):
|
||||
"""Returns a bool indicating whether `self` is a worker.
|
||||
"""
|
||||
return not self.is_master()
|
||||
|
||||
def start(self):
|
||||
"""Starts up workers.
|
||||
A check is performed on the master whether the executor
|
||||
has already been terminated, in which case a RuntimeError
|
||||
is raised on the master.
|
||||
"""
|
||||
if self.is_master():
|
||||
if self._up:
|
||||
raise RuntimeError('start has already been called')
|
||||
self._up = True
|
||||
|
||||
_log.info('Starting up MPIExecutor master/workers')
|
||||
if self.is_worker():
|
||||
self.__wait()
|
||||
|
||||
def join(self):
|
||||
"""Joins the workers.
|
||||
"""
|
||||
if self.is_master():
|
||||
|
||||
_log.debug('Terminating. Sending sentinel to all workers.')
|
||||
# send sentinel to all workers
|
||||
for worker in self.workers:
|
||||
self.comm.send(None, dest=worker, tag=Tags.EXIT)
|
||||
|
||||
_log.debug('Waiting for all workers to terminate.')
|
||||
|
||||
down = [False] * len(self.workers)
|
||||
while True:
|
||||
for i, worker in enumerate(self.workers):
|
||||
if self.comm.Iprobe(source=worker, tag=Tags.EXIT):
|
||||
self.comm.recv(source=worker, tag=Tags.EXIT)
|
||||
down[i] = True
|
||||
if all(down):
|
||||
break
|
||||
|
||||
self._up = False
|
||||
_log.debug('All workers terminated.')
|
||||
|
||||
def submit(self, jobs, sleep=0.0):
|
||||
"""Submits a list of jobs to the workers and returns the results.
|
||||
Parameters
|
||||
----------
|
||||
jobs: list
|
||||
A list of keyword argument dicts. Each dict describes
|
||||
a job and will be unpacked and supplied to the work function.
|
||||
sleep: float
|
||||
The number of seconds the master will sleep for when trying
|
||||
to find an idle worker. The default value is 0.0, which means
|
||||
the master will not sleep at all.
|
||||
Returns
|
||||
-------
|
||||
results: list
|
||||
A list of results, i.e. the return values of the work function,
|
||||
received from the workers. The order of results is identical to
|
||||
the order of `jobs`.
|
||||
"""
|
||||
if not self._up:
|
||||
raise RuntimeError('cannot run jobs without a call to start()')
|
||||
|
||||
_log.info('Running {:d} jobs.'.format(len(jobs)))
|
||||
assert self.is_master(), 'run() must not be called on a worker process'
|
||||
|
||||
my_jobs = jobs.copy()
|
||||
num_jobs = len(my_jobs)
|
||||
results = [None] * num_jobs
|
||||
while len(my_jobs) or not self.is_idle():
|
||||
|
||||
for i, worker in enumerate(self.workers):
|
||||
|
||||
if self.comm.Iprobe(source=worker, tag=Tags.DONE):
|
||||
job_idx, result = self.comm.recv(source=worker, tag=Tags.DONE)
|
||||
_log.debug(f'Received finished job {job_idx} from worker {worker:d}.')
|
||||
results[job_idx] = result
|
||||
self.busy[i] = False
|
||||
elif self.comm.Iprobe(source=worker, tag=Tags.READY):
|
||||
if len(my_jobs):
|
||||
self.comm.recv(source=worker, tag=Tags.READY)
|
||||
self.busy[i] = True
|
||||
job_idx = num_jobs - len(my_jobs)
|
||||
_log.debug(f'Sending job {job_idx} to worker {worker:d}.')
|
||||
self.comm.send((job_idx, my_jobs.pop(0)), dest=worker, tag=Tags.START)
|
||||
elif self.comm.Iprobe(source=worker, tag=Tags.EXIT):
|
||||
_log.debug(f'Worker on rank {worker:d} has terminated.')
|
||||
self.comm.recv(source=worker, tag=Tags.EXIT)
|
||||
self.busy[i] = False
|
||||
|
||||
time.sleep(sleep)
|
||||
|
||||
_log.info('Finished all jobs.')
|
||||
return results
|
||||
|
||||
def __wait(self):
|
||||
"""The worker main loop.
|
||||
The worker will enter the loop after `start()` has been called
|
||||
and stay here until it receives the sentinel, e.g. by calling
|
||||
`join()` on the master. In the mean time, the worker is
|
||||
accepting work.
|
||||
"""
|
||||
assert self.is_worker()
|
||||
|
||||
status = MPI.Status()
|
||||
|
||||
_log.debug(f'Starting up worker.')
|
||||
|
||||
while True:
|
||||
|
||||
self.comm.send(None, dest=self.master, tag=Tags.READY)
|
||||
_log.debug(f'Worker on rank {self.rank} waiting for job.')
|
||||
|
||||
data = self.comm.recv(source=self.master, tag=MPI.ANY_TAG, status=status)
|
||||
tag = status.tag
|
||||
|
||||
if tag == Tags.START:
|
||||
job_idx, work = data
|
||||
_log.debug(f'Received job {job_idx} (work={work}).')
|
||||
result = self.__guarded_work(work)
|
||||
_log.debug(f'Finished job. Sending results to master.')
|
||||
self.comm.send((job_idx, result), dest=self.master, tag=Tags.DONE)
|
||||
elif tag == Tags.EXIT:
|
||||
_log.debug(f'Received sentinel from master.')
|
||||
break
|
||||
|
||||
_log.debug('Terminating worker.')
|
||||
self.comm.send(None, dest=self.master, tag=Tags.EXIT)
|
||||
|
||||
def __guarded_work(self, work):
|
||||
"""Executes work safely on the workers.
|
||||
Parameters
|
||||
----------
|
||||
work: dict
|
||||
Keyword arguments that are unpacked and given to the
|
||||
work function.
|
||||
Notes
|
||||
-----
|
||||
All exceptions that occur in the work function `func` are caught
|
||||
and logged. The worker returns `None` to the master in that case
|
||||
instead of the actual result.
|
||||
"""
|
||||
assert self.is_worker()
|
||||
try:
|
||||
return self.func(**work)
|
||||
except Exception as e:
|
||||
_log.exception(str(e))
|
||||
return None
|
||||
|
||||
|
||||
# def main(args=None):
|
||||
# """CLI for gprMax in MPI mode.
|
||||
# Example Usage:
|
||||
# mpirun -np 4 python -m mpi -n 10 my_input_file.in
|
||||
# """
|
||||
# import argparse
|
||||
# import os
|
||||
# from gprMax.constants import c, e0, m0, z0
|
||||
# from gprMax.model_build_run import run_model
|
||||
#
|
||||
# # Parse command line arguments
|
||||
# parser = argparse.ArgumentParser(prog='gprMax', formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
# parser.add_argument(
|
||||
# 'inputfile',
|
||||
# help='relative or absolute path to inputfile.')
|
||||
# parser.add_argument(
|
||||
# '-n', '--num-traces', type=int, default=1,
|
||||
# help='number of model runs (traces) to create a B-scan')
|
||||
# parser.add_argument(
|
||||
# '--geometry-only', action='store_true', default=False,
|
||||
# help='flag to only build model and produce geometry file(s)')
|
||||
# parser.add_argument(
|
||||
# '--geometry-fixed', action='store_true', default=False,
|
||||
# help='flag to not reprocess model geometry, e.g. for B-scans where the geometry is fixed')
|
||||
# parser.add_argument(
|
||||
# '--write-processed', action='store_true', default=False,
|
||||
# help='flag to write an input file after any Python code and include commands '
|
||||
# 'in the original input file have been processed')
|
||||
# parser.add_argument(
|
||||
# '-r', '--restart', type=int, default=1,
|
||||
# help='model number to restart from, e.g. when creating B-scan')
|
||||
# parser.add_argument(
|
||||
# '-l', '--logfile', action='store_true', default=False,
|
||||
# help='flag to enable writing to a log file')
|
||||
# parser.add_argument(
|
||||
# '-v', '--verbose', action='store_true', default=False,
|
||||
# help="flag to increase output")
|
||||
# parser.add_argument(
|
||||
# '--gpu', type=int, action='append', nargs='*',
|
||||
# help='flag to use Nvidia GPU or option to give list of device ID(s)')
|
||||
#
|
||||
# args = parser.parse_args(args)
|
||||
#
|
||||
# comm = MPI.COMM_WORLD
|
||||
# rank = comm.rank
|
||||
#
|
||||
# # set-up logging
|
||||
# logger = logging.getLogger('gprMax')
|
||||
# level = logging.DEBUG if args.verbose else logging.INFO
|
||||
# logger.setLevel(level)
|
||||
#
|
||||
# if args.logfile != "":
|
||||
# mh = logging.FileHandler(f"log_{rank}.txt", mode='w')
|
||||
# mh.setLevel(level)
|
||||
# formatter = logging.Formatter('%(asctime)s:%(name)s:%(levelname)s: %(message)s')
|
||||
# mh.setFormatter(formatter)
|
||||
# logger.addHandler(mh)
|
||||
#
|
||||
# namespace = {
|
||||
# 'c': c,
|
||||
# 'e0': e0,
|
||||
# 'm0': m0,
|
||||
# 'z0': z0,
|
||||
# 'number_model_runs': args.num_traces,
|
||||
# 'inputfile': os.path.abspath(args.inputfile)
|
||||
# }
|
||||
#
|
||||
# model_args = argparse.Namespace(**{
|
||||
# 'geometry_only': args.geometry_only,
|
||||
# 'geometry_fixed': args.geometry_fixed,
|
||||
# 'write_processed': args.write_processed,
|
||||
# 'task': False,
|
||||
# 'restart': False,
|
||||
# 'gpu': args.gpu
|
||||
# })
|
||||
#
|
||||
# # compile jobs
|
||||
# jobs = []
|
||||
# for i in range(args.num_traces):
|
||||
# jobs.append({
|
||||
# 'args': model_args,
|
||||
# 'inputfile': args.inputfile,
|
||||
# 'currentmodelrun': i + 1,
|
||||
# 'modelend': args.num_traces,
|
||||
# 'numbermodelruns': args.num_traces,
|
||||
# 'usernamespace': namespace.copy()
|
||||
# })
|
||||
#
|
||||
# # execute jobs
|
||||
# logger.info(f'Starting execution of {args.num_traces} gprMax model runs.')
|
||||
# with MPIExecutor(run_model, comm=comm) as gpr:
|
||||
# if gpr is not None:
|
||||
# results = gpr.submit(jobs)
|
||||
# logger.info('Results: %s' % str(results))
|
||||
# logger.info('Finished.')
|
||||
#
|
||||
#
|
||||
# if __name__ == '__main__':
|
||||
# main()
|
@@ -31,7 +31,7 @@ from .subgrids.user_objects import SubGridBase as SubGridUserBase
|
||||
from .user_inputs import create_user_input_points
|
||||
from .utilities import human_size
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Scene:
|
||||
@@ -93,7 +93,7 @@ class Scene:
|
||||
try:
|
||||
obj.create(grid, uip)
|
||||
except CmdInputError:
|
||||
log.exception('Error creating user input object')
|
||||
logger.exception('Error creating user input object')
|
||||
|
||||
return self
|
||||
|
||||
|
@@ -38,8 +38,6 @@ from .sources import htod_src_arrays
|
||||
from .utilities import round32
|
||||
from .utilities import timer
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CPUUpdates:
|
||||
"""Defines update functions for CPU-based solver."""
|
||||
|
@@ -29,7 +29,7 @@ from .exceptions import CmdInputError
|
||||
from .subgrids.base import SubGridBase
|
||||
from .utilities import round_value
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
"""Module contains classes to handle points supplied by a user. The
|
||||
classes implement a common interface such that geometry building objects
|
||||
@@ -76,7 +76,7 @@ class UserInput:
|
||||
s = f"'{cmd_str}' the {err.args[0]} {name}-coordinate {i * dl:g} is not within the model domain"
|
||||
else:
|
||||
s = f"'{cmd_str}' {err.args[0]}-coordinate {i * dl:g} is not within the model domain"
|
||||
raise CmdInputError(log.exception(s))
|
||||
raise CmdInputError(logger.exception(s))
|
||||
|
||||
def discretise_point(self, p):
|
||||
"""Function to get the index of a continuous point with the grid."""
|
||||
@@ -104,7 +104,7 @@ class MainGridUserInput(UserInput):
|
||||
p = self.check_point(p, cmd_str, name)
|
||||
|
||||
if self.grid.within_pml(p):
|
||||
log.warning(Fore.RED + f"'{cmd_str}' sources and receivers should not normally be positioned within the PML." + Style.RESET_ALL)
|
||||
logger.warning(Fore.RED + f"'{cmd_str}' sources and receivers should not normally be positioned within the PML." + Style.RESET_ALL)
|
||||
|
||||
return p
|
||||
|
||||
@@ -113,7 +113,7 @@ class MainGridUserInput(UserInput):
|
||||
p2 = self.check_point(p2, cmd_str, name='upper')
|
||||
|
||||
if np.greater(p1, p2).any():
|
||||
raise CmdInputError(log.exception(f"'{cmd_str}' the lower coordinates should be less than the upper coordinates."))
|
||||
raise CmdInputError(logger.exception(f"'{cmd_str}' the lower coordinates should be less than the upper coordinates."))
|
||||
|
||||
return p1, p2
|
||||
|
||||
@@ -175,5 +175,5 @@ class SubgridUserInput(MainGridUserInput):
|
||||
# the OS non-working region.
|
||||
if (np.less(p_t, self.inner_bound).any() or
|
||||
np.greater(p_t, self.outer_bound).any()):
|
||||
log.warning(Fore.RED + f"'{cmd_str}' this object traverses the Outer Surface. This is an advanced feature." + Style.RESET_ALL)
|
||||
logger.warning(Fore.RED + f"'{cmd_str}' this object traverses the Outer Surface. This is an advanced feature." + Style.RESET_ALL)
|
||||
return p_t
|
||||
|
@@ -44,7 +44,35 @@ import numpy as np
|
||||
import gprMax.config as config
|
||||
from .exceptions import GeneralError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_logging(level=logging.INFO, logfile=False):
|
||||
"""Setup and configure logging.
|
||||
|
||||
Args:
|
||||
level (logging level): set logging level to stdout.
|
||||
logfile (bool): additional logging to file.
|
||||
"""
|
||||
|
||||
# Get root logger
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(level)
|
||||
|
||||
# Logging to console
|
||||
mh = logging.StreamHandler()
|
||||
formatter = logging.Formatter('%(message)s')
|
||||
mh.setLevel(level)
|
||||
mh.setFormatter(formatter)
|
||||
logger.addHandler(mh)
|
||||
|
||||
# Logging to file
|
||||
if logfile:
|
||||
mh = logging.FileHandler("log_gprMax.txt", mode='w')
|
||||
formatter = logging.Formatter('%(asctime)s:%(name)s:%(levelname)s: %(message)s')
|
||||
mh.setLevel(logging.DEBUG)
|
||||
mh.setFormatter(formatter)
|
||||
logger.addHandler(mh)
|
||||
|
||||
|
||||
def get_terminal_width():
|
||||
@@ -83,39 +111,14 @@ def logo(version):
|
||||
|___/|_|
|
||||
v""" + version
|
||||
|
||||
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
|
||||
def open_path_file(path_or_file):
|
||||
"""Accepts either a path as a string or a file object and returns a file
|
||||
object (http://stackoverflow.com/a/6783680).
|
||||
|
||||
Args:
|
||||
path_or_file: path as a string or a file object.
|
||||
|
||||
Returns:
|
||||
f (object): File object.
|
||||
"""
|
||||
|
||||
if isinstance(path_or_file, str):
|
||||
f = file_to_close = codecs.open(path_or_file, 'r', encoding='utf-8')
|
||||
else:
|
||||
f = path_or_file
|
||||
file_to_close = None
|
||||
|
||||
try:
|
||||
yield f
|
||||
finally:
|
||||
if file_to_close:
|
||||
file_to_close.close()
|
||||
logger.info(f"{description} {'=' * (get_terminal_width() - len(description) - 1)}\n")
|
||||
logger.info(Fore.CYAN + f'{logo}\n')
|
||||
logger.info(Style.RESET_ALL + textwrap.fill(copyright, width=get_terminal_width() - 1, initial_indent=' '))
|
||||
logger.info(textwrap.fill(authors, width=get_terminal_width() - 1, initial_indent=' '))
|
||||
logger.info('')
|
||||
logger.info(textwrap.fill(licenseinfo1, width=get_terminal_width() - 1, initial_indent=' ', subsequent_indent=' '))
|
||||
logger.info(textwrap.fill(licenseinfo2, width=get_terminal_width() - 1, initial_indent=' ', subsequent_indent=' '))
|
||||
logger.info(textwrap.fill(licenseinfo3, width=get_terminal_width() - 1, initial_indent=' ', subsequent_indent=' '))
|
||||
|
||||
|
||||
def round_value(value, decimalplaces=0):
|
||||
@@ -518,10 +521,6 @@ def detect_check_gpus(deviceIDs):
|
||||
else:
|
||||
deviceIDsavail = range(drv.Device.count())
|
||||
|
||||
# If no device ID is given use default of 0
|
||||
if not deviceIDs:
|
||||
deviceIDs = [0]
|
||||
|
||||
# Check if requested device ID(s) exist
|
||||
for ID in deviceIDs:
|
||||
if ID not in deviceIDsavail:
|
||||
@@ -532,13 +531,12 @@ def detect_check_gpus(deviceIDs):
|
||||
for ID in deviceIDsavail:
|
||||
gpu = GPU(deviceID=ID)
|
||||
gpu.get_gpu_info(drv)
|
||||
if ID in deviceIDs:
|
||||
gpus.append(gpu)
|
||||
gpus.append(gpu)
|
||||
|
||||
return gpus
|
||||
|
||||
|
||||
def timer():
|
||||
"""Function to return time in fractional seconds."""
|
||||
log.debug('Review "thread_time" not currently available in macOS and bug (https://bugs.python.org/issue36205) with "process_time"')
|
||||
logger.debug('"thread_time" not currently available in macOS and bug (https://bugs.python.org/issue36205) with "process_time"')
|
||||
return timer_fn()
|
||||
|
@@ -16,12 +16,8 @@
|
||||
# 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
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Waveform:
|
||||
"""Definitions of waveform shapes that can be used with sources."""
|
||||
|
在新工单中引用
屏蔽一个用户