diff --git a/.gitattributes b/.gitattributes index 73f2c564..bec33da4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,3 +2,5 @@ tools/Jupyter_notebooks/* linguist-vendored reframe_tests/regression_checks/TestGeometryView_5176823e/partial_volume.vtkhdf filter=lfs diff=lfs merge=lfs -text reframe_tests/regression_checks/TestGeometryView_5176823e/full_volume.vtkhdf filter=lfs diff=lfs merge=lfs -text reframe_tests/regression_checks/TestGeometryView_77980202/full_volume.vtkhdf filter=lfs diff=lfs merge=lfs -text +reframe_tests/regression_checks/TestGeometryObject_a6a096cb/full_volume.h5 filter=lfs diff=lfs merge=lfs -text +reframe_tests/regression_checks/TestGeometryObject_a6a096cb/partial_volume.h5 filter=lfs diff=lfs merge=lfs -text diff --git a/reframe_tests/regression_checks/TestGeometryObject_a6a096cb/full_volume.h5 b/reframe_tests/regression_checks/TestGeometryObject_a6a096cb/full_volume.h5 new file mode 100644 index 00000000..0cd34b68 --- /dev/null +++ b/reframe_tests/regression_checks/TestGeometryObject_a6a096cb/full_volume.h5 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac30e1b942acb3e17bec7d2a16167429ed3b1590642d9c403ee5d0adb6be27c8 +size 75462640 diff --git a/reframe_tests/regression_checks/TestGeometryObject_a6a096cb/full_volume_materials.txt b/reframe_tests/regression_checks/TestGeometryObject_a6a096cb/full_volume_materials.txt new file mode 100644 index 00000000..d7a4e603 --- /dev/null +++ b/reframe_tests/regression_checks/TestGeometryObject_a6a096cb/full_volume_materials.txt @@ -0,0 +1,9 @@ +#material: 1 inf 1 0 pec +#material: 1 0 1 0 free_space +#material: 4.9 0 1 0 myWater +#material: 3 0 2 0 boxMaterial +#material: 1.5 0 1.25 0 boxMaterial+free_space+free_space+free_space +#material: 2 0 1.5 0 boxMaterial+free_space+free_space+boxMaterial +#material: 1.975 0 1 0 myWater+free_space+free_space+free_space +#material: 2.95 0 1 0 myWater+free_space+free_space+myWater +#material: 3.925 0 1 0 myWater+myWater+free_space+myWater diff --git a/reframe_tests/regression_checks/TestGeometryObject_a6a096cb/partial_volume.h5 b/reframe_tests/regression_checks/TestGeometryObject_a6a096cb/partial_volume.h5 new file mode 100644 index 00000000..b46a2c6b --- /dev/null +++ b/reframe_tests/regression_checks/TestGeometryObject_a6a096cb/partial_volume.h5 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:224975ddea1c1a4cd3f99aaa9e7ce1fb1ec31de07b0352424f66be73a5645a4f +size 4980400 diff --git a/reframe_tests/regression_checks/TestGeometryObject_a6a096cb/partial_volume_materials.txt b/reframe_tests/regression_checks/TestGeometryObject_a6a096cb/partial_volume_materials.txt new file mode 100644 index 00000000..d7a4e603 --- /dev/null +++ b/reframe_tests/regression_checks/TestGeometryObject_a6a096cb/partial_volume_materials.txt @@ -0,0 +1,9 @@ +#material: 1 inf 1 0 pec +#material: 1 0 1 0 free_space +#material: 4.9 0 1 0 myWater +#material: 3 0 2 0 boxMaterial +#material: 1.5 0 1.25 0 boxMaterial+free_space+free_space+free_space +#material: 2 0 1.5 0 boxMaterial+free_space+free_space+boxMaterial +#material: 1.975 0 1 0 myWater+free_space+free_space+free_space +#material: 2.95 0 1 0 myWater+free_space+free_space+myWater +#material: 3.925 0 1 0 myWater+myWater+free_space+myWater diff --git a/reframe_tests/tests/mixins.py b/reframe_tests/tests/mixins.py index 2bd1a04f..f7181ce9 100644 --- a/reframe_tests/tests/mixins.py +++ b/reframe_tests/tests/mixins.py @@ -9,9 +9,11 @@ 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, - RegressionCheck, SnapshotRegressionCheck, ) @@ -38,7 +40,7 @@ class ReceiverMixin(GprMaxMixin): ) self.regression_checks.append(regression_check) else: - regression_check = RegressionCheck(self.output_file, reference_file) + regression_check = H5RegressionCheck(self.output_file, reference_file) self.regression_checks.append(regression_check) @@ -86,6 +88,61 @@ class GeometryOnlyMixin(GprMaxMixin): 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. diff --git a/reframe_tests/tests/regression_checks.py b/reframe_tests/tests/regression_checks.py index e6adcf7d..85c91b89 100644 --- a/reframe_tests/tests/regression_checks.py +++ b/reframe_tests/tests/regression_checks.py @@ -9,7 +9,7 @@ from reframe.utility import osext class RegressionCheck: - """Compare two hdf5 files using h5diff""" + """Compare two files using diff""" def __init__( self, output_file: Union[str, PathLike], reference_file: Union[str, PathLike] @@ -23,7 +23,8 @@ class RegressionCheck: """ self.output_file = Path(output_file) self.reference_file = Path(reference_file) - self.h5diff_options: list[str] = [] + self.cmd = "diff" + self.options: list[str] = [] @property def error_msg(self) -> str: @@ -57,26 +58,22 @@ class RegressionCheck: return sn.path_isfile(self.reference_file) def run(self) -> Literal[True]: - """Run the regression check using h5diff. + """Run the regression check. Returns: check_passed: Returns True if the output file matches the - reference file (i.e. no output from h5diff). Otherwise, + 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. """ - if runtime().system.name == "archer2": - h5diff = "/opt/cray/pe/hdf5/default/bin/h5diff" - else: - h5diff = "h5diff" - h5diff_output = osext.run_command( + completed_process = osext.run_command( [ - h5diff, - *self.h5diff_options, + self.cmd, + *self.options, str(self.output_file.absolute()), str(self.reference_file), ] @@ -86,16 +83,29 @@ class RegressionCheck: sn.path_isfile(self.output_file), f"Expected output file '{self.output_file}' does not exist", ) and sn.assert_false( - h5diff_output.stdout, + completed_process.stdout, ( f"{self.error_msg}\n" - f"For more details run: '{' '.join(h5diff_output.args)}'\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 ReceiverRegressionCheck(RegressionCheck): +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 @@ -106,7 +116,7 @@ class ReceiverRegressionCheck(RegressionCheck): self, output_file: Union[str, PathLike], reference_file: Union[str, PathLike], - output_receiver: Optional[str], + output_receiver: str, reference_receiver: Optional[str] = None, ) -> None: """Create a new receiver regression check. @@ -125,26 +135,42 @@ class ReceiverRegressionCheck(RegressionCheck): self.output_receiver = output_receiver self.reference_receiver = reference_receiver - self.h5diff_options.append(f"rxs/{self.output_receiver}") + self.options.append(f"rxs/{self.output_receiver}") if self.reference_receiver is not None: - self.h5diff_options.append(f"rxs/{self.reference_receiver}") + 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(RegressionCheck): +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 " + return f"Snapshot '{self.output_file.name}' failed regression check" -class GeometryViewRegressionCheck(RegressionCheck): +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 " + return f"GeometryView '{self.output_file.name}' failed regression check" diff --git a/reframe_tests/tests/src/geometry_object_tests/geometry_object_write.in b/reframe_tests/tests/src/geometry_object_tests/geometry_object_write.in new file mode 100644 index 00000000..25110c67 --- /dev/null +++ b/reframe_tests/tests/src/geometry_object_tests/geometry_object_write.in @@ -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 diff --git a/reframe_tests/tests/standard_tests.py b/reframe_tests/tests/standard_tests.py index 9f268cb2..cab5ec7e 100644 --- a/reframe_tests/tests/standard_tests.py +++ b/reframe_tests/tests/standard_tests.py @@ -1,5 +1,6 @@ from reframe_tests.tests.base_tests import GprMaxBaseTest from reframe_tests.tests.mixins import ( + GeometryObjectMixin, GeometryOnlyMixin, GeometryViewMixin, ReceiverMixin, @@ -17,3 +18,7 @@ class GprMaxSnapshotTest(SnapshotMixin, GprMaxBaseTest): class GprMaxGeometryViewTest(GeometryViewMixin, GeometryOnlyMixin, GprMaxBaseTest): pass + + +class GprMaxGeometryObjectTest(GeometryObjectMixin, GeometryOnlyMixin, GprMaxBaseTest): + pass diff --git a/reframe_tests/tests/test_geometry_objects.py b/reframe_tests/tests/test_geometry_objects.py new file mode 100644 index 00000000..2cad4fbc --- /dev/null +++ b/reframe_tests/tests/test_geometry_objects.py @@ -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