diff --git a/reframe_tests/.gitignore b/reframe_tests/.gitignore new file mode 100644 index 00000000..0c672a2f --- /dev/null +++ b/reframe_tests/.gitignore @@ -0,0 +1,8 @@ +output/ +perflogs/ +stage/ +reframe.log +reframe.out +reframe_perf.out + +configuration/user_config.py diff --git a/reframe_tests/benchmark_results/MPIStrongScalingBenchmark_2024-08-30_16-20-05.csv b/reframe_tests/benchmark_results/MPIStrongScalingBenchmark_2024-08-30_16-20-05.csv new file mode 100644 index 00000000..25be1592 --- /dev/null +++ b/reframe_tests/benchmark_results/MPIStrongScalingBenchmark_2024-08-30_16-20-05.csv @@ -0,0 +1,11 @@ +cpu_freq,model,num_cpus_per_task,num_nodes,num_tasks,num_tasks_per_node,run_time,simulation_time +2000000,benchmark_model_40,16,1,8,8,147.94,61.11 +2000000,benchmark_model_40,16,16,128,8,74.45,16.03 +2000000,benchmark_model_40,16,2,16,8,108.6,45.41 +2000000,benchmark_model_40,16,4,32,8,92.18,35.0 +2000000,benchmark_model_40,16,8,64,8,73.71,16.56 +2250000,benchmark_model_40,16,1,8,8,171.95,53.94 +2250000,benchmark_model_40,16,16,128,8,58.13,12.04 +2250000,benchmark_model_40,16,2,16,8,97.73,38.73 +2250000,benchmark_model_40,16,4,32,8,87.61,28.54 +2250000,benchmark_model_40,16,8,64,8,68.29,14.47 diff --git a/reframe_tests/benchmark_results/SingleNodeBenchmark_2024-01-24_11-31-37.csv b/reframe_tests/benchmark_results/SingleNodeBenchmark_2024-01-24_11-31-37.csv new file mode 100644 index 00000000..66100484 --- /dev/null +++ b/reframe_tests/benchmark_results/SingleNodeBenchmark_2024-01-24_11-31-37.csv @@ -0,0 +1,145 @@ +cpu_freq,domain,num_cpus_per_task,num_tasks,num_tasks_per_node,omp_threads,run_time,simulation_time +2000000,0.1,1,1,,1,66.38,56.22 +2000000,0.1,2,1,,2,37.98,30.32 +2000000,0.1,4,1,,4,22.77,17.18 +2000000,0.1,8,1,,8,16.18,10.51 +2000000,0.1,16,1,,16,14.27,8.09 +2000000,0.1,32,1,,32,17.05,10.1 +2000000,0.1,64,1,,64,28.35,19.12 +2000000,0.1,128,1,,128,85.27,65.33 +2000000,0.15,1,1,,1,182.5,174.22 +2000000,0.15,2,1,,2,102.11,92.21 +2000000,0.15,4,1,,4,55.17,49.58 +2000000,0.15,8,1,,8,37.08,31.39 +2000000,0.15,16,1,,16,33.61,25.55 +2000000,0.15,32,1,,32,26.1,19.07 +2000000,0.15,64,1,,64,33.14,23.48 +2000000,0.15,128,1,,128,78.14,59.57 +2000000,0.2,1,1,,1,386.67,374.75 +2000000,0.2,2,1,,2,206.99,197.88 +2000000,0.2,4,1,,4,109.97,104.41 +2000000,0.2,8,1,,8,73.19,64.43 +2000000,0.2,16,1,,16,62.07,54.08 +2000000,0.2,32,1,,32,48.24,40.67 +2000000,0.2,64,1,,64,55.74,44.6 +2000000,0.2,128,1,,128,101.74,83.55 +2000000,0.3,1,1,,1,1151.43,1140.37 +2000000,0.3,2,1,,2,611.66,602.01 +2000000,0.3,4,1,,4,321.48,310.84 +2000000,0.3,8,1,,8,204.9,196.05 +2000000,0.3,16,1,,16,174.01,167.72 +2000000,0.3,32,1,,32,128.94,116.76 +2000000,0.3,64,1,,64,121.17,108.21 +2000000,0.3,128,1,,128,198.33,174.66 +2000000,0.4,1,1,,1,2610.57,2598.76 +2000000,0.4,2,1,,2,1371.05,1359.44 +2000000,0.4,4,1,,4,706.84,699.5 +2000000,0.4,8,1,,8,466.36,459.21 +2000000,0.4,16,1,,16,401.64,393.83 +2000000,0.4,32,1,,32,279.96,267.74 +2000000,0.4,64,1,,64,271.98,247.58 +2000000,0.4,128,1,,128,374.76,314.99 +2000000,0.5,1,1,,1,4818.72,4806.97 +2000000,0.5,2,1,,2,2549.42,2540.13 +2000000,0.5,4,1,,4,1315.68,1306.77 +2000000,0.5,8,1,,8,864.02,855.79 +2000000,0.5,16,1,,16,755.09,748.3 +2000000,0.5,32,1,,32,548.72,527.04 +2000000,0.5,64,1,,64,473.02,414.43 +2000000,0.5,128,1,,128,594.65,443.44 +2000000,0.6,1,1,,1,8219.78,8149.55 +2000000,0.6,2,1,,2,4277.96,4266.5 +2000000,0.6,4,1,,4,2199.55,2190.22 +2000000,0.6,8,1,,8,1445.58,1438.02 +2000000,0.6,16,1,,16,1319.07,1312.13 +2000000,0.6,32,1,,32,877.47,818.48 +2000000,0.6,64,1,,64,741.43,649.31 +2000000,0.6,128,1,,128,821.9,554.22 +2000000,0.7,1,1,,1,12964.86,12949.06 +2000000,0.7,2,1,,2,6769.45,6762.25 +2000000,0.7,4,1,,4,3471.68,3465.48 +2000000,0.7,8,1,,8,2270.26,2263.86 +2000000,0.7,16,1,,16,2040.48,2033.68 +2000000,0.7,32,1,,32,1364.91,1274.35 +2000000,0.7,64,1,,64,1094.98,936.72 +2000000,0.7,128,1,,128,1163.05,775.27 +2000000,0.8,1,1,,1,19115.24,18963.06 +2000000,0.8,2,1,,2,9894.57,9868.57 +2000000,0.8,4,1,,4,5021.3,5011.79 +2000000,0.8,8,1,,8,3285.76,3272.44 +2000000,0.8,16,1,,16,3010.09,3003.21 +2000000,0.8,32,1,,32,1961.83,1789.48 +2000000,0.8,64,1,,64,1528.58,1304.87 +2000000,0.8,128,1,,128,1671.89,1115.37 +2250000,0.1,1,1,,1,46.42,38.46 +2250000,0.1,2,1,,2,27.41,19.9 +2250000,0.1,4,1,,4,15.61,11.83 +2250000,0.1,8,1,,8,13.1,9.04 +2250000,0.1,16,1,,16,11.33,5.89 +2250000,0.1,32,1,,32,13.28,6.98 +2250000,0.1,64,1,,64,22.95,14.36 +2250000,0.1,128,1,,128,63.82,47.17 +2250000,0.15,1,1,,1,122.83,114.3 +2250000,0.15,2,1,,2,67.81,58.22 +2250000,0.15,4,1,,4,37.45,33.57 +2250000,0.15,8,1,,8,32.12,28.33 +2250000,0.15,16,1,,16,28.47,23.02 +2250000,0.15,32,1,,32,21.8,15.31 +2250000,0.15,64,1,,64,26.29,17.85 +2250000,0.15,128,1,,128,67.36,50.01 +2250000,0.2,1,1,,1,249.09,240.5 +2250000,0.2,2,1,,2,131.92,122.94 +2250000,0.2,4,1,,4,73.47,69.44 +2250000,0.2,8,1,,8,68.64,59.68 +2250000,0.2,16,1,,16,58.96,50.94 +2250000,0.2,32,1,,32,42.94,35.78 +2250000,0.2,64,1,,64,44.52,36.89 +2250000,0.2,128,1,,128,84.06,68.65 +2250000,0.3,1,1,,1,713.41,703.34 +2250000,0.3,2,1,,2,369.84,363.33 +2250000,0.3,4,1,,4,211.28,203.39 +2250000,0.3,8,1,,8,190.98,186.1 +2250000,0.3,16,1,,16,169.92,163.23 +2250000,0.3,32,1,,32,117.74,109.5 +2250000,0.3,64,1,,64,116.59,101.76 +2250000,0.3,128,1,,128,162.47,134.58 +2250000,0.4,1,1,,1,1593.84,1584.41 +2250000,0.4,2,1,,2,821.79,813.12 +2250000,0.4,4,1,,4,476.39,468.35 +2250000,0.4,8,1,,8,445.69,437.75 +2250000,0.4,16,1,,16,392.55,385.05 +2250000,0.4,32,1,,32,280.65,265.65 +2250000,0.4,64,1,,64,249.73,221.02 +2250000,0.4,128,1,,128,312.19,240.34 +2250000,0.5,1,1,,1,2917.2,2908.0 +2250000,0.5,2,1,,2,1501.78,1493.7 +2250000,0.5,4,1,,4,868.33,859.61 +2250000,0.5,8,1,,8,831.58,827.08 +2250000,0.5,16,1,,16,734.53,729.57 +2250000,0.5,32,1,,32,520.43,486.83 +2250000,0.5,64,1,,64,431.9,373.89 +2250000,0.5,128,1,,128,523.72,368.59 +2250000,0.6,1,1,,1,4930.04,4918.3 +2250000,0.6,2,1,,2,2513.92,2508.71 +2250000,0.6,4,1,,4,1437.79,1433.86 +2250000,0.6,8,1,,8,1385.16,1380.08 +2250000,0.6,16,1,,16,1278.64,1274.32 +2250000,0.6,32,1,,32,843.05,800.03 +2250000,0.6,64,1,,64,683.45,575.28 +2250000,0.6,128,1,,128,736.02,466.24 +2250000,0.7,1,1,,1,7778.74,7766.64 +2250000,0.7,2,1,,2,3979.22,3973.14 +2250000,0.7,4,1,,4,2290.33,2285.86 +2250000,0.7,8,1,,8,2193.65,2185.43 +2250000,0.7,16,1,,16,1984.77,1980.02 +2250000,0.7,32,1,,32,1302.34,1186.39 +2250000,0.7,64,1,,64,1011.7,830.39 +2250000,0.7,128,1,,128,1077.62,685.29 +2250000,0.8,1,1,,1,11319.94,11306.89 +2250000,0.8,2,1,,2,5715.39,5709.03 +2250000,0.8,4,1,,4,3279.14,3260.67 +2250000,0.8,8,1,,8,3194.27,3174.3 +2250000,0.8,16,1,,16,2923.77,2918.94 +2250000,0.8,32,1,,32,1871.78,1736.6 +2250000,0.8,64,1,,64,1424.7,1179.98 +2250000,0.8,128,1,,128,1530.45,930.84 diff --git a/reframe_tests/benchmark_results/SingleNodeMPIBenchmark_2024-08-30_14-33-28.csv b/reframe_tests/benchmark_results/SingleNodeMPIBenchmark_2024-08-30_14-33-28.csv new file mode 100644 index 00000000..34356ede --- /dev/null +++ b/reframe_tests/benchmark_results/SingleNodeMPIBenchmark_2024-08-30_14-33-28.csv @@ -0,0 +1,17 @@ +cpu_freq,model,mpi_tasks,num_cpus_per_task,num_tasks,num_tasks_per_node,run_time,simulation_time +2000000,benchmark_model_40,1,128,1,1,397.64,294.58 +2000000,benchmark_model_40,128,1,128,128,129.22,68.77 +2000000,benchmark_model_40,16,8,16,16,104.83,64.91 +2000000,benchmark_model_40,2,64,2,2,192.89,151.06 +2000000,benchmark_model_40,32,4,32,32,101.99,63.86 +2000000,benchmark_model_40,4,32,4,4,119.14,80.95 +2000000,benchmark_model_40,64,2,64,64,105.57,61.03 +2000000,benchmark_model_40,8,16,8,8,102.26,58.24 +2250000,benchmark_model_40,1,128,1,1,348.95,241.2 +2250000,benchmark_model_40,128,1,128,128,118.04,66.21 +2250000,benchmark_model_40,16,8,16,16,106.06,61.8 +2250000,benchmark_model_40,2,64,2,2,189.82,140.84 +2250000,benchmark_model_40,32,4,32,32,99.12,60.65 +2250000,benchmark_model_40,4,32,4,4,117.36,76.9 +2250000,benchmark_model_40,64,2,64,64,108.8,58.79 +2250000,benchmark_model_40,8,16,8,8,94.92,55.32 diff --git a/reframe_tests/benchmark_tests/reframe_benchmarks.py b/reframe_tests/benchmark_tests/reframe_benchmarks.py new file mode 100644 index 00000000..f1b98acc --- /dev/null +++ b/reframe_tests/benchmark_tests/reframe_benchmarks.py @@ -0,0 +1,169 @@ +import os +from pathlib import Path + +import numpy as np +from primePy import primes +from reframe import simple_test +from reframe.core.builtins import parameter, run_after + +from reframe_tests.tests.base_tests import GprMaxMPIRegressionTest, GprMaxRegressionTest + +"""ReFrame tests for performance benchmarking + + Usage: + cd gprMax/reframe_tests + reframe -C configuraiton/{CONFIG_FILE} -c reframe_benchmarks.py -c base_tests.py -r +""" + + +def calculate_mpi_decomposition(number: int): + factors: list[int] = primes.factors(number) + if len(factors) < 3: + factors += [1] * (3 - len(factors)) + elif len(factors) > 3: + base = factors[-3:] + factors = factors[:-3] + for factor in reversed(factors): # Use the largest factors first + min_index = np.argmin(base) + base[min_index] *= factor + factors = base + + return sorted(factors) + + +@simple_test +class SingleNodeBenchmark(GprMaxRegressionTest): + tags = {"benchmark", "single node", "openmp"} + + omp_threads = parameter([1, 2, 4, 8, 16, 32, 64, 128]) + # domain = parameter([0.1, 0.15, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]) + cpu_freq = parameter([2000000, 2250000]) + time_limit = "8h" + sourcesdir = "src" + model = parameter( + [ + "benchmark_model_10", + "benchmark_model_15", + "benchmark_model_20", + "benchmark_model_30", + "benchmark_model_40", + "benchmark_model_50", + "benchmark_model_60", + "benchmark_model_70", + "benchmark_model_80", + ] + ) + + @run_after("init") + def setup_env_vars(self): + self.num_cpus_per_task = self.omp_threads + self.env_vars["SLURM_CPU_FREQ_REQ"] = self.cpu_freq + super().setup_env_vars() + + +@simple_test +class SingleNodeMPIBenchmark(GprMaxRegressionTest): + tags = {"benchmark", "mpi", "openmp", "single node"} + mpi_tasks = parameter([1, 2, 4, 8, 16, 32, 64, 128, 256]) + # domain = parameter([0.1, 0.15, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]) + cpu_freq = parameter([2000000, 2250000]) + model = parameter(["benchmark_model_40"]) + sourcesdir = "src" + time_limit = "1h" + + @run_after("setup") + def setup_env_vars(self): + cpus_per_node = self.current_partition.processor.num_cpus + self.skip_if( + cpus_per_node < self.mpi_tasks, + f"Insufficient CPUs per node ({cpus_per_node}) to run test with at least {self.mpi_tasks} processors", + ) + + self.num_cpus_per_task = cpus_per_node // self.mpi_tasks + self.num_tasks = cpus_per_node // self.num_cpus_per_task + self.num_tasks_per_node = self.num_tasks + self.extra_executable_opts = [ + "--mpi", + *map(str, calculate_mpi_decomposition(self.num_tasks)), + ] + + self.env_vars["SLURM_CPU_FREQ_REQ"] = self.cpu_freq + super().setup_env_vars() + + +@simple_test +class MPIStrongScalingBenchmark(GprMaxRegressionTest): + tags = {"benchmark", "mpi", "openmp"} + + num_nodes = parameter([1, 2, 4, 8, 16]) + # domain = parameter([0.1, 0.15, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]) + cpu_freq = parameter([2000000, 2250000]) + time_limit = "8h" + sourcesdir = "src" + model = parameter(["benchmark_model_40"]) + + # serial_dependency = SingleNodeBenchmark + # mpi_layout = parameter([[1, 1, 1]]) # parameter([[2, 2, 2], [4, 4, 4], [6, 6, 6]]) + + def build_reference_filepath(self, suffix: str = "") -> str: + filename = ( + f"MPIWeakScalingBenchmark_{suffix}" if len(suffix) > 0 else "MPIWeakScalingBenchmark" + ) + reference_file = Path("regression_checks", filename).with_suffix(".h5") + return os.path.abspath(reference_file) + + @run_after("setup") + def setup_env_vars(self): + cpus_per_node = self.current_partition.processor.num_cpus + + self.num_cpus_per_task = 16 + self.num_tasks_per_node = cpus_per_node // self.num_cpus_per_task + self.num_tasks = self.num_tasks_per_node * self.num_nodes + self.extra_executable_opts = [ + "--mpi", + *map(str, calculate_mpi_decomposition(self.num_tasks)), + ] + + self.env_vars["SLURM_CPU_FREQ_REQ"] = self.cpu_freq + super().setup_env_vars() + + +@simple_test +class MPIWeakScalingBenchmark(GprMaxRegressionTest): + tags = {"benchmark", "mpi", "openmp"} + + num_nodes = parameter([1, 2, 4, 8, 16]) + # domain = parameter([0.1, 0.15, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]) + cpu_freq = parameter([2000000, 2250000]) + time_limit = "8h" + sourcesdir = "src" + model = parameter(["benchmark_model_40"]) + + def build_reference_filepath(self, suffix: str = "") -> str: + filename = ( + f"MPIStrongScalingBenchmark_{suffix}_{self.num_nodes}" + if len(suffix) > 0 + else f"MPIStrongScalingBenchmark_{self.num_nodes}" + ) + reference_file = Path("regression_checks", filename).with_suffix(".h5") + return os.path.abspath(reference_file) + + @run_after("setup") + def setup_env_vars(self): + cpus_per_node = self.current_partition.processor.num_cpus + + self.num_cpus_per_task = 16 + self.num_tasks_per_node = cpus_per_node // self.num_cpus_per_task + self.num_tasks = self.num_tasks_per_node * self.num_nodes + size = 0.4 + scale_factor = calculate_mpi_decomposition(self.num_nodes) + self.prerun_cmds.append( + f'sed -i "s/#domain: 0.4 0.4 0.4/#domain: {size * scale_factor[0]} {size * scale_factor[1]} {size * scale_factor[2]}/g" {self.model}.in' + ) + self.extra_executable_opts = [ + "--mpi", + *map(str, calculate_mpi_decomposition(self.num_tasks)), + ] + + self.env_vars["SLURM_CPU_FREQ_REQ"] = self.cpu_freq + super().setup_env_vars() diff --git a/reframe_tests/benchmark_tests/src/benchmark_model_10.in b/reframe_tests/benchmark_tests/src/benchmark_model_10.in new file mode 100644 index 00000000..d00104a1 --- /dev/null +++ b/reframe_tests/benchmark_tests/src/benchmark_model_10.in @@ -0,0 +1,7 @@ +#title: Benchmark model +#domain: 0.1 0.1 0.1 +#dx_dy_dz: 0.001 0.001 0.001 +#time_window: 3e-9 + +#waveform: gaussiandotnorm 1 900e6 myWave +#hertzian_dipole: x 0.05 0.05 0.05 myWave diff --git a/reframe_tests/benchmark_tests/src/benchmark_model_15.in b/reframe_tests/benchmark_tests/src/benchmark_model_15.in new file mode 100644 index 00000000..a3448555 --- /dev/null +++ b/reframe_tests/benchmark_tests/src/benchmark_model_15.in @@ -0,0 +1,7 @@ +#title: Benchmark model +#domain: 0.15 0.15 0.15 +#dx_dy_dz: 0.001 0.001 0.001 +#time_window: 3e-9 + +#waveform: gaussiandotnorm 1 900e6 myWave +#hertzian_dipole: x 0.075 0.075 0.075 myWave diff --git a/reframe_tests/benchmark_tests/src/benchmark_model_20.in b/reframe_tests/benchmark_tests/src/benchmark_model_20.in new file mode 100644 index 00000000..fc24d9f3 --- /dev/null +++ b/reframe_tests/benchmark_tests/src/benchmark_model_20.in @@ -0,0 +1,7 @@ +#title: Benchmark model +#domain: 0.2 0.2 0.2 +#dx_dy_dz: 0.001 0.001 0.001 +#time_window: 3e-9 + +#waveform: gaussiandotnorm 1 900e6 myWave +#hertzian_dipole: x 0.1 0.1 0.1 myWave diff --git a/reframe_tests/benchmark_tests/src/benchmark_model_30.in b/reframe_tests/benchmark_tests/src/benchmark_model_30.in new file mode 100644 index 00000000..76097313 --- /dev/null +++ b/reframe_tests/benchmark_tests/src/benchmark_model_30.in @@ -0,0 +1,7 @@ +#title: Benchmark model +#domain: 0.3 0.3 0.3 +#dx_dy_dz: 0.001 0.001 0.001 +#time_window: 3e-9 + +#waveform: gaussiandotnorm 1 900e6 myWave +#hertzian_dipole: x 0.15 0.15 0.15 myWave diff --git a/reframe_tests/benchmark_tests/src/benchmark_model_40.in b/reframe_tests/benchmark_tests/src/benchmark_model_40.in new file mode 100644 index 00000000..0986767b --- /dev/null +++ b/reframe_tests/benchmark_tests/src/benchmark_model_40.in @@ -0,0 +1,7 @@ +#title: Benchmark model +#domain: 0.4 0.4 0.4 +#dx_dy_dz: 0.001 0.001 0.001 +#time_window: 3e-9 + +#waveform: gaussiandotnorm 1 900e6 myWave +#hertzian_dipole: x 0.2 0.2 0.2 myWave diff --git a/reframe_tests/benchmark_tests/src/benchmark_model_50.in b/reframe_tests/benchmark_tests/src/benchmark_model_50.in new file mode 100644 index 00000000..54daa7f6 --- /dev/null +++ b/reframe_tests/benchmark_tests/src/benchmark_model_50.in @@ -0,0 +1,7 @@ +#title: Benchmark model +#domain: 0.5 0.5 0.5 +#dx_dy_dz: 0.001 0.001 0.001 +#time_window: 3e-9 + +#waveform: gaussiandotnorm 1 900e6 myWave +#hertzian_dipole: x 0.25 0.25 0.25 myWave diff --git a/reframe_tests/benchmark_tests/src/benchmark_model_60.in b/reframe_tests/benchmark_tests/src/benchmark_model_60.in new file mode 100644 index 00000000..24238a46 --- /dev/null +++ b/reframe_tests/benchmark_tests/src/benchmark_model_60.in @@ -0,0 +1,7 @@ +#title: Benchmark model +#domain: 0.6 0.6 0.6 +#dx_dy_dz: 0.001 0.001 0.001 +#time_window: 3e-9 + +#waveform: gaussiandotnorm 1 900e6 myWave +#hertzian_dipole: x 0.3 0.3 0.3 myWave diff --git a/reframe_tests/benchmark_tests/src/benchmark_model_70.in b/reframe_tests/benchmark_tests/src/benchmark_model_70.in new file mode 100644 index 00000000..941e8652 --- /dev/null +++ b/reframe_tests/benchmark_tests/src/benchmark_model_70.in @@ -0,0 +1,7 @@ +#title: Benchmark model +#domain: 0.7 0.7 0.7 +#dx_dy_dz: 0.001 0.001 0.001 +#time_window: 3e-9 + +#waveform: gaussiandotnorm 1 900e6 myWave +#hertzian_dipole: x 0.35 0.35 0.35 myWave diff --git a/reframe_tests/benchmark_tests/src/benchmark_model_80.in b/reframe_tests/benchmark_tests/src/benchmark_model_80.in new file mode 100644 index 00000000..17aeb002 --- /dev/null +++ b/reframe_tests/benchmark_tests/src/benchmark_model_80.in @@ -0,0 +1,7 @@ +#title: Benchmark model +#domain: 0.8 0.8 0.8 +#dx_dy_dz: 0.001 0.001 0.001 +#time_window: 3e-9 + +#waveform: gaussiandotnorm 1 900e6 myWave +#hertzian_dipole: x 0.4 0.4 0.4 myWave diff --git a/reframe_tests/configuration/archer2_settings.py b/reframe_tests/configuration/archer2_settings.py new file mode 100644 index 00000000..cf0d9fe1 --- /dev/null +++ b/reframe_tests/configuration/archer2_settings.py @@ -0,0 +1,136 @@ +site_configuration = { + "general": [ + { + # Necessary if using the --restore-session flag + "keep_stage_files": True + } + ], + "systems": [ + { + "name": "archer2", + "descr": "ARCHER2", + "hostnames": ["uan", "ln", "dvn"], + "modules_system": "lmod", + "partitions": [ + { + "name": "login", + "descr": "Login nodes", + "scheduler": "local", + "launcher": "local", + "environs": ["PrgEnv-gnu", "PrgEnv-cray", "PrgEnv-aocc"], + }, + { + "name": "compute", + "descr": "Compute nodes", + "scheduler": "slurm", + "launcher": "srun", + "access": [ + "--hint=nomultithread", + "--distribution=block:block", + "--partition=standard", + "--qos=standard", + ], + "environs": ["PrgEnv-gnu", "PrgEnv-cray", "PrgEnv-aocc"], + "max_jobs": 16, + "processor": { + "num_cpus": 128, + "num_cpus_per_socket": 64, + "num_sockets": 2, + }, + }, + ], + } + ], + "environments": [ + { + "name": "PrgEnv-gnu", + "modules": ["PrgEnv-gnu"], + "cc": "cc", + "cxx": "CC", + "ftn": "ftn", + "target_systems": ["archer2"], + }, + { + "name": "PrgEnv-cray", + "modules": ["PrgEnv-cray"], + "cc": "cc", + "cxx": "CC", + "ftn": "ftn", + "target_systems": ["archer2"], + }, + { + "name": "PrgEnv-aocc", + "modules": ["PrgEnv-aocc"], + "cc": "cc", + "cxx": "CC", + "ftn": "ftn", + "target_systems": ["archer2"], + }, + ], + "logging": [ + { + "level": "debug", + "handlers": [ + {"type": "stream", "name": "stdout", "level": "info", "format": "%(message)s"}, + { + "type": "file", + "name": "reframe.out", + "level": "info", + "format": "[%(asctime)s] %(check_info)s: %(message)s", + "append": True, + }, + { + "type": "file", + "name": "reframe.log", + "level": "debug", + "format": "[%(asctime)s] %(levelname)s %(levelno)s: %(check_info)s: %(message)s", # noqa: E501 + "append": False, + }, + ], + "handlers_perflog": [ + { + "type": "file", + "name": "reframe_perf.out", + "level": "info", + "format": "[%(asctime)s] %(check_info)s job_id=%(check_jobid)s %(check_perfvalues)s", + "format_perfvars": "| %(check_perf_var)s: %(check_perf_value)s %(check_perf_unit)s (r: %(check_perf_ref)s l: %(check_perf_lower_thres)s u: %(check_perf_upper_thres)s) ", + "append": True, + }, + { + "type": "filelog", + "prefix": "%(check_system)s/%(check_partition)s", + "level": "info", + "format": ( + "%(check_result)s,%(check_job_completion_time)s," + "%(check_info)s,%(check_jobid)s," + "%(check_num_tasks)s,%(check_num_cpus_per_task)s,%(check_num_tasks_per_node)s," + "%(check_perfvalues)s" + ), + "format_perfvars": ( + "%(check_perf_value)s,%(check_perf_unit)s," + "%(check_perf_ref)s,%(check_perf_lower_thres)s," + "%(check_perf_upper_thres)s," + ), + "append": True, + }, + { + "type": "filelog", + "prefix": "%(check_system)s/%(check_partition)s/latest", + "level": "info", + "format": ( + "%(check_result)s,%(check_job_completion_time)s," + "%(check_info)s,%(check_jobid)s," + "%(check_num_tasks)s,%(check_num_cpus_per_task)s,%(check_num_tasks_per_node)s," + "%(check_perfvalues)s" + ), + "format_perfvars": ( + "%(check_perf_value)s,%(check_perf_unit)s," + "%(check_perf_ref)s,%(check_perf_lower_thres)s," + "%(check_perf_upper_thres)s," + ), + "append": False, + }, + ], + } + ], +} diff --git a/reframe_tests/job_scripts/archer2_benchmarks.slurm b/reframe_tests/job_scripts/archer2_benchmarks.slurm new file mode 100644 index 00000000..a0879fea --- /dev/null +++ b/reframe_tests/job_scripts/archer2_benchmarks.slurm @@ -0,0 +1,19 @@ +#!/bin/bash +#SBATCH --job-name=gprMax-benchmarks +#SBATCH --time=24:0:0 +#SBATCH --ntasks=1 +#SBATCH --partition=serial +#SBATCH --qos=serial +#SBATCH --output=output/archer2/rfm_bench_%J.out + +# Set the number of threads to 1 +# This prevents any threaded system libraries from automatically +# using threading. +export OMP_NUM_THREADS=1 + +source ../.venv/bin/activate + +# Any commandline arguments provided will be passed to reframe +reframe -C configuration/archer2_settings.py -c benchmark_tests/ -c tests/base_tests.py -r --performance-report "$@" + +sacct --format=JobID,State,Submit,Start,End,Elapsed,NodeList,ReqMem --units=M -j $SLURM_JOBID diff --git a/reframe_tests/job_scripts/archer2_map_single_node.slurm b/reframe_tests/job_scripts/archer2_map_single_node.slurm new file mode 100644 index 00000000..19c70946 --- /dev/null +++ b/reframe_tests/job_scripts/archer2_map_single_node.slurm @@ -0,0 +1,24 @@ +#!/bin/bash +#SBATCH --job-name=gprMax-map +#SBATCH --time=00:20:0 +#SBATCH --partition=standard +#SBATCH --qos=short +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=1 +#SBATCH --output=output/archer2/map_%J.out + +# USAGE: sbatch -c [ompthreads] job_scripts/archer2_map_single_node.slurm [domain] + +export OMP_NUM_THREADS=$SLURM_CPUS_PER_TASK +export SRUN_CPUS_PER_TASK=$SLURM_CPUS_PER_TASK + +module load arm/forge + +source ../.venv/bin/activate + +mkdir -p profile/archer2/ + +map -o="profile/archer2/gprMax_${1}d_${SLURM_CPUS_PER_TASK}t_$(date +%F_%H-%M)" --mpi=slurm --mpiargs="--hint=nomultithread --distribution=block:block" --profile python -m gprMax --log-level 25 src/benchmark_model_$1.in + +sacct --format=JobID,State,Submit,Start,End,Elapsed,NodeList --units=M -j $SLURM_JOBID diff --git a/reframe_tests/job_scripts/archer2_tests.slurm b/reframe_tests/job_scripts/archer2_tests.slurm new file mode 100644 index 00000000..6a50fa19 --- /dev/null +++ b/reframe_tests/job_scripts/archer2_tests.slurm @@ -0,0 +1,20 @@ +#!/bin/bash +#SBATCH --job-name=gprMax-tests +#SBATCH --time=24:0:0 +#SBATCH --ntasks=1 +#SBATCH --partition=serial +#SBATCH --qos=serial +#SBATCH --output=output/archer2/rfm_tests_%J.out + +# Set the number of threads to 1 +# This prevents any threaded system libraries from automatically +# using threading. +export OMP_NUM_THREADS=1 + +module load cray-python + +source ../.venv/bin/activate + +reframe -C configuration/archer2_settings.py -c tests/ -r "$@" + +sacct --format=JobID,State,Submit,Start,End,Elapsed,NodeList --units=M -j $SLURM_JOBID diff --git a/reframe_tests/profile/archer2/gprMax_0.5d_128t_2024-01-18_17-34.map b/reframe_tests/profile/archer2/gprMax_0.5d_128t_2024-01-18_17-34.map new file mode 100644 index 00000000..d477142a Binary files /dev/null and b/reframe_tests/profile/archer2/gprMax_0.5d_128t_2024-01-18_17-34.map differ diff --git a/reframe_tests/profile/archer2/gprMax_0.5d_16t_2024-01-18_17-34.map b/reframe_tests/profile/archer2/gprMax_0.5d_16t_2024-01-18_17-34.map new file mode 100644 index 00000000..3290de0b Binary files /dev/null and b/reframe_tests/profile/archer2/gprMax_0.5d_16t_2024-01-18_17-34.map differ diff --git a/reframe_tests/profile/archer2/gprMax_0.5d_32t_2024-01-18_17-34.map b/reframe_tests/profile/archer2/gprMax_0.5d_32t_2024-01-18_17-34.map new file mode 100644 index 00000000..42a938fa Binary files /dev/null and b/reframe_tests/profile/archer2/gprMax_0.5d_32t_2024-01-18_17-34.map differ diff --git a/reframe_tests/profile/archer2/gprMax_0.5d_4t_2024-01-22_14-02.map b/reframe_tests/profile/archer2/gprMax_0.5d_4t_2024-01-22_14-02.map new file mode 100644 index 00000000..5fb0f37f Binary files /dev/null and b/reframe_tests/profile/archer2/gprMax_0.5d_4t_2024-01-22_14-02.map differ diff --git a/reframe_tests/profile/archer2/gprMax_0.5d_64t_2024-01-18_17-34.map b/reframe_tests/profile/archer2/gprMax_0.5d_64t_2024-01-18_17-34.map new file mode 100644 index 00000000..b1a2364d Binary files /dev/null and b/reframe_tests/profile/archer2/gprMax_0.5d_64t_2024-01-18_17-34.map differ diff --git a/reframe_tests/profile/archer2/gprMax_0.5d_64t_2024-01-19_11-52.map b/reframe_tests/profile/archer2/gprMax_0.5d_64t_2024-01-19_11-52.map new file mode 100644 index 00000000..d1b8c822 Binary files /dev/null and b/reframe_tests/profile/archer2/gprMax_0.5d_64t_2024-01-19_11-52.map differ diff --git a/reframe_tests/profile/archer2/gprMax_0.5d_8t_2024-01-18_17-34.map b/reframe_tests/profile/archer2/gprMax_0.5d_8t_2024-01-18_17-34.map new file mode 100644 index 00000000..a842e54c Binary files /dev/null and b/reframe_tests/profile/archer2/gprMax_0.5d_8t_2024-01-18_17-34.map differ diff --git a/reframe_tests/regression_checks/MPIStrongScalingBenchmark_1.h5 b/reframe_tests/regression_checks/MPIStrongScalingBenchmark_1.h5 new file mode 100644 index 00000000..2107a440 Binary files /dev/null and b/reframe_tests/regression_checks/MPIStrongScalingBenchmark_1.h5 differ diff --git a/reframe_tests/regression_checks/MPIStrongScalingBenchmark_2.h5 b/reframe_tests/regression_checks/MPIStrongScalingBenchmark_2.h5 new file mode 100644 index 00000000..acfdc15c Binary files /dev/null and b/reframe_tests/regression_checks/MPIStrongScalingBenchmark_2.h5 differ diff --git a/reframe_tests/regression_checks/MPIWeakScalingBenchmark.h5 b/reframe_tests/regression_checks/MPIWeakScalingBenchmark.h5 new file mode 100644 index 00000000..2107a440 Binary files /dev/null and b/reframe_tests/regression_checks/MPIWeakScalingBenchmark.h5 differ diff --git a/reframe_tests/regression_checks/SingleNodeBenchmark_91b61150.h5 b/reframe_tests/regression_checks/SingleNodeBenchmark_91b61150.h5 new file mode 100644 index 00000000..7284d636 Binary files /dev/null and b/reframe_tests/regression_checks/SingleNodeBenchmark_91b61150.h5 differ diff --git a/reframe_tests/regression_checks/SingleNodeBenchmark_c016fbe9.h5 b/reframe_tests/regression_checks/SingleNodeBenchmark_c016fbe9.h5 new file mode 100644 index 00000000..0a408363 Binary files /dev/null and b/reframe_tests/regression_checks/SingleNodeBenchmark_c016fbe9.h5 differ diff --git a/reframe_tests/regression_checks/SingleNodeMPIBenchmark_06879fd2.h5 b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_06879fd2.h5 new file mode 100644 index 00000000..2107a440 Binary files /dev/null and b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_06879fd2.h5 differ diff --git a/reframe_tests/regression_checks/SingleNodeMPIBenchmark_1732e646.h5 b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_1732e646.h5 new file mode 100644 index 00000000..2107a440 Binary files /dev/null and b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_1732e646.h5 differ diff --git a/reframe_tests/regression_checks/SingleNodeMPIBenchmark_4c51e431.h5 b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_4c51e431.h5 new file mode 100644 index 00000000..2107a440 Binary files /dev/null and b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_4c51e431.h5 differ diff --git a/reframe_tests/regression_checks/SingleNodeMPIBenchmark_4dcc097d.h5 b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_4dcc097d.h5 new file mode 100644 index 00000000..2107a440 Binary files /dev/null and b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_4dcc097d.h5 differ diff --git a/reframe_tests/regression_checks/SingleNodeMPIBenchmark_506b9ee4.h5 b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_506b9ee4.h5 new file mode 100644 index 00000000..2107a440 Binary files /dev/null and b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_506b9ee4.h5 differ diff --git a/reframe_tests/regression_checks/SingleNodeMPIBenchmark_813d8fe2.h5 b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_813d8fe2.h5 new file mode 100644 index 00000000..2107a440 Binary files /dev/null and b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_813d8fe2.h5 differ diff --git a/reframe_tests/regression_checks/SingleNodeMPIBenchmark_84280a4a.h5 b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_84280a4a.h5 new file mode 100644 index 00000000..2107a440 Binary files /dev/null and b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_84280a4a.h5 differ diff --git a/reframe_tests/regression_checks/SingleNodeMPIBenchmark_87b5c3c8.h5 b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_87b5c3c8.h5 new file mode 100644 index 00000000..2107a440 Binary files /dev/null and b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_87b5c3c8.h5 differ diff --git a/reframe_tests/regression_checks/SingleNodeMPIBenchmark_a1304dd9.h5 b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_a1304dd9.h5 new file mode 100644 index 00000000..2107a440 Binary files /dev/null and b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_a1304dd9.h5 differ diff --git a/reframe_tests/regression_checks/SingleNodeMPIBenchmark_bc9340a0.h5 b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_bc9340a0.h5 new file mode 100644 index 00000000..2107a440 Binary files /dev/null and b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_bc9340a0.h5 differ diff --git a/reframe_tests/regression_checks/SingleNodeMPIBenchmark_c53fa676.h5 b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_c53fa676.h5 new file mode 100644 index 00000000..2107a440 Binary files /dev/null and b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_c53fa676.h5 differ diff --git a/reframe_tests/regression_checks/SingleNodeMPIBenchmark_cbc84981.h5 b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_cbc84981.h5 new file mode 100644 index 00000000..2107a440 Binary files /dev/null and b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_cbc84981.h5 differ diff --git a/reframe_tests/regression_checks/SingleNodeMPIBenchmark_dae3076c.h5 b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_dae3076c.h5 new file mode 100644 index 00000000..2107a440 Binary files /dev/null and b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_dae3076c.h5 differ diff --git a/reframe_tests/regression_checks/SingleNodeMPIBenchmark_deb8b755.h5 b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_deb8b755.h5 new file mode 100644 index 00000000..2107a440 Binary files /dev/null and b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_deb8b755.h5 differ diff --git a/reframe_tests/regression_checks/SingleNodeMPIBenchmark_eff6d396.h5 b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_eff6d396.h5 new file mode 100644 index 00000000..2107a440 Binary files /dev/null and b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_eff6d396.h5 differ diff --git a/reframe_tests/regression_checks/SingleNodeMPIBenchmark_fad85e0d.h5 b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_fad85e0d.h5 new file mode 100644 index 00000000..2107a440 Binary files /dev/null and b/reframe_tests/regression_checks/SingleNodeMPIBenchmark_fad85e0d.h5 differ diff --git a/reframe_tests/regression_checks/Test2DModelXY_e26f33ea/2D_EzHxHy.h5 b/reframe_tests/regression_checks/Test2DModelXY_e26f33ea/2D_EzHxHy.h5 new file mode 100644 index 00000000..c6504ca5 Binary files /dev/null and b/reframe_tests/regression_checks/Test2DModelXY_e26f33ea/2D_EzHxHy.h5 differ diff --git a/reframe_tests/regression_checks/Test2DModelXZ_e21872c4/2D_EyHxHz.h5 b/reframe_tests/regression_checks/Test2DModelXZ_e21872c4/2D_EyHxHz.h5 new file mode 100644 index 00000000..140ea3a6 Binary files /dev/null and b/reframe_tests/regression_checks/Test2DModelXZ_e21872c4/2D_EyHxHz.h5 differ diff --git a/reframe_tests/regression_checks/Test2DModelYZ_3e050c27/2D_ExHyHz.h5 b/reframe_tests/regression_checks/Test2DModelYZ_3e050c27/2D_ExHyHz.h5 new file mode 100644 index 00000000..18d6b26c Binary files /dev/null and b/reframe_tests/regression_checks/Test2DModelYZ_3e050c27/2D_ExHyHz.h5 differ diff --git a/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_x_05.h5 b/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_x_05.h5 new file mode 100644 index 00000000..d9634609 Binary files /dev/null and b/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_x_05.h5 differ diff --git a/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_x_35.h5 b/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_x_35.h5 new file mode 100644 index 00000000..e3dfa88e Binary files /dev/null and b/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_x_35.h5 differ diff --git a/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_x_65.h5 b/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_x_65.h5 new file mode 100644 index 00000000..98f1deb2 Binary files /dev/null and b/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_x_65.h5 differ diff --git a/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_x_95.h5 b/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_x_95.h5 new file mode 100644 index 00000000..89affa0b Binary files /dev/null and b/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_x_95.h5 differ diff --git a/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_y_15.h5 b/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_y_15.h5 new file mode 100644 index 00000000..c2f91316 Binary files /dev/null and b/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_y_15.h5 differ diff --git a/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_y_40.h5 b/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_y_40.h5 new file mode 100644 index 00000000..4670b09f Binary files /dev/null and b/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_y_40.h5 differ diff --git a/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_y_45.h5 b/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_y_45.h5 new file mode 100644 index 00000000..e76402a1 Binary files /dev/null and b/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_y_45.h5 differ diff --git a/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_y_50.h5 b/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_y_50.h5 new file mode 100644 index 00000000..874ff5cd Binary files /dev/null and b/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_y_50.h5 differ diff --git a/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_y_75.h5 b/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_y_75.h5 new file mode 100644 index 00000000..6c3586ff Binary files /dev/null and b/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_y_75.h5 differ diff --git a/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_z_25.h5 b/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_z_25.h5 new file mode 100644 index 00000000..a18c537e Binary files /dev/null and b/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_z_25.h5 differ diff --git a/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_z_55.h5 b/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_z_55.h5 new file mode 100644 index 00000000..2225ac64 Binary files /dev/null and b/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_z_55.h5 differ diff --git a/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_z_85.h5 b/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_z_85.h5 new file mode 100644 index 00000000..f6aa1ef5 Binary files /dev/null and b/reframe_tests/regression_checks/Test2DSliceSnapshot_061052b1/snapshot_z_85.h5 differ diff --git a/reframe_tests/regression_checks/Test2DSnapshot_c8b63c79/snapshot_0.h5 b/reframe_tests/regression_checks/Test2DSnapshot_c8b63c79/snapshot_0.h5 new file mode 100644 index 00000000..b970cecd Binary files /dev/null and b/reframe_tests/regression_checks/Test2DSnapshot_c8b63c79/snapshot_0.h5 differ diff --git a/reframe_tests/regression_checks/Test2DSnapshot_c8b63c79/snapshot_1.h5 b/reframe_tests/regression_checks/Test2DSnapshot_c8b63c79/snapshot_1.h5 new file mode 100644 index 00000000..378b7a8f Binary files /dev/null and b/reframe_tests/regression_checks/Test2DSnapshot_c8b63c79/snapshot_1.h5 differ diff --git a/reframe_tests/regression_checks/Test2DSnapshot_c8b63c79/snapshot_2.h5 b/reframe_tests/regression_checks/Test2DSnapshot_c8b63c79/snapshot_2.h5 new file mode 100644 index 00000000..ce88b83c Binary files /dev/null and b/reframe_tests/regression_checks/Test2DSnapshot_c8b63c79/snapshot_2.h5 differ diff --git a/reframe_tests/regression_checks/Test2DSnapshot_c8b63c79/snapshot_3.h5 b/reframe_tests/regression_checks/Test2DSnapshot_c8b63c79/snapshot_3.h5 new file mode 100644 index 00000000..17b832b4 Binary files /dev/null and b/reframe_tests/regression_checks/Test2DSnapshot_c8b63c79/snapshot_3.h5 differ diff --git a/reframe_tests/regression_checks/TestAscan_e9940356/cylinder_Ascan_2D.h5 b/reframe_tests/regression_checks/TestAscan_e9940356/cylinder_Ascan_2D.h5 new file mode 100644 index 00000000..cf31909a Binary files /dev/null and b/reframe_tests/regression_checks/TestAscan_e9940356/cylinder_Ascan_2D.h5 differ diff --git a/reframe_tests/regression_checks/TestBoxGeometryDefaultPml_0d642269/box_full_model.h5 b/reframe_tests/regression_checks/TestBoxGeometryDefaultPml_0d642269/box_full_model.h5 new file mode 100644 index 00000000..119a88ce Binary files /dev/null and b/reframe_tests/regression_checks/TestBoxGeometryDefaultPml_0d642269/box_full_model.h5 differ diff --git a/reframe_tests/regression_checks/TestBoxGeometryDefaultPml_3b4b184a/box_outside_pml.h5 b/reframe_tests/regression_checks/TestBoxGeometryDefaultPml_3b4b184a/box_outside_pml.h5 new file mode 100644 index 00000000..a0f33f75 Binary files /dev/null and b/reframe_tests/regression_checks/TestBoxGeometryDefaultPml_3b4b184a/box_outside_pml.h5 differ diff --git a/reframe_tests/regression_checks/TestBoxGeometryDefaultPml_a8674323/box_single_rank_outside_pml.h5 b/reframe_tests/regression_checks/TestBoxGeometryDefaultPml_a8674323/box_single_rank_outside_pml.h5 new file mode 100644 index 00000000..079ae387 Binary files /dev/null and b/reframe_tests/regression_checks/TestBoxGeometryDefaultPml_a8674323/box_single_rank_outside_pml.h5 differ diff --git a/reframe_tests/regression_checks/TestBoxGeometryDefaultPml_ce6262f3/box_half_model.h5 b/reframe_tests/regression_checks/TestBoxGeometryDefaultPml_ce6262f3/box_half_model.h5 new file mode 100644 index 00000000..6caaf7e8 Binary files /dev/null and b/reframe_tests/regression_checks/TestBoxGeometryDefaultPml_ce6262f3/box_half_model.h5 differ diff --git a/reframe_tests/regression_checks/TestBoxGeometryDefaultPml_f30e6d10/box_single_rank.h5 b/reframe_tests/regression_checks/TestBoxGeometryDefaultPml_f30e6d10/box_single_rank.h5 new file mode 100644 index 00000000..d2fd31d9 Binary files /dev/null and b/reframe_tests/regression_checks/TestBoxGeometryDefaultPml_f30e6d10/box_single_rank.h5 differ diff --git a/reframe_tests/regression_checks/TestBoxGeometryNoPml_0e734b72/box_full_model.h5 b/reframe_tests/regression_checks/TestBoxGeometryNoPml_0e734b72/box_full_model.h5 new file mode 100644 index 00000000..6b5280f3 Binary files /dev/null and b/reframe_tests/regression_checks/TestBoxGeometryNoPml_0e734b72/box_full_model.h5 differ diff --git a/reframe_tests/regression_checks/TestBoxGeometryNoPml_0ebe9cc0/box_half_model.h5 b/reframe_tests/regression_checks/TestBoxGeometryNoPml_0ebe9cc0/box_half_model.h5 new file mode 100644 index 00000000..3a588ada Binary files /dev/null and b/reframe_tests/regression_checks/TestBoxGeometryNoPml_0ebe9cc0/box_half_model.h5 differ diff --git a/reframe_tests/regression_checks/TestBoxGeometryNoPml_7804157f/box_single_rank.h5 b/reframe_tests/regression_checks/TestBoxGeometryNoPml_7804157f/box_single_rank.h5 new file mode 100644 index 00000000..343ebae5 Binary files /dev/null and b/reframe_tests/regression_checks/TestBoxGeometryNoPml_7804157f/box_single_rank.h5 differ diff --git a/reframe_tests/regression_checks/TestBscan_efecb66e/cylinder_Bscan_2D.h5 b/reframe_tests/regression_checks/TestBscan_efecb66e/cylinder_Bscan_2D.h5 new file mode 100644 index 00000000..c588813f Binary files /dev/null and b/reframe_tests/regression_checks/TestBscan_efecb66e/cylinder_Bscan_2D.h5 differ diff --git a/reframe_tests/regression_checks/TestDispersiveMaterials_99243fd2/hertzian_dipole_dispersive.h5 b/reframe_tests/regression_checks/TestDispersiveMaterials_99243fd2/hertzian_dipole_dispersive.h5 new file mode 100644 index 00000000..556ec5fc Binary files /dev/null and b/reframe_tests/regression_checks/TestDispersiveMaterials_99243fd2/hertzian_dipole_dispersive.h5 differ diff --git a/reframe_tests/regression_checks/TestEdgeGeometry_c4eb9ee8/antenna_wire_dipole_fs.h5 b/reframe_tests/regression_checks/TestEdgeGeometry_c4eb9ee8/antenna_wire_dipole_fs.h5 new file mode 100644 index 00000000..a1eb5455 Binary files /dev/null and b/reframe_tests/regression_checks/TestEdgeGeometry_c4eb9ee8/antenna_wire_dipole_fs.h5 differ 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..fe81bea6 --- /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:a23ccc9ed81685385ff300c0c00e70329945db364dd7eb9607a511870469c720 +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..30a7b6a3 --- /dev/null +++ b/reframe_tests/regression_checks/TestGeometryObject_a6a096cb/full_volume_materials.txt @@ -0,0 +1,8 @@ +#material: 1 0 1 0 free_space +#material: 4.9 0 1 0 myWater +#material: 3 0 2 0 boxMaterial +#material: 2 0 1.5 0 boxMaterial+boxMaterial+free_space+free_space +#material: 1.5 0 1.25 0 boxMaterial+free_space+free_space+free_space +#material: 1.975 0 1 0 free_space+free_space+free_space+myWater +#material: 2.95 0 1 0 free_space+free_space+myWater+myWater +#material: 3.925 0 1 0 free_space+myWater+myWater+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..91fe354b --- /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:4b8f8c4979f4c82eb4be8a9a63cfaf19408b9f493d1bf3082a8226e97542ae4a +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..30a7b6a3 --- /dev/null +++ b/reframe_tests/regression_checks/TestGeometryObject_a6a096cb/partial_volume_materials.txt @@ -0,0 +1,8 @@ +#material: 1 0 1 0 free_space +#material: 4.9 0 1 0 myWater +#material: 3 0 2 0 boxMaterial +#material: 2 0 1.5 0 boxMaterial+boxMaterial+free_space+free_space +#material: 1.5 0 1.25 0 boxMaterial+free_space+free_space+free_space +#material: 1.975 0 1 0 free_space+free_space+free_space+myWater +#material: 2.95 0 1 0 free_space+free_space+myWater+myWater +#material: 3.925 0 1 0 free_space+myWater+myWater+myWater diff --git a/reframe_tests/regression_checks/TestGeometryView_5176823e/full_volume.vtkhdf b/reframe_tests/regression_checks/TestGeometryView_5176823e/full_volume.vtkhdf new file mode 100644 index 00000000..2e04533f --- /dev/null +++ b/reframe_tests/regression_checks/TestGeometryView_5176823e/full_volume.vtkhdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a8134fca3e6228211e4a4bc3e504e64f888c55f22e025b482ec501e0051d253 +size 75740296 diff --git a/reframe_tests/regression_checks/TestGeometryView_5176823e/partial_volume.vtkhdf b/reframe_tests/regression_checks/TestGeometryView_5176823e/partial_volume.vtkhdf new file mode 100644 index 00000000..a7986519 --- /dev/null +++ b/reframe_tests/regression_checks/TestGeometryView_5176823e/partial_volume.vtkhdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ccd95ff4ef73c224c99a8c321ad6f8014c673a1b33bbb741bff415dfbffd0aa +size 4931176 diff --git a/reframe_tests/regression_checks/TestGeometryView_77980202/full_volume.vtkhdf b/reframe_tests/regression_checks/TestGeometryView_77980202/full_volume.vtkhdf new file mode 100644 index 00000000..8472bcc7 --- /dev/null +++ b/reframe_tests/regression_checks/TestGeometryView_77980202/full_volume.vtkhdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82b8ac914a9fe20b49f2512215782f01305a2e3211aaac51913f88296a0e3ac5 +size 4012444 diff --git a/reframe_tests/regression_checks/TestGeometryView_77980202/partial_volume.vtkhdf b/reframe_tests/regression_checks/TestGeometryView_77980202/partial_volume.vtkhdf new file mode 100644 index 00000000..43d5f5b0 Binary files /dev/null and b/reframe_tests/regression_checks/TestGeometryView_77980202/partial_volume.vtkhdf differ diff --git a/reframe_tests/regression_checks/TestHertzianDipoleSource_ca97d05d/hertzian_dipole_fs.h5 b/reframe_tests/regression_checks/TestHertzianDipoleSource_ca97d05d/hertzian_dipole_fs.h5 new file mode 100644 index 00000000..c3a50601 Binary files /dev/null and b/reframe_tests/regression_checks/TestHertzianDipoleSource_ca97d05d/hertzian_dipole_fs.h5 differ diff --git a/reframe_tests/regression_checks/TestMagneticDipoleSource_58b9b32a/magnetic_dipole_fs.h5 b/reframe_tests/regression_checks/TestMagneticDipoleSource_58b9b32a/magnetic_dipole_fs.h5 new file mode 100644 index 00000000..4ff882c5 Binary files /dev/null and b/reframe_tests/regression_checks/TestMagneticDipoleSource_58b9b32a/magnetic_dipole_fs.h5 differ diff --git a/reframe_tests/regression_checks/TestSingleCellPml_c05c95d0/single_cell_pml_2d.h5 b/reframe_tests/regression_checks/TestSingleCellPml_c05c95d0/single_cell_pml_2d.h5 new file mode 100644 index 00000000..9a9a1550 Binary files /dev/null and b/reframe_tests/regression_checks/TestSingleCellPml_c05c95d0/single_cell_pml_2d.h5 differ diff --git a/reframe_tests/regression_checks/TestSnapshot_8af76f0a/snapshot_0.h5 b/reframe_tests/regression_checks/TestSnapshot_8af76f0a/snapshot_0.h5 new file mode 100644 index 00000000..c158aa2b Binary files /dev/null and b/reframe_tests/regression_checks/TestSnapshot_8af76f0a/snapshot_0.h5 differ diff --git a/reframe_tests/regression_checks/TestSnapshot_8af76f0a/snapshot_1.h5 b/reframe_tests/regression_checks/TestSnapshot_8af76f0a/snapshot_1.h5 new file mode 100644 index 00000000..313810e9 Binary files /dev/null and b/reframe_tests/regression_checks/TestSnapshot_8af76f0a/snapshot_1.h5 differ diff --git a/reframe_tests/regression_checks/TestSnapshot_8af76f0a/snapshot_2.h5 b/reframe_tests/regression_checks/TestSnapshot_8af76f0a/snapshot_2.h5 new file mode 100644 index 00000000..63f379d4 Binary files /dev/null and b/reframe_tests/regression_checks/TestSnapshot_8af76f0a/snapshot_2.h5 differ diff --git a/reframe_tests/regression_checks/TestSnapshot_8af76f0a/snapshot_3.h5 b/reframe_tests/regression_checks/TestSnapshot_8af76f0a/snapshot_3.h5 new file mode 100644 index 00000000..85144450 Binary files /dev/null and b/reframe_tests/regression_checks/TestSnapshot_8af76f0a/snapshot_3.h5 differ diff --git a/reframe_tests/regression_checks/TestSubgrids_36a9f022/cylinder_fs.h5 b/reframe_tests/regression_checks/TestSubgrids_36a9f022/cylinder_fs.h5 new file mode 100644 index 00000000..17c22d40 Binary files /dev/null and b/reframe_tests/regression_checks/TestSubgrids_36a9f022/cylinder_fs.h5 differ diff --git a/reframe_tests/regression_checks/TestTransmissionLineSource_ced17afa/transmission_line_fs.h5 b/reframe_tests/regression_checks/TestTransmissionLineSource_ced17afa/transmission_line_fs.h5 new file mode 100644 index 00000000..f23f8136 Binary files /dev/null and b/reframe_tests/regression_checks/TestTransmissionLineSource_ced17afa/transmission_line_fs.h5 differ diff --git a/reframe_tests/tests/base_tests.py b/reframe_tests/tests/base_tests.py new file mode 100644 index 00000000..d4551cee --- /dev/null +++ b/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\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+ (?Phour|minute|second)", + self.stdout, + "timeframe", + ) + if timeframe == "hour": + simulation_time = sn.extractall( + r"=== Simulation completed in (?P\S+) hours?, (?P\S+) minutes? and (?P\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\S+) minutes? and (?P\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\S+) seconds? =*", + self.stdout, + "seconds", + float, + ) + return hours * 3600 + minutes * 60 + seconds diff --git a/reframe_tests/tests/mixins.py b/reframe_tests/tests/mixins.py new file mode 100644 index 00000000..f7181ce9 --- /dev/null +++ b/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 diff --git a/reframe_tests/tests/regression_checks.py b/reframe_tests/tests/regression_checks.py new file mode 100644 index 00000000..85c91b89 --- /dev/null +++ b/reframe_tests/tests/regression_checks.py @@ -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" diff --git a/reframe_tests/tests/src/2d_tests/2D_ExHyHz.in b/reframe_tests/tests/src/2d_tests/2D_ExHyHz.in new file mode 100644 index 00000000..3c37b9c3 --- /dev/null +++ b/reframe_tests/tests/src/2d_tests/2D_ExHyHz.in @@ -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 diff --git a/reframe_tests/tests/src/2d_tests/2D_ExHyHz_hs.in b/reframe_tests/tests/src/2d_tests/2D_ExHyHz_hs.in new file mode 100644 index 00000000..682f51d0 --- /dev/null +++ b/reframe_tests/tests/src/2d_tests/2D_ExHyHz_hs.in @@ -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 diff --git a/reframe_tests/tests/src/2d_tests/2D_EyHxHz.in b/reframe_tests/tests/src/2d_tests/2D_EyHxHz.in new file mode 100644 index 00000000..fb1d2678 --- /dev/null +++ b/reframe_tests/tests/src/2d_tests/2D_EyHxHz.in @@ -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 diff --git a/reframe_tests/tests/src/2d_tests/2D_EzHxHy.in b/reframe_tests/tests/src/2d_tests/2D_EzHxHy.in new file mode 100644 index 00000000..ea0a6544 --- /dev/null +++ b/reframe_tests/tests/src/2d_tests/2D_EzHxHy.in @@ -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 diff --git a/reframe_tests/tests/src/bscan_tests/cylinder_Bscan_2D.in b/reframe_tests/tests/src/bscan_tests/cylinder_Bscan_2D.in new file mode 100644 index 00000000..e41f9857 --- /dev/null +++ b/reframe_tests/tests/src/bscan_tests/cylinder_Bscan_2D.in @@ -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 diff --git a/reframe_tests/tests/src/example_models/cylinder_Ascan_2D.in b/reframe_tests/tests/src/example_models/cylinder_Ascan_2D.in new file mode 100644 index 00000000..da294270 --- /dev/null +++ b/reframe_tests/tests/src/example_models/cylinder_Ascan_2D.in @@ -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 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/src/geometry_tests/box_geometry/box_full_model.in b/reframe_tests/tests/src/geometry_tests/box_geometry/box_full_model.in new file mode 100644 index 00000000..e1a36b06 --- /dev/null +++ b/reframe_tests/tests/src/geometry_tests/box_geometry/box_full_model.in @@ -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 diff --git a/reframe_tests/tests/src/geometry_tests/box_geometry/box_half_model.in b/reframe_tests/tests/src/geometry_tests/box_geometry/box_half_model.in new file mode 100644 index 00000000..7fb4278e --- /dev/null +++ b/reframe_tests/tests/src/geometry_tests/box_geometry/box_half_model.in @@ -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 diff --git a/reframe_tests/tests/src/geometry_tests/box_geometry/box_outside_pml.in b/reframe_tests/tests/src/geometry_tests/box_geometry/box_outside_pml.in new file mode 100644 index 00000000..3cdd4c36 --- /dev/null +++ b/reframe_tests/tests/src/geometry_tests/box_geometry/box_outside_pml.in @@ -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 diff --git a/reframe_tests/tests/src/geometry_tests/box_geometry/box_single_rank.in b/reframe_tests/tests/src/geometry_tests/box_geometry/box_single_rank.in new file mode 100644 index 00000000..618a462f --- /dev/null +++ b/reframe_tests/tests/src/geometry_tests/box_geometry/box_single_rank.in @@ -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 diff --git a/reframe_tests/tests/src/geometry_tests/box_geometry/box_single_rank_outside_pml.in b/reframe_tests/tests/src/geometry_tests/box_geometry/box_single_rank_outside_pml.in new file mode 100644 index 00000000..701698b1 --- /dev/null +++ b/reframe_tests/tests/src/geometry_tests/box_geometry/box_single_rank_outside_pml.in @@ -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 diff --git a/reframe_tests/tests/src/geometry_tests/edge_geometry/antenna_wire_dipole_fs.in b/reframe_tests/tests/src/geometry_tests/edge_geometry/antenna_wire_dipole_fs.in new file mode 100644 index 00000000..cc0f1fed --- /dev/null +++ b/reframe_tests/tests/src/geometry_tests/edge_geometry/antenna_wire_dipole_fs.in @@ -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 diff --git a/reframe_tests/tests/src/geometry_view_tests/geometry_view_fine.in b/reframe_tests/tests/src/geometry_view_tests/geometry_view_fine.in new file mode 100644 index 00000000..d6709644 --- /dev/null +++ b/reframe_tests/tests/src/geometry_view_tests/geometry_view_fine.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_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 diff --git a/reframe_tests/tests/src/geometry_view_tests/geometry_view_voxel.in b/reframe_tests/tests/src/geometry_view_tests/geometry_view_voxel.in new file mode 100644 index 00000000..64596d82 --- /dev/null +++ b/reframe_tests/tests/src/geometry_view_tests/geometry_view_voxel.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_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 diff --git a/reframe_tests/tests/src/material_tests/hertzian_dipole_dispersive.in b/reframe_tests/tests/src/material_tests/hertzian_dipole_dispersive.in new file mode 100644 index 00000000..a49754d7 --- /dev/null +++ b/reframe_tests/tests/src/material_tests/hertzian_dipole_dispersive.in @@ -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 diff --git a/reframe_tests/tests/src/material_tests/magnetic_dipole_hs.in b/reframe_tests/tests/src/material_tests/magnetic_dipole_hs.in new file mode 100644 index 00000000..57e24168 --- /dev/null +++ b/reframe_tests/tests/src/material_tests/magnetic_dipole_hs.in @@ -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 diff --git a/reframe_tests/tests/src/pml_tests/single_cell_pml_2d.in b/reframe_tests/tests/src/pml_tests/single_cell_pml_2d.in new file mode 100644 index 00000000..6590f2b7 --- /dev/null +++ b/reframe_tests/tests/src/pml_tests/single_cell_pml_2d.in @@ -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 diff --git a/reframe_tests/tests/src/snapshot_tests/2d_slices.in b/reframe_tests/tests/src/snapshot_tests/2d_slices.in new file mode 100644 index 00000000..9515c49a --- /dev/null +++ b/reframe_tests/tests/src/snapshot_tests/2d_slices.in @@ -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 diff --git a/reframe_tests/tests/src/snapshot_tests/whole_domain.in b/reframe_tests/tests/src/snapshot_tests/whole_domain.in new file mode 100644 index 00000000..27c42fc3 --- /dev/null +++ b/reframe_tests/tests/src/snapshot_tests/whole_domain.in @@ -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 diff --git a/reframe_tests/tests/src/snapshot_tests/whole_domain_2d.in b/reframe_tests/tests/src/snapshot_tests/whole_domain_2d.in new file mode 100644 index 00000000..4f6a959e --- /dev/null +++ b/reframe_tests/tests/src/snapshot_tests/whole_domain_2d.in @@ -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 diff --git a/reframe_tests/tests/src/source_tests/hertzian_dipole_fs.in b/reframe_tests/tests/src/source_tests/hertzian_dipole_fs.in new file mode 100644 index 00000000..5c5ecca7 --- /dev/null +++ b/reframe_tests/tests/src/source_tests/hertzian_dipole_fs.in @@ -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 diff --git a/reframe_tests/tests/src/source_tests/magnetic_dipole_fs.in b/reframe_tests/tests/src/source_tests/magnetic_dipole_fs.in new file mode 100644 index 00000000..6088be9a --- /dev/null +++ b/reframe_tests/tests/src/source_tests/magnetic_dipole_fs.in @@ -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 diff --git a/reframe_tests/tests/src/source_tests/transmission_line_fs.in b/reframe_tests/tests/src/source_tests/transmission_line_fs.in new file mode 100644 index 00000000..02ec8fd5 --- /dev/null +++ b/reframe_tests/tests/src/source_tests/transmission_line_fs.in @@ -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 diff --git a/reframe_tests/tests/src/subgrid_tests/cylinder_fs.py b/reframe_tests/tests/src/subgrid_tests/cylinder_fs.py new file mode 100644 index 00000000..f6a2c0a7 --- /dev/null +++ b/reframe_tests/tests/src/subgrid_tests/cylinder_fs.py @@ -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, +) diff --git a/reframe_tests/tests/src/subgrid_tests/gssi_400_over_fractal_subsurface.py b/reframe_tests/tests/src/subgrid_tests/gssi_400_over_fractal_subsurface.py new file mode 100644 index 00000000..de3442b8 --- /dev/null +++ b/reframe_tests/tests/src/subgrid_tests/gssi_400_over_fractal_subsurface.py @@ -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 +) diff --git a/reframe_tests/tests/standard_tests.py b/reframe_tests/tests/standard_tests.py new file mode 100644 index 00000000..cab5ec7e --- /dev/null +++ b/reframe_tests/tests/standard_tests.py @@ -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 diff --git a/reframe_tests/tests/test_2d_models.py b/reframe_tests/tests/test_2d_models.py new file mode 100644 index 00000000..ad301e39 --- /dev/null +++ b/reframe_tests/tests/test_2d_models.py @@ -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 diff --git a/reframe_tests/tests/test_example_models.py b/reframe_tests/tests/test_example_models.py new file mode 100644 index 00000000..e669064b --- /dev/null +++ b/reframe_tests/tests/test_example_models.py @@ -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 diff --git a/reframe_tests/tests/test_geometry.py b/reframe_tests/tests/test_geometry.py new file mode 100644 index 00000000..aaae282d --- /dev/null +++ b/reframe_tests/tests/test_geometry.py @@ -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 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 diff --git a/reframe_tests/tests/test_geometry_views.py b/reframe_tests/tests/test_geometry_views.py new file mode 100644 index 00000000..8f4091d5 --- /dev/null +++ b/reframe_tests/tests/test_geometry_views.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 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 diff --git a/reframe_tests/tests/test_materials.py b/reframe_tests/tests/test_materials.py new file mode 100644 index 00000000..bb31fdea --- /dev/null +++ b/reframe_tests/tests/test_materials.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 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 diff --git a/reframe_tests/tests/test_pmls.py b/reframe_tests/tests/test_pmls.py new file mode 100644 index 00000000..13f503f9 --- /dev/null +++ b/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 diff --git a/reframe_tests/tests/test_snapshots.py b/reframe_tests/tests/test_snapshots.py new file mode 100644 index 00000000..cd7aee50 --- /dev/null +++ b/reframe_tests/tests/test_snapshots.py @@ -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 diff --git a/reframe_tests/tests/test_sources.py b/reframe_tests/tests/test_sources.py new file mode 100644 index 00000000..9bca3b42 --- /dev/null +++ b/reframe_tests/tests/test_sources.py @@ -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 diff --git a/reframe_tests/tests/test_subgrids.py b/reframe_tests/tests/test_subgrids.py new file mode 100644 index 00000000..6135b2a5 --- /dev/null +++ b/reframe_tests/tests/test_subgrids.py @@ -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") diff --git a/reframe_tests/tests/test_taskfarm_functionality.py b/reframe_tests/tests/test_taskfarm_functionality.py new file mode 100644 index 00000000..53aa20ef --- /dev/null +++ b/reframe_tests/tests/test_taskfarm_functionality.py @@ -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 diff --git a/reframe_tests/utilities/data.py b/reframe_tests/utilities/data.py new file mode 100644 index 00000000..abab8fe1 --- /dev/null +++ b/reframe_tests/utilities/data.py @@ -0,0 +1,60 @@ +import logging +from typing import Tuple + +import h5py +import numpy as np +import numpy.typing as npt + +from gprMax.utilities.logging import logging_config + +logger = logging.getLogger(__name__) +logging_config(name=__name__) + + +FIELD_COMPONENTS_BASE_PATH = "/rxs/rx1/" + + +def get_data_from_h5_file(h5_filepath: str) -> Tuple[npt.NDArray, npt.NDArray]: + with h5py.File(h5_filepath, "r") as h5_file: + # Get available field output component names and datatype + field_components = list(h5_file[FIELD_COMPONENTS_BASE_PATH].keys()) + dtype = h5_file[FIELD_COMPONENTS_BASE_PATH + field_components[0]].dtype + shape = h5_file[FIELD_COMPONENTS_BASE_PATH + str(field_components[0])].shape + + # Arrays for storing field data + if len(shape) == 1: + data = np.zeros((h5_file.attrs["Iterations"], len(field_components)), dtype=dtype) + else: # Merged B-scan data + data = np.zeros( + (h5_file.attrs["Iterations"], len(field_components), shape[1]), dtype=dtype + ) + for index, field_component in enumerate(field_components): + data[:, index] = h5_file[FIELD_COMPONENTS_BASE_PATH + str(field_component)] + if np.any(np.isnan(data[:, index])): + logger.exception("Data contains NaNs") + raise ValueError + + max_time = (h5_file.attrs["Iterations"] - 1) * h5_file.attrs["dt"] / 1e-9 + time = np.linspace(0, max_time, num=h5_file.attrs["Iterations"]) + + return time, data + + +def calculate_diffs(test_data: npt.NDArray, ref_data: npt.NDArray) -> npt.NDArray: + diffs = np.zeros(test_data.shape, dtype=np.float64) + for i in range(test_data.shape[1]): + maxi = np.amax(np.abs(ref_data[:, i])) + diffs[:, i] = np.divide( + np.abs(ref_data[:, i] - test_data[:, i]), + maxi, + out=np.zeros_like(ref_data[:, i]), + where=maxi != 0, + ) # Replace any division by zero with zero + + # Calculate power (ignore warning from taking a log of any zero values) + with np.errstate(divide="ignore"): + diffs[:, i] = 20 * np.log10(diffs[:, i]) + # Replace any NaNs or Infs from zero division + # diffs[:, i][np.invert(np.isfinite(diffs[:, i]))] = 0 + + return diffs diff --git a/reframe_tests/utilities/deferrable.py b/reframe_tests/utilities/deferrable.py new file mode 100644 index 00000000..3e00d3e9 --- /dev/null +++ b/reframe_tests/utilities/deferrable.py @@ -0,0 +1,9 @@ +import os + +import reframe.utility.sanity as sn + + +@sn.deferrable +def path_join(*path): + """Deferable version of os.path.join""" + return os.path.join(*path) diff --git a/reframe_tests/utilities/plotting.py b/reframe_tests/utilities/plotting.py new file mode 100644 index 00000000..84973cd4 --- /dev/null +++ b/reframe_tests/utilities/plotting.py @@ -0,0 +1,114 @@ +import argparse + +import numpy as np +from matplotlib import pyplot as plt + +from reframe_tests.utilities.data import calculate_diffs, get_data_from_h5_file + + +def _plot_data(subplots, time, data, label=None, colour="r", line_style="-"): + for i in range(data.shape[1]): + subplots[i].plot(time, data[:, i], colour, lw=2, ls=line_style, label=label) + + +def plot_dataset_comparison(test_time, test_data, ref_time, ref_data, model_name): + fig, ((ex, hx), (ey, hy), (ez, hz)) = plt.subplots( + nrows=3, + ncols=2, + sharex=False, + sharey="col", + subplot_kw=dict(xlabel="Time [ns]"), + figsize=(20, 10), + facecolor="w", + edgecolor="w", + ) + + subplots = [ex, ey, ez, hx, hy, hz] + _plot_data(subplots, test_time, test_data, model_name) + _plot_data(subplots, ref_time, ref_data, f"{model_name} (Ref)", "g", "--") + + ylabels = [ + "$E_x$, field strength [V/m]", + "$H_x$, field strength [A/m]", + "$E_y$, field strength [V/m]", + "$H_y$, field strength [A/m]", + "$E_z$, field strength [V/m]", + "$H_z$, field strength [A/m]", + ] + + x_max = max(np.max(test_time), np.max(ref_time)) + for i, ax in enumerate(fig.axes): + ax.set_ylabel(ylabels[i]) + ax.set_xlim(0, x_max) + ax.grid() + ax.legend() + + return fig + + +def plot_diffs(time, diffs, plot_min=-160): + """Plots ... + + Args: + time: + diffs: + plot_min: minimum value of difference to plot (dB). Default: -160 + + Returns: + plt: matplotlib plot object. + """ + fig, ((ex, hx), (ey, hy), (ez, hz)) = plt.subplots( + nrows=3, + ncols=2, + sharex=False, + sharey="col", + subplot_kw=dict(xlabel="Time [ns]"), + figsize=(20, 10), + facecolor="w", + edgecolor="w", + ) + _plot_data([ex, ey, ez, hx, hy, hz], time, diffs) + + ylabels = [ + "$E_x$, difference [dB]", + "$H_x$, difference [dB]", + "$E_y$, difference [dB]", + "$H_y$, difference [dB]", + "$E_z$, difference [dB]", + "$H_z$, difference [dB]", + ] + + x_max = np.max(time) + y_max = np.max(diffs) + + if not np.isfinite(y_max): + y_max = 0 + + for i, ax in enumerate(fig.axes): + ax.set_ylabel(ylabels[i]) + ax.set_xlim(0, x_max) + ax.set_ylim(plot_min, y_max) + ax.grid() + + return fig + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument("input_file", help="Path to input file") + parser.add_argument("reference_file", help="Path to reference file") + parser.add_argument("--model-name", "--name", "-n", help="Name of the model", default="model") + + args = parser.parse_args() + + input_time, input_data = get_data_from_h5_file(args.input_file) + ref_time, ref_data = get_data_from_h5_file(args.reference_file) + + figure = plot_dataset_comparison(input_time, input_data, ref_time, ref_data, args.model_name) + figure.tight_layout(h_pad=3, w_pad=4, pad=2) + figure.savefig(f"{args.model_name}.pdf", dpi=300) + + diffs = calculate_diffs(input_data, ref_data) + figure = plot_diffs(input_time, diffs) + figure.tight_layout(h_pad=3, w_pad=4, pad=2) + figure.savefig(f"{args.model_name}_diffs.pdf", dpi=300) diff --git a/reframe_tests/utilities/process_perflog.py b/reframe_tests/utilities/process_perflog.py new file mode 100644 index 00000000..e347aaf6 --- /dev/null +++ b/reframe_tests/utilities/process_perflog.py @@ -0,0 +1,60 @@ +import argparse +import re +from datetime import datetime +from pathlib import Path + +import pandas as pd + + +def get_parameter(row, parameter): + value = re.search(f"\s%{parameter}=(?P\S+)\s", row["info"])["value"] + return value + + +def get_parameter_names(item): + return re.findall(f"\s%(?P\S+)=\S+", item) + + +columns_to_keep = [ + "num_tasks", + "num_cpus_per_task", + "num_tasks_per_node", + "run_time_value", + "simulation_time_value", +] + +if __name__ == "__main__": + # Parse command line arguments + parser = argparse.ArgumentParser( + usage="cd gprMax/reframe_tests; python -m utilities.process_perflog inputfile [-o OUTPUT]", + description="Extract perfvars from reframe perflog file.", + ) + parser.add_argument("inputfile", help="name of input file including path") + parser.add_argument("--output", "-o", help="name of output file including path", required=False) + + args = parser.parse_args() + + perflog = pd.read_csv(args.inputfile, index_col=False) + + # Extract recorded parameters and create a new column for them in the dataframe + parameters = perflog["info"].agg(get_parameter_names).explode().unique() + for parameter in parameters: + perflog[parameter] = perflog.apply(get_parameter, args=[parameter], axis=1) + + # Organise dataframe + columns_to_keep += parameters.tolist() + columns_to_keep.sort() + perflog = perflog[columns_to_keep].sort_values(columns_to_keep) + perflog["simulation_time_value"] = perflog["simulation_time_value"].apply(round, args=[2]) + perflog = perflog.rename( + columns={"simulation_time_value": "simulation_time", "run_time_value": "run_time"} + ) + + # Save output to file + if args.output: + outputfile = args.output + else: + stem = f"{Path(args.inputfile).stem}_{datetime.today().strftime('%Y-%m-%d_%H-%M-%S')}" + outputfile = Path("benchmark_results", stem).with_suffix(".csv") + perflog.to_csv(outputfile, index=False) + print(f"Saved benchmark: '{outputfile}'")