diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml index 160a674..9128dbd 100644 --- a/.github/workflows/nightly.yaml +++ b/.github/workflows/nightly.yaml @@ -27,12 +27,5 @@ jobs: shell: bash run: | module load opensn/clang/17 python3/3.12.3 - cd OpenSn_Logo_CAD && mpirun -np 8 ../opensn/build/python/opensn -i opensn.py && cd .. - cd Urban_Source && mpirun -np 96 ../opensn/build/python/opensn -i urban_source.py && cd .. - cd HEU_MET_FAST_003 && mpirun -np 12 ../opensn/build/python/opensn -i HEU_MET_FAST_003.py && cd .. - cd Six_1g_spherical_benchmarks && mpirun -np 12 ../opensn/build/python/opensn -i Problem_1.py && cd .. - cd Six_1g_spherical_benchmarks && mpirun -np 12 ../opensn/build/python/opensn -i Problem_2.py && cd .. - cd Six_1g_spherical_benchmarks && mpirun -np 12 ../opensn/build/python/opensn -i Problem_3.py && cd .. - cd Six_1g_spherical_benchmarks && mpirun -np 12 ../opensn/build/python/opensn -i Problem_4.py && cd .. - cd Six_1g_spherical_benchmarks && mpirun -np 12 ../opensn/build/python/opensn -i Problem_5.py && cd .. - cd Six_1g_spherical_benchmarks && mpirun -np 12 ../opensn/build/python/opensn -i Problem_6.py && cd .. + export OPENSN=$PWD/opensn/build/python/opensn + python3 tests.py diff --git a/HEU_MET_FAST_003/HEU_MET_FAST_003.py b/HEU_MET_FAST_003/HEU_MET_FAST_003.py index 1a2fc05..624f2f5 100644 --- a/HEU_MET_FAST_003/HEU_MET_FAST_003.py +++ b/HEU_MET_FAST_003/HEU_MET_FAST_003.py @@ -88,7 +88,7 @@ groupsets=[ { "groups_from_to": (0, num_groups - 1), - "angular_quadrature": GLCProductQuadrature3DXYZ(8, 16), + "angular_quadrature": GLCProductQuadrature3DXYZ(n_polar=8, n_azimuthal=16), "inner_linear_method": "petsc_gmres", "angle_aggregation_type": "single", "angle_aggregation_num_subsets": 1, diff --git a/OpenSn_Logo_CAD/opensn.py b/OpenSn_Logo_CAD/opensn.py index 39fa380..aa95c2b 100644 --- a/OpenSn_Logo_CAD/opensn.py +++ b/OpenSn_Logo_CAD/opensn.py @@ -39,9 +39,7 @@ src = VolumetricSource(block_ids=[1], group_strength=src_strength) # Quadrature - Npolar = 8 - Nazimuthal = 16 - pquad = GLCProductQuadrature3DXYZ(Npolar, Nazimuthal) + pquad = GLCProductQuadrature3DXYZ(n_polar=8, n_azimuthal=16) # Solver phys = DiscreteOrdinatesProblem( diff --git a/Six_1g_spherical_benchmarks/Problem_1.py b/Six_1g_spherical_benchmarks/Problem_1.py index 0069453..1f7bcf1 100644 --- a/Six_1g_spherical_benchmarks/Problem_1.py +++ b/Six_1g_spherical_benchmarks/Problem_1.py @@ -80,9 +80,7 @@ bsrc = [1.0] # Angular quadrature - nazimu = 4 - npolar = 2 - pquad = GLCProductQuadrature3DXYZ(npolar, nazimu) + pquad = GLCProductQuadrature3DXYZ(n_polar=2, n_azimuthal=4) # Solver phys = DiscreteOrdinatesProblem( diff --git a/Six_1g_spherical_benchmarks/Problem_2.py b/Six_1g_spherical_benchmarks/Problem_2.py index 0069453..1f7bcf1 100644 --- a/Six_1g_spherical_benchmarks/Problem_2.py +++ b/Six_1g_spherical_benchmarks/Problem_2.py @@ -80,9 +80,7 @@ bsrc = [1.0] # Angular quadrature - nazimu = 4 - npolar = 2 - pquad = GLCProductQuadrature3DXYZ(npolar, nazimu) + pquad = GLCProductQuadrature3DXYZ(n_polar=2, n_azimuthal=4) # Solver phys = DiscreteOrdinatesProblem( diff --git a/Six_1g_spherical_benchmarks/Problem_3.py b/Six_1g_spherical_benchmarks/Problem_3.py index ed100e2..15df624 100644 --- a/Six_1g_spherical_benchmarks/Problem_3.py +++ b/Six_1g_spherical_benchmarks/Problem_3.py @@ -42,9 +42,7 @@ mg_src = VolumetricSource(block_ids=[2], group_strength=[1.0]) # Angular quadrature - nazimu = 4 - npolar = 2 - pquad = GLCProductQuadrature3DXYZ(npolar, nazimu) + pquad = GLCProductQuadrature3DXYZ(n_polar=2, n_azimuthal=4) # Solver phys = DiscreteOrdinatesProblem( diff --git a/Six_1g_spherical_benchmarks/Problem_4.py b/Six_1g_spherical_benchmarks/Problem_4.py index 68d1a47..286f82a 100644 --- a/Six_1g_spherical_benchmarks/Problem_4.py +++ b/Six_1g_spherical_benchmarks/Problem_4.py @@ -43,9 +43,7 @@ bsrc = [1.0] # Angular quadrature - nazimu = 4 - npolar = 2 - pquad = GLCProductQuadrature3DXYZ(npolar, nazimu) + pquad = GLCProductQuadrature3DXYZ(n_polar=2, n_azimuthal=4) # Solver phys = DiscreteOrdinatesProblem( diff --git a/Six_1g_spherical_benchmarks/Problem_5.py b/Six_1g_spherical_benchmarks/Problem_5.py index 7a7562e..520b3b8 100644 --- a/Six_1g_spherical_benchmarks/Problem_5.py +++ b/Six_1g_spherical_benchmarks/Problem_5.py @@ -43,9 +43,7 @@ bsrc = [1.0] # Angular quadrature - nazimu = 4 - npolar = 2 - pquad = GLCProductQuadrature3DXYZ(npolar, nazimu) + pquad = GLCProductQuadrature3DXYZ(n_polar=2, n_azimuthal=4) # Solver phys = DiscreteOrdinatesProblem( diff --git a/Six_1g_spherical_benchmarks/Problem_6.py b/Six_1g_spherical_benchmarks/Problem_6.py index d62c24d..e9e7dcf 100644 --- a/Six_1g_spherical_benchmarks/Problem_6.py +++ b/Six_1g_spherical_benchmarks/Problem_6.py @@ -44,9 +44,7 @@ mg_src = VolumetricSource(block_ids=[1], group_strength=[1.0]) # Angular quadrature - nazimu = 4 - npolar = 2 - pquad = GLCProductQuadrature3DXYZ(npolar, nazimu) + pquad = GLCProductQuadrature3DXYZ(n_polar=2, n_azimuthal=4) # Solver phys = DiscreteOrdinatesProblem( diff --git a/Urban_Source/urban_source.py b/Urban_Source/urban_source.py index 0449200..382adee 100644 --- a/Urban_Source/urban_source.py +++ b/Urban_Source/urban_source.py @@ -41,9 +41,7 @@ src = VolumetricSource(block_ids=[2], group_strength=src_strength) # Quadrature - Npolar = 8 - Nazimuthal = 16 - pquad = GLCProductQuadrature3DXYZ(Npolar, Nazimuthal) + pquad = GLCProductQuadrature3DXYZ(n_polar=8, n_azimuthal=16) # Solver phys = DiscreteOrdinatesProblem( diff --git a/test.py b/test.py new file mode 100644 index 0000000..3a8c3a4 --- /dev/null +++ b/test.py @@ -0,0 +1,143 @@ +import os +import sys +import subprocess +import re +import pytest + +OPENSN = os.environ.get("OPENSN") + +# Test cases +TEST_CASES = [ + ( + os.path.join("HEU_MET_FAST_003", "HEU_MET_FAST_003.py"), # Directory name, input file name + {"k-eigenvalue:": 1.001655}, # key, value to test or None + 12, # Number of MPI ranks + 1.0e-6 # Tolerance or None + ), + ( + os.path.join("OpenSn_Logo_CAD", "opensn.py"), + None, + 8, + None, + ), + ( + os.path.join("Urban_Source", "urban_source.py"), + None, + 96, + None, + ), + ( + os.path.join("Six_1g_spherical_benchmarks", "Problem_1.py"), + {"end-radius:": 1.00, "avg-value:": 8.212354}, + 12, + 1.0e-6, + ), + ( + os.path.join("Six_1g_spherical_benchmarks", "Problem_2.py"), + {"end-radius:": 1.00, "avg-value:": 8.212354}, + 12, + 1.0e-6, + ), + ( + os.path.join("Six_1g_spherical_benchmarks", "Problem_3.py"), + {"end-radius:": 1.00, "avg-value:": 0.105997}, + 12, + 1.0e-6, + ), + ( + os.path.join("Six_1g_spherical_benchmarks", "Problem_4.py"), + {"end-radius:": 1.00, "avg-value:": 12.144526}, + 12, + 1.0e-6, + ), + ( + os.path.join("Six_1g_spherical_benchmarks", "Problem_5.py"), + {"end-radius:": 1.00, "avg-value:": 11.660929}, + 12, + 1.0e-6, + ), + ( + os.path.join("Six_1g_spherical_benchmarks", "Problem_6.py"), + {"end-radius:": 1.00, "avg-value:": 0.033569}, + 12, + 1.0e-6, + ), +] + + +@pytest.mark.parametrize("input_file, expected, mpi_ranks, rel_tol", TEST_CASES) +def test_application_output(input_file, expected, mpi_ranks, rel_tol): + # Ensure OPENSN is set and executable + assert OPENSN, "OPENSN environment variable must be set" + assert os.path.isfile(OPENSN) and os.access(OPENSN, os.X_OK), \ + f"OPENSN '{OPENSN}' not found or not executable" + + # Input files and working directory + abs_input = os.path.abspath(input_file) + assert os.path.isfile(abs_input), f"Input file not found: {abs_input}" + cwd = os.path.dirname(abs_input) + inp = os.path.basename(abs_input) + + # Validate mpi_ranks + assert isinstance(mpi_ranks, int) and mpi_ranks > 0, \ + f"mpi_ranks must be a positive integer, got {mpi_ranks}" + + # Run opensn + cmd = ["mpirun", "-n", str(mpi_ranks), OPENSN, "-i", inp] + result = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True) + assert result.returncode == 0, ( + f"Application exited with code {result.returncode}\n" + f"stdout:\n{result.stdout}\n" + f"stderr:\n{result.stderr}\n" + ) + + if expected is None or rel_tol is None: + return + + # Validate rel_tol + assert isinstance(rel_tol, float) and rel_tol > 0.0, \ + f"rel_tol must be a positive float, got {rel_tol}" + + # Combine outputs and process line-by-line attempting to match key/value pairs + lines = (result.stdout + result.stderr).splitlines() + matched = False + for line in lines: + tokens = [tok for tok in re.split(r"[ ,]+", line.strip()) if tok] + ok = True + for key, exp_val in expected.items(): + try: + idx = tokens.index(key) + except ValueError: + ok = False + break + if idx + 1 >= len(tokens): + ok = False + break + next_tok = tokens[idx + 1] + if isinstance(exp_val, (int, float)): + try: + val = float(next_tok) + except ValueError: + ok = False + break + if not (val == pytest.approx(exp_val, rel=rel_tol)): + ok = False + break + else: + if next_tok != exp_val: + ok = False + break + if ok: + matched = True + break + if not matched: + full_output = result.stdout + " " + result.stderr + pytest.fail(f"No line matches expected key/value pairs: {expected}\n {full_output}") + + +if __name__ == "__main__": + if not OPENSN or not os.path.isfile(OPENSN) or not os.access(OPENSN, os.X_OK): + print(f"Error: OPENSN '{OPENSN}' not found or not executable", file=sys.stderr) + sys.exit(1) + ret = pytest.main([os.path.abspath(__file__)]) + sys.exit(0 if ret == 0 else 1)