你已经派生过 gprMax
镜像自地址
https://gitee.com/sunhf/gprMax.git
已同步 2025-08-08 07:24:19 +08:00
Merge branch '38-refactor-scene-and-userobjects' into mpi
这个提交包含在:
@@ -10,28 +10,29 @@ Electromagnetic wave propagation simulation software.
|
||||
import gprMax.config as config
|
||||
|
||||
from ._version import __version__
|
||||
from .cmds_geometry.add_grass import AddGrass
|
||||
from .cmds_geometry.add_surface_roughness import AddSurfaceRoughness
|
||||
from .cmds_geometry.add_surface_water import AddSurfaceWater
|
||||
from .cmds_geometry.box import Box
|
||||
from .cmds_geometry.cone import Cone
|
||||
from .cmds_geometry.cylinder import Cylinder
|
||||
from .cmds_geometry.cylindrical_sector import CylindricalSector
|
||||
from .cmds_geometry.edge import Edge
|
||||
from .cmds_geometry.ellipsoid import Ellipsoid
|
||||
from .cmds_geometry.fractal_box import FractalBox
|
||||
from .cmds_geometry.geometry_objects_read import GeometryObjectsRead
|
||||
from .cmds_geometry.plate import Plate
|
||||
from .cmds_geometry.sphere import Sphere
|
||||
from .cmds_geometry.triangle import Triangle
|
||||
from .cmds_multiuse import (
|
||||
from .gprMax import run as run
|
||||
from .scene import Scene
|
||||
from .subgrids.user_objects import SubGridHSG
|
||||
from .user_objects.cmds_geometry.add_grass import AddGrass
|
||||
from .user_objects.cmds_geometry.add_surface_roughness import AddSurfaceRoughness
|
||||
from .user_objects.cmds_geometry.add_surface_water import AddSurfaceWater
|
||||
from .user_objects.cmds_geometry.box import Box
|
||||
from .user_objects.cmds_geometry.cone import Cone
|
||||
from .user_objects.cmds_geometry.cylinder import Cylinder
|
||||
from .user_objects.cmds_geometry.cylindrical_sector import CylindricalSector
|
||||
from .user_objects.cmds_geometry.edge import Edge
|
||||
from .user_objects.cmds_geometry.ellipsoid import Ellipsoid
|
||||
from .user_objects.cmds_geometry.fractal_box import FractalBox
|
||||
from .user_objects.cmds_geometry.geometry_objects_read import GeometryObjectsRead
|
||||
from .user_objects.cmds_geometry.plate import Plate
|
||||
from .user_objects.cmds_geometry.sphere import Sphere
|
||||
from .user_objects.cmds_geometry.triangle import Triangle
|
||||
from .user_objects.cmds_multiuse import (
|
||||
PMLCFS,
|
||||
AddDebyeDispersion,
|
||||
AddDrudeDispersion,
|
||||
AddLorentzDispersion,
|
||||
ExcitationFile,
|
||||
GeometryObjectsWrite,
|
||||
GeometryView,
|
||||
HertzianDipole,
|
||||
MagneticDipole,
|
||||
Material,
|
||||
@@ -41,12 +42,12 @@ from .cmds_multiuse import (
|
||||
RxArray,
|
||||
Snapshot,
|
||||
SoilPeplinski,
|
||||
Subgrid,
|
||||
TransmissionLine,
|
||||
VoltageSource,
|
||||
Waveform,
|
||||
)
|
||||
from .cmds_singleuse import (
|
||||
from .user_objects.cmds_output import GeometryObjectsWrite, GeometryView
|
||||
from .user_objects.cmds_singleuse import (
|
||||
Discretisation,
|
||||
Domain,
|
||||
OMPThreads,
|
||||
@@ -58,8 +59,5 @@ from .cmds_singleuse import (
|
||||
TimeWindow,
|
||||
Title,
|
||||
)
|
||||
from .gprMax import run as run
|
||||
from .scene import Scene
|
||||
from .subgrids.user_objects import SubGridHSG
|
||||
|
||||
__name__ = "gprMax"
|
||||
|
@@ -1,409 +0,0 @@
|
||||
# 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"])
|
@@ -61,6 +61,9 @@ class FDTDGrid:
|
||||
self.dl = np.ones(3, dtype=np.float64)
|
||||
self.dt = 0.0
|
||||
|
||||
self.iterations = 0 # Total number of iterations
|
||||
self.timewindow = 0.0
|
||||
|
||||
# Field Arrays
|
||||
self.Ex: npt.NDArray[np.float32]
|
||||
self.Ey: npt.NDArray[np.float32]
|
||||
@@ -362,7 +365,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 +390,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 +417,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:
|
||||
|
@@ -20,20 +20,20 @@ import logging
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .cmds_geometry.add_grass import AddGrass
|
||||
from .cmds_geometry.add_surface_roughness import AddSurfaceRoughness
|
||||
from .cmds_geometry.add_surface_water import AddSurfaceWater
|
||||
from .cmds_geometry.box import Box
|
||||
from .cmds_geometry.cmds_geometry import check_averaging
|
||||
from .cmds_geometry.cone import Cone
|
||||
from .cmds_geometry.cylinder import Cylinder
|
||||
from .cmds_geometry.cylindrical_sector import CylindricalSector
|
||||
from .cmds_geometry.edge import Edge
|
||||
from .cmds_geometry.ellipsoid import Ellipsoid
|
||||
from .cmds_geometry.fractal_box import FractalBox
|
||||
from .cmds_geometry.plate import Plate
|
||||
from .cmds_geometry.sphere import Sphere
|
||||
from .cmds_geometry.triangle import Triangle
|
||||
from .user_objects.cmds_geometry.add_grass import AddGrass
|
||||
from .user_objects.cmds_geometry.add_surface_roughness import AddSurfaceRoughness
|
||||
from .user_objects.cmds_geometry.add_surface_water import AddSurfaceWater
|
||||
from .user_objects.cmds_geometry.box import Box
|
||||
from .user_objects.cmds_geometry.cmds_geometry import check_averaging
|
||||
from .user_objects.cmds_geometry.cone import Cone
|
||||
from .user_objects.cmds_geometry.cylinder import Cylinder
|
||||
from .user_objects.cmds_geometry.cylindrical_sector import CylindricalSector
|
||||
from .user_objects.cmds_geometry.edge import Edge
|
||||
from .user_objects.cmds_geometry.ellipsoid import Ellipsoid
|
||||
from .user_objects.cmds_geometry.fractal_box import FractalBox
|
||||
from .user_objects.cmds_geometry.plate import Plate
|
||||
from .user_objects.cmds_geometry.sphere import Sphere
|
||||
from .user_objects.cmds_geometry.triangle import Triangle
|
||||
from .utilities.utilities import round_value
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -57,8 +57,7 @@ def process_geometrycmds(geometry):
|
||||
tmp = object.split()
|
||||
|
||||
if tmp[0] == "#geometry_objects_read:":
|
||||
from .cmds_geometry.geometry_objects_read import \
|
||||
GeometryObjectsRead
|
||||
from .user_objects.cmds_geometry.geometry_objects_read import GeometryObjectsRead
|
||||
|
||||
if len(tmp) != 6:
|
||||
logger.exception("'" + " ".join(tmp) + "'" + " requires exactly five parameters")
|
||||
@@ -126,7 +125,14 @@ def process_geometrycmds(geometry):
|
||||
# Isotropic case with user specified averaging
|
||||
elif len(tmp) == 13:
|
||||
averaging = check_averaging(tmp[12].lower())
|
||||
triangle = Triangle(p1=p1, p2=p2, p3=p3, thickness=thickness, material_id=tmp[11], averaging=averaging)
|
||||
triangle = Triangle(
|
||||
p1=p1,
|
||||
p2=p2,
|
||||
p3=p3,
|
||||
thickness=thickness,
|
||||
material_id=tmp[11],
|
||||
averaging=averaging,
|
||||
)
|
||||
|
||||
# Uniaxial anisotropic case
|
||||
elif len(tmp) == 14:
|
||||
@@ -330,7 +336,9 @@ def process_geometrycmds(geometry):
|
||||
# Isotropic case with user specified averaging
|
||||
elif len(tmp) == 9:
|
||||
averaging = check_averaging(tmp[8].lower())
|
||||
ellipsoid = Ellipsoid(p1=p1, xr=xr, yr=yr, zr=zr, material_id=tmp[7], averaging=averaging)
|
||||
ellipsoid = Ellipsoid(
|
||||
p1=p1, xr=xr, yr=yr, zr=zr, material_id=tmp[7], averaging=averaging
|
||||
)
|
||||
|
||||
# Uniaxial anisotropic case
|
||||
elif len(tmp) == 8:
|
||||
@@ -346,7 +354,9 @@ def process_geometrycmds(geometry):
|
||||
# Default is no dielectric smoothing for a fractal box
|
||||
|
||||
if len(tmp) < 14:
|
||||
logger.exception("'" + " ".join(tmp) + "'" + " requires at least thirteen parameters")
|
||||
logger.exception(
|
||||
"'" + " ".join(tmp) + "'" + " requires at least thirteen parameters"
|
||||
)
|
||||
raise ValueError
|
||||
|
||||
p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
|
||||
@@ -402,7 +412,9 @@ def process_geometrycmds(geometry):
|
||||
|
||||
if tmp[0] == "#add_surface_roughness:":
|
||||
if len(tmp) < 13:
|
||||
logger.exception("'" + " ".join(tmp) + "'" + " requires at least twelve parameters")
|
||||
logger.exception(
|
||||
"'" + " ".join(tmp) + "'" + " requires at least twelve parameters"
|
||||
)
|
||||
raise ValueError
|
||||
|
||||
p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
|
||||
@@ -432,14 +444,18 @@ def process_geometrycmds(geometry):
|
||||
seed=int(tmp[13]),
|
||||
)
|
||||
else:
|
||||
logger.exception("'" + " ".join(tmp) + "'" + " too many parameters have been given")
|
||||
logger.exception(
|
||||
"'" + " ".join(tmp) + "'" + " too many parameters have been given"
|
||||
)
|
||||
raise ValueError
|
||||
|
||||
scene_objects.append(asr)
|
||||
|
||||
if tmp[0] == "#add_surface_water:":
|
||||
if len(tmp) != 9:
|
||||
logger.exception("'" + " ".join(tmp) + "'" + " requires exactly eight parameters")
|
||||
logger.exception(
|
||||
"'" + " ".join(tmp) + "'" + " requires exactly eight parameters"
|
||||
)
|
||||
raise ValueError
|
||||
|
||||
p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
|
||||
@@ -452,7 +468,9 @@ def process_geometrycmds(geometry):
|
||||
|
||||
if tmp[0] == "#add_grass:":
|
||||
if len(tmp) < 12:
|
||||
logger.exception("'" + " ".join(tmp) + "'" + " requires at least eleven parameters")
|
||||
logger.exception(
|
||||
"'" + " ".join(tmp) + "'" + " requires at least eleven parameters"
|
||||
)
|
||||
raise ValueError
|
||||
|
||||
p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
|
||||
@@ -482,7 +500,9 @@ def process_geometrycmds(geometry):
|
||||
seed=int(tmp[12]),
|
||||
)
|
||||
else:
|
||||
logger.exception("'" + " ".join(tmp) + "'" + " too many parameters have been given")
|
||||
logger.exception(
|
||||
"'" + " ".join(tmp) + "'" + " too many parameters have been given"
|
||||
)
|
||||
raise ValueError
|
||||
|
||||
scene_objects.append(grass)
|
||||
|
@@ -18,14 +18,12 @@
|
||||
|
||||
import logging
|
||||
|
||||
from .cmds_multiuse import (
|
||||
from .user_objects.cmds_multiuse import (
|
||||
PMLCFS,
|
||||
AddDebyeDispersion,
|
||||
AddDrudeDispersion,
|
||||
AddLorentzDispersion,
|
||||
ExcitationFile,
|
||||
GeometryObjectsWrite,
|
||||
GeometryView,
|
||||
HertzianDipole,
|
||||
MagneticDipole,
|
||||
Material,
|
||||
@@ -39,6 +37,7 @@ from .cmds_multiuse import (
|
||||
VoltageSource,
|
||||
Waveform,
|
||||
)
|
||||
from .user_objects.cmds_output import GeometryObjectsWrite, GeometryView
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@@ -18,9 +18,18 @@
|
||||
|
||||
import logging
|
||||
|
||||
from .cmds_singleuse import (Discretisation, Domain, OMPThreads, OutputDir,
|
||||
PMLProps, RxSteps, SrcSteps,
|
||||
TimeStepStabilityFactor, TimeWindow, Title)
|
||||
from .user_objects.cmds_singleuse import (
|
||||
Discretisation,
|
||||
Domain,
|
||||
OMPThreads,
|
||||
OutputDir,
|
||||
PMLProps,
|
||||
RxSteps,
|
||||
SrcSteps,
|
||||
TimeStepStabilityFactor,
|
||||
TimeWindow,
|
||||
Title,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -54,7 +63,9 @@ def process_singlecmds(singlecmds):
|
||||
if singlecmds[cmd] is not None:
|
||||
tmp = tuple(int(x) for x in singlecmds[cmd].split())
|
||||
if len(tmp) != 1:
|
||||
logger.exception(f"{cmd} requires exactly one parameter to specify the number of CPU OpenMP threads to use")
|
||||
logger.exception(
|
||||
f"{cmd} requires exactly one parameter to specify the number of CPU OpenMP threads to use"
|
||||
)
|
||||
raise ValueError
|
||||
|
||||
omp_threads = OMPThreads(n=tmp[0])
|
||||
@@ -144,7 +155,12 @@ def process_singlecmds(singlecmds):
|
||||
pml_props = PMLProps(thickness=int(tmp[0]))
|
||||
else:
|
||||
pml_props = PMLProps(
|
||||
x0=int(tmp[0]), y0=int(tmp[1]), z0=int(tmp[2]), xmax=int(tmp[3]), ymax=int(tmp[4]), zmax=int(tmp[5])
|
||||
x0=int(tmp[0]),
|
||||
y0=int(tmp[1]),
|
||||
z0=int(tmp[2]),
|
||||
xmax=int(tmp[3]),
|
||||
ymax=int(tmp[4]),
|
||||
zmax=int(tmp[5]),
|
||||
)
|
||||
|
||||
scene_objects.append(pml_props)
|
||||
|
@@ -56,11 +56,9 @@ class Model:
|
||||
self.dt_mod = 1.0 # Time step stability factor
|
||||
|
||||
self.iteration = 0 # Current iteration number
|
||||
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] = []
|
||||
@@ -141,6 +139,22 @@ class Model:
|
||||
def dt(self, value: float):
|
||||
self.G.dt = value
|
||||
|
||||
@property
|
||||
def iterations(self) -> int:
|
||||
return self.G.iterations
|
||||
|
||||
@iterations.setter
|
||||
def iterations(self, value: int):
|
||||
self.G.iterations = value
|
||||
|
||||
@property
|
||||
def timewindow(self) -> float:
|
||||
return self.G.timewindow
|
||||
|
||||
@timewindow.setter
|
||||
def timewindow(self, value: float):
|
||||
self.G.timewindow = value
|
||||
|
||||
def _create_grid(self) -> FDTDGrid:
|
||||
"""Create grid object according to solver.
|
||||
|
||||
|
234
gprMax/scene.py
234
gprMax/scene.py
@@ -16,22 +16,24 @@
|
||||
# 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 List, Optional, Union
|
||||
from typing import List, Sequence
|
||||
|
||||
from gprMax import config
|
||||
from gprMax.cmds_geometry.add_grass import AddGrass
|
||||
from gprMax.cmds_geometry.add_surface_roughness import AddSurfaceRoughness
|
||||
from gprMax.cmds_geometry.add_surface_water import AddSurfaceWater
|
||||
from gprMax.cmds_geometry.cmds_geometry import UserObjectGeometry
|
||||
from gprMax.cmds_geometry.fractal_box import FractalBox
|
||||
from gprMax.cmds_multiuse import UserObjectMulti
|
||||
from gprMax.cmds_singleuse import Discretisation, Domain, TimeWindow, UserObjectSingle
|
||||
from gprMax.grid.fdtd_grid import FDTDGrid
|
||||
from gprMax.materials import create_built_in_materials
|
||||
from gprMax.model import Model
|
||||
from gprMax.subgrids.grid import SubGridBaseGrid
|
||||
from gprMax.subgrids.user_objects import SubGridBase as SubGridUserBase
|
||||
from gprMax.user_inputs import MainGridUserInput, SubgridUserInput
|
||||
from gprMax.user_objects.cmds_geometry.add_grass import AddGrass
|
||||
from gprMax.user_objects.cmds_geometry.add_surface_roughness import AddSurfaceRoughness
|
||||
from gprMax.user_objects.cmds_geometry.add_surface_water import AddSurfaceWater
|
||||
from gprMax.user_objects.cmds_geometry.fractal_box import FractalBox
|
||||
from gprMax.user_objects.cmds_singleuse import Discretisation, Domain, TimeWindow
|
||||
from gprMax.user_objects.user_objects import (
|
||||
GeometryUserObject,
|
||||
GridUserObject,
|
||||
ModelUserObject,
|
||||
OutputUserObject,
|
||||
UserObject,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -39,128 +41,85 @@ logger = logging.getLogger(__name__)
|
||||
class Scene:
|
||||
"""Scene stores all of the user created objects."""
|
||||
|
||||
def __init__(self):
|
||||
self.multiple_cmds: List[UserObjectMulti] = []
|
||||
self.single_cmds: List[UserObjectSingle] = []
|
||||
self.geometry_cmds: List[UserObjectGeometry] = []
|
||||
self.multiple_cmds: List[UserObjectMulti] = []
|
||||
self.single_cmds: List[UserObjectSingle] = []
|
||||
self.geometry_cmds: List[UserObjectGeometry] = []
|
||||
self.essential_cmds = [Domain, TimeWindow, Discretisation]
|
||||
ESSENTIAL_CMDS = [Domain, TimeWindow, Discretisation]
|
||||
|
||||
def add(self, user_object):
|
||||
def __init__(self):
|
||||
self.single_use_objects: List[ModelUserObject] = []
|
||||
self.grid_objects: List[GridUserObject] = []
|
||||
self.geometry_objects: List[GeometryUserObject] = []
|
||||
self.output_objects: List[OutputUserObject] = []
|
||||
self.subgrid_objects: List[SubGridUserBase] = []
|
||||
|
||||
def add(self, user_object: UserObject):
|
||||
"""Add the user object to the scene.
|
||||
|
||||
Args:
|
||||
user_object: user object to add to the scene. For example,
|
||||
:class:`gprMax.cmds_single_use.Domain`
|
||||
`gprMax.user_objects.cmds_singleuse.Domain`
|
||||
"""
|
||||
if isinstance(user_object, UserObjectMulti):
|
||||
self.multiple_cmds.append(user_object)
|
||||
elif isinstance(user_object, UserObjectGeometry):
|
||||
self.geometry_cmds.append(user_object)
|
||||
elif isinstance(user_object, UserObjectSingle):
|
||||
self.single_cmds.append(user_object)
|
||||
# Check for
|
||||
if isinstance(user_object, SubGridUserBase):
|
||||
self.subgrid_objects.append(user_object)
|
||||
elif isinstance(user_object, ModelUserObject):
|
||||
self.single_use_objects.append(user_object)
|
||||
elif isinstance(user_object, GeometryUserObject):
|
||||
self.geometry_objects.append(user_object)
|
||||
elif isinstance(user_object, GridUserObject):
|
||||
self.grid_objects.append(user_object)
|
||||
elif isinstance(user_object, OutputUserObject):
|
||||
self.output_objects.append(user_object)
|
||||
else:
|
||||
logger.exception("This object is unknown to gprMax")
|
||||
raise ValueError
|
||||
raise TypeError(f"Object of type '{type(user_object)}' is unknown to gprMax")
|
||||
|
||||
def build_grid_obj(self, obj: UserObjectGeometry, grid: FDTDGrid):
|
||||
"""Builds objects in FDTDGrids.
|
||||
|
||||
Args:
|
||||
obj: user object
|
||||
grid: FDTDGrid class describing a grid in a model.
|
||||
"""
|
||||
uip = create_user_input_points(grid, obj)
|
||||
try:
|
||||
obj.build(grid, uip)
|
||||
except ValueError:
|
||||
logger.exception("Error creating user input object")
|
||||
raise
|
||||
|
||||
def build_model_obj(
|
||||
self,
|
||||
obj: Union[UserObjectSingle, UserObjectMulti],
|
||||
model: Model,
|
||||
subgrid: Optional[FDTDGrid] = None,
|
||||
):
|
||||
def build_model_objects(self, objects: Sequence[ModelUserObject], model: Model):
|
||||
"""Builds objects in models.
|
||||
|
||||
Args:
|
||||
obj: user object
|
||||
model: Model being built
|
||||
"""
|
||||
|
||||
grid = model.G if subgrid is None else subgrid
|
||||
uip = create_user_input_points(grid, obj)
|
||||
|
||||
try:
|
||||
obj.build(model, uip)
|
||||
for model_user_object in sorted(objects):
|
||||
model_user_object.build(model)
|
||||
except ValueError:
|
||||
logger.exception("Error creating user input object")
|
||||
logger.exception(f"Error creating user object '{model_user_object}'")
|
||||
raise
|
||||
|
||||
def process_subgrid_cmds(self, model: Model):
|
||||
"""Process all commands in any sub-grids."""
|
||||
def build_grid_objects(self, objects: Sequence[GridUserObject], grid: FDTDGrid):
|
||||
"""Builds objects in FDTDGrids.
|
||||
|
||||
# Subgrid user objects
|
||||
subgrid_cmds = [
|
||||
sg_cmd for sg_cmd in self.multiple_cmds if isinstance(sg_cmd, SubGridUserBase)
|
||||
]
|
||||
subgrid_cmds = [
|
||||
sg_cmd for sg_cmd in self.multiple_cmds if isinstance(sg_cmd, SubGridUserBase)
|
||||
]
|
||||
Args:
|
||||
objects: user object
|
||||
grid: FDTDGrid class describing a grid in a model.
|
||||
"""
|
||||
try:
|
||||
for grid_user_object in sorted(objects):
|
||||
grid_user_object.build(grid)
|
||||
except ValueError:
|
||||
logger.exception(f"Error creating user object '{grid_user_object}'")
|
||||
raise
|
||||
|
||||
# Iterate through the user command objects under the subgrid user object
|
||||
for sg_cmd in subgrid_cmds:
|
||||
# When the subgrid is created its reference is attached to its user
|
||||
# object. This reference allows the multi and geo user objects
|
||||
# to build in the correct subgrid.
|
||||
sg = sg_cmd.subgrid
|
||||
self.process_cmds(sg_cmd.children_multiple, model, sg)
|
||||
self.process_geocmds(sg_cmd.children_geometry, sg)
|
||||
|
||||
def process_cmds(
|
||||
self,
|
||||
commands: Union[List[UserObjectMulti], List[UserObjectSingle]],
|
||||
model: Model,
|
||||
subgrid: Optional[SubGridBaseGrid] = None,
|
||||
def build_output_objects(
|
||||
self, objects: Sequence[OutputUserObject], model: Model, grid: FDTDGrid
|
||||
):
|
||||
"""Process list of commands."""
|
||||
cmds_sorted = sorted(commands, key=lambda cmd: cmd.order)
|
||||
for obj in cmds_sorted:
|
||||
self.build_model_obj(obj, model, subgrid)
|
||||
try:
|
||||
for output_user_object in sorted(objects):
|
||||
output_user_object.build(model, grid)
|
||||
except ValueError:
|
||||
logger.exception(f"Error creating user object '{output_user_object}'")
|
||||
raise
|
||||
|
||||
return self
|
||||
|
||||
def process_geocmds(self, commands, grid):
|
||||
# Check for fractal boxes and modifications and pre-process them first
|
||||
proc_cmds = []
|
||||
for obj in commands:
|
||||
if isinstance(obj, (FractalBox, AddGrass, AddSurfaceRoughness, AddSurfaceWater)):
|
||||
self.build_grid_obj(obj, grid)
|
||||
if isinstance(obj, (FractalBox)):
|
||||
proc_cmds.append(obj)
|
||||
else:
|
||||
proc_cmds.append(obj)
|
||||
|
||||
# Process all geometry commands
|
||||
for obj in proc_cmds:
|
||||
self.build_grid_obj(obj, grid)
|
||||
|
||||
return self
|
||||
|
||||
def process_singlecmds(self, model: Model):
|
||||
def process_single_use_objects(self, model: Model):
|
||||
# Check for duplicate commands and warn user if they exist
|
||||
cmds_unique = list(set(self.single_cmds))
|
||||
if len(cmds_unique) != len(self.single_cmds):
|
||||
# TODO: Test this works
|
||||
unique_commands = list(set(self.single_use_objects))
|
||||
if len(unique_commands) != len(self.single_use_objects):
|
||||
logger.exception("Duplicate single-use commands exist in the input.")
|
||||
raise ValueError
|
||||
|
||||
# Check essential commands and warn user if missing
|
||||
for cmd_type in self.essential_cmds:
|
||||
d = any(isinstance(cmd, cmd_type) for cmd in cmds_unique)
|
||||
for cmd_type in self.ESSENTIAL_CMDS:
|
||||
d = any(isinstance(cmd, cmd_type) for cmd in unique_commands)
|
||||
if not d:
|
||||
logger.exception(
|
||||
"Your input file is missing essential commands "
|
||||
@@ -169,7 +128,39 @@ class Scene:
|
||||
)
|
||||
raise ValueError
|
||||
|
||||
self.process_cmds(cmds_unique, model)
|
||||
self.build_model_objects(unique_commands, model)
|
||||
|
||||
def process_multi_use_objects(self, model: Model):
|
||||
self.build_grid_objects(self.grid_objects, model.G)
|
||||
self.build_output_objects(self.output_objects, model, model.G)
|
||||
self.build_model_objects(self.subgrid_objects, model)
|
||||
|
||||
def process_geometry_objects(self, geometry_objects: List[GeometryUserObject], grid: FDTDGrid):
|
||||
# Check for fractal boxes and modifications and pre-process them first
|
||||
# TODO: Can this be removed in favour of sorting geometry objects?
|
||||
objects_to_be_built: List[GeometryUserObject] = []
|
||||
for obj in geometry_objects:
|
||||
if isinstance(obj, (FractalBox, AddGrass, AddSurfaceRoughness, AddSurfaceWater)):
|
||||
self.build_grid_objects([obj], grid)
|
||||
if isinstance(obj, (FractalBox)):
|
||||
objects_to_be_built.append(obj)
|
||||
else:
|
||||
objects_to_be_built.append(obj)
|
||||
|
||||
# Process all geometry commands
|
||||
self.build_grid_objects(objects_to_be_built, grid)
|
||||
|
||||
def process_subgrid_objects(self, model: Model):
|
||||
"""Process all commands in any sub-grids."""
|
||||
# Iterate through the user command objects under the subgrid user object
|
||||
for subgrid_object in self.subgrid_objects:
|
||||
# When the subgrid is created its reference is attached to its user
|
||||
# object. This reference allows the multi and geo user objects
|
||||
# to build in the correct subgrid.
|
||||
subgrid = subgrid_object.subgrid
|
||||
self.build_grid_objects(subgrid_object.children_grid, subgrid)
|
||||
self.build_output_objects(subgrid_object.children_output, model, subgrid)
|
||||
self.process_geometry_objects(subgrid_object.children_geometry, subgrid)
|
||||
|
||||
def create_internal_objects(self, model: Model):
|
||||
"""Calls the UserObject.build() function in the correct way - API
|
||||
@@ -181,36 +172,17 @@ class Scene:
|
||||
create_built_in_materials(model.G)
|
||||
|
||||
# Process commands that can only have a single instance
|
||||
self.process_singlecmds(model)
|
||||
self.process_single_use_objects(model)
|
||||
|
||||
# Process main grid multiple commands
|
||||
self.process_cmds(self.multiple_cmds, model)
|
||||
# Process multiple commands
|
||||
self.process_multi_use_objects(model)
|
||||
|
||||
# Initialise geometry arrays for main and subgrids
|
||||
for grid in [model.G] + model.subgrids:
|
||||
grid.initialise_geometry_arrays()
|
||||
|
||||
# Process the main grid geometry commands
|
||||
self.process_geocmds(self.geometry_cmds, model.G)
|
||||
self.process_geometry_objects(self.geometry_objects, model.G)
|
||||
|
||||
# Process all the commands for subgrids
|
||||
self.process_subgrid_cmds(model)
|
||||
|
||||
|
||||
def create_user_input_points(
|
||||
grid: FDTDGrid, user_obj: Union[UserObjectSingle, UserObjectMulti, UserObjectGeometry]
|
||||
) -> Union[MainGridUserInput, SubgridUserInput]:
|
||||
"""Returns a point checker class based on the grid supplied."""
|
||||
|
||||
if isinstance(grid, SubGridBaseGrid):
|
||||
# Local object configuration trumps. User can turn off autotranslate for
|
||||
# specific objects.
|
||||
if not user_obj.autotranslate and config.sim_config.args.autotranslate:
|
||||
return MainGridUserInput(grid)
|
||||
|
||||
if config.sim_config.args.autotranslate:
|
||||
return SubgridUserInput(grid)
|
||||
else:
|
||||
return MainGridUserInput(grid)
|
||||
else:
|
||||
return MainGridUserInput(grid)
|
||||
self.process_subgrid_objects(model)
|
||||
|
@@ -110,7 +110,7 @@ class Snapshot:
|
||||
filename: str,
|
||||
fileext: str,
|
||||
outputs: Dict[str, bool],
|
||||
grid_dl: npt.NDArray[np.float32],
|
||||
grid_dl: npt.NDArray[np.float64],
|
||||
grid_dt: float,
|
||||
):
|
||||
"""
|
||||
@@ -358,7 +358,7 @@ class MPISnapshot(Snapshot):
|
||||
filename: str,
|
||||
fileext: str,
|
||||
outputs: Dict[str, bool],
|
||||
grid_dl: npt.NDArray[np.float32],
|
||||
grid_dl: npt.NDArray[np.float64],
|
||||
grid_dt: float,
|
||||
):
|
||||
super().__init__(
|
||||
|
@@ -25,33 +25,42 @@ import numpy as np
|
||||
from gprMax.grid.fdtd_grid import FDTDGrid
|
||||
from gprMax.model import Model
|
||||
from gprMax.subgrids.grid import SubGridBaseGrid
|
||||
from gprMax.subgrids.subgrid_hsg import SubGridHSG as SubGridHSGUser
|
||||
from gprMax.user_inputs import MainGridUserInput
|
||||
|
||||
from ..cmds_geometry.cmds_geometry import UserObjectGeometry
|
||||
from ..cmds_multiuse import UserObjectMulti
|
||||
from .subgrid_hsg import SubGridHSG as SubGridHSGUser
|
||||
from gprMax.user_objects.user_objects import (
|
||||
GeometryUserObject,
|
||||
GridUserObject,
|
||||
ModelUserObject,
|
||||
OutputUserObject,
|
||||
UserObject,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SubGridBase(UserObjectMulti):
|
||||
class SubGridBase(ModelUserObject):
|
||||
"""Allows UserObjectMulti and UserObjectGeometry to be nested in SubGrid
|
||||
type user objects.
|
||||
"""
|
||||
|
||||
@property
|
||||
def is_single_use(self) -> bool:
|
||||
return False
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.children_multiple: List[UserObjectMulti] = []
|
||||
self.children_geometry: List[UserObjectGeometry] = []
|
||||
self.children_multiple: List[UserObjectMulti] = []
|
||||
self.children_geometry: List[UserObjectGeometry] = []
|
||||
self.children_grid: List[GridUserObject] = []
|
||||
self.children_geometry: List[GeometryUserObject] = []
|
||||
self.children_output: List[OutputUserObject] = []
|
||||
|
||||
def add(self, node: Union[UserObjectMulti, UserObjectGeometry]):
|
||||
def add(self, node: UserObject):
|
||||
"""Adds other user objects. Geometry and multi only."""
|
||||
if isinstance(node, UserObjectMulti):
|
||||
self.children_multiple.append(node)
|
||||
elif isinstance(node, UserObjectGeometry):
|
||||
if isinstance(node, GeometryUserObject):
|
||||
self.children_geometry.append(node)
|
||||
elif isinstance(node, GridUserObject):
|
||||
self.children_grid.append(node)
|
||||
elif isinstance(node, OutputUserObject):
|
||||
self.children_output.append(node)
|
||||
else:
|
||||
logger.exception(f"{str(node)} this Object can not be added to a sub grid")
|
||||
raise ValueError
|
||||
@@ -92,11 +101,12 @@ class SubGridBase(UserObjectMulti):
|
||||
"""Sets number of iterations that will take place in the subgrid."""
|
||||
sg.iterations = model.iterations * sg.ratio
|
||||
|
||||
def setup(self, sg: SubGridBaseGrid, model: Model, uip: MainGridUserInput):
|
||||
def setup(self, sg: SubGridBaseGrid, model: Model):
|
||||
""" "Common setup to both all subgrid types."""
|
||||
p1 = self.kwargs["p1"]
|
||||
p2 = self.kwargs["p2"]
|
||||
|
||||
uip = self._create_uip(model.G)
|
||||
p1, p2 = uip.check_box_points(p1, p2, self.__str__())
|
||||
|
||||
self.set_discretisation(sg, model.G)
|
||||
@@ -170,6 +180,14 @@ class SubGridHSG(SubGridBase):
|
||||
stability. Defaults to True.
|
||||
"""
|
||||
|
||||
@property
|
||||
def order(self):
|
||||
return 18
|
||||
|
||||
@property
|
||||
def hash(self):
|
||||
return "#subgrid_hsg"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
p1=None,
|
||||
@@ -197,10 +215,8 @@ class SubGridHSG(SubGridBase):
|
||||
kwargs["filter"] = filter
|
||||
|
||||
super().__init__(**kwargs)
|
||||
self.order = 18
|
||||
self.hash = "#subgrid_hsg"
|
||||
|
||||
def build(self, model: Model, uip: MainGridUserInput) -> SubGridHSGUser:
|
||||
def build(self, model: Model) -> SubGridHSGUser:
|
||||
sg = SubGridHSGUser(**self.kwargs)
|
||||
self.setup(sg, model, uip)
|
||||
self.setup(sg, model)
|
||||
return sg
|
||||
|
@@ -20,15 +20,19 @@ import logging
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ..fractals import FractalSurface, Grass
|
||||
from ..materials import create_grass
|
||||
from ..utilities.utilities import round_value
|
||||
from .cmds_geometry import UserObjectGeometry, rotate_2point_object
|
||||
from gprMax.fractals import FractalSurface, Grass
|
||||
from gprMax.grid.fdtd_grid import FDTDGrid
|
||||
from gprMax.materials import create_grass
|
||||
from gprMax.user_objects.rotatable import RotatableMixin
|
||||
from gprMax.user_objects.user_objects import GeometryUserObject
|
||||
from gprMax.utilities.utilities import round_value
|
||||
|
||||
from .cmds_geometry import rotate_2point_object
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AddGrass(UserObjectGeometry):
|
||||
class AddGrass(RotatableMixin, GeometryUserObject):
|
||||
"""Adds grass with roots to a FractalBox class in the model.
|
||||
|
||||
Attributes:
|
||||
@@ -46,25 +50,21 @@ class AddGrass(UserObjectGeometry):
|
||||
grass should be applied to.
|
||||
"""
|
||||
|
||||
@property
|
||||
def hash(self):
|
||||
return "#add_grass"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.hash = "#add_grass"
|
||||
|
||||
def rotate(self, axis, angle, origin=None):
|
||||
"""Set parameters for rotation."""
|
||||
self.axis = axis
|
||||
self.angle = angle
|
||||
self.origin = origin
|
||||
self.do_rotate = True
|
||||
|
||||
def _do_rotate(self):
|
||||
def _do_rotate(self, grid: FDTDGrid):
|
||||
"""Perform rotation."""
|
||||
pts = np.array([self.kwargs["p1"], self.kwargs["p2"]])
|
||||
rot_pts = rotate_2point_object(pts, self.axis, self.angle, self.origin)
|
||||
self.kwargs["p1"] = tuple(rot_pts[0, :])
|
||||
self.kwargs["p2"] = tuple(rot_pts[1, :])
|
||||
|
||||
def build(self, grid, uip):
|
||||
def build(self, grid: FDTDGrid):
|
||||
"""Add Grass to fractal box."""
|
||||
try:
|
||||
p1 = self.kwargs["p1"]
|
||||
@@ -88,7 +88,7 @@ class AddGrass(UserObjectGeometry):
|
||||
seed = None
|
||||
|
||||
if self.do_rotate:
|
||||
self._do_rotate()
|
||||
self._do_rotate(grid)
|
||||
|
||||
# Get the correct fractal volume
|
||||
volumes = [volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id]
|
||||
@@ -98,6 +98,7 @@ class AddGrass(UserObjectGeometry):
|
||||
logger.exception(f"{self.__str__()} cannot find FractalBox {fractal_box_id}")
|
||||
raise
|
||||
|
||||
uip = self._create_uip(grid)
|
||||
p1, p2 = uip.check_box_points(p1, p2, self.__str__())
|
||||
xs, ys, zs = p1
|
||||
xf, yf, zf = p2
|
@@ -20,14 +20,18 @@ import logging
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ..fractals import FractalSurface
|
||||
from ..utilities.utilities import round_value
|
||||
from .cmds_geometry import UserObjectGeometry, rotate_2point_object
|
||||
from gprMax.fractals import FractalSurface
|
||||
from gprMax.grid.fdtd_grid import FDTDGrid
|
||||
from gprMax.user_objects.rotatable import RotatableMixin
|
||||
from gprMax.user_objects.user_objects import GeometryUserObject
|
||||
from gprMax.utilities.utilities import round_value
|
||||
|
||||
from .cmds_geometry import rotate_2point_object
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AddSurfaceRoughness(UserObjectGeometry):
|
||||
class AddSurfaceRoughness(RotatableMixin, GeometryUserObject):
|
||||
"""Adds surface roughness to a FractalBox class in the model.
|
||||
|
||||
Attributes:
|
||||
@@ -47,25 +51,21 @@ class AddSurfaceRoughness(UserObjectGeometry):
|
||||
number generator used to create the fractals.
|
||||
"""
|
||||
|
||||
@property
|
||||
def hash(self):
|
||||
return "#add_surface_roughness"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.hash = "#add_surface_roughness"
|
||||
|
||||
def rotate(self, axis, angle, origin=None):
|
||||
"""Set parameters for rotation."""
|
||||
self.axis = axis
|
||||
self.angle = angle
|
||||
self.origin = origin
|
||||
self.do_rotate = True
|
||||
|
||||
def _do_rotate(self):
|
||||
def _do_rotate(self, grid: FDTDGrid):
|
||||
"""Perform rotation."""
|
||||
pts = np.array([self.kwargs["p1"], self.kwargs["p2"]])
|
||||
rot_pts = rotate_2point_object(pts, self.axis, self.angle, self.origin)
|
||||
self.kwargs["p1"] = tuple(rot_pts[0, :])
|
||||
self.kwargs["p2"] = tuple(rot_pts[1, :])
|
||||
|
||||
def build(self, grid, uip):
|
||||
def build(self, grid: FDTDGrid):
|
||||
try:
|
||||
p1 = self.kwargs["p1"]
|
||||
p2 = self.kwargs["p2"]
|
||||
@@ -88,7 +88,7 @@ class AddSurfaceRoughness(UserObjectGeometry):
|
||||
seed = None
|
||||
|
||||
if self.do_rotate:
|
||||
self._do_rotate()
|
||||
self._do_rotate(grid)
|
||||
|
||||
# Get the correct fractal volume
|
||||
volumes = [volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id]
|
||||
@@ -98,6 +98,7 @@ class AddSurfaceRoughness(UserObjectGeometry):
|
||||
logger.exception(f"{self.__str__()} cannot find FractalBox {fractal_box_id}")
|
||||
raise ValueError
|
||||
|
||||
uip = self._create_uip(grid)
|
||||
p1, p2 = uip.check_box_points(p1, p2, self.__str__())
|
||||
xs, ys, zs = p1
|
||||
xf, yf, zf = p2
|
@@ -20,14 +20,18 @@ import logging
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ..materials import create_water
|
||||
from ..utilities.utilities import round_value
|
||||
from .cmds_geometry import UserObjectGeometry, rotate_2point_object
|
||||
from gprMax.grid.fdtd_grid import FDTDGrid
|
||||
from gprMax.materials import create_water
|
||||
from gprMax.user_objects.rotatable import RotatableMixin
|
||||
from gprMax.user_objects.user_objects import GeometryUserObject
|
||||
from gprMax.utilities.utilities import round_value
|
||||
|
||||
from .cmds_geometry import rotate_2point_object
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AddSurfaceWater(UserObjectGeometry):
|
||||
class AddSurfaceWater(RotatableMixin, GeometryUserObject):
|
||||
"""Adds surface water to a FractalBox class in the model.
|
||||
|
||||
Attributes:
|
||||
@@ -42,25 +46,21 @@ class AddSurfaceWater(UserObjectGeometry):
|
||||
surface water should be applied to.
|
||||
"""
|
||||
|
||||
@property
|
||||
def hash(self):
|
||||
return "#add_surface_water"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.hash = "#add_surface_water"
|
||||
|
||||
def rotate(self, axis, angle, origin=None):
|
||||
"""Set parameters for rotation."""
|
||||
self.axis = axis
|
||||
self.angle = angle
|
||||
self.origin = origin
|
||||
self.do_rotate = True
|
||||
|
||||
def _do_rotate(self):
|
||||
def _do_rotate(self, grid: FDTDGrid):
|
||||
"""Perform rotation."""
|
||||
pts = np.array([self.kwargs["p1"], self.kwargs["p2"]])
|
||||
rot_pts = rotate_2point_object(pts, self.axis, self.angle, self.origin)
|
||||
self.kwargs["p1"] = tuple(rot_pts[0, :])
|
||||
self.kwargs["p2"] = tuple(rot_pts[1, :])
|
||||
|
||||
def build(self, grid, uip):
|
||||
def build(self, grid: FDTDGrid):
|
||||
""" "Create surface water on fractal box."""
|
||||
try:
|
||||
p1 = self.kwargs["p1"]
|
||||
@@ -72,7 +72,7 @@ class AddSurfaceWater(UserObjectGeometry):
|
||||
raise
|
||||
|
||||
if self.do_rotate:
|
||||
self._do_rotate()
|
||||
self._do_rotate(grid)
|
||||
|
||||
if volumes := [volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id]:
|
||||
volume = volumes[0]
|
||||
@@ -80,6 +80,7 @@ class AddSurfaceWater(UserObjectGeometry):
|
||||
logger.exception(f"{self.__str__()} cannot find FractalBox {fractal_box_id}")
|
||||
raise ValueError
|
||||
|
||||
uip = self._create_uip(grid)
|
||||
p1, p2 = uip.check_box_points(p1, p2, self.__str__())
|
||||
xs, ys, zs = p1
|
||||
xf, yf, zf = p2
|
@@ -21,15 +21,18 @@ import logging
|
||||
import numpy as np
|
||||
|
||||
import gprMax.config as config
|
||||
from gprMax.cython.geometry_primitives import build_box
|
||||
from gprMax.grid.fdtd_grid import FDTDGrid
|
||||
from gprMax.materials import Material
|
||||
from gprMax.user_objects.rotatable import RotatableMixin
|
||||
from gprMax.user_objects.user_objects import GeometryUserObject
|
||||
|
||||
from ..cython.geometry_primitives import build_box
|
||||
from ..materials import Material
|
||||
from .cmds_geometry import UserObjectGeometry, check_averaging, rotate_2point_object
|
||||
from .cmds_geometry import rotate_2point_object
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Box(UserObjectGeometry):
|
||||
class Box(RotatableMixin, GeometryUserObject):
|
||||
"""Introduces an orthogonal parallelepiped with specific properties into
|
||||
the model.
|
||||
|
||||
@@ -42,25 +45,21 @@ class Box(UserObjectGeometry):
|
||||
averaging: string (y or n) used to switch on and off dielectric smoothing.
|
||||
"""
|
||||
|
||||
@property
|
||||
def hash(self):
|
||||
return "#box"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.hash = "#box"
|
||||
|
||||
def rotate(self, axis, angle, origin=None):
|
||||
"""Set parameters for rotation."""
|
||||
self.axis = axis
|
||||
self.angle = angle
|
||||
self.origin = origin
|
||||
self.do_rotate = True
|
||||
|
||||
def _do_rotate(self):
|
||||
def _do_rotate(self, grid: FDTDGrid):
|
||||
"""Perform rotation."""
|
||||
pts = np.array([self.kwargs["p1"], self.kwargs["p2"]])
|
||||
rot_pts = rotate_2point_object(pts, self.axis, self.angle, self.origin)
|
||||
self.kwargs["p1"] = tuple(rot_pts[0, :])
|
||||
self.kwargs["p2"] = tuple(rot_pts[1, :])
|
||||
|
||||
def build(self, grid, uip):
|
||||
def build(self, grid: FDTDGrid):
|
||||
try:
|
||||
p1 = self.kwargs["p1"]
|
||||
p2 = self.kwargs["p2"]
|
||||
@@ -69,7 +68,7 @@ class Box(UserObjectGeometry):
|
||||
raise
|
||||
|
||||
if self.do_rotate:
|
||||
self._do_rotate()
|
||||
self._do_rotate(grid)
|
||||
|
||||
# Check materials have been specified
|
||||
# Isotropic case
|
||||
@@ -91,6 +90,7 @@ class Box(UserObjectGeometry):
|
||||
# Otherwise go with the grid default
|
||||
averagebox = grid.averagevolumeobjects
|
||||
|
||||
uip = self._create_uip(grid)
|
||||
p3, p4 = uip.check_box_points(p1, p2, self.__str__())
|
||||
# Find nearest point on grid without translation
|
||||
p5 = uip.round_to_grid_static_point(p1)
|
@@ -26,44 +26,6 @@ import gprMax.config as config
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UserObjectGeometry:
|
||||
"""Specific Geometry object."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.kwargs = kwargs
|
||||
self.hash = "#example"
|
||||
self.autotranslate = True
|
||||
self.do_rotate = False
|
||||
|
||||
def __str__(self):
|
||||
"""Readable string of parameters given to object."""
|
||||
s = ""
|
||||
for _, v in self.kwargs.items():
|
||||
if isinstance(v, (tuple, list)):
|
||||
v = " ".join([str(el) for el in v])
|
||||
s += f"{str(v)} "
|
||||
|
||||
return f"{self.hash}: {s[:-1]}"
|
||||
|
||||
def build(self, grid, uip):
|
||||
"""Creates object and adds it to the grid."""
|
||||
pass
|
||||
|
||||
def rotate(self, axis, angle, origin=None):
|
||||
"""Rotates object - specialised for each object."""
|
||||
pass
|
||||
|
||||
def grid_name(self, grid):
|
||||
"""Returns subgrid name for use with logging info. Returns an empty
|
||||
string if the grid is the main grid.
|
||||
"""
|
||||
|
||||
if config.sim_config.general["subgrid"] and grid.name != "main_grid":
|
||||
return f"[{grid.name}] "
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
def check_averaging(averaging):
|
||||
"""Check and set material averaging value.
|
||||
|
@@ -20,14 +20,15 @@ import logging
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ..cython.geometry_primitives import build_cone
|
||||
from ..materials import Material
|
||||
from .cmds_geometry import UserObjectGeometry, check_averaging
|
||||
from gprMax.cython.geometry_primitives import build_cone
|
||||
from gprMax.grid.fdtd_grid import FDTDGrid
|
||||
from gprMax.materials import Material
|
||||
from gprMax.user_objects.user_objects import GeometryUserObject
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Cone(UserObjectGeometry):
|
||||
class Cone(GeometryUserObject):
|
||||
"""Introduces a circular cone into the model. The difference with the cylinder is that the faces of the cone
|
||||
can have different radii and one of them can be zero.
|
||||
|
||||
@@ -44,11 +45,14 @@ class Cone(UserObjectGeometry):
|
||||
averaging: string (y or n) used to switch on and off dielectric smoothing.
|
||||
"""
|
||||
|
||||
@property
|
||||
def hash(self):
|
||||
return "#cone"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.hash = "#cone"
|
||||
|
||||
def build(self, grid, uip):
|
||||
def build(self, grid: FDTDGrid) -> None:
|
||||
try:
|
||||
p1 = self.kwargs["p1"]
|
||||
p2 = self.kwargs["p2"]
|
||||
@@ -78,6 +82,7 @@ class Cone(UserObjectGeometry):
|
||||
logger.exception(f"{self.__str__()} no materials have been specified")
|
||||
raise
|
||||
|
||||
uip = self._create_uip(grid)
|
||||
p3 = uip.round_to_grid_static_point(p1)
|
||||
p4 = uip.round_to_grid_static_point(p2)
|
||||
|
@@ -20,14 +20,15 @@ import logging
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ..cython.geometry_primitives import build_cylinder
|
||||
from ..materials import Material
|
||||
from .cmds_geometry import UserObjectGeometry, check_averaging
|
||||
from gprMax.cython.geometry_primitives import build_cylinder
|
||||
from gprMax.grid.fdtd_grid import FDTDGrid
|
||||
from gprMax.materials import Material
|
||||
from gprMax.user_objects.user_objects import GeometryUserObject
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Cylinder(UserObjectGeometry):
|
||||
class Cylinder(GeometryUserObject):
|
||||
"""Introduces a circular cylinder into the model.
|
||||
|
||||
Attributes:
|
||||
@@ -42,11 +43,14 @@ class Cylinder(UserObjectGeometry):
|
||||
averaging: string (y or n) used to switch on and off dielectric smoothing.
|
||||
"""
|
||||
|
||||
@property
|
||||
def hash(self):
|
||||
return "#cylinder"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.hash = "#cylinder"
|
||||
|
||||
def build(self, grid, uip):
|
||||
def build(self, grid: FDTDGrid):
|
||||
try:
|
||||
p1 = self.kwargs["p1"]
|
||||
p2 = self.kwargs["p2"]
|
||||
@@ -75,6 +79,7 @@ class Cylinder(UserObjectGeometry):
|
||||
logger.exception(f"{self.__str__()} no materials have been specified")
|
||||
raise
|
||||
|
||||
uip = self._create_uip(grid)
|
||||
p3 = uip.round_to_grid_static_point(p1)
|
||||
p4 = uip.round_to_grid_static_point(p2)
|
||||
|
@@ -20,14 +20,15 @@ import logging
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ..cython.geometry_primitives import build_cylindrical_sector
|
||||
from ..materials import Material
|
||||
from .cmds_geometry import UserObjectGeometry, check_averaging
|
||||
from gprMax.cython.geometry_primitives import build_cylindrical_sector
|
||||
from gprMax.grid.fdtd_grid import FDTDGrid
|
||||
from gprMax.materials import Material
|
||||
from gprMax.user_objects.user_objects import GeometryUserObject
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CylindricalSector(UserObjectGeometry):
|
||||
class CylindricalSector(GeometryUserObject):
|
||||
"""Introduces a cylindrical sector (shaped like a slice of pie) into the model.
|
||||
|
||||
Attributes:
|
||||
@@ -51,11 +52,14 @@ class CylindricalSector(UserObjectGeometry):
|
||||
averaging: string (y or n) used to switch on and off dielectric smoothing.
|
||||
"""
|
||||
|
||||
@property
|
||||
def hash(self):
|
||||
return "#cylindrical_sector"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.hash = "#cylindrical_sector"
|
||||
|
||||
def build(self, grid, uip):
|
||||
def build(self, grid: FDTDGrid):
|
||||
try:
|
||||
normal = self.kwargs["normal"].lower()
|
||||
ctr1 = self.kwargs["ctr1"]
|
||||
@@ -158,6 +162,7 @@ class CylindricalSector(UserObjectGeometry):
|
||||
numIDy = materials[1].numID
|
||||
numIDz = materials[2].numID
|
||||
|
||||
uip = self._create_uip(grid)
|
||||
# yz-plane cylindrical sector
|
||||
if normal == "x":
|
||||
level, ctr1, ctr2 = uip.round_to_grid((extent1, ctr1, ctr2))
|
@@ -20,14 +20,17 @@ import logging
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ..cython.geometry_primitives import (build_edge_x, build_edge_y,
|
||||
build_edge_z)
|
||||
from .cmds_geometry import UserObjectGeometry, rotate_2point_object
|
||||
from gprMax.cython.geometry_primitives import build_edge_x, build_edge_y, build_edge_z
|
||||
from gprMax.grid.fdtd_grid import FDTDGrid
|
||||
from gprMax.user_objects.rotatable import RotatableMixin
|
||||
from gprMax.user_objects.user_objects import GeometryUserObject
|
||||
|
||||
from .cmds_geometry import rotate_2point_object
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Edge(UserObjectGeometry):
|
||||
class Edge(RotatableMixin, GeometryUserObject):
|
||||
"""Introduces a wire with specific properties into the model.
|
||||
|
||||
Attributes:
|
||||
@@ -37,25 +40,21 @@ class Edge(UserObjectGeometry):
|
||||
to material that has already been defined.
|
||||
"""
|
||||
|
||||
@property
|
||||
def hash(self):
|
||||
return "#edge"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.hash = "#edge"
|
||||
|
||||
def rotate(self, axis, angle, origin=None):
|
||||
"""Set parameters for rotation."""
|
||||
self.axis = axis
|
||||
self.angle = angle
|
||||
self.origin = origin
|
||||
self.do_rotate = True
|
||||
|
||||
def _do_rotate(self):
|
||||
def _do_rotate(self, grid: FDTDGrid):
|
||||
"""Performs rotation."""
|
||||
pts = np.array([self.kwargs["p1"], self.kwargs["p2"]])
|
||||
rot_pts = rotate_2point_object(pts, self.axis, self.angle, self.origin)
|
||||
self.kwargs["p1"] = tuple(rot_pts[0, :])
|
||||
self.kwargs["p2"] = tuple(rot_pts[1, :])
|
||||
|
||||
def build(self, grid, uip):
|
||||
def build(self, grid: FDTDGrid):
|
||||
"""Creates edge and adds it to the grid."""
|
||||
try:
|
||||
p1 = self.kwargs["p1"]
|
||||
@@ -66,8 +65,9 @@ class Edge(UserObjectGeometry):
|
||||
raise
|
||||
|
||||
if self.do_rotate:
|
||||
self._do_rotate()
|
||||
self._do_rotate(grid)
|
||||
|
||||
uip = self._create_uip(grid)
|
||||
p3 = uip.round_to_grid_static_point(p1)
|
||||
p4 = uip.round_to_grid_static_point(p2)
|
||||
|
@@ -20,14 +20,15 @@ import logging
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ..cython.geometry_primitives import build_ellipsoid
|
||||
from ..materials import Material
|
||||
from .cmds_geometry import UserObjectGeometry, check_averaging
|
||||
from gprMax.cython.geometry_primitives import build_ellipsoid
|
||||
from gprMax.grid.fdtd_grid import FDTDGrid
|
||||
from gprMax.materials import Material
|
||||
from gprMax.user_objects.user_objects import GeometryUserObject
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Ellipsoid(UserObjectGeometry):
|
||||
class Ellipsoid(GeometryUserObject):
|
||||
"""Introduces an ellipsoidal object with specific parameters into the model.
|
||||
|
||||
Attributes:
|
||||
@@ -41,11 +42,14 @@ class Ellipsoid(UserObjectGeometry):
|
||||
averaging: string (y or n) used to switch on and off dielectric smoothing.
|
||||
"""
|
||||
|
||||
@property
|
||||
def hash(self):
|
||||
return "#ellipsoid"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.hash = "#ellipsoid"
|
||||
|
||||
def build(self, grid, uip):
|
||||
def build(self, grid: FDTDGrid):
|
||||
try:
|
||||
p1 = self.kwargs["p1"]
|
||||
xr = self.kwargs["xr"]
|
||||
@@ -77,6 +81,7 @@ class Ellipsoid(UserObjectGeometry):
|
||||
raise
|
||||
|
||||
# Centre of ellipsoid
|
||||
uip = self._create_uip(grid)
|
||||
p2 = uip.round_to_grid_static_point(p1)
|
||||
xc, yc, zc = uip.discretise_point(p1)
|
||||
|
@@ -21,16 +21,18 @@ import logging
|
||||
import numpy as np
|
||||
|
||||
import gprMax.config as config
|
||||
from gprMax.cmds_geometry.cmds_geometry import UserObjectGeometry, rotate_2point_object
|
||||
from gprMax.cython.geometry_primitives import build_voxels_from_array, build_voxels_from_array_mask
|
||||
from gprMax.fractals import FractalVolume
|
||||
from gprMax.grid.fdtd_grid import FDTDGrid
|
||||
from gprMax.materials import ListMaterial
|
||||
|
||||
from ..cython.geometry_primitives import build_voxels_from_array, build_voxels_from_array_mask
|
||||
from gprMax.user_objects.cmds_geometry.cmds_geometry import rotate_2point_object
|
||||
from gprMax.user_objects.rotatable import RotatableMixin
|
||||
from gprMax.user_objects.user_objects import GeometryUserObject
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FractalBox(UserObjectGeometry):
|
||||
class FractalBox(RotatableMixin, GeometryUserObject):
|
||||
"""Introduces an orthogonal parallelepiped with fractal distributed
|
||||
properties which are related to a mixing model or normal material into
|
||||
the model.
|
||||
@@ -54,26 +56,22 @@ class FractalBox(UserObjectGeometry):
|
||||
averaging: string (y or n) used to switch on and off dielectric smoothing.
|
||||
"""
|
||||
|
||||
@property
|
||||
def hash(self):
|
||||
return "#fractal_box"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.do_pre_build = True
|
||||
self.hash = "#fractal_box"
|
||||
|
||||
def rotate(self, axis, angle, origin=None):
|
||||
"""Set parameters for rotation."""
|
||||
self.axis = axis
|
||||
self.angle = angle
|
||||
self.origin = origin
|
||||
self.do_rotate = True
|
||||
|
||||
def _do_rotate(self):
|
||||
def _do_rotate(self, grid: FDTDGrid):
|
||||
"""Performs rotation."""
|
||||
pts = np.array([self.kwargs["p1"], self.kwargs["p2"]])
|
||||
rot_pts = rotate_2point_object(pts, self.axis, self.angle, self.origin)
|
||||
self.kwargs["p1"] = tuple(rot_pts[0, :])
|
||||
self.kwargs["p2"] = tuple(rot_pts[1, :])
|
||||
|
||||
def pre_build(self, grid, uip):
|
||||
def pre_build(self, grid: FDTDGrid):
|
||||
try:
|
||||
p1 = self.kwargs["p1"]
|
||||
p2 = self.kwargs["p2"]
|
||||
@@ -97,7 +95,7 @@ class FractalBox(UserObjectGeometry):
|
||||
seed = None
|
||||
|
||||
if self.do_rotate:
|
||||
self._do_rotate()
|
||||
self._do_rotate(grid)
|
||||
|
||||
# Check averaging
|
||||
try:
|
||||
@@ -108,6 +106,7 @@ class FractalBox(UserObjectGeometry):
|
||||
# a fractal box.
|
||||
averagefractalbox = False
|
||||
|
||||
uip = self._create_uip(grid)
|
||||
p3 = uip.round_to_grid_static_point(p1)
|
||||
p4 = uip.round_to_grid_static_point(p2)
|
||||
|
||||
@@ -188,9 +187,9 @@ class FractalBox(UserObjectGeometry):
|
||||
)
|
||||
grid.fractalvolumes.append(self.volume)
|
||||
|
||||
def build(self, grid, uip):
|
||||
def build(self, grid: FDTDGrid):
|
||||
if self.do_pre_build:
|
||||
self.pre_build(grid, uip)
|
||||
self.pre_build(grid)
|
||||
self.do_pre_build = False
|
||||
else:
|
||||
if self.volume.fractalsurfaces:
|
@@ -22,24 +22,24 @@ from pathlib import Path
|
||||
import h5py
|
||||
|
||||
import gprMax.config as config
|
||||
|
||||
from ..cython.geometry_primitives import build_voxels_from_array
|
||||
from ..hash_cmds_file import get_user_objects
|
||||
from ..utilities.utilities import round_value
|
||||
from .cmds_geometry import UserObjectGeometry
|
||||
from gprMax.cython.geometry_primitives import build_voxels_from_array
|
||||
from gprMax.grid.fdtd_grid import FDTDGrid
|
||||
from gprMax.hash_cmds_file import get_user_objects
|
||||
from gprMax.user_objects.user_objects import GeometryUserObject
|
||||
from gprMax.utilities.utilities import round_value
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GeometryObjectsRead(UserObjectGeometry):
|
||||
class GeometryObjectsRead(GeometryUserObject):
|
||||
@property
|
||||
def hash(self):
|
||||
return "#geometry_objects_read"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.hash = "#geometry_objects_read"
|
||||
|
||||
def rotate(self, axis, angle, origin=None):
|
||||
pass
|
||||
|
||||
def build(self, grid, uip):
|
||||
def build(self, grid: FDTDGrid):
|
||||
"""Creates the object and adds it to the grid."""
|
||||
try:
|
||||
p1 = self.kwargs["p1"]
|
||||
@@ -52,6 +52,7 @@ class GeometryObjectsRead(UserObjectGeometry):
|
||||
# Discretise the point using uip object. This has different behaviour
|
||||
# depending on the type of uip object. So we can use it for
|
||||
# the main grid or the subgrid.
|
||||
uip = self._create_uip(grid)
|
||||
xs, ys, zs = uip.discretise_point(p1)
|
||||
|
||||
# See if material file exists at specified path and if not try input
|
||||
@@ -82,7 +83,7 @@ class GeometryObjectsRead(UserObjectGeometry):
|
||||
scene.add(material_obj)
|
||||
|
||||
# Creates the internal simulation objects
|
||||
scene.process_cmds(material_objs, grid)
|
||||
scene.build_grid_objects(material_objs, grid)
|
||||
|
||||
# Update material type
|
||||
for material in grid.materials:
|
@@ -20,14 +20,17 @@ import logging
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ..cython.geometry_primitives import (build_face_xy, build_face_xz,
|
||||
build_face_yz)
|
||||
from .cmds_geometry import UserObjectGeometry, rotate_2point_object
|
||||
from gprMax.cython.geometry_primitives import build_face_xy, build_face_xz, build_face_yz
|
||||
from gprMax.grid.fdtd_grid import FDTDGrid
|
||||
from gprMax.user_objects.rotatable import RotatableMixin
|
||||
from gprMax.user_objects.user_objects import GeometryUserObject
|
||||
|
||||
from .cmds_geometry import rotate_2point_object
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Plate(UserObjectGeometry):
|
||||
class Plate(RotatableMixin, GeometryUserObject):
|
||||
"""Introduces a plate with specific properties into the model.
|
||||
|
||||
Attributes:
|
||||
@@ -38,25 +41,21 @@ class Plate(UserObjectGeometry):
|
||||
material_ids: list of material identifiers in the x, y, z directions.
|
||||
"""
|
||||
|
||||
@property
|
||||
def hash(self):
|
||||
return "#plate"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.hash = "#plate"
|
||||
|
||||
def rotate(self, axis, angle, origin=None):
|
||||
"""Set parameters for rotation."""
|
||||
self.axis = axis
|
||||
self.angle = angle
|
||||
self.origin = origin
|
||||
self.do_rotate = True
|
||||
|
||||
def _do_rotate(self):
|
||||
def _do_rotate(self, grid: FDTDGrid):
|
||||
"""Performs rotation."""
|
||||
pts = np.array([self.kwargs["p1"], self.kwargs["p2"]])
|
||||
rot_pts = rotate_2point_object(pts, self.axis, self.angle, self.origin)
|
||||
self.kwargs["p1"] = tuple(rot_pts[0, :])
|
||||
self.kwargs["p2"] = tuple(rot_pts[1, :])
|
||||
|
||||
def build(self, grid, uip):
|
||||
def build(self, grid: FDTDGrid):
|
||||
try:
|
||||
p1 = self.kwargs["p1"]
|
||||
p2 = self.kwargs["p2"]
|
||||
@@ -76,8 +75,9 @@ class Plate(UserObjectGeometry):
|
||||
raise
|
||||
|
||||
if self.do_rotate:
|
||||
self._do_rotate()
|
||||
self._do_rotate(grid)
|
||||
|
||||
uip = self._create_uip(grid)
|
||||
p3 = uip.round_to_grid_static_point(p1)
|
||||
p4 = uip.round_to_grid_static_point(p2)
|
||||
|
@@ -20,14 +20,15 @@ import logging
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ..cython.geometry_primitives import build_sphere
|
||||
from ..materials import Material
|
||||
from .cmds_geometry import UserObjectGeometry, check_averaging
|
||||
from gprMax.cython.geometry_primitives import build_sphere
|
||||
from gprMax.grid.fdtd_grid import FDTDGrid
|
||||
from gprMax.materials import Material
|
||||
from gprMax.user_objects.user_objects import GeometryUserObject
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Sphere(UserObjectGeometry):
|
||||
class Sphere(GeometryUserObject):
|
||||
"""Introduces a spherical object with specific parameters into the model.
|
||||
|
||||
Attributes:
|
||||
@@ -39,11 +40,14 @@ class Sphere(UserObjectGeometry):
|
||||
averaging: string (y or n) used to switch on and off dielectric smoothing.
|
||||
"""
|
||||
|
||||
@property
|
||||
def hash(self):
|
||||
return "#sphere"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.hash = "#sphere"
|
||||
|
||||
def build(self, grid, uip):
|
||||
def build(self, grid: FDTDGrid):
|
||||
try:
|
||||
p1 = self.kwargs["p1"]
|
||||
r = self.kwargs["r"]
|
||||
@@ -72,6 +76,7 @@ class Sphere(UserObjectGeometry):
|
||||
raise
|
||||
|
||||
# Centre of sphere
|
||||
uip = self._create_uip(grid)
|
||||
p2 = uip.round_to_grid_static_point(p1)
|
||||
xc, yc, zc = uip.discretise_point(p1)
|
||||
|
@@ -20,14 +20,18 @@ import logging
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ..cython.geometry_primitives import build_triangle
|
||||
from ..materials import Material
|
||||
from .cmds_geometry import UserObjectGeometry, check_averaging, rotate_point
|
||||
from gprMax.cython.geometry_primitives import build_triangle
|
||||
from gprMax.grid.fdtd_grid import FDTDGrid
|
||||
from gprMax.materials import Material
|
||||
from gprMax.user_objects.rotatable import RotatableMixin
|
||||
from gprMax.user_objects.user_objects import GeometryUserObject
|
||||
|
||||
from .cmds_geometry import rotate_point
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Triangle(UserObjectGeometry):
|
||||
class Triangle(RotatableMixin, GeometryUserObject):
|
||||
"""Introduces a triangular patch or a triangular prism with specific
|
||||
properties into the model.
|
||||
|
||||
@@ -43,18 +47,14 @@ class Triangle(UserObjectGeometry):
|
||||
averaging: string (y or n) used to switch on and off dielectric smoothing.
|
||||
"""
|
||||
|
||||
@property
|
||||
def hash(self):
|
||||
return "#triangle"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.hash = "#triangle"
|
||||
|
||||
def rotate(self, axis, angle, origin=None):
|
||||
"""Sets parameters for rotation."""
|
||||
self.axis = axis
|
||||
self.angle = angle
|
||||
self.origin = origin
|
||||
self.do_rotate = True
|
||||
|
||||
def _do_rotate(self):
|
||||
def _do_rotate(self, grid: FDTDGrid):
|
||||
"""Performs rotation."""
|
||||
p1 = rotate_point(self.kwargs["p1"], self.axis, self.angle, self.origin)
|
||||
p2 = rotate_point(self.kwargs["p2"], self.axis, self.angle, self.origin)
|
||||
@@ -63,7 +63,7 @@ class Triangle(UserObjectGeometry):
|
||||
self.kwargs["p2"] = tuple(p2)
|
||||
self.kwargs["p3"] = tuple(p3)
|
||||
|
||||
def build(self, grid, uip):
|
||||
def build(self, grid: FDTDGrid):
|
||||
try:
|
||||
up1 = self.kwargs["p1"]
|
||||
up2 = self.kwargs["p2"]
|
||||
@@ -74,7 +74,7 @@ class Triangle(UserObjectGeometry):
|
||||
raise
|
||||
|
||||
if self.do_rotate:
|
||||
self._do_rotate()
|
||||
self._do_rotate(grid)
|
||||
|
||||
# Check averaging
|
||||
try:
|
||||
@@ -96,6 +96,7 @@ class Triangle(UserObjectGeometry):
|
||||
logger.exception(f"{self.__str__()} no materials have been specified")
|
||||
raise
|
||||
|
||||
uip = self._create_uip(grid)
|
||||
p4 = uip.round_to_grid_static_point(up1)
|
||||
p5 = uip.round_to_grid_static_point(up2)
|
||||
p6 = uip.round_to_grid_static_point(up3)
|
文件差异内容过多而无法显示
加载差异
180
gprMax/user_objects/cmds_output.py
普通文件
180
gprMax/user_objects/cmds_output.py
普通文件
@@ -0,0 +1,180 @@
|
||||
import logging
|
||||
|
||||
from gprMax.geometry_outputs import GeometryObjects as GeometryObjectsUser
|
||||
from gprMax.geometry_outputs import MPIGeometryObjects as MPIGeometryObjectsUser
|
||||
from gprMax.grid.fdtd_grid import FDTDGrid
|
||||
from gprMax.grid.mpi_grid import MPIGrid
|
||||
from gprMax.model import Model
|
||||
from gprMax.subgrids.grid import SubGridBaseGrid
|
||||
from gprMax.user_objects.user_objects import OutputUserObject
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GeometryView(OutputUserObject):
|
||||
"""Outputs to file(s) information about the geometry (mesh) of model.
|
||||
|
||||
The geometry information is saved in Visual Toolkit (VTK) formats.
|
||||
|
||||
Attributes:
|
||||
p1: tuple required for lower left (x,y,z) coordinates of volume of
|
||||
geometry view in metres.
|
||||
p2: tuple required for upper right (x,y,z) coordinates of volume of
|
||||
geometry view in metres.
|
||||
dl: tuple required for spatial discretisation of geometry view in metres.
|
||||
output_tuple: string required for per-cell 'n' (normal) or per-cell-edge
|
||||
'f' (fine) geometry views.
|
||||
filename: string required for filename where geometry view will be
|
||||
stored in the same directory as input file.
|
||||
"""
|
||||
|
||||
@property
|
||||
def order(self):
|
||||
return 17
|
||||
|
||||
@property
|
||||
def hash(self):
|
||||
return "#geometry_view"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def geometry_view_constructor(self, output_type):
|
||||
"""Selects appropriate class for geometry view dependent on geometry
|
||||
view type, i.e. normal or fine.
|
||||
"""
|
||||
|
||||
if output_type == "n":
|
||||
from gprMax.geometry_outputs import GeometryViewVoxels as GeometryViewUser
|
||||
else:
|
||||
from gprMax.geometry_outputs import GeometryViewLines as GeometryViewUser
|
||||
|
||||
return GeometryViewUser
|
||||
|
||||
def build(self, model: Model, grid: FDTDGrid):
|
||||
try:
|
||||
p1 = self.kwargs["p1"]
|
||||
p2 = self.kwargs["p2"]
|
||||
dl = self.kwargs["dl"]
|
||||
output_type = self.kwargs["output_type"].lower()
|
||||
filename = self.kwargs["filename"]
|
||||
except KeyError:
|
||||
logger.exception(f"{self.params_str()} requires exactly eleven parameters.")
|
||||
raise
|
||||
|
||||
GeometryViewUser = self.geometry_view_constructor(output_type)
|
||||
|
||||
uip = self._create_uip(grid)
|
||||
try:
|
||||
p3 = uip.round_to_grid_static_point(p1)
|
||||
p4 = uip.round_to_grid_static_point(p2)
|
||||
p1, p2 = uip.check_box_points(p1, p2, self.params_str())
|
||||
except ValueError:
|
||||
logger.exception(f"{self.params_str()} point is outside the domain.")
|
||||
raise
|
||||
xs, ys, zs = p1
|
||||
xf, yf, zf = p2
|
||||
|
||||
dx, dy, dz = uip.discretise_static_point(dl)
|
||||
|
||||
if dx < 0 or dy < 0 or dz < 0:
|
||||
logger.exception(f"{self.params_str()} the step size should not be less than zero.")
|
||||
raise ValueError
|
||||
if dx > grid.nx or dy > grid.ny or dz > grid.nz:
|
||||
logger.exception(
|
||||
f"{self.params_str()} the step size should be less than the domain size."
|
||||
)
|
||||
raise ValueError
|
||||
if dx < 1 or dy < 1 or dz < 1:
|
||||
logger.exception(
|
||||
f"{self.params_str()} the step size should not be less than the spatial discretisation."
|
||||
)
|
||||
raise ValueError
|
||||
if output_type not in ["n", "f"]:
|
||||
logger.exception(
|
||||
f"{self.params_str()} requires type to be either n (normal) or f (fine)."
|
||||
)
|
||||
raise ValueError
|
||||
if output_type == "f" and (
|
||||
dx * grid.dx != grid.dx or dy * grid.dy != grid.dy or dz * grid.dz != grid.dz
|
||||
):
|
||||
logger.exception(
|
||||
f"{self.params_str()} requires the spatial "
|
||||
"discretisation for the geometry view to be the "
|
||||
"same as the model for geometry view of "
|
||||
"type f (fine)"
|
||||
)
|
||||
raise ValueError
|
||||
|
||||
g = GeometryViewUser(xs, ys, zs, xf, yf, zf, dx, dy, dz, filename, grid)
|
||||
|
||||
logger.info(
|
||||
f"{self.grid_name(grid)}Geometry view from {p3[0]:g}m, "
|
||||
f"{p3[1]:g}m, {p3[2]:g}m, to {p4[0]:g}m, {p4[1]:g}m, "
|
||||
f"{p4[2]:g}m, discretisation {dx * grid.dx:g}m, "
|
||||
f"{dy * grid.dy:g}m, {dz * grid.dz:g}m, with filename "
|
||||
f"base {g.filename} created."
|
||||
)
|
||||
|
||||
model.geometryviews.append(g)
|
||||
|
||||
|
||||
class GeometryObjectsWrite(OutputUserObject):
|
||||
"""Writes geometry generated in a model to file which can be imported into
|
||||
other models.
|
||||
|
||||
Attributes:
|
||||
p1: tuple required for lower left (x,y,z) coordinates of volume of
|
||||
output in metres.
|
||||
p2: tuple required for upper right (x,y,z) coordinates of volume of
|
||||
output in metres.
|
||||
filename: string required for filename where output will be stored in
|
||||
the same directory as input file.
|
||||
"""
|
||||
|
||||
@property
|
||||
def order(self):
|
||||
return 18
|
||||
|
||||
@property
|
||||
def hash(self):
|
||||
return "#geometry_objects_write"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def build(self, model: Model, grid: FDTDGrid):
|
||||
if isinstance(grid, SubGridBaseGrid):
|
||||
logger.exception(f"{self.params_str()} do not add geometry objects to subgrids.")
|
||||
raise ValueError
|
||||
try:
|
||||
p1 = self.kwargs["p1"]
|
||||
p2 = self.kwargs["p2"]
|
||||
basefilename = self.kwargs["filename"]
|
||||
except KeyError:
|
||||
logger.exception(f"{self.params_str()} requires exactly seven parameters.")
|
||||
raise
|
||||
|
||||
uip = self._create_uip(grid)
|
||||
p1, p2 = uip.check_box_points(p1, p2, self.params_str())
|
||||
x0, y0, z0 = p1
|
||||
x1, y1, z1 = p2
|
||||
|
||||
# TODO: Remove these when add parallel build
|
||||
if isinstance(grid, MPIGrid):
|
||||
geometry_object_type = MPIGeometryObjectsUser
|
||||
else:
|
||||
geometry_object_type = GeometryObjectsUser
|
||||
|
||||
g = geometry_object_type(x0, y0, z0, x1, y1, z1, basefilename)
|
||||
|
||||
logger.info(
|
||||
f"Geometry objects in the volume from {p1[0] * grid.dx:g}m, "
|
||||
f"{p1[1] * grid.dy:g}m, {p1[2] * grid.dz:g}m, to "
|
||||
f"{p2[0] * grid.dx:g}m, {p2[1] * grid.dy:g}m, "
|
||||
f"{p2[2] * grid.dz:g}m, will be written to "
|
||||
f"{g.filename_hdf5}, with materials written to "
|
||||
f"{g.filename_materials}"
|
||||
)
|
||||
|
||||
model.geometryobjects.append(g)
|
@@ -0,0 +1,594 @@
|
||||
# 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 = int(np.ceil(self.time / model.dt)) + 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")
|
||||
|
||||
thickness = model.G.pmls["thickness"]
|
||||
|
||||
logger.info(
|
||||
f"PML thickness: x0={thickness['x0']}, y0={thickness['y0']},"
|
||||
f" z0={thickness['z0']}, xmax={thickness['xmax']},"
|
||||
f" ymax={thickness['ymax']}, zmax={thickness['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: Optional[str] = None,
|
||||
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."
|
||||
)
|
||||
|
||||
if formulation is not None:
|
||||
self.pml_formulation = PMLFormulation(formulation)
|
||||
else:
|
||||
self.pml_formulation = None
|
||||
|
||||
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:
|
||||
self.pml_thickness = None
|
||||
|
||||
if self.pml_formulation is None and self.pml_thickness is None:
|
||||
raise ValueError(
|
||||
"Must set PML formulation or thickness. Thickness can be set by specifying all of x0, y0, z0, xmax, ymax, zmax."
|
||||
)
|
||||
|
||||
def build(self, model):
|
||||
if self.pml_formulation is not None:
|
||||
self.pml_formulation.build(model)
|
||||
|
||||
if self.pml_thickness is not None:
|
||||
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."
|
||||
)
|
||||
|
||||
|
||||
class OutputDir(ModelUserObject):
|
||||
"""Set the directory where output file(s) will be stored.
|
||||
|
||||
Attributes:
|
||||
output_dir (str): File path to directory.
|
||||
"""
|
||||
|
||||
@property
|
||||
def order(self):
|
||||
return 10
|
||||
|
||||
@property
|
||||
def hash(self):
|
||||
return "#output_dir"
|
||||
|
||||
def __init__(self, dir: str):
|
||||
super().__init__(dir=dir)
|
||||
self.output_dir = dir
|
||||
|
||||
def build(self, model: Model):
|
||||
config.get_model_config().set_output_file_path(self.output_dir)
|
46
gprMax/user_objects/rotatable.py
普通文件
46
gprMax/user_objects/rotatable.py
普通文件
@@ -0,0 +1,46 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from gprMax.grid.fdtd_grid import FDTDGrid
|
||||
|
||||
|
||||
class RotatableMixin(ABC):
|
||||
"""Stores parameters and defines an interface for rotatable objects.
|
||||
|
||||
Attributes:
|
||||
axis (str): Defines the axis about which to perform the
|
||||
rotation. Must have value "x", "y", or "z". Default x.
|
||||
angle (int): Specifies the angle of rotation (degrees).
|
||||
Default 0.
|
||||
origin (tuple | None): Optional point about which to perform the
|
||||
rotation (x, y, z). Default None.
|
||||
do_rotate (bool): True if the object should be rotated. False
|
||||
otherwise. Default False.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs) # Forward all unused arguments
|
||||
self.axis = "x"
|
||||
self.angle = 0
|
||||
self.origin = None
|
||||
self.do_rotate = False
|
||||
|
||||
def rotate(self, axis: str, angle: int, origin: Optional[Tuple[float, float, float]] = None):
|
||||
"""Sets parameters for rotation.
|
||||
|
||||
Args:
|
||||
axis: Defines the axis about which to perform the rotation.
|
||||
Must have value "x", "y", or "z".
|
||||
angle: Specifies the angle of rotation (degrees).
|
||||
origin: Optional point about which to perform the rotation
|
||||
(x, y, z). Default None.
|
||||
"""
|
||||
self.axis = axis
|
||||
self.angle = angle
|
||||
self.origin = origin
|
||||
self.do_rotate = True
|
||||
|
||||
@abstractmethod
|
||||
def _do_rotate(self, grid: FDTDGrid):
|
||||
"""Performs the rotation."""
|
||||
pass
|
154
gprMax/user_objects/user_objects.py
普通文件
154
gprMax/user_objects/user_objects.py
普通文件
@@ -0,0 +1,154 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List, Union
|
||||
|
||||
from gprMax import config
|
||||
from gprMax.grid.fdtd_grid import FDTDGrid
|
||||
from gprMax.model import Model
|
||||
from gprMax.subgrids.grid import SubGridBaseGrid
|
||||
from gprMax.user_inputs import MainGridUserInput, SubgridUserInput
|
||||
|
||||
|
||||
class UserObject(ABC):
|
||||
"""User defined object.
|
||||
|
||||
Attributes:
|
||||
order (int): Specifies the order user objects should be
|
||||
constructed in.
|
||||
hash (str): gprMax hash command used to create the user object
|
||||
in an input file.
|
||||
kwargs (dict): Keyword arguments used to construct the user
|
||||
object.
|
||||
autotranslate (bool): TODO
|
||||
is_single_use (bool): True if the object can only appear once in a
|
||||
given model. False otherwise. Default True.
|
||||
is_geometry_object (bool): True if the object adds geometry to the
|
||||
model. False otherwise. Default False.
|
||||
"""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def order(self) -> int:
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def hash(self) -> str:
|
||||
pass
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
self.kwargs = kwargs
|
||||
self.autotranslate = True
|
||||
|
||||
def __lt__(self, obj: "UserObject"):
|
||||
return self.order < obj.order
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Readable user object as per hash commands."""
|
||||
args: List[str] = []
|
||||
for value in self.kwargs.values():
|
||||
if isinstance(value, (tuple, list)):
|
||||
for element in value:
|
||||
args.append(str(element))
|
||||
else:
|
||||
args.append(str(value))
|
||||
|
||||
return f"{self.hash}: {' '.join(args)}"
|
||||
|
||||
def params_str(self) -> str:
|
||||
"""Readable string of parameters given to object."""
|
||||
return f"{self.hash}: {str(self.kwargs)}"
|
||||
|
||||
def _create_uip(self, grid: FDTDGrid) -> Union[SubgridUserInput, MainGridUserInput]:
|
||||
"""Returns a point checker class based on the grid supplied.
|
||||
|
||||
Args:
|
||||
grid: Grid to get a UserInput object for.
|
||||
|
||||
Returns:
|
||||
uip: UserInput object for the grid provided.
|
||||
"""
|
||||
|
||||
# If autotranslate is set as True globally, local object
|
||||
# configuration trumps. I.e. User can turn off autotranslate for
|
||||
# specific objects.
|
||||
if (
|
||||
isinstance(grid, SubGridBaseGrid)
|
||||
and config.sim_config.args.autotranslate
|
||||
and self.autotranslate
|
||||
):
|
||||
return SubgridUserInput(grid)
|
||||
else:
|
||||
return MainGridUserInput(grid)
|
||||
|
||||
|
||||
class ModelUserObject(UserObject):
|
||||
"""User defined object to add to the model."""
|
||||
|
||||
@abstractmethod
|
||||
def build(self, model: Model):
|
||||
"""Build user object and set model properties.
|
||||
|
||||
Args:
|
||||
model: Model to set the properties of.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class GridUserObject(UserObject):
|
||||
"""User defined object to add to a grid."""
|
||||
|
||||
@abstractmethod
|
||||
def build(self, grid: FDTDGrid):
|
||||
pass
|
||||
|
||||
def grid_name(self, grid: FDTDGrid) -> str:
|
||||
"""Format grid name for use with logging info.
|
||||
|
||||
Returns an empty string if the grid is the main grid.
|
||||
|
||||
Args:
|
||||
grid: Grid to get the name of.
|
||||
|
||||
Returns:
|
||||
grid_name: Formatted version of the grid name.
|
||||
"""
|
||||
if isinstance(grid, SubGridBaseGrid):
|
||||
return f"[{grid.name}] "
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
class OutputUserObject(UserObject):
|
||||
"""User defined object that controls the output of data."""
|
||||
|
||||
@abstractmethod
|
||||
def build(self, model: Model, grid: FDTDGrid):
|
||||
pass
|
||||
|
||||
def grid_name(self, grid: FDTDGrid) -> str:
|
||||
"""Format grid name for use with logging info.
|
||||
|
||||
Returns an empty string if the grid is the main grid.
|
||||
|
||||
Args:
|
||||
grid: Grid to get the name of.
|
||||
|
||||
Returns:
|
||||
grid_name: Formatted version of the grid name.
|
||||
"""
|
||||
if isinstance(grid, SubGridBaseGrid):
|
||||
return f"[{grid.name}] "
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
class GeometryUserObject(GridUserObject):
|
||||
"""User defined object that adds geometry to a grid."""
|
||||
|
||||
@property
|
||||
def order(self):
|
||||
"""Geometry Objects do not have an ordering.
|
||||
|
||||
They should be built in the order they were added to the scene.
|
||||
"""
|
||||
return 1
|
在新工单中引用
屏蔽一个用户