From c92b926275b278d2bc2b913a3166a72018356f37 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 20 May 2026 12:16:11 +0000 Subject: [PATCH 1/2] Initial plan From 16b3ca1c49c46b711725aae64cdea3a6b0dfbfac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 20 May 2026 12:22:24 +0000 Subject: [PATCH 2/2] Fix mean validation tolerance regression Agent-Logs-Url: https://github.com/OpenPIV/openpiv-python-gpu/sessions/a3cc42d6-86ec-47a5-8cce-cc1b4931aba8 Co-authored-by: alexlib <747110+alexlib@users.noreply.github.com> --- openpiv/gpu/validation.py | 2 +- openpiv/test/test_gpu_validation_logic.py | 107 ++++++++++++++++++++++ 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 openpiv/test/test_gpu_validation_logic.py diff --git a/openpiv/gpu/validation.py b/openpiv/gpu/validation.py index 6d8e646..cde3654 100644 --- a/openpiv/gpu/validation.py +++ b/openpiv/gpu/validation.py @@ -372,7 +372,7 @@ def _mean_validation(self): self._f[k], self._mean[k], f_mean_residual, - self.median_tol, + self.mean_tol, self.val_locations, ) diff --git a/openpiv/test/test_gpu_validation_logic.py b/openpiv/test/test_gpu_validation_logic.py new file mode 100644 index 0000000..b10d237 --- /dev/null +++ b/openpiv/test/test_gpu_validation_logic.py @@ -0,0 +1,107 @@ +import importlib.util +import sys +import types +from pathlib import Path + + +class _Descriptor: + def __init__(self, *args, **kwargs): + self.storage_name = None + + def __set_name__(self, owner, name): + self.storage_name = f"_{name}_value" + + def __get__(self, obj, owner=None): + if obj is None: + return self + return getattr(obj, self.storage_name, None) + + def __set__(self, obj, value): + setattr(obj, self.storage_name, value) + + +def load_gpu_validation_module(): + validation_path = Path(__file__).resolve().parents[1] / "gpu" / "validation.py" + + pycuda = types.ModuleType("pycuda") + pycuda_gpuarray = types.ModuleType("pycuda.gpuarray") + pycuda_gpuarray.GPUArray = type("GPUArray", (), {}) + pycuda_compiler = types.ModuleType("pycuda.compiler") + + class DummySourceModule: + def __init__(self, source): + self.source = source + + def get_function(self, name): + def _function(*args, **kwargs): + raise AssertionError(f"Unexpected kernel call: {name}") + + return _function + + pycuda_compiler.SourceModule = DummySourceModule + pycuda.gpuarray = pycuda_gpuarray + + openpiv_gpu = types.ModuleType("openpiv.gpu") + openpiv_gpu.DTYPE_i = int + openpiv_gpu.DTYPE_f = float + + openpiv_gpu_misc = types.ModuleType("openpiv.gpu.misc") + openpiv_gpu_misc._Subset = _Descriptor + openpiv_gpu_misc._Number = _Descriptor + openpiv_gpu_misc._check_arrays = lambda *args, **kwargs: None + openpiv_gpu_misc.gpu_mask = lambda values, mask: values + openpiv_gpu.misc = openpiv_gpu_misc + + original_modules = {} + stubbed_modules = { + "pycuda": pycuda, + "pycuda.gpuarray": pycuda_gpuarray, + "pycuda.compiler": pycuda_compiler, + "openpiv.gpu": openpiv_gpu, + "openpiv.gpu.misc": openpiv_gpu_misc, + } + for name, module in stubbed_modules.items(): + original_modules[name] = sys.modules.get(name) + sys.modules[name] = module + + module_name = "openpiv.test._gpu_validation_under_test" + spec = importlib.util.spec_from_file_location(module_name, validation_path) + module = importlib.util.module_from_spec(spec) + sys.modules[module_name] = module + try: + spec.loader.exec_module(module) + return module + finally: + sys.modules.pop(module_name, None) + for name, original_module in original_modules.items(): + if original_module is None: + sys.modules.pop(name, None) + else: + sys.modules[name] = original_module + + +def test_mean_validation_uses_mean_tol(): + validation = load_gpu_validation_module() + calls = [] + + validation._gpu_residual = lambda *args, **kwargs: "residual" + + def neighbour_validation(f, f_mean, residual, tol, val_locations=None): + calls.append(tol) + return val_locations + + validation._neighbour_validation = neighbour_validation + + validation_gpu = validation.Validation.__new__(validation.Validation) + validation_gpu._num_fields = 2 + validation_gpu.mean_tol = 1.5 + validation_gpu.median_tol = 9.5 + validation_gpu._f = ["u", "v"] + validation_gpu._mean_ = ["u_mean", "v_mean"] + validation_gpu._neighbours_ = ["u_neighbours", "v_neighbours"] + validation_gpu._neighbours_present = "neighbours_present" + validation_gpu.val_locations = None + + validation_gpu._mean_validation() + + assert calls == [1.5, 1.5]