From b98a2dff0e08ce6a64297641a2588dfb681a3a07 Mon Sep 17 00:00:00 2001 From: Manan Gupta Date: Sun, 9 Aug 2026 11:57:14 -0700 Subject: [PATCH] Fix SPSA optimizer ignoring parameter bounds _check_bounds returned None when one (min, max) pair was given per variable, so _jail_inside treated it as no bounds and never clipped. The single-pair branch multiplied the bounds array by d instead of repeating the pair d times. --- pyqpanda-algorithm/pyqpanda_alg/QAOA/spsa.py | 5 +- test/QAOA/Test_spsa_minimize.py | 49 +++++++++++++++++++- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/pyqpanda-algorithm/pyqpanda_alg/QAOA/spsa.py b/pyqpanda-algorithm/pyqpanda_alg/QAOA/spsa.py index 6d7ae7b1..7f62924c 100644 --- a/pyqpanda-algorithm/pyqpanda_alg/QAOA/spsa.py +++ b/pyqpanda-algorithm/pyqpanda_alg/QAOA/spsa.py @@ -15,7 +15,7 @@ def _check_bounds(d, bounds): - """Check if the given bounds fits the variables.""" + """Check if the given bounds fits the variables. Returns d (min, max) pairs, or None.""" bounds = np.array(bounds) bn = len(bounds) if bn == 0: @@ -29,8 +29,9 @@ def _check_bounds(d, bounds): elif bn > 1: if d != bn: raise IndexError('Dimension of ``bounds`` does not match the dimension of variable ``x``.') + return bounds elif bn == 1: - return bounds * d + return np.tile(bounds, (d, 1)) def _jail_inside(x, bounds): diff --git a/test/QAOA/Test_spsa_minimize.py b/test/QAOA/Test_spsa_minimize.py index 819e394c..ad57fc9f 100644 --- a/test/QAOA/Test_spsa_minimize.py +++ b/test/QAOA/Test_spsa_minimize.py @@ -54,5 +54,52 @@ def test_spsa_basic_functionality(self, noise_function): # 验证回调函数被调用 assert len(noise_function.history) > 0 assert noise_function.eval_count > 0 - + + def test_spsa_bounds_respected(self, simple_function): + """Result stays inside per-variable bounds.""" + np.random.seed(0) + x0 = np.array([5.0, 5.0]) + bounds = [(1.0, 2.0), (1.0, 2.0)] + + result = spsa.spsa_minimize(simple_function, x0, bounds=bounds, maxiter=100) + + assert (result >= 1.0).all() + assert (result <= 2.0).all() + + def test_spsa_single_pair_bounds_respected(self, simple_function): + """A single (min, max) pair applies to every variable.""" + np.random.seed(0) + x0 = np.array([5.0, 5.0, 5.0]) + + result = spsa.spsa_minimize(simple_function, x0, bounds=[(1.0, 2.0)], maxiter=100) + + assert result.shape == x0.shape + assert (result >= 1.0).all() + assert (result <= 2.0).all() + + def test_check_bounds_per_variable(self): + """One pair per variable is returned as given.""" + bounds = [(0.0, 1.0), (-2.0, 2.0), (3.0, 4.0)] + + checked = spsa._check_bounds(3, bounds) + + assert checked is not None + assert np.array_equal(checked, np.array(bounds)) + + def test_check_bounds_single_pair_tiled(self): + """A single pair is tiled, not multiplied.""" + checked = spsa._check_bounds(3, [(0, 1)]) + + assert np.array_equal(checked, np.array([[0, 1], [0, 1], [0, 1]])) + + def test_check_bounds_validation(self): + """Invalid bounds still raise, empty bounds still mean no bounds.""" + with pytest.raises(ValueError): + spsa._check_bounds(2, [(2, 1), (0, 1)]) + + with pytest.raises(IndexError): + spsa._check_bounds(3, [(0, 1), (0, 1)]) + + assert spsa._check_bounds(2, []) is None +