Checkout reframe tests from mpi branch

这个提交包含在:
Nathan Mannall
2025-03-17 12:17:13 +00:00
父节点 d4a757902c
当前提交 4451f43747
共有 139 个文件被更改,包括 2905 次插入0 次删除

查看文件

@@ -0,0 +1,437 @@
"""ReFrame base classes for GprMax tests
Usage (run all tests):
cd gprMax/reframe_tests
reframe -C configuration/{CONFIG_FILE} -c tests/ -r
"""
import os
from pathlib import Path
from typing import Literal, Optional, Union
import reframe.utility.sanity as sn
import reframe.utility.typecheck as typ
from reframe import RunOnlyRegressionTest, simple_test
from reframe.core.builtins import (
parameter,
performance_function,
require_deps,
required,
run_after,
run_before,
sanity_function,
variable,
)
from reframe.core.exceptions import DependencyError
from reframe.utility import udeps
from reframe_tests.tests.regression_checks import RegressionCheck
from reframe_tests.utilities.deferrable import path_join
GPRMAX_ROOT_DIR = Path(__file__).parent.parent.parent.resolve()
PATH_TO_PYENV = os.path.join(".venv", "bin", "activate")
@simple_test
class CreatePyenvTest(RunOnlyRegressionTest):
"""Create a fresh virtual environment for running the tests.
The test checks for any errors from pip installing gprMax and its
dependencies.
"""
valid_systems = ["generic", "archer2:login"]
valid_prog_environs = ["builtin", "PrgEnv-gnu"]
modules = ["cray-python"]
prerun_cmds = [
"python -m venv --system-site-packages --prompt gprMax .venv",
f"source {PATH_TO_PYENV}",
"CC=cc CXX=CC FC=ftn python -m pip install --upgrade pip",
f"CC=cc CXX=CC FC=ftn python -m pip install -r {os.path.join(GPRMAX_ROOT_DIR, 'requirements.txt')}",
]
executable = f"CC=cc CXX=CC FC=ftn python -m pip install -e {GPRMAX_ROOT_DIR}"
@run_after("init")
def install_system_specific_dependencies(self):
"""Install additional dependencies for specific systems."""
if self.current_system.name == "archer2":
"""
Needed to prevent a pip install error.
dask 2022.2.1 (installed) requires cloudpickle>=1.1.1, which
is not installed and is missed by the pip dependency checks.
Not necessary for gprMax, but any error message is picked up
by the sanity checks.
"""
self.prerun_cmds.insert(3, "CC=cc CXX=CC FC=ftn python -m pip install cloudpickle")
"""
A default pip install of h5py does not have MPI support so
it needs to built as described here:
https://docs.h5py.org/en/stable/mpi.html#building-against-parallel-hdf5
"""
self.modules.append("cray-hdf5-parallel")
self.prerun_cmds.insert(
4, "CC=mpicc HDF5_MPI='ON' python -m pip install --no-binary=h5py h5py"
)
@sanity_function
def check_requirements_installed(self):
"""Check packages were successfully installed.
Check pip is up to date and gprMax dependencies from
requirements.txt were successfully installed. Check gprMax was
installed successfully and no other errors were thrown.
"""
return (
sn.assert_found(
r"(Successfully installed pip)|(Requirement already satisfied: pip.*\n(?!Collecting pip))",
self.stdout,
"Failed to update pip",
)
and sn.assert_found(
r"Successfully installed (?!(gprMax)|(pip))",
self.stdout,
"Failed to install requirements",
)
and sn.assert_found(
r"Successfully installed gprMax", self.stdout, "Failed to install gprMax"
)
and sn.assert_not_found(r"finished with status 'error'", self.stdout)
and sn.assert_not_found(r"(ERROR|error):", self.stderr)
)
class GprMaxBaseTest(RunOnlyRegressionTest):
"""Base class that all GprMax tests should inherit from.
Test functionality can be augmented by using Mixin classes.
Attributes:
model (parameter[str]): ReFrame parameter to specify the model
name(s).
sourcesdir (str): Relative path to the test's src directory.
regression_checks (list[RegressionCheck]): List of regression
checks to perform.
test_dependency (type[GprMaxBaseTest] | None): Optional test
dependency. If specified, regression checks will use
reference files created by the test dependency.
"""
valid_systems = ["archer2:compute"]
valid_prog_environs = ["PrgEnv-gnu"]
modules = ["cray-python"]
num_cpus_per_task = 16
exclusive_access = True
model = parameter()
sourcesdir = required
executable = "time -p python -m gprMax"
regression_checks = variable(typ.List[RegressionCheck], value=[])
# TODO: Make this a ReFrame variable
# Not currently possible as ReFrame does not think an object of type
# reframe.core.meta.RegressionTestMeta is copyable, and so ReFrame
# test classes cannot be specified in a variable.
test_dependency: Optional[type["GprMaxBaseTest"]] = None
# test_dependency = variable(type(None), type, value=None)
def get_test_dependency_variant_name(self, **kwargs) -> Optional[str]:
"""Get unique ReFrame name of the test dependency variant.
By default, filter test dependencies by the model name.
Args:
**kwargs: Additional key-value pairs to filter the parameter
space of the test dependency. The key is the test
parameter name and the value is either a single value or
a unary function that evaluates to True if the parameter
point must be kept, False otherwise.
Returns:
variant_name: Unique name of the test dependency variant.
"""
if self.test_dependency is None:
return None
# Always filter by the model parameter, but allow child classes
# (or mixins) to override how models are filtered.
kwargs.setdefault("model", self.model)
variant_nums = self.test_dependency.get_variant_nums(**kwargs)
if len(variant_nums) < 1:
raise DependencyError(
f"No variant of '{self.test_dependency.__name__}' meets conditions: {kwargs}",
)
return self.test_dependency.variant_name(variant_nums[0])
def get_test_dependency(self) -> Optional["GprMaxBaseTest"]:
"""Get correct ReFrame test case from the test dependency.
Returns:
test_case: ReFrame test case.
"""
variant = self.get_test_dependency_variant_name()
if variant is None:
return None
else:
return self.getdep(variant)
def build_reference_filepath(self, name: Union[str, os.PathLike], suffix: str = ".h5") -> Path:
"""Build path to the specified reference file.
Reference files are saved in directories per test case. If this
test does not specify a test dependency, it will save and manage
its own reference files in its own directory. Otherwise, it will
use reference files saved by its test dependency.
Args:
name: Name of the file.
suffix: File extension. Default ".h5".
Returns:
filepath: Absolute path to the reference file.
"""
target = self.get_test_dependency()
if target is None:
reference_dir = self.short_name
else:
reference_dir = target.short_name
reference_file = Path("regression_checks", reference_dir, name).with_suffix(suffix)
return reference_file.absolute()
# TODO: Change CreatePyenvTest to a fixture instead of a test dependency
@run_after("init")
def inject_dependencies(self):
"""Specify test dependencies.
All tests depend on the Python virtual environment building
correctly and their own test dependency if specified.
"""
self.depends_on("CreatePyenvTest", udeps.by_env)
if self.test_dependency is not None:
variant = self.get_test_dependency_variant_name()
self.depends_on(variant, udeps.by_env)
@require_deps
def get_pyenv_path(self, CreatePyenvTest):
"""Add prerun command to load the built Python environment."""
path_to_pyenv = os.path.join(CreatePyenvTest(part="login").stagedir, PATH_TO_PYENV)
self.prerun_cmds.append(f"source {path_to_pyenv}")
@run_after("init")
def setup_env_vars(self):
"""Set necessary environment variables.
Set OMP_NUM_THREADS environment variable from num_cpus_per_task
and other system specific varaibles.
"""
self.env_vars["OMP_NUM_THREADS"] = self.num_cpus_per_task
if self.current_system.name == "archer2":
# Avoid inheriting slurm memory environment variables from any previous slurm job (i.e. the reframe job)
self.prerun_cmds.append("unset SLURM_MEM_PER_NODE")
self.prerun_cmds.append("unset SLURM_MEM_PER_CPU")
# Set the matplotlib cache to the work filesystem
self.env_vars["MPLCONFIGDIR"] = "${HOME/home/work}/.config/matplotlib"
@run_after("init")
def set_file_paths(self):
"""Set default test input and output files.
These are set in a post-init hook to allow mixins to use them
later in the pipeline.
"""
self.input_file = Path(f"{self.model}.in")
self.output_file = Path(f"{self.model}.h5")
@run_before("run")
def configure_test_run(self):
"""Configure gprMax commandline arguments and files to keep."""
input_file = str(self.input_file)
output_file = str(self.output_file)
self.executable_opts += [
input_file,
"-o",
output_file,
"--log-level",
"10",
"--hide-progress-bars",
]
regression_output_files = [str(r.output_file) for r in self.regression_checks]
self.keep_files += [input_file, output_file, *regression_output_files]
"""
if self.has_receiver_output:
self.postrun_cmds = [
f"python -m reframe_tests.utilities.plotting {self.output_file} {self.reference_file} -m {self.model}"
]
self.keep_files += [self.output_file, f"{self.model}.pdf"]
if self.is_antenna_model:
self.postrun_cmds = [
f"python -m toolboxes.Plotting.plot_antenna_params -save {self.output_file}"
]
antenna_t1_params = f"{self.model}_t1_params.pdf"
antenna_ant_params = f"{self.model}_ant_params.pdf"
self.keep_files += [
antenna_t1_params,
antenna_ant_params,
]
"""
@run_before("run")
def combine_task_outputs(self):
"""Split output from each MPI rank.
If running with multiple MPI ranks, split the output into
seperate files and add postrun commands to combine the files
after the simulation has run.
"""
if self.num_tasks > 1:
stdout = self.stdout.evaluate().split(".")[0]
stderr = self.stderr.evaluate().split(".")[0]
self.prerun_cmds.append(f"mkdir out")
self.prerun_cmds.append(f"mkdir err")
self.job.launcher.options = [
f"--output=out/{stdout}_%t.out",
f"--error=err/{stderr}_%t.err",
]
self.executable_opts += ["--log-all-ranks"]
self.postrun_cmds.append(f"cat out/{stdout}_*.out >> {self.stdout}")
self.postrun_cmds.append(f"cat err/{stderr}_*.err >> {self.stderr}")
def test_simulation_complete(self) -> Literal[True]:
"""Check simulation completed successfully.
Returns:
simulation_completed: Returns True if the simulation
completed, otherwise it fails the test.
Raises:
reframe.core.exceptions.SanityError: If the simulation did
not complete.
"""
return sn.assert_not_found(
r"(?i)error",
self.stderr,
f"An error occured. See '{path_join(self.stagedir, self.stderr)}' for details.",
) and sn.assert_found(
r"=== Simulation completed in ", self.stdout, "Simulation did not complete"
)
def test_reference_files_exist(self) -> Literal[True]:
"""Check all reference files exist and create any missing ones.
Returns:
files_exist: Returns True if all reference files exist,
otherwise it fails the test.
Raises:
reframe.core.exceptions.SanityError: If any reference files
do not exist.
"""
# Store error messages so all references files can be checked
# (and created if necessary) before the test is failed.
error_messages = []
for check in self.regression_checks:
if not check.reference_file_exists():
if self.test_dependency is None and check.create_reference_file():
error_messages.append(
f"Reference file does not exist. Creating... '{check.reference_file}'"
)
elif self.test_dependency is not None:
error_messages.append(
f"ERROR: Test dependency did not create reference file: '{check.reference_file}'"
)
else:
error_messages.append(
f"ERROR: Unable to create reference file: '{check.reference_file}'"
)
return sn.assert_true(len(error_messages) < 1, "\n".join(error_messages))
@sanity_function
def regression_check(self) -> bool:
"""Run sanity checks and regression checks.
Checks will run in the following order:
- Check the simulation completed.
- Check all reference files exist.
- Run all regression checks.
If any of these checks fail, the test will fail and none of the
other later checks will run.
Returns:
test_passed: Returns True if all checks pass.
Raises:
reframe.core.exceptions.SanityError: If any regression
checks fail.
"""
return (
self.test_simulation_complete()
and self.test_reference_files_exist()
and sn.all(sn.map(lambda check: check.run(), self.regression_checks))
)
@performance_function("s", perf_key="run_time")
def extract_run_time(self):
"""Extract total runtime from the last task to complete."""
return sn.extractsingle(
r"real\s+(?P<run_time>\S+)", self.stderr, "run_time", float, self.num_tasks - 1
)
@performance_function("s", perf_key="simulation_time")
def extract_simulation_time(self):
"""Extract simulation time reported by gprMax."""
# sn.extractall throws an error if a group has value None.
# Therefore have to handle the < 1 min, >= 1 min and >= 1 hour cases separately.
timeframe = sn.extractsingle(
r"=== Simulation completed in \S+ (?P<timeframe>hour|minute|second)",
self.stdout,
"timeframe",
)
if timeframe == "hour":
simulation_time = sn.extractall(
r"=== Simulation completed in (?P<hours>\S+) hours?, (?P<minutes>\S+) minutes? and (?P<seconds>\S+) seconds? =*",
self.stdout,
["hours", "minutes", "seconds"],
float,
)
hours = simulation_time[0][0]
minutes = simulation_time[0][1]
seconds = simulation_time[0][2]
elif timeframe == "minute":
hours = 0
simulation_time = sn.extractall(
r"=== Simulation completed in (?P<minutes>\S+) minutes? and (?P<seconds>\S+) seconds? =*",
self.stdout,
["minutes", "seconds"],
float,
)
minutes = simulation_time[0][0]
seconds = simulation_time[0][1]
else:
hours = 0
minutes = 0
seconds = sn.extractsingle(
r"=== Simulation completed in (?P<seconds>\S+) seconds? =*",
self.stdout,
"seconds",
float,
)
return hours * 3600 + minutes * 60 + seconds

查看文件

@@ -0,0 +1,268 @@
from pathlib import Path
from typing import Optional
import reframe.utility.typecheck as typ
from numpy import prod
from reframe import RegressionMixin
from reframe.core.builtins import parameter, required, run_after, variable
from typing_extensions import TYPE_CHECKING
from reframe_tests.tests.base_tests import GprMaxBaseTest
from reframe_tests.tests.regression_checks import (
GeometryObjectMaterialsRegressionCheck,
GeometryObjectRegressionCheck,
GeometryViewRegressionCheck,
H5RegressionCheck,
ReceiverRegressionCheck,
SnapshotRegressionCheck,
)
# If using a static type checker, inherit from GprMaxBaseTest as the
# Mixin classes should always have access to resources from that class.
# However, during execution inherit from RegressionMixin.
if TYPE_CHECKING:
GprMaxMixin = GprMaxBaseTest
else:
GprMaxMixin = RegressionMixin
class ReceiverMixin(GprMaxMixin):
number_of_receivers = variable(int, value=-1)
@run_after("setup")
def add_receiver_regression_checks(self):
reference_file = self.build_reference_filepath(self.output_file)
if self.number_of_receivers > 0:
for i in range(self.number_of_receivers):
regression_check = ReceiverRegressionCheck(
self.output_file, reference_file, f"r{i}"
)
self.regression_checks.append(regression_check)
else:
regression_check = H5RegressionCheck(self.output_file, reference_file)
self.regression_checks.append(regression_check)
class SnapshotMixin(GprMaxMixin):
"""Add regression tests for snapshots.
Attributes:
snapshots (list[str]): List of snapshots to run regression
checks on.
"""
snapshots = variable(typ.List[str], value=[])
def build_snapshot_filepath(self, snapshot: str) -> Path:
"""Build filepath to the specified snapshot.
Args:
snapshot: Name of the snapshot.
"""
return Path(f"{self.model}_snaps", snapshot).with_suffix(".h5")
@run_after("setup")
def add_snapshot_regression_checks(self):
"""Add a regression check for each snapshot.
The test will be skipped if no snapshots have been specified.
"""
self.skip_if(
len(self.snapshots) < 0,
f"Must provide a list of snapshots.",
)
for snapshot in self.snapshots:
snapshot_file = self.build_snapshot_filepath(snapshot)
reference_file = self.build_reference_filepath(snapshot)
regression_check = SnapshotRegressionCheck(snapshot_file, reference_file)
self.regression_checks.append(regression_check)
class GeometryOnlyMixin(GprMaxMixin):
"""Run test with geometry only flag"""
@run_after("setup")
def add_geometry_only_flag(self):
self.executable_opts += ["--geometry-only"]
class GeometryObjectMixin(GprMaxMixin):
"""Add regression tests for geometry objects.
Attributes:
geometry_objects (list[str]): List of geometry objects to run
regression checks on.
"""
geometry_objects = variable(typ.List[str], value=[])
def build_geometry_object_filepath(self, geometry_object: str) -> Path:
"""Build filepath to the specified geometry object.
Args:
geometry_object: Name of the geometry object.
"""
return Path(geometry_object).with_suffix(".h5")
def build_materials_filepath(self, geometry_object: str) -> Path:
"""Build filepath to the materials output by the geometry object.
Args:
geometry_object: Name of the geometry object.
"""
return Path(f"{geometry_object}_materials").with_suffix(".txt")
@run_after("setup")
def add_geometry_object_regression_checks(self):
"""Add a regression check for each geometry object.
The test will be skipped if no geometry objects have been specified.
"""
self.skip_if(
len(self.geometry_objects) < 0,
f"Must provide a list of geometry objects.",
)
for geometry_object in self.geometry_objects:
# Add materials regression check first as if this fails,
# checking the .h5 file will almost definitely fail.
materials_file = self.build_materials_filepath(geometry_object)
materials_reference_file = self.build_reference_filepath(
materials_file.name, suffix=materials_file.suffix
)
materials_regression_check = GeometryObjectMaterialsRegressionCheck(
materials_file, materials_reference_file
)
self.regression_checks.append(materials_regression_check)
geometry_object_file = self.build_geometry_object_filepath(geometry_object)
reference_file = self.build_reference_filepath(geometry_object)
regression_check = GeometryObjectRegressionCheck(geometry_object_file, reference_file)
self.regression_checks.append(regression_check)
class GeometryViewMixin(GprMaxMixin):
"""Add regression tests for geometry views.
Attributes:
geometry_views (list[str]): List of geometry views to run
regression checks on.
"""
geometry_views = variable(typ.List[str], value=[])
def build_geometry_view_filepath(self, geometry_view: str) -> Path:
"""Build filepath to the specified geometry view.
Args:
geometry_view: Name of the geometry view.
"""
return Path(geometry_view).with_suffix(".vtkhdf")
@run_after("setup")
def add_geometry_view_regression_checks(self):
"""Add a regression check for each geometry view.
The test will be skipped if no geometry views have been specified.
"""
self.skip_if(
len(self.geometry_views) < 0,
f"Must provide a list of geometry views.",
)
for geometry_view in self.geometry_views:
geometry_view_file = self.build_geometry_view_filepath(geometry_view)
reference_file = self.build_reference_filepath(geometry_view, ".vtkhdf")
regression_check = GeometryViewRegressionCheck(geometry_view_file, reference_file)
self.regression_checks.append(regression_check)
class PythonApiMixin(GprMaxMixin):
"""Use the GprMax Python API rather than a standard input file."""
@run_after("setup")
def use_python_input_file(self):
"""Input files for API tests will be python files."""
self.executable = "time -p python"
self.input_file = self.input_file.with_suffix(".py")
class MpiMixin(GprMaxMixin):
"""Run test using GprMax MPI functionality.
Attributes:
mpi_layout (parameter[list[int]]): ReFrame parameter to specify
how MPI tasks should be arranged.
"""
mpi_layout = parameter()
@run_after("setup")
def configure_mpi_tasks(self):
"""Set num_tasks and add MPI specific commandline arguments."""
self.num_tasks = int(prod(self.mpi_layout))
self.executable_opts += ["--mpi", *map(str, self.mpi_layout)]
class BScanMixin(GprMaxMixin):
"""Test a B-scan model - a model with a moving source and receiver.
Attributes:
num_models (parameter[int]): Number of models to run.
"""
num_models = parameter()
@run_after("setup")
def setup_bscan_test(self):
"""Add B-scan specific commandline arguments and postrun cmds.
Set the number of models to run, and merge the output files.
"""
self.executable_opts += ["-n", str(self.num_models)]
self.postrun_cmds += [
f"python -m toolboxes.Utilities.outputfiles_merge {self.model}",
f"mv {self.model}_merged.h5 {self.output_file}",
]
def get_test_dependency_variant_name(self, **kwargs) -> Optional[str]:
"""Get unique ReFrame name of the test dependency variant.
By default, filter test dependencies by the model name and the
number of models.
Args:
**kwargs: Additional key-value pairs to filter the parameter
space of the test dependency. The key is the test
parameter name and the value is either a single value or
a unary function that evaluates to True if the parameter
point must be kept, False otherwise.
Returns:
variant_name: Unique name of the test dependency variant.
"""
kwargs.setdefault("num_models", self.num_models)
return super().get_test_dependency_variant_name(**kwargs)
class TaskfarmMixin(GprMaxMixin):
"""Run test using GprMax taskfarm functionality."""
# TODO: Make this a required variabe, or create a new variable to
# proxy it.
# num_tasks = required
@run_after("setup")
def add_taskfarm_flag(self):
"""Add taskfarm specific commandline arguments."""
self.executable_opts += ["--taskfarm"]
class AntennaModelMixin(GprMaxMixin):
"""Test an antenna model."""
pass

查看文件

@@ -0,0 +1,176 @@
from os import PathLike
from pathlib import Path
from shutil import copyfile
from typing import Literal, Optional, Union
import reframe.utility.sanity as sn
from reframe.core.runtime import runtime
from reframe.utility import osext
class RegressionCheck:
"""Compare two files using diff"""
def __init__(
self, output_file: Union[str, PathLike], reference_file: Union[str, PathLike]
) -> None:
"""Create a new regression check.
Args:
output_file: Path to output file generate by the test.
reference_file: Path to reference file to run the regression
check against.
"""
self.output_file = Path(output_file)
self.reference_file = Path(reference_file)
self.cmd = "diff"
self.options: list[str] = []
@property
def error_msg(self) -> str:
"""Message to display if the regression check fails"""
return "Failed regression check"
def create_reference_file(self) -> bool:
"""Create reference file if it does not already exist.
The reference file is created as a copy of the current output
file.
Returns:
file_created: Returns True if a new file was created, False
if the path already exists.
"""
if not sn.path_exists(self.reference_file):
self.reference_file.parent.mkdir(parents=True, exist_ok=True)
copyfile(self.output_file, self.reference_file)
return True
else:
return False
def reference_file_exists(self) -> bool:
"""Check if the reference file exists.
Returns:
file_exists: Returns true if the reference filepath is a
regular file, False otherwise.
"""
return sn.path_isfile(self.reference_file)
def run(self) -> Literal[True]:
"""Run the regression check.
Returns:
check_passed: Returns True if the output file matches the
reference file (i.e. no output from diff). Otherwise,
raises a SanityError.
Raises:
reframe.core.exceptions.SanityError: If the output file does
not exist, or the regression check fails.
"""
completed_process = osext.run_command(
[
self.cmd,
*self.options,
str(self.output_file.absolute()),
str(self.reference_file),
]
)
return sn.assert_true(
sn.path_isfile(self.output_file),
f"Expected output file '{self.output_file}' does not exist",
) and sn.assert_false(
completed_process.stdout,
(
f"{self.error_msg}\n"
f"For more details run: '{' '.join(completed_process.args)}'\n"
f"To re-create regression file, delete '{self.reference_file}' and rerun the test."
),
)
class H5RegressionCheck(RegressionCheck):
"""Compare two hdf5 files using h5diff"""
def __init__(
self, output_file: Union[str, PathLike], reference_file: Union[str, PathLike]
) -> None:
super().__init__(output_file, reference_file)
if runtime().system.name == "archer2":
self.cmd = "/opt/cray/pe/hdf5/default/bin/h5diff"
else:
self.cmd = "h5diff"
class ReceiverRegressionCheck(H5RegressionCheck):
"""Run regression check on individual reveivers in output files.
This can include arbitrary receivers in each file, or two receivers
in the same file.
"""
def __init__(
self,
output_file: Union[str, PathLike],
reference_file: Union[str, PathLike],
output_receiver: str,
reference_receiver: Optional[str] = None,
) -> None:
"""Create a new receiver regression check.
Args:
output_file: Path to output file generate by the test.
reference_file: Path to reference file to run the regression
check against.
output_receiver: Output receiver to check.
reference_receiver: Optional receiver to check against in
the reference file. If None, this will be the same as
the output receiver.
"""
super().__init__(output_file, reference_file)
self.output_receiver = output_receiver
self.reference_receiver = reference_receiver
self.options.append(f"rxs/{self.output_receiver}")
if self.reference_receiver is not None:
self.options.append(f"rxs/{self.reference_receiver}")
@property
def error_msg(self) -> str:
return f"Receiver '{self.output_receiver}' failed regression check"
class SnapshotRegressionCheck(H5RegressionCheck):
"""Run regression check on a gprMax Snapshot."""
@property
def error_msg(self) -> str:
return f"Snapshot '{self.output_file.name}' failed regression check"
class GeometryObjectRegressionCheck(H5RegressionCheck):
"""Run regression check on a GprMax GeometryObject."""
@property
def error_msg(self) -> str:
return f"GeometryObject '{self.output_file.name}' failed regression check"
class GeometryObjectMaterialsRegressionCheck(RegressionCheck):
"""Run regression check on materials output by a GeometryObject."""
@property
def error_msg(self) -> str:
return f"GeometryObject materials file '{self.output_file}' failed regression check"
class GeometryViewRegressionCheck(H5RegressionCheck):
"""Run regression check on a GprMax GeometryView."""
@property
def error_msg(self) -> str:
return f"GeometryView '{self.output_file.name}' failed regression check"

查看文件

@@ -0,0 +1,8 @@
#title: 2D test Ex, Hy, Hz components
#domain: 0.001 0.100 0.100
#dx_dy_dz: 0.001 0.001 0.001
#time_window: 3e-9
#waveform: gaussiandot 1 1e9 myWave
#hertzian_dipole: x 0 0.050 0.050 myWave
#rx: 0 0.070 0.070

查看文件

@@ -0,0 +1,13 @@
#title: 2D test Ex, Hy, Hz components
#domain: 0.001 0.010 0.010
#dx_dy_dz: 0.001 0.001 0.001
#time_window: 3e-9
#pml_cells: 0
#waveform: gaussiandot 1 1e9 myWave
#hertzian_dipole: x 0 0.005 0.005 myWave
#rx: 0 0.002 0.002
#material: 8 0 1 0 half_space
#box: 0 0 0 0.001 0.004 0.004 half_space

查看文件

@@ -0,0 +1,8 @@
#title: 2D test Ey, Hx, Hz components
#domain: 0.100 0.001 0.100
#dx_dy_dz: 0.001 0.001 0.001
#time_window: 3e-9
#waveform: gaussiandot 1 1e9 myWave
#hertzian_dipole: y 0.050 0 0.050 myWave
#rx: 0.070 0 0.070

查看文件

@@ -0,0 +1,8 @@
#title: 2D test Ez, Hx, Hy components
#domain: 0.100 0.100 0.001
#dx_dy_dz: 0.001 0.001 0.001
#time_window: 3e-9
#waveform: gaussiandot 1 1e9 myWave
#hertzian_dipole: z 0.050 0.050 0 myWave
#rx: 0.070 0.070 0

查看文件

@@ -0,0 +1,15 @@
#title: B-scan from a metal cylinder buried in a dielectric half-space
#domain: 0.240 0.210 0.002
#dx_dy_dz: 0.002 0.002 0.002
#time_window: 3e-9
#material: 6 0 1 0 half_space
#waveform: ricker 1 1.5e9 my_ricker
#hertzian_dipole: z 0.040 0.170 0 my_ricker
#rx: 0.080 0.170 0
#src_steps: 0.002 0 0
#rx_steps: 0.002 0 0
#box: 0 0 0 0.240 0.170 0.002 half_space
#cylinder: 0.120 0.080 0 0.120 0.080 0.002 0.010 pec

查看文件

@@ -0,0 +1,13 @@
#title: A-scan from a metal cylinder buried in a dielectric half-space
#domain: 0.240 0.210 0.002
#dx_dy_dz: 0.002 0.002 0.002
#time_window: 3e-9
#material: 6 0 1 0 half_space
#waveform: ricker 1 1.5e9 my_ricker
#hertzian_dipole: z 0.100 0.170 0 my_ricker
#rx: 0.140 0.170 0
#box: 0 0 0 0.240 0.170 0.002 half_space
#cylinder: 0.120 0.080 0 0.120 0.080 0.002 0.010 pec

查看文件

@@ -0,0 +1,17 @@
#title: Hertzian dipole in free-space
#domain: 0.100 0.100 0.100
#dx_dy_dz: 0.001 0.001 0.001
#time_window: 3e-9
#waveform: gaussiandot 1 1e9 myWave
#hertzian_dipole: z 0.050 0.050 0.050 myWave
#material: 4.9 0 1 0 myWater
#material: 2 0 1.4 0 unusedMaterial
#material: 3 0 2 0 boxMaterial
#sphere: 0.05 0.05 0.05 0.03 myWater y
#box: 0.01 0.01 0.01 0.09 0.025 0.025 boxMaterial y
#geometry_objects_write: 0.02 0.02 0.02 0.06 0.06 0.06 partial_volume
#geometry_objects_write: 0 0 0 0.1 0.1 0.1 full_volume

查看文件

@@ -0,0 +1,11 @@
#title: Hertzian dipole over a half-space
#domain: 0.100 0.100 0.100
#dx_dy_dz: 0.001 0.001 0.001
#time_window: 3e-9
#waveform: gaussiandot 1 1e9 myWave
#hertzian_dipole: z 0.050 0.050 0.050 myWave
#rx: 0.070 0.070 0.070
#material: 8 0 1 0 half_space
#box: 0 0 0 0.100 0.100 0.100 half_space

查看文件

@@ -0,0 +1,11 @@
#title: Hertzian dipole over a half-space
#domain: 0.100 0.100 0.100
#dx_dy_dz: 0.001 0.001 0.001
#time_window: 3e-9
#waveform: gaussiandot 1 1e9 myWave
#hertzian_dipole: z 0.050 0.050 0.050 myWave
#rx: 0.070 0.070 0.070
#material: 8 0 1 0 half_space
#box: 0 0 0 0.100 0.100 0.050 half_space

查看文件

@@ -0,0 +1,11 @@
#title: Hertzian dipole over a half-space
#domain: 0.100 0.100 0.100
#dx_dy_dz: 0.001 0.001 0.001
#time_window: 3e-9
#waveform: gaussiandot 1 1e9 myWave
#hertzian_dipole: z 0.050 0.050 0.050 myWave
#rx: 0.070 0.070 0.070
#material: 8 0 1 0 half_space
#box: 0.020 0.020 0.020 0.080 0.080 0.050 half_space

查看文件

@@ -0,0 +1,11 @@
#title: Hertzian dipole over a half-space
#domain: 0.100 0.100 0.100
#dx_dy_dz: 0.001 0.001 0.001
#time_window: 3e-9
#waveform: gaussiandot 1 1e9 myWave
#hertzian_dipole: z 0.050 0.050 0.050 myWave
#rx: 0.070 0.070 0.070
#material: 8 0 1 0 half_space
#box: 0 0 0 0.020 0.020 0.020 half_space

查看文件

@@ -0,0 +1,11 @@
#title: Hertzian dipole over a half-space
#domain: 0.100 0.100 0.100
#dx_dy_dz: 0.001 0.001 0.001
#time_window: 3e-9
#waveform: gaussiandot 1 1e9 myWave
#hertzian_dipole: z 0.050 0.050 0.050 myWave
#rx: 0.070 0.070 0.070
#material: 8 0 1 0 half_space
#box: 0.030 0.030 0.030 0.045 0.045 0.045 half_space

查看文件

@@ -0,0 +1,15 @@
#title: Wire antenna - half-wavelength dipole in free-space
#domain: 0.050 0.050 0.200
#dx_dy_dz: 0.001 0.001 0.001
#time_window: 60e-9
#waveform: gaussian 1 1e9 mypulse
#transmission_line: z 0.025 0.025 0.100 73 mypulse
## 150mm length
#edge: 0.025 0.025 0.025 0.025 0.025 0.175 pec
## 1mm gap at centre of dipole
#edge: 0.025 0.025 0.100 0.025 0.025 0.101 free_space
#geometry_view: 0.020 0.020 0.020 0.030 0.030 0.180 0.001 0.001 0.001 antenna_wire_dipole_fs f

查看文件

@@ -0,0 +1,17 @@
#title: Hertzian dipole in free-space
#domain: 0.100 0.100 0.100
#dx_dy_dz: 0.001 0.001 0.001
#time_window: 3e-9
#waveform: gaussiandot 1 1e9 myWave
#hertzian_dipole: z 0.050 0.050 0.050 myWave
#material: 4.9 0 1 0 myWater
#material: 2 0 1.4 0 unusedMaterial
#material: 3 0 2 0 boxMaterial
#sphere: 0.05 0.05 0.05 0.03 myWater y
#box: 0.01 0.01 0.01 0.09 0.025 0.025 boxMaterial y
#geometry_view: 0.02 0.02 0.02 0.06 0.06 0.06 0.001 0.001 0.001 partial_volume f
#geometry_view: 0 0 0 0.1 0.1 0.1 0.001 0.001 0.001 full_volume f

查看文件

@@ -0,0 +1,17 @@
#title: Hertzian dipole in free-space
#domain: 0.100 0.100 0.100
#dx_dy_dz: 0.001 0.001 0.001
#time_window: 3e-9
#waveform: gaussiandot 1 1e9 myWave
#hertzian_dipole: z 0.050 0.050 0.050 myWave
#material: 4.9 0 1 0 myWater
#material: 2 0 1.4 0 unusedMaterial
#material: 3 0 2 0 boxMaterial
#sphere: 0.05 0.05 0.05 0.03 myWater y
#box: 0.01 0.01 0.01 0.09 0.025 0.025 boxMaterial y
#geometry_view: 0.02 0.02 0.02 0.06 0.06 0.06 0.001 0.001 0.001 partial_volume n
#geometry_view: 0 0 0 0.1 0.1 0.1 0.001 0.001 0.001 full_volume n

查看文件

@@ -0,0 +1,12 @@
#title: Hertzian dipole in water
#domain: 0.100 0.100 0.100
#dx_dy_dz: 0.001 0.001 0.001
#time_window: 3e-9
#waveform: gaussiandot 1 1e9 myWave
#hertzian_dipole: z 0.050 0.050 0.050 myWave
#rx: 0.070 0.070 0.070
#material: 4.9 0 1 0 myWater
#add_dispersion_debye: 1 75.2 9.231e-12 myWater
#box: 0 0 0 0.100 0.100 0.100 myWater

查看文件

@@ -0,0 +1,11 @@
#title: Magnetic dipole over a half-space
#domain: 0.100 0.100 0.100
#dx_dy_dz: 0.001 0.001 0.001
#time_window: 3e-9
#waveform: gaussiandot 1 1e9 myWave
#magnetic_dipole: z 0.050 0.050 0.050 myWave
#rx: 0.070 0.070 0.070
#material: 8 0 1 0 half_space
#box: 0 0 0 0.100 0.100 0.050 half_space

查看文件

@@ -0,0 +1,31 @@
#title: Single depth PML
#domain: 0.6 0.6 0.1
#dx_dy_dz: 0.1 0.1 0.1
#time_window: 10
#waveform: gaussiandot 1 1e7 myWave
#hertzian_dipole: z 0.3 0.3 0 myWave
#rx: 0.1 0.1 0 r01 Hx
#rx: 0.1 0.2 0 r02 Hx
#rx: 0.1 0.3 0 r03 Hx
#rx: 0.1 0.4 0 r04 Hx
#rx: 0.2 0.1 0 r05 Hx
#rx: 0.2 0.2 0 r06 Hx
#rx: 0.2 0.3 0 r07 Hx
#rx: 0.2 0.4 0 r08 Hx
#rx: 0.3 0.1 0 r09 Hx
#rx: 0.3 0.2 0 r10 Hx
#rx: 0.3 0.3 0 r11 Hx
#rx: 0.3 0.4 0 r12 Hx
#rx: 0.4 0.1 0 r13 Hx
#rx: 0.4 0.2 0 r14 Hx
#rx: 0.4 0.3 0 r15 Hx
#rx: 0.4 0.4 0 r16 Hx
#material: 8 0 1 0 half_space
#box: 0 0 0 0.6 0.3 0.1 half_space
#pml_cells: 1 1 0 1 1 0

查看文件

@@ -0,0 +1,22 @@
#title: Hertzian dipole in free-space
#domain: 0.100 0.100 0.100
#dx_dy_dz: 0.001 0.001 0.001
#time_window: 3e-9
#waveform: gaussiandot 1 1e9 myWave
#hertzian_dipole: z 0.050 0.050 0.050 myWave
#snapshot: 0.005 0 0 0.006 0.100 0.100 0.01 0.01 0.01 2e-9 snapshot_x_05.h5
#snapshot: 0.035 0 0 0.036 0.100 0.100 0.01 0.01 0.01 2e-9 snapshot_x_35.h5
#snapshot: 0.065 0 0 0.066 0.100 0.100 0.01 0.01 0.01 2e-9 snapshot_x_65.h5
#snapshot: 0.095 0 0 0.096 0.100 0.100 0.01 0.01 0.01 2e-9 snapshot_x_95.h5
#snapshot: 0 0.015 0 0.100 0.016 0.100 0.01 0.01 0.01 2e-9 snapshot_y_15.h5
#snapshot: 0 0.040 0 0.100 0.050 0.100 0.01 0.01 0.01 2e-9 snapshot_y_40.h5
#snapshot: 0 0.045 0 0.100 0.046 0.100 0.01 0.01 0.01 2e-9 snapshot_y_45.h5
#snapshot: 0 0.050 0 0.100 0.051 0.100 0.01 0.01 0.01 2e-9 snapshot_y_50.h5
#snapshot: 0 0.075 0 0.100 0.076 0.100 0.01 0.01 0.01 2e-9 snapshot_y_75.h5
#snapshot: 0 0 0.025 0.100 0.100 0.026 0.01 0.01 0.01 2e-9 snapshot_z_25.h5
#snapshot: 0 0 0.055 0.100 0.100 0.056 0.01 0.01 0.01 2e-9 snapshot_z_55.h5
#snapshot: 0 0 0.055 0.100 0.100 0.086 0.01 0.01 0.01 2e-9 snapshot_z_85.h5

查看文件

@@ -0,0 +1,12 @@
#title: Hertzian dipole in free-space
#domain: 0.100 0.100 0.100
#dx_dy_dz: 0.001 0.001 0.001
#time_window: 3e-9
#waveform: gaussiandot 1 1e9 myWave
#hertzian_dipole: z 0.050 0.050 0.050 myWave
#snapshot: 0 0 0 0.100 0.100 0.100 0.01 0.01 0.01 1 snapshot_0.h5
#snapshot: 0 0 0 0.100 0.100 0.100 0.01 0.01 0.01 1e-9 snapshot_1.h5
#snapshot: 0 0 0 0.100 0.100 0.100 0.01 0.01 0.01 2e-9 snapshot_2.h5
#snapshot: 0 0 0 0.100 0.100 0.100 0.01 0.01 0.01 3e-9 snapshot_3.h5

查看文件

@@ -0,0 +1,12 @@
#title: Hertzian dipole in free-space
#domain: 0.100 0.100 0.001
#dx_dy_dz: 0.001 0.001 0.001
#time_window: 3e-9
#waveform: gaussiandot 1 1e9 myWave
#hertzian_dipole: z 0.050 0.050 0 myWave
#snapshot: 0 0 0 0.100 0.100 0.001 0.01 0.01 0.01 1 snapshot_0.h5
#snapshot: 0 0 0 0.100 0.100 0.001 0.01 0.01 0.01 1e-9 snapshot_1.h5
#snapshot: 0 0 0 0.100 0.100 0.001 0.01 0.01 0.01 2e-9 snapshot_2.h5
#snapshot: 0 0 0 0.100 0.100 0.001 0.01 0.01 0.01 3e-9 snapshot_3.h5

查看文件

@@ -0,0 +1,8 @@
#title: Hertzian dipole in free-space
#domain: 0.100 0.100 0.100
#dx_dy_dz: 0.001 0.001 0.001
#time_window: 3e-9
#waveform: gaussiandot 1 1e9 myWave
#hertzian_dipole: z 0.050 0.050 0.050 myWave
#rx: 0.070 0.070 0.070

查看文件

@@ -0,0 +1,8 @@
#title: Magnetic dipole in free-space
#domain: 0.100 0.100 0.100
#dx_dy_dz: 0.001 0.001 0.001
#time_window: 3e-9
#waveform: gaussiandot 1 1e9 myWave
#magnetic_dipole: z 0.050 0.050 0.050 myWave
#rx: 0.070 0.070 0.070

查看文件

@@ -0,0 +1,8 @@
#title: Transmission line in free-space
#domain: 0.100 0.100 0.100
#dx_dy_dz: 0.001 0.001 0.001
#time_window: 3e-9
#waveform: gaussian 1 1e9 mypulse
#transmission_line: x 0.050 0.050 0.050 35 mypulse
#rx: 0.070 0.070 0.070

查看文件

@@ -0,0 +1,115 @@
"""Cylinder in freespace
This example model demonstrates how to use subgrids at a basic level.
The geometry is 3D (required for any use of subgrids) and is of a water-filled
cylindrical object in freespace. The subgrid encloses the cylinderical object
using a fine spatial discretisation (1mm), and a courser spatial discretisation
(5mm) is used in the rest of the model (main grid). A simple Hertzian dipole
source is used with a waveform shaped as the first derivative of a gaussian.
"""
from pathlib import Path
import gprMax
from gprMax.materials import calculate_water_properties
# File path - used later to specify name of output files
fn = Path(__file__)
parts = fn.parts
# Subgrid spatial discretisation in x, y, z directions
dl_sg = 1e-3
# Subgrid ratio - must always be an odd integer multiple
ratio = 5
dl = dl_sg * ratio
# Domain extent
x = 0.500
y = 0.500
z = 0.500
# Time window
tw = 6e-9
scene = gprMax.Scene()
title = gprMax.Title(name=fn.name)
dxdydz = gprMax.Discretisation(p1=(dl, dl, dl))
domain = gprMax.Domain(p1=(x, y, z))
time_window = gprMax.TimeWindow(time=tw)
wf = gprMax.Waveform(wave_type="gaussiandot", amp=1, freq=1.5e9, id="mypulse")
hd = gprMax.HertzianDipole(polarisation="z", p1=(0.205, 0.400, 0.250), waveform_id="mypulse")
rx = gprMax.Rx(p1=(0.245, 0.400, 0.250))
scene.add(title)
scene.add(dxdydz)
scene.add(domain)
scene.add(time_window)
scene.add(wf)
scene.add(hd)
scene.add(rx)
# Cylinder parameters
c1 = (0.225, 0.250, 0.100)
c2 = (0.225, 0.250, 0.400)
r = 0.010
sg1 = (c1[0] - r, c1[1] - r, c1[2])
sg2 = (c2[0] + r, c2[1] + r, c2[2])
# Create subgrid
subgrid = gprMax.SubGridHSG(p1=sg1, p2=sg2, ratio=ratio, id="sg")
scene.add(subgrid)
# Create water material
eri, er, tau, sig = calculate_water_properties()
water = gprMax.Material(er=eri, se=sig, mr=1, sm=0, id="water")
subgrid.add(water)
water = gprMax.AddDebyeDispersion(poles=1, er_delta=[er - eri], tau=[tau], material_ids=["water"])
subgrid.add(water)
# Add cylinder to subgrid
cylinder = gprMax.Cylinder(p1=c1, p2=c2, r=r, material_id="water")
subgrid.add(cylinder)
# Create some geometry views for both subgrid and main grid
gvsg = gprMax.GeometryView(
p1=sg1,
p2=sg2,
dl=(dl_sg, dl_sg, dl_sg),
filename=fn.with_suffix("").parts[-1] + "_sg",
output_type="n",
)
subgrid.add(gvsg)
gv1 = gprMax.GeometryView(
p1=(0, 0, 0),
p2=(x, y, z),
dl=(dl, dl, dl),
filename=fn.with_suffix("").parts[-1],
output_type="n",
)
scene.add(gv1)
# Create some snapshots of entire domain
for i in range(5):
s = gprMax.Snapshot(
p1=(0, 0, 0),
p2=(x, y, z),
dl=(dl, dl, dl),
time=(i + 0.5) * 1e-9,
filename=fn.with_suffix("").parts[-1] + "_" + str(i + 1),
)
scene.add(s)
gprMax.run(
scenes=[scene],
n=1,
geometry_only=False,
outputfile=fn,
subgrid=True,
autotranslate=True,
log_level=25,
)

查看文件

@@ -0,0 +1,166 @@
"""GPR antenna model (like a GSSI 400MHz antenna) over layered media with a
rough subsurface interface.
This example model demonstrates how to use subgrids at a more advanced level -
combining use of an imported antenna model and rough subsurface interface.
The geometry is 3D (required for any use of subgrids) and is of a 2 layered
subsurface. The top layer in a sandy soil and the bottom layer a soil with
higher permittivity (both have some simple conductive loss). There is a rough
interface between the soil layers. A GPR antenna model (like a GSSI 400MHz
antenna) is imported and placed on the surface of the layered media. The antenna
is meshed using a subgrid with a fine spatial discretisation (1mm), and a
courser spatial discretisation (9mm) is used in the rest of the model (main
grid).
"""
from pathlib import Path
import numpy as np
import gprMax
from toolboxes.GPRAntennaModels.GSSI import antenna_like_GSSI_400
# File path - used later to specify name of output files
fn = Path(__file__)
parts = fn.parts
# Subgrid spatial discretisation in x, y, z directions
dl_sg = 1e-3
# Subgrid ratio - must always be an odd integer multiple
ratio = 9
dl = dl_sg * ratio
# Domain extent
x = 3
y = 1
z = 2
# Time window
# Estimated two way travel time over 1 metre in material with highest
# permittivity, slowest velocity.
tw = 2 / 3e8 * (np.sqrt(3.2) + np.sqrt(9))
scene = gprMax.Scene()
title = gprMax.Title(name=fn.name)
dxdydz = gprMax.Discretisation(p1=(dl, dl, dl))
domain = gprMax.Domain(p1=(x, y, z))
time_window = gprMax.TimeWindow(time=tw)
scene.add(title)
scene.add(dxdydz)
scene.add(domain)
scene.add(time_window)
# Dimensions of antenna case
antenna_case = (0.3, 0.3, 0.178)
# Position of antenna
antenna_p = (x / 2, y / 2, 170 * dl)
# Extra distance surrounding antenna for subgrid
bounding_box = 2 * dl
# Subgrid extent
sg_x0 = antenna_p[0] - antenna_case[0] / 2 - bounding_box
sg_y0 = antenna_p[1] - antenna_case[1] / 2 - bounding_box
sg_z0 = antenna_p[2] - bounding_box
sg_x1 = antenna_p[0] + antenna_case[0] / 2 + bounding_box
sg_y1 = antenna_p[1] + antenna_case[1] / 2 + bounding_box
sg_z1 = antenna_p[2] + antenna_case[2] + bounding_box
# Create subgrid
sg = gprMax.SubGridHSG(p1=[sg_x0, sg_y0, sg_z0], p2=[sg_x1, sg_y1, sg_z1], ratio=ratio, id="sg")
scene.add(sg)
# Create and add a box of homogeneous material to main grid - sandy_soil
sandy_soil = gprMax.Material(er=3.2, se=0.397e-3, mr=1, sm=0, id="sandy_soil")
scene.add(sandy_soil)
b1 = gprMax.Box(p1=(0, 0, 0), p2=(x, y, antenna_p[2]), material_id="sandy_soil")
scene.add(b1)
# Position box of sandy_soil in the subgrid.
# It has to be positioned manually because it traverses the main grid/subgrid
# interface. Grid traversal is when objects extend beyond the outer surface.
# Setting autotranslate to false allows you to place objects beyond the outer
# surface.
# PML separation from the outer surface
ps = ratio // 2 + 2
# Number of PML cells in the subgrid
pc = 6
# Inner surface/outer surface separation
isos = 3 * ratio
# Calculate maximum z-coordinate (height) for box of sandy_soil in subgrid
h = antenna_p[2] - sg_z0 + (ps + pc + isos) * dl_sg
# Create and add a box of homogeneous material to subgrid - sandy_soil
sg.add(sandy_soil)
b2 = gprMax.Box(p1=(0, 0, 0), p2=(411 * dl_sg, 411 * dl_sg, h), material_id="sandy_soil")
# Set autotranslate for the box object to false
b2.autotranslate = False
sg.add(b2)
# Import antenna model and add components to subgrid
gssi_objects = antenna_like_GSSI_400(*antenna_p, resolution=dl_sg)
for obj in gssi_objects:
sg.add(obj)
# Create and add a homogeneous material with a rough surface
soil = gprMax.Material(er=9, se=0.397e-3, mr=1, sm=0, id="soil")
scene.add(soil)
fb = gprMax.FractalBox(
p1=(0, 0, 0),
p2=(3, 1, 1),
frac_dim=1.5,
weighting=(1, 1, 1),
n_materials=1,
mixing_model_id="soil",
id="fbox",
seed=1,
)
scene.add(fb)
rough_surf = gprMax.AddSurfaceRoughness(
p1=(0, 0, 1),
p2=(3, 1, 1),
frac_dim=1.5,
weighting=(1, 1),
limits=(0.4, 1.2),
fractal_box_id="fbox",
seed=1,
)
scene.add(rough_surf)
# Create some snapshots and geometry views
for i in range(1, 51):
snap = gprMax.Snapshot(
p1=(0, y / 2, 0),
p2=(x, y / 2 + dl, z),
dl=(dl, dl, dl),
filename=Path(*parts[:-1], f"{parts[-1]}_{str(i)}").name,
time=i * tw / 50,
)
scene.add(snap)
gvsg = gprMax.GeometryView(
p1=(sg_x0, sg_y0, sg_z0),
p2=(sg_x1, sg_y1, sg_z1),
dl=(dl_sg, dl_sg, dl_sg),
filename=fn.with_suffix("").parts[-1] + "_sg",
output_type="n",
)
sg.add(gvsg)
gv1 = gprMax.GeometryView(
p1=(0, 0, 0), p2=domain.props.p1, dl=dl, filename=fn.with_suffix("").parts[-1], output_type="n"
)
scene.add(gv1)
gprMax.run(
scenes=[scene], n=1, geometry_only=False, outputfile=fn, subgrid=True, autotranslate=True
)

查看文件

@@ -0,0 +1,24 @@
from reframe_tests.tests.base_tests import GprMaxBaseTest
from reframe_tests.tests.mixins import (
GeometryObjectMixin,
GeometryOnlyMixin,
GeometryViewMixin,
ReceiverMixin,
SnapshotMixin,
)
class GprMaxRegressionTest(ReceiverMixin, GprMaxBaseTest):
pass
class GprMaxSnapshotTest(SnapshotMixin, GprMaxBaseTest):
pass
class GprMaxGeometryViewTest(GeometryViewMixin, GeometryOnlyMixin, GprMaxBaseTest):
pass
class GprMaxGeometryObjectTest(GeometryObjectMixin, GeometryOnlyMixin, GprMaxBaseTest):
pass

查看文件

@@ -0,0 +1,54 @@
import reframe as rfm
from reframe.core.builtins import parameter
from reframe_tests.tests.mixins import MpiMixin
from reframe_tests.tests.standard_tests import GprMaxRegressionTest
"""Reframe regression tests for 2D models (TMx, TMy, and TMz)
"""
@rfm.simple_test
class Test2DModelXY(GprMaxRegressionTest):
tags = {"test", "serial", "2d", "waveform", "hertzian_dipole"}
sourcesdir = "src/2d_tests"
model = parameter(["2D_EzHxHy"])
@rfm.simple_test
class Test2DModelXZ(GprMaxRegressionTest):
tags = {"test", "serial", "2d", "waveform", "hertzian_dipole"}
sourcesdir = "src/2d_tests"
model = parameter(["2D_EyHxHz"])
@rfm.simple_test
class Test2DModelYZ(GprMaxRegressionTest):
tags = {"test", "serial", "2d", "waveform", "hertzian_dipole"}
sourcesdir = "src/2d_tests"
model = parameter(["2D_ExHyHz"])
"""Test MPI Functionality
"""
@rfm.simple_test
class Test2DModelXYMpi(MpiMixin, Test2DModelXY):
tags = {"test", "mpi", "2d", "waveform", "hertzian_dipole"}
mpi_layout = parameter([[4, 4, 1]])
test_dependency = Test2DModelXY
@rfm.simple_test
class Test2DModelXZMpi(MpiMixin, Test2DModelXZ):
tags = {"test", "mpi", "2d", "waveform", "hertzian_dipole"}
mpi_layout = parameter([[4, 1, 4]])
test_dependency = Test2DModelXZ
@rfm.simple_test
class Test2DModelYZMpi(MpiMixin, Test2DModelYZ):
tags = {"test", "mpi", "2d", "waveform", "hertzian_dipole"}
mpi_layout = parameter([[1, 4, 4]])
test_dependency = Test2DModelYZ

查看文件

@@ -0,0 +1,77 @@
import reframe as rfm
from reframe.core.builtins import parameter
from reframe_tests.tests.mixins import BScanMixin, MpiMixin
from reframe_tests.tests.standard_tests import GprMaxRegressionTest
"""Reframe regression tests for example models in gprMax documentation
"""
@rfm.simple_test
class TestAscan(GprMaxRegressionTest):
tags = {
"test",
"serial",
"ascan",
"2d",
"hertzian_dipole",
"waveform",
"material",
"box",
"cylinder",
}
sourcesdir = "src/example_models"
model = parameter(["cylinder_Ascan_2D"])
@rfm.simple_test
class TestAscanMPI(MpiMixin, TestAscan):
tags = {
"test",
"mpi",
"ascan",
"2d",
"hertzian_dipole",
"waveform",
"material",
"box",
"cylinder",
}
mpi_layout = parameter([[2, 2, 1]])
test_dependency = TestAscan
@rfm.simple_test
class TestBscan(BScanMixin, GprMaxRegressionTest):
tags = {
"test",
"serial",
"bscan",
"steps",
"waveform",
"hertzian_dipole",
"material",
"box",
"cylinder",
}
sourcesdir = "src/bscan_tests"
model = parameter(["cylinder_Bscan_2D"])
num_models = parameter([64])
@rfm.simple_test
class TestBscanMPI(MpiMixin, TestBscan):
tags = {
"test",
"mpi",
"bscan",
"steps",
"waveform",
"hertzian_dipole",
"material",
"box",
"cylinder",
}
mpi_layout = parameter([[2, 2, 1]])
test_dependency = TestBscan

查看文件

@@ -0,0 +1,66 @@
import reframe as rfm
from reframe.core.builtins import parameter, run_before
from reframe_tests.tests.mixins import AntennaModelMixin, MpiMixin
from reframe_tests.tests.standard_tests import GprMaxRegressionTest
"""Reframe regression tests for models defining geometry
"""
@rfm.simple_test
class TestBoxGeometryDefaultPml(GprMaxRegressionTest):
tags = {"test", "serial", "geometery", "box"}
sourcesdir = "src/geometry_tests/box_geometry"
model = parameter(
[
"box_full_model",
"box_half_model",
"box_single_rank",
"box_outside_pml",
"box_single_rank_outside_pml",
]
)
@rfm.simple_test
class TestBoxGeometryNoPml(GprMaxRegressionTest):
tags = {"test", "serial", "geometery", "box"}
sourcesdir = "src/geometry_tests/box_geometry"
model = parameter(["box_full_model", "box_half_model", "box_single_rank"])
@run_before("run")
def add_gprmax_commands(self):
self.prerun_cmds.append(f"echo '#pml_cells: 0' >> {self.input_file}")
@rfm.simple_test
class TestEdgeGeometry(AntennaModelMixin, GprMaxRegressionTest):
tags = {"test", "serial", "geometry", "edge", "transmission_line", "waveform", "antenna"}
sourcesdir = "src/geometry_tests/edge_geometry"
model = parameter(["antenna_wire_dipole_fs"])
"""Test MPI Functionality
"""
@rfm.simple_test
class TestBoxGeometryDefaultPmlMpi(MpiMixin, TestBoxGeometryDefaultPml):
tags = {"test", "mpi", "geometery", "box"}
mpi_layout = parameter([[2, 2, 2], [3, 3, 3], [4, 4, 4]])
test_dependency = TestBoxGeometryDefaultPml
@rfm.simple_test
class TestBoxGeometryNoPmlMpi(MpiMixin, TestBoxGeometryNoPml):
tags = {"test", "mpi", "geometery", "box"}
mpi_layout = parameter([[2, 2, 2], [3, 3, 3], [4, 4, 4]])
test_dependency = TestBoxGeometryNoPml
@rfm.simple_test
class TestEdgeGeometryMpi(MpiMixin, TestEdgeGeometry):
tags = {"test", "mpi", "geometry", "edge", "transmission_line", "waveform", "antenna"}
mpi_layout = parameter([[3, 3, 3]])
test_dependency = TestEdgeGeometry

查看文件

@@ -0,0 +1,20 @@
import reframe as rfm
from reframe.core.builtins import parameter
from reframe_tests.tests.mixins import MpiMixin
from reframe_tests.tests.standard_tests import GprMaxGeometryObjectTest
@rfm.simple_test
class TestGeometryObject(GprMaxGeometryObjectTest):
tags = {"test", "serial", "geometry only", "geometry object"}
sourcesdir = "src/geometry_object_tests"
model = parameter(["geometry_object_write"])
geometry_objects = ["partial_volume", "full_volume"]
@rfm.simple_test
class TestGeometryObjectMPI(MpiMixin, TestGeometryObject):
tags = {"test", "mpi", "geometry only", "geometry object"}
mpi_layout = parameter([[2, 2, 2], [4, 4, 1]])
test_dependency = TestGeometryObject

查看文件

@@ -0,0 +1,20 @@
import reframe as rfm
from reframe.core.builtins import parameter
from reframe_tests.tests.mixins import MpiMixin
from reframe_tests.tests.standard_tests import GprMaxGeometryViewTest
@rfm.simple_test
class TestGeometryView(GprMaxGeometryViewTest):
tags = {"test", "serial", "geometry only", "geometry view"}
sourcesdir = "src/geometry_view_tests"
model = parameter(["geometry_view_voxel", "geometry_view_fine"])
geometry_views = ["partial_volume", "full_volume"]
@rfm.simple_test
class TestGeometryViewMPI(MpiMixin, TestGeometryView):
tags = {"test", "mpi", "geometry only", "geometry view"}
mpi_layout = parameter([[2, 2, 2], [4, 4, 1]])
test_dependency = TestGeometryView

查看文件

@@ -0,0 +1,26 @@
import reframe as rfm
from reframe.core.builtins import parameter
from reframe_tests.tests.mixins import MpiMixin
from reframe_tests.tests.standard_tests import GprMaxRegressionTest
"""Reframe regression tests for each gprMax source
"""
@rfm.simple_test
class TestDispersiveMaterials(GprMaxRegressionTest):
tags = {"test", "serial", "hertzian_dipole", "waveform", "material", "dispersive", "box"}
sourcesdir = "src/material_tests"
model = parameter(["hertzian_dipole_dispersive"])
"""Test MPI Functionality
"""
@rfm.simple_test
class TestDispersiveMaterialsMpi(MpiMixin, TestDispersiveMaterials):
tags = {"test", "mpi", "hertzian_dipole", "waveform", "material", "dispersive", "box"}
mpi_layout = parameter([[3, 3, 3]])
test_dependency = TestDispersiveMaterials

查看文件

@@ -0,0 +1,26 @@
import reframe as rfm
from reframe.core.builtins import parameter
from reframe_tests.tests.mixins import MpiMixin
from reframe_tests.tests.standard_tests import GprMaxRegressionTest
"""Reframe regression tests for models defining geometry
"""
@rfm.simple_test
class TestSingleCellPml(GprMaxRegressionTest):
tags = {"test", "serial", "geometery", "box", "pml"}
sourcesdir = "src/pml_tests"
model = parameter(["single_cell_pml_2d"])
"""Test MPI Functionality
"""
@rfm.simple_test
class TestSingleCellPmlMpi(MpiMixin, TestSingleCellPml):
tags = {"test", "mpi", "geometery", "box", "pml"}
mpi_layout = parameter([[2, 2, 1], [3, 3, 1]])
test_dependency = TestSingleCellPml

查看文件

@@ -0,0 +1,91 @@
import reframe as rfm
from reframe.core.builtins import parameter
from reframe_tests.tests.mixins import MpiMixin
from reframe_tests.tests.standard_tests import GprMaxSnapshotTest
@rfm.simple_test
class Test2DSnapshot(GprMaxSnapshotTest):
tags = {"test", "serial", "2d", "waveform", "hertzian_dipole", "snapshot"}
sourcesdir = "src/snapshot_tests"
model = parameter(["whole_domain_2d"])
snapshots = ["snapshot_0.h5", "snapshot_1.h5", "snapshot_2.h5", "snapshot_3.h5"]
@rfm.simple_test
class TestSnapshot(GprMaxSnapshotTest):
tags = {"test", "serial", "2d", "waveform", "hertzian_dipole", "snapshot"}
sourcesdir = "src/snapshot_tests"
model = parameter(["whole_domain"])
snapshots = ["snapshot_0.h5", "snapshot_1.h5", "snapshot_2.h5", "snapshot_3.h5"]
@rfm.simple_test
class Test2DSliceSnapshot(GprMaxSnapshotTest):
tags = {"test", "serial", "2d", "waveform", "hertzian_dipole", "snapshot"}
sourcesdir = "src/snapshot_tests"
model = parameter(["2d_slices"])
snapshots = [
"snapshot_x_05.h5",
"snapshot_x_35.h5",
"snapshot_x_65.h5",
"snapshot_x_95.h5",
"snapshot_y_15.h5",
"snapshot_y_40.h5",
"snapshot_y_45.h5",
"snapshot_y_50.h5",
"snapshot_y_75.h5",
"snapshot_z_25.h5",
"snapshot_z_55.h5",
"snapshot_z_85.h5",
]
"""Test MPI Functionality
"""
@rfm.simple_test
class Test2DSnapshotMpi(MpiMixin, Test2DSnapshot):
tags = {"test", "mpi", "2d", "waveform", "hertzian_dipole", "snapshot"}
mpi_layout = parameter([[2, 2, 1], [3, 3, 1], [4, 4, 1]])
test_dependency = Test2DSnapshot
@rfm.simple_test
class TestSnapshotMpi(MpiMixin, TestSnapshot):
tags = {"test", "mpi", "2d", "waveform", "hertzian_dipole", "snapshot"}
mpi_layout = parameter(
[
[2, 1, 1],
[1, 2, 1],
[1, 1, 2],
[3, 1, 1],
[1, 3, 1],
[1, 1, 3],
[2, 2, 2],
[3, 3, 3],
[4, 4, 4],
]
)
test_dependency = TestSnapshot
@rfm.simple_test
class Test2DSliceSnapshotMpi(MpiMixin, Test2DSliceSnapshot):
tags = {"test", "mpi", "2d", "waveform", "hertzian_dipole", "snapshot"}
mpi_layout = parameter(
[
[2, 1, 1],
[1, 2, 1],
[1, 1, 2],
[3, 1, 1],
[1, 3, 1],
[1, 1, 3],
[2, 2, 2],
[3, 3, 3],
[4, 4, 4],
]
)
test_dependency = Test2DSliceSnapshot

查看文件

@@ -0,0 +1,54 @@
import reframe as rfm
from reframe.core.builtins import parameter
from reframe_tests.tests.mixins import MpiMixin
from reframe_tests.tests.standard_tests import GprMaxRegressionTest
"""Reframe regression tests for each gprMax source
"""
@rfm.simple_test
class TestHertzianDipoleSource(GprMaxRegressionTest):
tags = {"test", "serial", "hertzian_dipole", "waveform"}
sourcesdir = "src/source_tests"
model = parameter(["hertzian_dipole_fs"])
@rfm.simple_test
class TestMagneticDipoleSource(GprMaxRegressionTest):
tags = {"test", "serial", "magnetic_dipole", "waveform"}
sourcesdir = "src/source_tests"
model = parameter(["magnetic_dipole_fs"])
@rfm.simple_test
class TestTransmissionLineSource(GprMaxRegressionTest):
tags = {"test", "serial", "transmission_line", "waveform"}
sourcesdir = "src/source_tests"
model = parameter(["transmission_line_fs"])
"""Test MPI Functionality
"""
@rfm.simple_test
class TestHertzianDipoleSourceMpi(MpiMixin, TestHertzianDipoleSource):
tags = {"test", "mpi", "hertzian_dipole", "waveform"}
mpi_layout = parameter([[3, 3, 3]])
test_dependency = TestHertzianDipoleSource
@rfm.simple_test
class TestMagneticDipoleSourceMpi(MpiMixin, TestMagneticDipoleSource):
tags = {"test", "mpi", "magnetic_dipole", "waveform"}
mpi_layout = parameter([[3, 3, 3]])
test_dependency = TestMagneticDipoleSource
@rfm.simple_test
class TestTransmissionLineSourceMpi(MpiMixin, TestTransmissionLineSource):
tags = {"test", "mpi", "transmission_line", "waveform"}
mpi_layout = parameter([[3, 3, 3]])
test_dependency = TestTransmissionLineSource

查看文件

@@ -0,0 +1,46 @@
import reframe as rfm
from reframe.core.builtins import parameter, run_after
from reframe_tests.tests.mixins import AntennaModelMixin, PythonApiMixin
from reframe_tests.tests.standard_tests import GprMaxRegressionTest
"""Reframe regression tests for subgrids
"""
@rfm.simple_test
class TestSubgrids(PythonApiMixin, GprMaxRegressionTest):
tags = {
"test",
"api",
"serial",
"subgrid",
"hertzian_dipole",
"waveform",
"material",
"dispersive",
"cylinder",
}
sourcesdir = "src/subgrid_tests"
model = parameter(["cylinder_fs"])
@rfm.simple_test
class TestSubgridsWithAntennaModel(AntennaModelMixin, PythonApiMixin, GprMaxRegressionTest):
tags = {
"test",
"api",
"serial",
"subgrid",
"antenna",
"material",
"box",
"fractal_box",
"add_surface_roughness",
}
sourcesdir = "src/subgrid_tests"
model = parameter(["gssi_400_over_fractal_subsurface"])
@run_after("init")
def skip_test(self):
self.skip_if(self.current_system.name == "archer2", "Takes ~1hr 30m on ARCHER2")

查看文件

@@ -0,0 +1,43 @@
import reframe as rfm
from reframe_tests.tests.mixins import TaskfarmMixin
from reframe_tests.tests.test_example_models import TestBscan
"""Reframe regression tests for taskfarm functionality
"""
@rfm.simple_test
class TestSingleNodeTaskfarm(TaskfarmMixin, TestBscan):
tags = {
"test",
"mpi",
"taskfarm",
"steps",
"waveform",
"hertzian_dipole",
"material",
"box",
"cylinder",
}
num_tasks = 8
num_tasks_per_node = 8
test_dependency = TestBscan
@rfm.simple_test
class TestMultiNodeTaskfarm(TaskfarmMixin, TestBscan):
tags = {
"test",
"mpi",
"taskfarm",
"steps",
"waveform",
"hertzian_dipole",
"material",
"box",
"cylinder",
}
num_tasks = 32
num_tasks_per_node = 8
test_dependency = TestBscan