From 4cebfe2a02827e153eba7d74377989adf28a0114 Mon Sep 17 00:00:00 2001 From: Luis Manuel Diaz Angulo Date: Fri, 24 Apr 2026 09:34:43 +0200 Subject: [PATCH 1/2] Add point/wire output regressions for new output module --- .github/workflows/ubuntu.yml | 8 ++++ .../test_output_module_regression.py | 48 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 test/pyWrapper/test_output_module_regression.py diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index efce0957..ed92069a 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -104,6 +104,14 @@ jobs: SEMBA_FDTD_ENABLE_MTLN: ${{ matrix.mtln }} SEMBA_FDTD_ENABLE_HDF: ${{ matrix.hdf }} run: python -m pytest test/ --durations=20 + + - name: Run python output regression tests + if: matrix.new-output-module=='ON' && matrix.mpi=='OFF' + env: + SEMBA_FDTD_ENABLE_MPI: ${{ matrix.mpi }} + SEMBA_FDTD_ENABLE_MTLN: ${{ matrix.mtln }} + SEMBA_FDTD_ENABLE_HDF: ${{ matrix.hdf }} + run: python -m pytest test/pyWrapper/test_output_module_regression.py --durations=20 diff --git a/test/pyWrapper/test_output_module_regression.py b/test/pyWrapper/test_output_module_regression.py new file mode 100644 index 00000000..2871baaf --- /dev/null +++ b/test/pyWrapper/test_output_module_regression.py @@ -0,0 +1,48 @@ +from utils import * + + +def test_point_probe_regression_planewave_in_box(tmp_path): + fn = CASES_FOLDER + 'planewave/pw-in-box.fdtd.json' + solver = FDTD(fn, path_to_exe=SEMBA_EXE, run_in_folder=tmp_path) + + solver.run() + + before = Probe(solver.getSolvedProbeFilenames("before")[0]) + inbox = Probe(solver.getSolvedProbeFilenames("inbox")[0]) + after = Probe(solver.getSolvedProbeFilenames("after")[0]) + + assert np.corrcoef( + inbox.data['field'].to_numpy(), + inbox.data['incident'].to_numpy(), + )[0, 1] > 0.999 + + zeros = np.zeros_like(before.data['field']) + assert np.allclose(before.data['field'].to_numpy(), zeros, atol=5e-4) + assert np.allclose(after.data['field'].to_numpy(), zeros, atol=5e-4) + + +@mtln_skip +def test_wire_probe_regression_towel_hanger(tmp_path): + fn = CASES_FOLDER + 'towelHanger/towelHanger.fdtd.json' + solver = FDTD(input_filename=fn, path_to_exe=SEMBA_EXE, run_in_folder=tmp_path) + solver.run() + + p_solved = [ + Probe(solver.getSolvedProbeFilenames("wire_start")[0]), + Probe(solver.getSolvedProbeFilenames("wire_mid")[0]), + Probe(solver.getSolvedProbeFilenames("wire_end")[0]), + ] + + p_expected = [ + Probe(OUTPUTS_FOLDER + 'towelHanger.fdtd_wire_start_Wz_27_25_30_s1.dat'), + Probe(OUTPUTS_FOLDER + 'towelHanger.fdtd_wire_mid_Wx_35_25_32_s5.dat'), + Probe(OUTPUTS_FOLDER + 'towelHanger.fdtd_wire_end_Wz_43_25_30_s4.dat'), + ] + + for i in range(3): + solved = np.interp( + p_expected[i]['time'].to_numpy(), + p_solved[i]['time'].to_numpy(), + p_solved[i]['current_0'].to_numpy(), + ) + assert np.corrcoef(solved, p_expected[i]['current_0'])[0, 1] > 0.999 From c98fd126902831f12454c05bcab1c75d0df0676f Mon Sep 17 00:00:00 2001 From: Luis Manuel Diaz Angulo Date: Fri, 24 Apr 2026 09:48:14 +0200 Subject: [PATCH 2/2] Compare new observation output against legacy baseline --- .github/workflows/ubuntu.yml | 19 ++++- .../test_output_module_equivalence.py | 77 +++++++++++++++++++ .../test_output_module_regression.py | 48 ------------ 3 files changed, 94 insertions(+), 50 deletions(-) create mode 100644 test/pyWrapper/test_output_module_equivalence.py delete mode 100644 test/pyWrapper/test_output_module_regression.py diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index ed92069a..068b2060 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -103,15 +103,30 @@ jobs: SEMBA_FDTD_ENABLE_MPI: ${{ matrix.mpi }} SEMBA_FDTD_ENABLE_MTLN: ${{ matrix.mtln }} SEMBA_FDTD_ENABLE_HDF: ${{ matrix.hdf }} + SEMBA_FDTD_ENABLE_OUTPUT_MODULE: ${{ matrix.new-output-module }} run: python -m pytest test/ --durations=20 - - name: Run python output regression tests + - name: Build legacy observation executable + if: matrix.new-output-module=='ON' && matrix.mpi=='OFF' + run: | + cmake -S . -B build-legacy \ + -DCMAKE_BUILD_TYPE=${{matrix.build-type}} \ + -DSEMBA_FDTD_ENABLE_MPI=${{matrix.mpi}} \ + -DSEMBA_FDTD_ENABLE_HDF=${{matrix.hdf}} \ + -DSEMBA_FDTD_ENABLE_MTLN=${{matrix.mtln}} \ + -DSEMBA_FDTD_ENABLE_DOUBLE_PRECISION=${{matrix.double-precision}} \ + -DSEMBA_FDTD_ENABLE_OUTPUT_MODULE=OFF + cmake --build build-legacy -j + + - name: Run python output equivalence tests if: matrix.new-output-module=='ON' && matrix.mpi=='OFF' env: SEMBA_FDTD_ENABLE_MPI: ${{ matrix.mpi }} SEMBA_FDTD_ENABLE_MTLN: ${{ matrix.mtln }} SEMBA_FDTD_ENABLE_HDF: ${{ matrix.hdf }} - run: python -m pytest test/pyWrapper/test_output_module_regression.py --durations=20 + SEMBA_FDTD_ENABLE_OUTPUT_MODULE: ${{ matrix.new-output-module }} + SEMBA_LEGACY_EXE: ${{ github.workspace }}/build-legacy/bin/semba-fdtd + run: python -m pytest test/pyWrapper/test_output_module_equivalence.py --durations=20 diff --git a/test/pyWrapper/test_output_module_equivalence.py b/test/pyWrapper/test_output_module_equivalence.py new file mode 100644 index 00000000..b988d1cc --- /dev/null +++ b/test/pyWrapper/test_output_module_equivalence.py @@ -0,0 +1,77 @@ +from utils import * + + +legacy_exe = os.getenv( + "SEMBA_LEGACY_EXE", + os.path.join(os.getcwd(), "build-legacy", "bin", "semba-fdtd"), +) + +missing_legacy_exe_skip = pytest.mark.skipif( + not os.path.isfile(legacy_exe), + reason="Legacy executable not found. Set SEMBA_LEGACY_EXE or build build-legacy/bin/semba-fdtd", +) + + +def _run_solver(case_file, exe_path, run_dir): + solver = FDTD(input_filename=case_file, path_to_exe=exe_path, run_in_folder=run_dir) + solver.run() + return solver + + +def _assert_probe_columns_equal(probe_legacy, probe_new, columns, atol=1e-8, rtol=1e-5): + legacy_time = probe_legacy["time"].to_numpy() + new_time = probe_new["time"].to_numpy() + + for col in columns: + new_vals = np.interp(new_time, legacy_time, probe_legacy[col].to_numpy()) + assert np.allclose(new_vals, probe_new[col].to_numpy(), atol=atol, rtol=rtol) + + +def _compare_case_probes(case_file, probe_names, columns_by_probe, tmp_path): + legacy_dir = tmp_path / "legacy" + new_dir = tmp_path / "new" + legacy_dir.mkdir() + new_dir.mkdir() + + solver_legacy = _run_solver(case_file, legacy_exe, legacy_dir) + solver_new = _run_solver(case_file, SEMBA_EXE, new_dir) + + for probe_name in probe_names: + legacy_probe = Probe(solver_legacy.getSolvedProbeFilenames(probe_name)[0]) + new_probe = Probe(solver_new.getSolvedProbeFilenames(probe_name)[0]) + _assert_probe_columns_equal( + legacy_probe, + new_probe, + columns_by_probe[probe_name], + ) + + +@missing_legacy_exe_skip +def test_point_probe_output_equivalence_planewave_in_box(tmp_path): + case_file = CASES_FOLDER + "planewave/pw-in-box.fdtd.json" + _compare_case_probes( + case_file=case_file, + probe_names=["before", "inbox", "after"], + columns_by_probe={ + "before": ["field"], + "inbox": ["field", "incident"], + "after": ["field"], + }, + tmp_path=tmp_path, + ) + + +@mtln_skip +@missing_legacy_exe_skip +def test_wire_probe_output_equivalence_towel_hanger(tmp_path): + case_file = CASES_FOLDER + "towelHanger/towelHanger.fdtd.json" + _compare_case_probes( + case_file=case_file, + probe_names=["wire_start", "wire_mid", "wire_end"], + columns_by_probe={ + "wire_start": ["current_0"], + "wire_mid": ["current_0"], + "wire_end": ["current_0"], + }, + tmp_path=tmp_path, + ) diff --git a/test/pyWrapper/test_output_module_regression.py b/test/pyWrapper/test_output_module_regression.py deleted file mode 100644 index 2871baaf..00000000 --- a/test/pyWrapper/test_output_module_regression.py +++ /dev/null @@ -1,48 +0,0 @@ -from utils import * - - -def test_point_probe_regression_planewave_in_box(tmp_path): - fn = CASES_FOLDER + 'planewave/pw-in-box.fdtd.json' - solver = FDTD(fn, path_to_exe=SEMBA_EXE, run_in_folder=tmp_path) - - solver.run() - - before = Probe(solver.getSolvedProbeFilenames("before")[0]) - inbox = Probe(solver.getSolvedProbeFilenames("inbox")[0]) - after = Probe(solver.getSolvedProbeFilenames("after")[0]) - - assert np.corrcoef( - inbox.data['field'].to_numpy(), - inbox.data['incident'].to_numpy(), - )[0, 1] > 0.999 - - zeros = np.zeros_like(before.data['field']) - assert np.allclose(before.data['field'].to_numpy(), zeros, atol=5e-4) - assert np.allclose(after.data['field'].to_numpy(), zeros, atol=5e-4) - - -@mtln_skip -def test_wire_probe_regression_towel_hanger(tmp_path): - fn = CASES_FOLDER + 'towelHanger/towelHanger.fdtd.json' - solver = FDTD(input_filename=fn, path_to_exe=SEMBA_EXE, run_in_folder=tmp_path) - solver.run() - - p_solved = [ - Probe(solver.getSolvedProbeFilenames("wire_start")[0]), - Probe(solver.getSolvedProbeFilenames("wire_mid")[0]), - Probe(solver.getSolvedProbeFilenames("wire_end")[0]), - ] - - p_expected = [ - Probe(OUTPUTS_FOLDER + 'towelHanger.fdtd_wire_start_Wz_27_25_30_s1.dat'), - Probe(OUTPUTS_FOLDER + 'towelHanger.fdtd_wire_mid_Wx_35_25_32_s5.dat'), - Probe(OUTPUTS_FOLDER + 'towelHanger.fdtd_wire_end_Wz_43_25_30_s4.dat'), - ] - - for i in range(3): - solved = np.interp( - p_expected[i]['time'].to_numpy(), - p_solved[i]['time'].to_numpy(), - p_solved[i]['current_0'].to_numpy(), - ) - assert np.corrcoef(solved, p_expected[i]['current_0'])[0, 1] > 0.999