From 1288a0c3ce09c1c587fb79ff1100047871ce20c1 Mon Sep 17 00:00:00 2001 From: Florian Pfaff <6773539+FlorianPfaff@users.noreply.github.com> Date: Sun, 24 May 2026 09:20:40 +0200 Subject: [PATCH 1/4] Add empty assignment-index backend regression test --- tests/test_backend_contract.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_backend_contract.py b/tests/test_backend_contract.py index c1e9db036..32dc8604d 100644 --- a/tests/test_backend_contract.py +++ b/tests/test_backend_contract.py @@ -27,6 +27,15 @@ def test_convert_to_wider_dtype_preserves_matching_boolean_dtype(self): self.assertEqual(to_numpy(first).dtype, np.dtype("bool")) self.assertEqual(to_numpy(second).dtype, np.dtype("bool")) + def test_assignment_with_empty_indices_is_a_noop(self): + original = array([1.0, 2.0, 3.0]) + + assigned = backend.assignment(original, 99.0, []) + added = backend.assignment_by_sum(original, 99.0, []) + + npt.assert_allclose(to_numpy(assigned), [1.0, 2.0, 3.0]) + npt.assert_allclose(to_numpy(added), [1.0, 2.0, 3.0]) + def test_choice_supports_numpy_like_size_replace_and_probabilities(self): values = array([0, 1, 2, 3]) weights = array([0.1, 0.2, 0.3, 0.4]) From dd7213285aa45fa68fdab20d46a32e9a3ecd7417 Mon Sep 17 00:00:00 2001 From: Florian Pfaff <6773539+FlorianPfaff@users.noreply.github.com> Date: Sun, 24 May 2026 09:21:39 +0200 Subject: [PATCH 2/4] Fix empty assignment-index backend no-op handling --- src/pyrecest/_backend/__init__.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/pyrecest/_backend/__init__.py b/src/pyrecest/_backend/__init__.py index a52e577fd..7f54bc1ce 100644 --- a/src/pyrecest/_backend/__init__.py +++ b/src/pyrecest/_backend/__init__.py @@ -215,7 +215,6 @@ def get_backend_name(): "jacobian_vec", "jacobian_and_hessian", "value_and_grad", - "value_and_jacobian", "value_jacobian_and_hessian", ], "linalg": [ @@ -349,6 +348,28 @@ def mean(a, axis=None, dtype=None, out=None, keepdims=False): return mean +def _is_empty_assignment_index(indices): + """Return whether ``indices`` selects no elements for assignment helpers.""" + if isinstance(indices, (list, tuple)): + return len(indices) == 0 + + ndim = getattr(indices, "ndim", None) + shape = getattr(indices, "shape", None) + return ndim is not None and ndim > 0 and shape is not None and shape[0] == 0 + + +def _assignment_with_empty_indices_noop(assignment_func, copy_func): + """Return an assignment wrapper that treats empty indices as a no-op.""" + + @wraps(assignment_func) + def assignment(x, values, indices, axis=0): + if _is_empty_assignment_index(indices): + return copy_func(x) + return assignment_func(x, values, indices, axis=axis) + + return assignment + + class BackendImporter(importlib.abc.MetaPathFinder, importlib.abc.Loader): """ Meta path finder and loader for dynamically creating backend modules. @@ -442,6 +463,14 @@ def _create_backend_module(self, backend_name: str): attribute, getattr(backend, "asarray"), ) + if ( + module_name == "" + and attribute_name in {"assignment", "assignment_by_sum"} + ): + attribute = _assignment_with_empty_indices_noop( + attribute, + getattr(backend, "copy"), + ) setattr(new_submodule, attribute_name, attribute) for attribute_name in OPTIONAL_BACKEND_ATTRIBUTES.get(module_name, []): From e8be580983819fec6d8b49344c2ce0645c829c5e Mon Sep 17 00:00:00 2001 From: Florian Pfaff <6773539+FlorianPfaff@users.noreply.github.com> Date: Sun, 24 May 2026 09:24:22 +0200 Subject: [PATCH 3/4] Restore autodiff backend attribute export --- src/pyrecest/_backend/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pyrecest/_backend/__init__.py b/src/pyrecest/_backend/__init__.py index 7f54bc1ce..7775c1ccb 100644 --- a/src/pyrecest/_backend/__init__.py +++ b/src/pyrecest/_backend/__init__.py @@ -215,6 +215,7 @@ def get_backend_name(): "jacobian_vec", "jacobian_and_hessian", "value_and_grad", + "value_and_jacobian", "value_jacobian_and_hessian", ], "linalg": [ From a2795be14550bb5f4ae68cc56fb0b61aac98afcf Mon Sep 17 00:00:00 2001 From: Florian Pfaff <6773539+FlorianPfaff@users.noreply.github.com> Date: Sun, 24 May 2026 09:26:30 +0200 Subject: [PATCH 4/4] Keep empty tuple assignment semantics unchanged --- src/pyrecest/_backend/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pyrecest/_backend/__init__.py b/src/pyrecest/_backend/__init__.py index 7775c1ccb..acdfbf22a 100644 --- a/src/pyrecest/_backend/__init__.py +++ b/src/pyrecest/_backend/__init__.py @@ -351,8 +351,10 @@ def mean(a, axis=None, dtype=None, out=None, keepdims=False): def _is_empty_assignment_index(indices): """Return whether ``indices`` selects no elements for assignment helpers.""" - if isinstance(indices, (list, tuple)): + if isinstance(indices, list): return len(indices) == 0 + if isinstance(indices, tuple): + return False ndim = getattr(indices, "ndim", None) shape = getattr(indices, "shape", None)