你已经派生过 gprMax
镜像自地址
https://gitee.com/sunhf/gprMax.git
已同步 2025-08-07 15:10:13 +08:00
Checkout reframe tests from mpi branch
这个提交包含在:
437
reframe_tests/tests/base_tests.py
普通文件
437
reframe_tests/tests/base_tests.py
普通文件
@@ -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
|
268
reframe_tests/tests/mixins.py
普通文件
268
reframe_tests/tests/mixins.py
普通文件
@@ -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
|
26
reframe_tests/tests/test_pmls.py
普通文件
26
reframe_tests/tests/test_pmls.py
普通文件
@@ -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
|
在新工单中引用
屏蔽一个用户