Refactor single use commands

这个提交包含在:
nmannall
2024-12-09 17:33:52 +00:00
父节点 0869010fc6
当前提交 f9b73406ca
共有 4 个文件被更改,包括 571 次插入417 次删除

查看文件

@@ -362,7 +362,7 @@ class FDTDGrid:
logger.info(f"Materials [{self.name}]:\n{materialstable.table}\n")
def _update_positions(
self, items: Iterable[Union[Source, Rx]], step_size: List[int], step_number: int
self, items: Iterable[Union[Source, Rx]], step_size: npt.NDArray[np.int32], step_number: int
) -> None:
"""Update the grid positions of the provided items.
@@ -387,11 +387,11 @@ class FDTDGrid:
or item.zcoord + step_size[2] * config.sim_config.model_end > self.nz
):
raise ValueError
item.xcoord = item.xcoordorigin + step_number * step_size[0]
item.ycoord = item.ycoordorigin + step_number * step_size[1]
item.zcoord = item.zcoordorigin + step_number * step_size[2]
item.coord = item.coordorigin + step_number * step_size
def update_simple_source_positions(self, step_size: List[int], step: int = 0) -> None:
def update_simple_source_positions(
self, step_size: npt.NDArray[np.int32], step: int = 0
) -> None:
"""Update the positions of sources in the grid.
Move hertzian dipole and magnetic dipole sources. Transmission
@@ -414,7 +414,7 @@ class FDTDGrid:
logger.exception("Source(s) will be stepped to a position outside the domain.")
raise ValueError from e
def update_receiver_positions(self, step_size: List[int], step: int = 0) -> None:
def update_receiver_positions(self, step_size: npt.NDArray[np.int32], step: int = 0) -> None:
"""Update the positions of receivers in the grid.
Args:

查看文件

@@ -59,8 +59,8 @@ class Model:
self.iterations = 0 # Total number of iterations
self.timewindow = 0.0
self.srcsteps: List[int] = [0, 0, 0]
self.rxsteps: List[int] = [0, 0, 0]
self.srcsteps = np.zeros(3, dtype=np.int32)
self.rxsteps = np.zeros(3, dtype=np.int32)
self.G = self._create_grid()
self.subgrids: List[SubGridBaseGrid] = []

查看文件

@@ -1,409 +1,558 @@
# Copyright (C) 2015-2024: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# 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/>.
import logging
from abc import ABC, abstractmethod
import numpy as np
import gprMax.config as config
from gprMax.grid.mpi_grid import MPIGrid
from gprMax.model import Model
from gprMax.user_inputs import MainGridUserInput
from ..pml import PML
from ..utilities.host_info import set_omp_threads
logger = logging.getLogger(__name__)
class Properties:
pass
class UserObjectSingle(ABC):
"""Object that can only occur a single time in a model."""
def __init__(self, **kwargs):
# Each single command has an order to specify the order in which
# the commands are constructed, e.g. discretisation must be
# created before the domain
self.order = 0
self.kwargs = kwargs
self.props = Properties()
self.autotranslate = True
for k, v in kwargs.items():
setattr(self.props, k, v)
@abstractmethod
def build(self, model: Model, uip: MainGridUserInput):
pass
# TODO: Check if this is actually needed
def rotate(self, axis, angle, origin=None):
pass
class Title(UserObjectSingle):
"""Includes a title for your model.
Attributes:
name: string for model title.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.order = 1
def build(self, model, uip):
try:
title = self.kwargs["name"]
model.title = title
logger.info(f"Model title: {model.title}")
except KeyError:
pass
class Discretisation(UserObjectSingle):
"""Specifies the discretization of space in the x, y, and z directions.
Attributes:
p1: tuple of floats to specify spatial discretisation in x, y, z direction.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.order = 2
def build(self, model, uip):
try:
model.dl = np.array(self.kwargs["p1"], dtype=np.float64)
except KeyError:
logger.exception(f"{self.__str__()} discretisation requires a point")
raise
if model.dl[0] <= 0:
logger.exception(
f"{self.__str__()} discretisation requires the "
f"x-direction spatial step to be greater than zero"
)
raise ValueError
if model.dl[1] <= 0:
logger.exception(
f"{self.__str__()} discretisation requires the "
f"y-direction spatial step to be greater than zero"
)
raise ValueError
if model.dl[2] <= 0:
logger.exception(
f"{self.__str__()} discretisation requires the "
f"z-direction spatial step to be greater than zero"
)
raise ValueError
logger.info(f"Spatial discretisation: {model.dl[0]:g} x {model.dl[1]:g} x {model.dl[2]:g}m")
class Domain(UserObjectSingle):
"""Specifies the size of the model.
Attributes:
p1: tuple of floats specifying extent of model domain (x, y, z).
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.order = 3
def build(self, model, uip):
try:
model.nx, model.ny, model.nz = uip.discretise_point(self.kwargs["p1"])
# TODO: Remove when distribute full build for MPI
if isinstance(model.G, MPIGrid):
model.G.nx = model.nx
model.G.ny = model.ny
model.G.nz = model.nz
except KeyError:
logger.exception(f"{self.__str__()} please specify a point")
raise
if model.nx == 0 or model.ny == 0 or model.nz == 0:
logger.exception(f"{self.__str__()} requires at least one cell in every dimension")
raise ValueError
logger.info(
f"Domain size: {self.kwargs['p1'][0]:g} x {self.kwargs['p1'][1]:g} x "
+ f"{self.kwargs['p1'][2]:g}m ({model.nx:d} x {model.ny:d} x {model.nz:d} = "
+ f"{(model.nx * model.ny * model.nz):g} cells)"
)
# Calculate time step at CFL limit; switch off appropriate PMLs for 2D
G = model.G
if model.nx == 1:
config.get_model_config().mode = "2D TMx"
G.pmls["thickness"]["x0"] = 0
G.pmls["thickness"]["xmax"] = 0
elif model.ny == 1:
config.get_model_config().mode = "2D TMy"
G.pmls["thickness"]["y0"] = 0
G.pmls["thickness"]["ymax"] = 0
elif model.nz == 1:
config.get_model_config().mode = "2D TMz"
G.pmls["thickness"]["z0"] = 0
G.pmls["thickness"]["zmax"] = 0
else:
config.get_model_config().mode = "3D"
G.calculate_dt()
logger.info(f"Mode: {config.get_model_config().mode}")
# Sub-grids cannot be used with 2D models. There would typically be
# minimal performance benefit with sub-gridding and 2D models.
if "2D" in config.get_model_config().mode and config.sim_config.general["subgrid"]:
logger.exception("Sub-gridding cannot be used with 2D models")
raise ValueError
logger.info(f"Time step (at CFL limit): {G.dt:g} secs")
class TimeStepStabilityFactor(UserObjectSingle):
"""Factor by which to reduce the time step from the CFL limit.
Attributes:
f: float for factor to multiply time step.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.order = 4
def build(self, model, uip):
try:
f = self.kwargs["f"]
except KeyError:
logger.exception(f"{self.__str__()} requires exactly one parameter")
raise
if f <= 0 or f > 1:
logger.exception(
f"{self.__str__()} requires the value of the time "
f"step stability factor to be between zero and one"
)
raise ValueError
model.dt_mod = f
model.dt *= model.dt_mod
logger.info(f"Time step (modified): {model.dt:g} secs")
class TimeWindow(UserObjectSingle):
"""Specifies the total required simulated time.
Attributes:
time: float of required simulated time in seconds.
iterations: int of required number of iterations.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.order = 5
def build(self, model, uip):
# If number of iterations given
# The +/- 1 used in calculating the number of iterations is to account for
# the fact that the solver (iterations) loop runs from 0 to < G.iterations
try:
iterations = int(self.kwargs["iterations"])
model.timewindow = (iterations - 1) * model.dt
model.iterations = iterations
except KeyError:
pass
try:
tmp = float(self.kwargs["time"])
if tmp > 0:
model.timewindow = tmp
model.iterations = int(np.ceil(tmp / model.dt)) + 1
else:
logger.exception(self.__str__() + " must have a value greater than zero")
raise ValueError
except KeyError:
pass
if not model.timewindow:
logger.exception(self.__str__() + " specify a time or number of iterations")
raise ValueError
logger.info(f"Time window: {model.timewindow:g} secs ({model.iterations} iterations)")
class OMPThreads(UserObjectSingle):
"""Controls how many OpenMP threads (usually the number of physical CPU
cores available) are used when running the model.
Attributes:
n: int for number of threads.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.order = 6
def build(self, model, uip):
try:
n = self.kwargs["n"]
except KeyError:
logger.exception(
f"{self.__str__()} requires exactly one parameter "
f"to specify the number of CPU OpenMP threads to use"
)
raise
if n < 1:
logger.exception(
f"{self.__str__()} requires the value to be an " f"integer not less than one"
)
raise ValueError
config.get_model_config().ompthreads = set_omp_threads(n)
class PMLProps(UserObjectSingle):
"""Specifies the formulation used and thickness (number of cells) of PML
that are used on the six sides of the model domain. Current options are
to use the Higher Order RIPML (HORIPML) - https://doi.org/10.1109/TAP.2011.2180344,
or Multipole RIPML (MRIPML) - https://doi.org/10.1109/TAP.2018.2823864.
Attributes:
formulation: string specifying formulation to be used for all PMLs
either 'HORIPML' or 'MRIPML'.
thickness or x0, y0, z0, xmax, ymax, zmax: ints for thickness of PML
on all 6 sides or individual
sides of the model domain.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.order = 7
def build(self, model, uip):
G = model.G
try:
G.pmls["formulation"] = self.kwargs["formulation"]
if G.pmls["formulation"] not in PML.formulations:
logger.exception(
self.__str__()
+ f" requires the value to be "
+ f"one of {' '.join(PML.formulations)}"
)
except KeyError:
pass
try:
thickness = self.kwargs["thickness"]
for key in G.pmls["thickness"].keys():
G.pmls["thickness"][key] = int(thickness)
except KeyError:
try:
G.pmls["thickness"]["x0"] = int(self.kwargs["x0"])
G.pmls["thickness"]["y0"] = int(self.kwargs["y0"])
G.pmls["thickness"]["z0"] = int(self.kwargs["z0"])
G.pmls["thickness"]["xmax"] = int(self.kwargs["xmax"])
G.pmls["thickness"]["ymax"] = int(self.kwargs["ymax"])
G.pmls["thickness"]["zmax"] = int(self.kwargs["zmax"])
except KeyError:
logger.exception(f"{self.__str__()} requires either one or six parameter(s)")
raise
if (
2 * G.pmls["thickness"]["x0"] >= G.nx
or 2 * G.pmls["thickness"]["y0"] >= G.ny
or 2 * G.pmls["thickness"]["z0"] >= G.nz
or 2 * G.pmls["thickness"]["xmax"] >= G.nx
or 2 * G.pmls["thickness"]["ymax"] >= G.ny
or 2 * G.pmls["thickness"]["zmax"] >= G.nz
):
logger.exception(f"{self.__str__()} has too many cells for the domain size")
raise ValueError
class SrcSteps(UserObjectSingle):
"""Moves the location of all simple sources.
Attributes:
p1: tuple of float increments (x,y,z) to move all simple sources.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.order = 8
def build(self, model, uip):
try:
model.srcsteps = uip.discretise_point(self.kwargs["p1"])
except KeyError:
logger.exception(f"{self.__str__()} requires exactly three parameters")
raise
logger.info(
f"Simple sources will step {model.srcsteps[0] * model.dx:g}m, "
f"{model.srcsteps[1] * model.dy:g}m, {model.srcsteps[2] * model.dz:g}m "
"for each model run."
)
class RxSteps(UserObjectSingle):
"""Moves the location of all receivers.
Attributes:
p1: tuple of float increments (x,y,z) to move all receivers.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.order = 9
def build(self, model, uip):
try:
model.rxsteps = uip.discretise_point(self.kwargs["p1"])
except KeyError:
logger.exception(f"{self.__str__()} requires exactly three parameters")
raise
logger.info(
f"All receivers will step {model.rxsteps[0] * model.dx:g}m, "
f"{model.rxsteps[1] * model.dy:g}m, {model.rxsteps[2] * model.dz:g}m "
"for each model run."
)
class OutputDir(UserObjectSingle):
"""Controls the directory where output file(s) will be stored.
Attributes:
dir: string of file path to directory.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.order = 10
def build(self, grid, uip):
config.get_model_config().set_output_file_path(self.kwargs["dir"])
# Copyright (C) 2015-2024: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# 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/>.
import logging
from typing import Optional, Tuple, Union
import numpy as np
import numpy.typing as npt
from gprMax import config
from gprMax.grid.mpi_grid import MPIGrid
from gprMax.model import Model
from gprMax.pml import PML
from gprMax.user_objects.user_objects import ModelUserObject
from gprMax.utilities.host_info import set_omp_threads
logger = logging.getLogger(__name__)
class Title(ModelUserObject):
"""Title of the model.
Attributes:
title (str): Model title.
"""
@property
def order(self):
return 1
@property
def hash(self):
return "#title"
def __init__(self, name: str):
"""Create a Title user object.
Args:
name: Title of the model.
"""
super().__init__(name=name)
self.title = name
def build(self, model: Model):
model.title = self.title
logger.info(f"Model title: {model.title}")
class Discretisation(ModelUserObject):
"""Spatial discretisation of the model in the x, y, and z dimensions.
Attributes:
discretisation (np.array): Spatial discretisation of the model
(x, y, z)
"""
@property
def order(self):
return 2
@property
def hash(self):
return "#dx_dy_dz"
def __init__(self, p1: Tuple[float, float, float]):
"""Create a Discretisation user object.
Args:
p1: Spatial discretisation in the x, y, and z dimensions.
"""
super().__init__(p1=p1)
self.discretisation = p1
def build(self, model: Model):
if any(self.discretisation) <= 0:
raise ValueError(
f"{self} discretisation requires the spatial step to be"
" greater than zero in all dimensions"
)
model.dl = np.array(self.discretisation, dtype=np.float64)
logger.info(f"Spatial discretisation: {model.dl[0]:g} x {model.dl[1]:g} x {model.dl[2]:g}m")
class Domain(ModelUserObject):
"""Size of the model.
Attributes:
domain_size (tuple): Extent of the model domain (x, y, z).
"""
@property
def order(self):
return 3
@property
def hash(self):
return "#domain"
def __init__(self, p1: Tuple[float, float, float]):
"""Create a Domain user object.
Args:
p1: Model extent in the x, y, and z dimensions.
"""
super().__init__(p1=p1)
self.domain_size = p1
def build(self, model: Model):
uip = self._create_uip(model.G)
model.nx, model.ny, model.nz = uip.discretise_point(self.domain_size)
# TODO: Remove when distribute full build for MPI
if isinstance(model.G, MPIGrid):
model.G.nx = model.nx
model.G.ny = model.ny
model.G.nz = model.nz
if model.nx == 0 or model.ny == 0 or model.nz == 0:
raise ValueError(f"{self} requires at least one cell in every dimension")
logger.info(
f"Domain size: {self.domain_size[0]:g} x {self.domain_size[1]:g} x "
+ f"{self.domain_size[2]:g}m ({model.nx:d} x {model.ny:d} x {model.nz:d} = "
+ f"{(model.nx * model.ny * model.nz):g} cells)"
)
# Set mode and switch off appropriate PMLs for 2D models
grid = model.G
if model.nx == 1:
config.get_model_config().mode = "2D TMx"
grid.pmls["thickness"]["x0"] = 0
grid.pmls["thickness"]["xmax"] = 0
elif model.ny == 1:
config.get_model_config().mode = "2D TMy"
grid.pmls["thickness"]["y0"] = 0
grid.pmls["thickness"]["ymax"] = 0
elif model.nz == 1:
config.get_model_config().mode = "2D TMz"
grid.pmls["thickness"]["z0"] = 0
grid.pmls["thickness"]["zmax"] = 0
else:
config.get_model_config().mode = "3D"
logger.info(f"Mode: {config.get_model_config().mode}")
# Sub-grids cannot be used with 2D models. There would typically be
# minimal performance benefit with sub-gridding and 2D models.
if "2D" in config.get_model_config().mode and config.sim_config.general["subgrid"]:
raise ValueError("Sub-gridding cannot be used with 2D models")
# Calculate time step at CFL limit
grid.calculate_dt()
logger.info(f"Time step (at CFL limit): {grid.dt:g} secs")
class TimeStepStabilityFactor(ModelUserObject):
"""Factor by which to reduce the time step from the CFL limit.
Attributes:
stability_factor (flaot): Factor to multiply time step by.
"""
@property
def order(self):
return 4
@property
def hash(self):
return "#time_step_stability_factor"
def __init__(self, f: float):
"""Create a TimeStepStabilityFactor user object.
Args:
f: Factor to multiply the model time step by.
"""
super().__init__(f=f)
self.stability_factor = f
def build(self, model: Model):
if self.stability_factor <= 0 or self.stability_factor > 1:
raise ValueError(
f"{self} requires the value of the time step stability"
" factor to be between zero and one"
)
model.dt_mod = self.stability_factor
model.dt *= model.dt_mod
logger.info(f"Time step (modified): {model.dt:g} secs")
class TimeWindow(ModelUserObject):
"""Specifies the total required simulated time.
Either time or iterations must be specified. If both are specified,
time takes precedence.
Attributes:
time: float of required simulated time in seconds.
iterations: int of required number of iterations.
"""
@property
def order(self):
return 5
@property
def hash(self):
return "#time_window"
def __init__(self, time: Optional[float] = None, iterations: Optional[int] = None):
"""Create a TimeWindow user object.
Args:
time: Optional simulation time in seconds. Default None.
iterations: Optional number of iterations. Default None.
"""
super().__init__(time=time, iterations=iterations)
self.time = time
self.iterations = iterations
def build(self, model: Model):
if self.time is not None:
if self.time > 0:
model.timewindow = self.time
model.iterations = np.ceil(self.time / model.dt, dtype=np.int32) + 1
else:
raise ValueError(f"{self} must have a value greater than zero")
elif self.iterations is not None:
# The +/- 1 used in calculating the number of iterations is
# to account for the fact that the solver (iterations) loop
# runs from 0 to < G.iterations
model.timewindow = (self.iterations - 1) * model.dt
model.iterations = self.iterations
else:
raise ValueError(f"{self} specify a time or number of iterations")
if self.time is not None and self.iterations is not None:
logger.warning(
f"{self._params_str()} Time and iterations were both specified, using 'time'"
)
logger.info(f"Time window: {model.timewindow:g} secs ({model.iterations} iterations)")
class OMPThreads(ModelUserObject):
"""Set the number of OpenMP threads to use when running the model.
Usually this should match the number of physical CPU cores
available.
Attributes:
omp_threads (int): Number of OpenMP threads.
"""
@property
def order(self):
return 6
@property
def hash(self):
return "#num_threads"
def __init__(self, n: int):
"""Create an OMPThreads user object.
Args:
n: Number of OpenMP threads.
"""
super().__init__(n=n)
self.omp_threads = n
def build(self, model: Model):
if self.omp_threads < 1:
raise ValueError(f"{self} requires the value to be an integer not less than one")
config.get_model_config().ompthreads = set_omp_threads(self.omp_threads)
logger.info(f"Simulation will use {config.get_model_config().ompthreads} OpenMP threads")
class PMLFormulation(ModelUserObject):
"""Set the formulation of the PMLs.
Current options are to use the Higher Order RIPML (HORIPML) -
https://doi.org/10.1109/TAP.2011.2180344, or Multipole RIPML
(MRIPML) - https://doi.org/10.1109/TAP.2018.2823864.
Attributes:
formulation (str): Formulation to be used for all PMLs. Either
'HORIPML' or 'MRIPML'.
"""
@property
def order(self):
return 7
@property
def hash(self):
return "#pml_formulation"
def __init__(self, formulation: str):
"""Create a PMLFormulation user object.
Args:
formulation: Formulation to be used for all PMLs. Either
'HORIPML' or 'MRIPML'.
"""
super().__init__(formulation=formulation)
self.formulation = formulation
def build(self, model: Model):
if self.formulation not in PML.formulations:
logger.exception(f"{self} requires the value to be one of {' '.join(PML.formulations)}")
model.G.pmls["formulation"] = self.formulation
logger.info(f"PML formulation set to {model.G.pmls['formulation']}")
class PMLThickness(ModelUserObject):
"""Set the thickness of the PMLs.
The thickness can be set globally, or individually for each of the
six sides of the model domain. Either thickness must be set, or all
of x0, y0, z0, xmax, ymax, zmax.
Attributes:
thickness (int | Tuple[int]): Thickness of the PML on all 6
sides or individual sides of the model domain.
"""
@property
def order(self):
return 7
@property
def hash(self):
return "#pml_cells"
def __init__(self, thickness: Union[int, Tuple[int, int, int, int, int, int]]):
"""Create a PMLThickness user object.
Args:
thickness: Thickness of the PML on all 6 sides or individual
sides of the model domain.
"""
super().__init__(thickness=thickness)
self.thickness = thickness
def build(self, model: Model):
grid = model.G
if isinstance(self.thickness, int) or len(self.thickness) == 1:
for key in grid.pmls["thickness"].keys():
grid.pmls["thickness"][key] = int(self.thickness)
elif len(self.thickness) == 6:
grid.pmls["thickness"]["x0"] = int(self.thickness[0])
grid.pmls["thickness"]["y0"] = int(self.thickness[1])
grid.pmls["thickness"]["z0"] = int(self.thickness[2])
grid.pmls["thickness"]["xmax"] = int(self.thickness[3])
grid.pmls["thickness"]["ymax"] = int(self.thickness[4])
grid.pmls["thickness"]["zmax"] = int(self.thickness[5])
else:
raise ValueError(f"{self} requires either one or six parameter(s)")
# Check each PML does not take up more than half the grid
if (
2 * grid.pmls["thickness"]["x0"] >= grid.nx
or 2 * grid.pmls["thickness"]["y0"] >= grid.ny
or 2 * grid.pmls["thickness"]["z0"] >= grid.nz
or 2 * grid.pmls["thickness"]["xmax"] >= grid.nx
or 2 * grid.pmls["thickness"]["ymax"] >= grid.ny
or 2 * grid.pmls["thickness"]["zmax"] >= grid.nz
):
raise ValueError(f"{self} has too many cells for the domain size")
logger.info(
f"PML thickness: x0={model.G.pmls['x0']}, y0={model.G.pmls['y0']},"
f" z0={model.G.pmls['z0']}, xmax={model.G.pmls['xmax']},"
f" ymax={model.G.pmls['yxmax']}, zmax={model.G.pmls['zmax']}"
)
class PMLProps(ModelUserObject):
"""Specify the formulation and thickness of the PMLs.
A PML can be set on each of the six sides of the model domain.
Current options are to use the Higher Order RIPML (HORIPML) -
https://doi.org/10.1109/TAP.2011.2180344, or Multipole RIPML
(MRIPML) - https://doi.org/10.1109/TAP.2018.2823864.
Deprecated: PMLProps is deprecated and may be removed in future
releases of gprMax. Use the new PMLFormulation and PMLThickness
user objects instead.
Attributes:
pml_formulation (PMLFormulation): User object to set the PML
formulation.
pml_thickness (PMLThickness): User object to set the PML
thickness.
"""
@property
def order(self):
return 7
@property
def hash(self):
return "#pml_properties"
def __init__(
self,
formulation: str,
thickness: Optional[int] = None,
x0: Optional[int] = None,
y0: Optional[int] = None,
z0: Optional[int] = None,
xmax: Optional[int] = None,
ymax: Optional[int] = None,
zmax: Optional[int] = None,
):
"""Create a PMLProps user object.
If 'thickness' is set, it will take precendence over any
individual thicknesses set. Additionally, if 'thickness' is not
set, the individual thickness must be set for all six sides of
the model domain.
Deprecated: PMLProps is deprecated and may be removed in future
releases of gprMax. Use the new PMLFormulation and PMLThickness
user objects instead.
Args:
formulation (str): Formulation to be used for all PMLs. Either
'HORIPML' or 'MRIPML'.
thickness: Optional thickness of the PML on all 6 sides of
the model domain. Default None.
x0, y0, z0, xmax, ymax, zmax: Optional thickness of the PML
on individual sides of the model domain. Default None.
"""
super().__init__()
logger.warning(
"PMLProps is deprecated and may be removed in future"
" releases of gprMax. Use the new PMLFormulation and"
" PMLThickness user objects instead."
)
self.pml_formulation = PMLFormulation(formulation)
if thickness is not None:
self.pml_thickness = PMLThickness(thickness)
elif (
x0 is not None
and y0 is not None
and z0 is not None
and xmax is not None
and ymax is not None
and zmax is not None
):
self.pml_thickness = PMLThickness((x0, y0, z0, xmax, ymax, zmax))
else:
raise ValueError("Either set thickness, or all of x0, y0, z0, xmax, ymax, zmax.")
def build(self, model):
self.pml_formulation.build(model)
self.pml_thickness.build(model)
class SrcSteps(ModelUserObject):
"""Move the location of all simple sources.
Attributes:
step_size (Tuple[float]): Increment (x, y, z) to move all
simple sources by for each step.
"""
@property
def order(self):
return 8
@property
def hash(self):
return "#src_steps"
def __init__(self, p1: Tuple[float, float, float]):
"""Create a SrcSteps user object.
Args:
p1: Increment (x, y, z) to move all simple sources by for
each step.
"""
super().__init__(p1=p1)
self.step_size = p1
def build(self, model: Model):
uip = self._create_uip(model.G)
model.srcsteps = np.array(uip.discretise_point(self.step_size), dtype=np.int32)
logger.info(
f"Simple sources will step {model.srcsteps[0] * model.dx:g}m, "
f"{model.srcsteps[1] * model.dy:g}m, {model.srcsteps[2] * model.dz:g}m "
"for each model run."
)
class RxSteps(ModelUserObject):
"""Move the location of all receivers.
Attributes:
step_size (Tuple[float]): Increment (x, y, z) to move all
receivers by for each step.
"""
@property
def order(self):
return 9
@property
def hash(self):
return "#rx_steps"
def __init__(self, p1: Tuple[float, float, float]):
"""Create a RxSteps user object.
Args:
p1: Increment (x, y, z) to move all receivers by for each
step.
"""
super().__init__(p1=p1)
self.step_size = p1
def build(self, model: Model):
uip = self._create_uip(model.G)
model.rxsteps = np.array(uip.discretise_point(self.step_size), dtype=np.int32)
logger.info(
f"All receivers will step {model.rxsteps[0] * model.dx:g}m, "
f"{model.rxsteps[1] * model.dy:g}m, {model.rxsteps[2] * model.dz:g}m "
"for each model run."
)

查看文件

@@ -100,6 +100,11 @@ class ModelUserObject(UserObject):
@abstractmethod
def build(self, model: Model):
"""Build user object and set model properties.
Args:
model: Model to set the properties of.
"""
pass