你已经派生过 gprMax
镜像自地址
https://gitee.com/sunhf/gprMax.git
已同步 2025-08-08 07:24:19 +08:00
Refactor scene.py to use new user object classes
这个提交包含在:
226
gprMax/scene.py
226
gprMax/scene.py
@@ -16,22 +16,24 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with gprMax. If not, see <http://www.gnu.org/licenses/>.
|
# along with gprMax. If not, see <http://www.gnu.org/licenses/>.
|
||||||
import logging
|
import logging
|
||||||
from 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_grass import AddGrass
|
||||||
from gprMax.cmds_geometry.add_surface_roughness import AddSurfaceRoughness
|
from gprMax.cmds_geometry.add_surface_roughness import AddSurfaceRoughness
|
||||||
from gprMax.cmds_geometry.add_surface_water import AddSurfaceWater
|
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_geometry.fractal_box import FractalBox
|
||||||
from gprMax.grid.fdtd_grid import FDTDGrid
|
from gprMax.grid.fdtd_grid import FDTDGrid
|
||||||
from gprMax.materials import create_built_in_materials
|
from gprMax.materials import create_built_in_materials
|
||||||
from gprMax.model import Model
|
from gprMax.model import Model
|
||||||
from gprMax.subgrids.grid import SubGridBaseGrid
|
|
||||||
from gprMax.subgrids.user_objects import SubGridBase as SubGridUserBase
|
from gprMax.subgrids.user_objects import SubGridBase as SubGridUserBase
|
||||||
from gprMax.user_inputs import MainGridUserInput, SubgridUserInput
|
from gprMax.user_objects.cmds_singleuse import Discretisation, Domain, TimeWindow
|
||||||
from gprMax.user_objects.cmds_multiuse import UserObjectMulti
|
from gprMax.user_objects.user_objects import (
|
||||||
from gprMax.user_objects.cmds_singleuse import Discretisation, Domain, TimeWindow, UserObjectSingle
|
GeometryUserObject,
|
||||||
|
GridUserObject,
|
||||||
|
ModelUserObject,
|
||||||
|
OutputUserObject,
|
||||||
|
UserObject,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -39,128 +41,85 @@ logger = logging.getLogger(__name__)
|
|||||||
class Scene:
|
class Scene:
|
||||||
"""Scene stores all of the user created objects."""
|
"""Scene stores all of the user created objects."""
|
||||||
|
|
||||||
def __init__(self):
|
ESSENTIAL_CMDS = [Domain, TimeWindow, Discretisation]
|
||||||
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]
|
|
||||||
|
|
||||||
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.
|
"""Add the user object to the scene.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
user_object: user object to add to the scene. For example,
|
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):
|
# Check for
|
||||||
self.multiple_cmds.append(user_object)
|
if isinstance(user_object, SubGridUserBase):
|
||||||
elif isinstance(user_object, UserObjectGeometry):
|
self.subgrid_objects.append(user_object)
|
||||||
self.geometry_cmds.append(user_object)
|
elif isinstance(user_object, ModelUserObject):
|
||||||
elif isinstance(user_object, UserObjectSingle):
|
self.single_use_objects.append(user_object)
|
||||||
self.single_cmds.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:
|
else:
|
||||||
logger.exception("This object is unknown to gprMax")
|
raise TypeError(f"Object of type '{type(user_object)}' is unknown to gprMax")
|
||||||
raise ValueError
|
|
||||||
|
|
||||||
def build_grid_obj(self, obj: UserObjectGeometry, grid: FDTDGrid):
|
def build_model_objects(self, objects: Sequence[ModelUserObject], model: Model):
|
||||||
"""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,
|
|
||||||
):
|
|
||||||
"""Builds objects in models.
|
"""Builds objects in models.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
obj: user object
|
obj: user object
|
||||||
model: Model being built
|
model: Model being built
|
||||||
"""
|
"""
|
||||||
|
|
||||||
grid = model.G if subgrid is None else subgrid
|
|
||||||
uip = create_user_input_points(grid, obj)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
obj.build(model, uip)
|
for model_user_object in sorted(objects):
|
||||||
|
model_user_object.build(model)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
logger.exception("Error creating user input object")
|
logger.exception(f"Error creating user object '{model_user_object}'")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def process_subgrid_cmds(self, model: Model):
|
def build_grid_objects(self, objects: Sequence[GridUserObject], grid: FDTDGrid):
|
||||||
"""Process all commands in any sub-grids."""
|
"""Builds objects in FDTDGrids.
|
||||||
|
|
||||||
# Subgrid user objects
|
Args:
|
||||||
subgrid_cmds = [
|
objects: user object
|
||||||
sg_cmd for sg_cmd in self.multiple_cmds if isinstance(sg_cmd, SubGridUserBase)
|
grid: FDTDGrid class describing a grid in a model.
|
||||||
]
|
"""
|
||||||
subgrid_cmds = [
|
try:
|
||||||
sg_cmd for sg_cmd in self.multiple_cmds if isinstance(sg_cmd, SubGridUserBase)
|
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
|
def build_output_objects(
|
||||||
for sg_cmd in subgrid_cmds:
|
self, objects: Sequence[OutputUserObject], model: Model, grid: FDTDGrid
|
||||||
# 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,
|
|
||||||
):
|
):
|
||||||
"""Process list of commands."""
|
try:
|
||||||
cmds_sorted = sorted(commands, key=lambda cmd: cmd.order)
|
for output_user_object in sorted(objects):
|
||||||
for obj in cmds_sorted:
|
output_user_object.build(model, grid)
|
||||||
self.build_model_obj(obj, model, subgrid)
|
except ValueError:
|
||||||
|
logger.exception(f"Error creating user object '{output_user_object}'")
|
||||||
|
raise
|
||||||
|
|
||||||
return self
|
def process_single_use_objects(self, model: Model):
|
||||||
|
|
||||||
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):
|
|
||||||
# Check for duplicate commands and warn user if they exist
|
# Check for duplicate commands and warn user if they exist
|
||||||
cmds_unique = list(set(self.single_cmds))
|
# TODO: Test this works
|
||||||
if len(cmds_unique) != len(self.single_cmds):
|
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.")
|
logger.exception("Duplicate single-use commands exist in the input.")
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
# Check essential commands and warn user if missing
|
# Check essential commands and warn user if missing
|
||||||
for cmd_type in self.essential_cmds:
|
for cmd_type in self.ESSENTIAL_CMDS:
|
||||||
d = any(isinstance(cmd, cmd_type) for cmd in cmds_unique)
|
d = any(isinstance(cmd, cmd_type) for cmd in unique_commands)
|
||||||
if not d:
|
if not d:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
"Your input file is missing essential commands "
|
"Your input file is missing essential commands "
|
||||||
@@ -169,7 +128,39 @@ class Scene:
|
|||||||
)
|
)
|
||||||
raise ValueError
|
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):
|
def create_internal_objects(self, model: Model):
|
||||||
"""Calls the UserObject.build() function in the correct way - API
|
"""Calls the UserObject.build() function in the correct way - API
|
||||||
@@ -181,36 +172,17 @@ class Scene:
|
|||||||
create_built_in_materials(model.G)
|
create_built_in_materials(model.G)
|
||||||
|
|
||||||
# Process commands that can only have a single instance
|
# Process commands that can only have a single instance
|
||||||
self.process_singlecmds(model)
|
self.process_single_use_objects(model)
|
||||||
|
|
||||||
# Process main grid multiple commands
|
# Process multiple commands
|
||||||
self.process_cmds(self.multiple_cmds, model)
|
self.process_multi_use_objects(model)
|
||||||
|
|
||||||
# Initialise geometry arrays for main and subgrids
|
# Initialise geometry arrays for main and subgrids
|
||||||
for grid in [model.G] + model.subgrids:
|
for grid in [model.G] + model.subgrids:
|
||||||
grid.initialise_geometry_arrays()
|
grid.initialise_geometry_arrays()
|
||||||
|
|
||||||
# Process the main grid geometry commands
|
# 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
|
# Process all the commands for subgrids
|
||||||
self.process_subgrid_cmds(model)
|
self.process_subgrid_objects(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)
|
|
||||||
|
@@ -28,29 +28,40 @@ from gprMax.model import Model
|
|||||||
from gprMax.subgrids.grid import SubGridBaseGrid
|
from gprMax.subgrids.grid import SubGridBaseGrid
|
||||||
from gprMax.subgrids.subgrid_hsg import SubGridHSG as SubGridHSGUser
|
from gprMax.subgrids.subgrid_hsg import SubGridHSG as SubGridHSGUser
|
||||||
from gprMax.user_inputs import MainGridUserInput
|
from gprMax.user_inputs import MainGridUserInput
|
||||||
from gprMax.user_objects.cmds_multiuse import UserObjectMulti
|
from gprMax.user_objects.user_objects import (
|
||||||
|
GeometryUserObject,
|
||||||
|
GridUserObject,
|
||||||
|
ModelUserObject,
|
||||||
|
OutputUserObject,
|
||||||
|
UserObject,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class SubGridBase(UserObjectMulti):
|
class SubGridBase(ModelUserObject):
|
||||||
"""Allows UserObjectMulti and UserObjectGeometry to be nested in SubGrid
|
"""Allows UserObjectMulti and UserObjectGeometry to be nested in SubGrid
|
||||||
type user objects.
|
type user objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_single_use(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.children_multiple: List[UserObjectMulti] = []
|
self.children_grid: List[GridUserObject] = []
|
||||||
self.children_geometry: List[UserObjectGeometry] = []
|
self.children_geometry: List[GeometryUserObject] = []
|
||||||
self.children_multiple: List[UserObjectMulti] = []
|
self.children_output: List[OutputUserObject] = []
|
||||||
self.children_geometry: List[UserObjectGeometry] = []
|
|
||||||
|
|
||||||
def add(self, node: Union[UserObjectMulti, UserObjectGeometry]):
|
def add(self, node: UserObject):
|
||||||
"""Adds other user objects. Geometry and multi only."""
|
"""Adds other user objects. Geometry and multi only."""
|
||||||
if isinstance(node, UserObjectMulti):
|
if isinstance(node, GeometryUserObject):
|
||||||
self.children_multiple.append(node)
|
|
||||||
elif isinstance(node, UserObjectGeometry):
|
|
||||||
self.children_geometry.append(node)
|
self.children_geometry.append(node)
|
||||||
|
elif isinstance(node, GridUserObject):
|
||||||
|
self.children_grid.append(node)
|
||||||
|
elif isinstance(node, OutputUserObject):
|
||||||
|
self.children_output.append(node)
|
||||||
else:
|
else:
|
||||||
logger.exception(f"{str(node)} this Object can not be added to a sub grid")
|
logger.exception(f"{str(node)} this Object can not be added to a sub grid")
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
@@ -35,18 +35,13 @@ class UserObject(ABC):
|
|||||||
def hash(self) -> str:
|
def hash(self) -> str:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@property
|
|
||||||
def is_single_use(self) -> bool:
|
|
||||||
return True
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_geometry_object(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def __init__(self, **kwargs) -> None:
|
def __init__(self, **kwargs) -> None:
|
||||||
self.kwargs = kwargs
|
self.kwargs = kwargs
|
||||||
self.autotranslate = True
|
self.autotranslate = True
|
||||||
|
|
||||||
|
def __lt__(self, obj: "UserObject"):
|
||||||
|
return self.order < obj.order
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
"""Readable user object as per hash commands."""
|
"""Readable user object as per hash commands."""
|
||||||
args: List[str] = []
|
args: List[str] = []
|
||||||
@@ -86,14 +81,6 @@ class UserObject(ABC):
|
|||||||
return MainGridUserInput(grid)
|
return MainGridUserInput(grid)
|
||||||
|
|
||||||
|
|
||||||
class MultiUserObject(UserObject):
|
|
||||||
"""User defined object that can occur multiple times."""
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_single_use(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class ModelUserObject(UserObject):
|
class ModelUserObject(UserObject):
|
||||||
"""User defined object to add to the model."""
|
"""User defined object to add to the model."""
|
||||||
|
|
||||||
@@ -107,7 +94,7 @@ class ModelUserObject(UserObject):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class GridUserObject(MultiUserObject):
|
class GridUserObject(UserObject):
|
||||||
"""User defined object to add to a grid."""
|
"""User defined object to add to a grid."""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@@ -131,7 +118,7 @@ class GridUserObject(MultiUserObject):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
class OutputUserObject(MultiUserObject):
|
class OutputUserObject(UserObject):
|
||||||
"""User defined object that controls the output of data."""
|
"""User defined object that controls the output of data."""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@@ -158,6 +145,4 @@ class OutputUserObject(MultiUserObject):
|
|||||||
class GeometryUserObject(GridUserObject):
|
class GeometryUserObject(GridUserObject):
|
||||||
"""User defined object that adds geometry to a grid."""
|
"""User defined object that adds geometry to a grid."""
|
||||||
|
|
||||||
@property
|
pass
|
||||||
def is_geometry_object(self) -> bool:
|
|
||||||
return True
|
|
||||||
|
在新工单中引用
屏蔽一个用户