From abecea768ea25ba298f705bdc012057656679d8b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Jun 2026 15:57:19 +0000 Subject: [PATCH 1/3] Initial plan From 0c8774a88806a1ce5601cb7652af5f013bde3ca9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:05:07 +0000 Subject: [PATCH 2/3] Fix Learner2D restore snapshot mutation in balancing asks --- adaptive/learner/learner2D.py | 6 +++--- adaptive/tests/test_balancing_learner.py | 14 +++++++++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/adaptive/learner/learner2D.py b/adaptive/learner/learner2D.py index 125fc055..349b0992 100644 --- a/adaptive/learner/learner2D.py +++ b/adaptive/learner/learner2D.py @@ -4,7 +4,7 @@ import warnings from collections import OrderedDict from collections.abc import Callable, Iterable -from copy import copy +from copy import copy, deepcopy from math import sqrt import cloudpickle @@ -897,8 +897,8 @@ def __getstate__(self): cloudpickle.dumps(self.function), self.bounds, self.loss_per_triangle, - self._stack, - self._get_data(), + deepcopy(self._stack), + deepcopy(self._get_data()), ) def __setstate__(self, state): diff --git a/adaptive/tests/test_balancing_learner.py b/adaptive/tests/test_balancing_learner.py index c50b2105..5f91506e 100644 --- a/adaptive/tests/test_balancing_learner.py +++ b/adaptive/tests/test_balancing_learner.py @@ -2,7 +2,7 @@ import pytest -from adaptive.learner import BalancingLearner, Learner1D +from adaptive.learner import BalancingLearner, Learner1D, Learner2D from adaptive.runner import simple strategies = ["loss", "loss_improvements", "npoints", "cycle"] @@ -51,6 +51,18 @@ def test_ask_0(strategy): assert len(points) == 0 +def test_ask_without_pending_restores_learner2d_state(): + learner = Learner2D(lambda xy: xy[0] + xy[1], bounds=((-1, 1), (-1, 1))) + initial_stack = list(learner._stack.items()) + initial_data = learner.data.copy() + + balancing_learner = BalancingLearner([learner]) + balancing_learner.ask(1, tell_pending=False) + + assert list(learner._stack.items()) == initial_stack + assert learner.data == initial_data + + @pytest.mark.parametrize( "strategy, goal_type, goal", [ From 5654448ea85babd5731e34beb73c266f88672bfc Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Wed, 10 Jun 2026 15:39:45 -0700 Subject: [PATCH 3/3] Use shallow copies instead of deepcopy in Learner2D.__getstate__ Key-level mutations (stack pops/inserts) are all that restore() needs to roll back; values are never mutated in place. A deepcopy of the full data dict would add O(npoints) allocation to every ask(tell_pending=False) call and to pickling. --- adaptive/learner/learner2D.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/adaptive/learner/learner2D.py b/adaptive/learner/learner2D.py index 349b0992..f7e1fc21 100644 --- a/adaptive/learner/learner2D.py +++ b/adaptive/learner/learner2D.py @@ -4,7 +4,7 @@ import warnings from collections import OrderedDict from collections.abc import Callable, Iterable -from copy import copy, deepcopy +from copy import copy from math import sqrt import cloudpickle @@ -897,8 +897,8 @@ def __getstate__(self): cloudpickle.dumps(self.function), self.bounds, self.loss_per_triangle, - deepcopy(self._stack), - deepcopy(self._get_data()), + self._stack.copy(), + self._get_data().copy(), ) def __setstate__(self, state):