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") logger.info(f"Materials [{self.name}]:\n{materialstable.table}\n")
def _update_positions( 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: ) -> None:
"""Update the grid positions of the provided items. """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 or item.zcoord + step_size[2] * config.sim_config.model_end > self.nz
): ):
raise ValueError raise ValueError
item.xcoord = item.xcoordorigin + step_number * step_size[0] item.coord = item.coordorigin + step_number * step_size
item.ycoord = item.ycoordorigin + step_number * step_size[1]
item.zcoord = item.zcoordorigin + step_number * step_size[2]
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. """Update the positions of sources in the grid.
Move hertzian dipole and magnetic dipole sources. Transmission 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.") logger.exception("Source(s) will be stepped to a position outside the domain.")
raise ValueError from e 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. """Update the positions of receivers in the grid.
Args: Args:

查看文件

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

查看文件

@@ -16,351 +16,503 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with gprMax. If not, see <http://www.gnu.org/licenses/>. # along with gprMax. If not, see <http://www.gnu.org/licenses/>.
import logging import logging
from abc import ABC, abstractmethod from typing import Optional, Tuple, Union
import numpy as np import numpy as np
import numpy.typing as npt
import gprMax.config as config from gprMax import config
from gprMax.grid.mpi_grid import MPIGrid from gprMax.grid.mpi_grid import MPIGrid
from gprMax.model import Model from gprMax.model import Model
from gprMax.user_inputs import MainGridUserInput from gprMax.pml import PML
from gprMax.user_objects.user_objects import ModelUserObject
from ..pml import PML from gprMax.utilities.host_info import set_omp_threads
from ..utilities.host_info import set_omp_threads
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Properties: class Title(ModelUserObject):
pass """Title of the model.
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: Attributes:
name: string for model title. title (str): Model title.
""" """
def __init__(self, **kwargs): @property
super().__init__(**kwargs) def order(self):
self.order = 1 return 1
def build(self, model, uip): @property
try: def hash(self):
title = self.kwargs["name"] return "#title"
model.title = 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}") logger.info(f"Model title: {model.title}")
except KeyError:
pass
class Discretisation(UserObjectSingle): class Discretisation(ModelUserObject):
"""Specifies the discretization of space in the x, y, and z directions. """Spatial discretisation of the model in the x, y, and z dimensions.
Attributes: Attributes:
p1: tuple of floats to specify spatial discretisation in x, y, z direction. discretisation (np.array): Spatial discretisation of the model
(x, y, z)
""" """
def __init__(self, **kwargs): @property
super().__init__(**kwargs) def order(self):
self.order = 2 return 2
def build(self, model, uip): @property
try: def hash(self):
model.dl = np.array(self.kwargs["p1"], dtype=np.float64) return "#dx_dy_dz"
except KeyError:
logger.exception(f"{self.__str__()} discretisation requires a point")
raise
if model.dl[0] <= 0: def __init__(self, p1: Tuple[float, float, float]):
logger.exception( """Create a Discretisation user object.
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
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") logger.info(f"Spatial discretisation: {model.dl[0]:g} x {model.dl[1]:g} x {model.dl[2]:g}m")
class Domain(UserObjectSingle): class Domain(ModelUserObject):
"""Specifies the size of the model. """Size of the model.
Attributes: Attributes:
p1: tuple of floats specifying extent of model domain (x, y, z). domain_size (tuple): Extent of the model domain (x, y, z).
""" """
def __init__(self, **kwargs): @property
super().__init__(**kwargs) def order(self):
self.order = 3 return 3
def build(self, model, uip): @property
try: def hash(self):
model.nx, model.ny, model.nz = uip.discretise_point(self.kwargs["p1"]) 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 # TODO: Remove when distribute full build for MPI
if isinstance(model.G, MPIGrid): if isinstance(model.G, MPIGrid):
model.G.nx = model.nx model.G.nx = model.nx
model.G.ny = model.ny model.G.ny = model.ny
model.G.nz = model.nz 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: 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(f"{self} requires at least one cell in every dimension")
raise ValueError
logger.info( logger.info(
f"Domain size: {self.kwargs['p1'][0]:g} x {self.kwargs['p1'][1]:g} x " f"Domain size: {self.domain_size[0]:g} x {self.domain_size[1]:g} x "
+ f"{self.kwargs['p1'][2]:g}m ({model.nx:d} x {model.ny:d} x {model.nz:d} = " + 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)" + f"{(model.nx * model.ny * model.nz):g} cells)"
) )
# Calculate time step at CFL limit; switch off appropriate PMLs for 2D # Set mode and switch off appropriate PMLs for 2D models
G = model.G grid = model.G
if model.nx == 1: if model.nx == 1:
config.get_model_config().mode = "2D TMx" config.get_model_config().mode = "2D TMx"
G.pmls["thickness"]["x0"] = 0 grid.pmls["thickness"]["x0"] = 0
G.pmls["thickness"]["xmax"] = 0 grid.pmls["thickness"]["xmax"] = 0
elif model.ny == 1: elif model.ny == 1:
config.get_model_config().mode = "2D TMy" config.get_model_config().mode = "2D TMy"
G.pmls["thickness"]["y0"] = 0 grid.pmls["thickness"]["y0"] = 0
G.pmls["thickness"]["ymax"] = 0 grid.pmls["thickness"]["ymax"] = 0
elif model.nz == 1: elif model.nz == 1:
config.get_model_config().mode = "2D TMz" config.get_model_config().mode = "2D TMz"
G.pmls["thickness"]["z0"] = 0 grid.pmls["thickness"]["z0"] = 0
G.pmls["thickness"]["zmax"] = 0 grid.pmls["thickness"]["zmax"] = 0
else: else:
config.get_model_config().mode = "3D" config.get_model_config().mode = "3D"
G.calculate_dt()
logger.info(f"Mode: {config.get_model_config().mode}") logger.info(f"Mode: {config.get_model_config().mode}")
# Sub-grids cannot be used with 2D models. There would typically be # Sub-grids cannot be used with 2D models. There would typically be
# minimal performance benefit with sub-gridding and 2D models. # minimal performance benefit with sub-gridding and 2D models.
if "2D" in config.get_model_config().mode and config.sim_config.general["subgrid"]: 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("Sub-gridding cannot be used with 2D models")
raise ValueError
logger.info(f"Time step (at CFL limit): {G.dt:g} secs") # Calculate time step at CFL limit
grid.calculate_dt()
logger.info(f"Time step (at CFL limit): {grid.dt:g} secs")
class TimeStepStabilityFactor(UserObjectSingle): class TimeStepStabilityFactor(ModelUserObject):
"""Factor by which to reduce the time step from the CFL limit. """Factor by which to reduce the time step from the CFL limit.
Attributes: Attributes:
f: float for factor to multiply time step. stability_factor (flaot): Factor to multiply time step by.
""" """
def __init__(self, **kwargs): @property
super().__init__(**kwargs) def order(self):
self.order = 4 return 4
def build(self, model, uip): @property
try: def hash(self):
f = self.kwargs["f"] return "#time_step_stability_factor"
except KeyError:
logger.exception(f"{self.__str__()} requires exactly one parameter")
raise
if f <= 0 or f > 1: def __init__(self, f: float):
logger.exception( """Create a TimeStepStabilityFactor user object.
f"{self.__str__()} requires the value of the time "
f"step stability factor to be between zero and one" 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"
) )
raise ValueError
model.dt_mod = f model.dt_mod = self.stability_factor
model.dt *= model.dt_mod model.dt *= model.dt_mod
logger.info(f"Time step (modified): {model.dt:g} secs") logger.info(f"Time step (modified): {model.dt:g} secs")
class TimeWindow(UserObjectSingle): class TimeWindow(ModelUserObject):
"""Specifies the total required simulated time. """Specifies the total required simulated time.
Either time or iterations must be specified. If both are specified,
time takes precedence.
Attributes: Attributes:
time: float of required simulated time in seconds. time: float of required simulated time in seconds.
iterations: int of required number of iterations. iterations: int of required number of iterations.
""" """
def __init__(self, **kwargs): @property
super().__init__(**kwargs) def order(self):
self.order = 5 return 5
def build(self, model, uip): @property
# If number of iterations given def hash(self):
# The +/- 1 used in calculating the number of iterations is to account for return "#time_window"
# 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: def __init__(self, time: Optional[float] = None, iterations: Optional[int] = None):
tmp = float(self.kwargs["time"]) """Create a TimeWindow user object.
if tmp > 0:
model.timewindow = tmp Args:
model.iterations = int(np.ceil(tmp / model.dt)) + 1 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: else:
logger.exception(self.__str__() + " must have a value greater than zero") raise ValueError(f"{self} must have a value greater than zero")
raise ValueError elif self.iterations is not None:
except KeyError: # The +/- 1 used in calculating the number of iterations is
pass # 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 not model.timewindow: if self.time is not None and self.iterations is not None:
logger.exception(self.__str__() + " specify a time or number of iterations") logger.warning(
raise ValueError f"{self._params_str()} Time and iterations were both specified, using 'time'"
)
logger.info(f"Time window: {model.timewindow:g} secs ({model.iterations} iterations)") logger.info(f"Time window: {model.timewindow:g} secs ({model.iterations} iterations)")
class OMPThreads(UserObjectSingle): class OMPThreads(ModelUserObject):
"""Controls how many OpenMP threads (usually the number of physical CPU """Set the number of OpenMP threads to use when running the model.
cores available) are used when running the model.
Usually this should match the number of physical CPU cores
available.
Attributes: Attributes:
n: int for number of threads. omp_threads (int): Number of OpenMP threads.
""" """
def __init__(self, **kwargs): @property
super().__init__(**kwargs) def order(self):
self.order = 6 return 6
def build(self, model, uip): @property
try: def hash(self):
n = self.kwargs["n"] return "#num_threads"
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) 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 PMLProps(UserObjectSingle): class PMLFormulation(ModelUserObject):
"""Specifies the formulation used and thickness (number of cells) of PML """Set the formulation of the PMLs.
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, Current options are to use the Higher Order RIPML (HORIPML) -
or Multipole RIPML (MRIPML) - https://doi.org/10.1109/TAP.2018.2823864. https://doi.org/10.1109/TAP.2011.2180344, or Multipole RIPML
(MRIPML) - https://doi.org/10.1109/TAP.2018.2823864.
Attributes: Attributes:
formulation: string specifying formulation to be used for all PMLs formulation (str): Formulation to be used for all PMLs. Either
either 'HORIPML' or 'MRIPML'. 'HORIPML' or 'MRIPML'.
thickness or x0, y0, z0, xmax, ymax, zmax: ints for thickness of PML """
on all 6 sides or individual
@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. sides of the model domain.
""" """
super().__init__(thickness=thickness)
self.thickness = thickness
def __init__(self, **kwargs): def build(self, model: Model):
super().__init__(**kwargs) grid = model.G
self.order = 7
def build(self, model, uip): if isinstance(self.thickness, int) or len(self.thickness) == 1:
G = model.G for key in grid.pmls["thickness"].keys():
try: grid.pmls["thickness"][key] = int(self.thickness)
G.pmls["formulation"] = self.kwargs["formulation"] elif len(self.thickness) == 6:
if G.pmls["formulation"] not in PML.formulations: grid.pmls["thickness"]["x0"] = int(self.thickness[0])
logger.exception( grid.pmls["thickness"]["y0"] = int(self.thickness[1])
self.__str__() grid.pmls["thickness"]["z0"] = int(self.thickness[2])
+ f" requires the value to be " grid.pmls["thickness"]["xmax"] = int(self.thickness[3])
+ f"one of {' '.join(PML.formulations)}" grid.pmls["thickness"]["ymax"] = int(self.thickness[4])
) grid.pmls["thickness"]["zmax"] = int(self.thickness[5])
except KeyError: else:
pass raise ValueError(f"{self} requires either one or six parameter(s)")
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
# Check each PML does not take up more than half the grid
if ( if (
2 * G.pmls["thickness"]["x0"] >= G.nx 2 * grid.pmls["thickness"]["x0"] >= grid.nx
or 2 * G.pmls["thickness"]["y0"] >= G.ny or 2 * grid.pmls["thickness"]["y0"] >= grid.ny
or 2 * G.pmls["thickness"]["z0"] >= G.nz or 2 * grid.pmls["thickness"]["z0"] >= grid.nz
or 2 * G.pmls["thickness"]["xmax"] >= G.nx or 2 * grid.pmls["thickness"]["xmax"] >= grid.nx
or 2 * G.pmls["thickness"]["ymax"] >= G.ny or 2 * grid.pmls["thickness"]["ymax"] >= grid.ny
or 2 * G.pmls["thickness"]["zmax"] >= G.nz or 2 * grid.pmls["thickness"]["zmax"] >= grid.nz
): ):
logger.exception(f"{self.__str__()} has too many cells for the domain size") raise ValueError(f"{self} has too many cells for the domain size")
raise ValueError
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 SrcSteps(UserObjectSingle): class PMLProps(ModelUserObject):
"""Moves the location of all simple sources. """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: Attributes:
p1: tuple of float increments (x,y,z) to move all simple sources. pml_formulation (PMLFormulation): User object to set the PML
formulation.
pml_thickness (PMLThickness): User object to set the PML
thickness.
""" """
def __init__(self, **kwargs): @property
super().__init__(**kwargs) def order(self):
self.order = 8 return 7
def build(self, model, uip): @property
try: def hash(self):
model.srcsteps = uip.discretise_point(self.kwargs["p1"]) return "#pml_properties"
except KeyError:
logger.exception(f"{self.__str__()} requires exactly three parameters") def __init__(
raise 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( logger.info(
f"Simple sources will step {model.srcsteps[0] * model.dx:g}m, " f"Simple sources will step {model.srcsteps[0] * model.dx:g}m, "
@@ -369,41 +521,38 @@ class SrcSteps(UserObjectSingle):
) )
class RxSteps(UserObjectSingle): class RxSteps(ModelUserObject):
"""Moves the location of all receivers. """Move the location of all receivers.
Attributes: Attributes:
p1: tuple of float increments (x,y,z) to move all receivers. step_size (Tuple[float]): Increment (x, y, z) to move all
receivers by for each step.
""" """
def __init__(self, **kwargs): @property
super().__init__(**kwargs) def order(self):
self.order = 9 return 9
def build(self, model, uip): @property
try: def hash(self):
model.rxsteps = uip.discretise_point(self.kwargs["p1"]) return "#rx_steps"
except KeyError:
logger.exception(f"{self.__str__()} requires exactly three parameters") def __init__(self, p1: Tuple[float, float, float]):
raise """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( logger.info(
f"All receivers will step {model.rxsteps[0] * model.dx:g}m, " 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 " f"{model.rxsteps[1] * model.dy:g}m, {model.rxsteps[2] * model.dz:g}m "
"for each model run." "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"])

查看文件

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