Skip to content

Commit 2069b83

Browse files
Fix parameter handling for NLocal(..., flatten=True) and standard gates (#13482) (#13495)
* Use _append_standard_gate directly * update params on cache * Revert "Use _append_standard_gate directly" This reverts commit 9769785. * Add test and reno (cherry picked from commit 4e5d9f8) Co-authored-by: Julien Gacon <jules.gacon@googlemail.com>
1 parent 3a1ee15 commit 2069b83

File tree

4 files changed

+54
-7
lines changed

4 files changed

+54
-7
lines changed

crates/circuit/src/circuit_data.rs

+9-7
Original file line numberDiff line numberDiff line change
@@ -1361,12 +1361,9 @@ impl CircuitData {
13611361
let Param::ParameterExpression(expr) = &params[parameter] else {
13621362
return Err(inconsistent());
13631363
};
1364-
params[parameter] = match bind_expr(
1365-
expr.bind_borrowed(py),
1366-
&param_ob,
1367-
value.as_ref(),
1368-
true,
1369-
)? {
1364+
let new_param =
1365+
bind_expr(expr.bind_borrowed(py), &param_ob, value.as_ref(), true)?;
1366+
params[parameter] = match new_param.clone_ref(py) {
13701367
Param::Obj(obj) => {
13711368
return Err(CircuitError::new_err(format!(
13721369
"bad type after binding for gate '{}': '{}'",
@@ -1382,8 +1379,13 @@ impl CircuitData {
13821379
#[cfg(feature = "cache_pygates")]
13831380
{
13841381
// Standard gates can all rebuild their definitions, so if the
1385-
// cached py_op exists, just clear out any existing cache.
1382+
// cached py_op exists, update the `params` attribute and clear out
1383+
// any existing cache.
13861384
if let Some(borrowed) = previous.py_op.get() {
1385+
borrowed
1386+
.bind(py)
1387+
.getattr(params_attr)?
1388+
.set_item(parameter, new_param)?;
13871389
borrowed.bind(py).setattr("_definition", py.None())?
13881390
}
13891391
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
fixes:
3+
- |
4+
Fixed a bug in :meth:`.QuantumCircuit.assign_parameters`, occurring when assigning parameters
5+
to standard gates whose definition has already been triggered. In this case, the new values
6+
were not properly propagated to the gate instances. While the circuit itself was still
7+
compiled as expected, inspecting the individual operations would still show the old parameter.
8+
9+
For example::
10+
11+
from qiskit.circuit.library import EfficientSU2
12+
13+
circuit = EfficientSU2(2, flatten=True)
14+
circuit.assign_parameters([1.25] * circuit.num_parameters, inplace=True)
15+
print(circuit.data[0].operation.params) # would print θ[0] instead of 1.25
16+
17+
Fixed `#13478 <https://github.com/Qiskit/qiskit/issues/13478>`__.

test/python/circuit/library/test_nlocal.py

+15
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,21 @@ def test_initial_state_as_circuit_object(self):
488488

489489
self.assertCircuitEqual(ref, expected)
490490

491+
def test_inplace_assignment_with_cache(self):
492+
"""Test parameters are correctly re-bound in the cached gates.
493+
494+
This test requires building with the Rust feature "cache_pygates" enabled, otherwise
495+
it does not test what it is supposed to.
496+
497+
Regression test of #13478.
498+
"""
499+
qc = EfficientSU2(2, flatten=True)
500+
binds = [1.25] * qc.num_parameters
501+
502+
qc.assign_parameters(binds, inplace=True)
503+
bound_op = qc.data[0].operation
504+
self.assertAlmostEqual(bound_op.params[0], binds[0])
505+
491506

492507
@ddt
493508
class TestNLocalFunction(QiskitTestCase):

test/python/circuit/test_parameters.py

+13
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,19 @@ def test_assign_parameters_by_iterable(self):
361361
self.assertEqual(qc.assign_parameters(dict(zip(qc.parameters, binds)).values()), expected)
362362
self.assertEqual(qc.assign_parameters(bind for bind in binds), expected)
363363

364+
def test_assign_parameters_with_cache(self):
365+
"""Test assigning parameters on a circuit with already triggered cache."""
366+
x = Parameter("x")
367+
qc = QuantumCircuit(1)
368+
qc.append(RZGate(x), [0]) # add via ``append`` to create a CircuitInstruction
369+
370+
_ = qc.data[0].operation.definition # trigger building the cache
371+
372+
binds = [1.2]
373+
qc.assign_parameters(binds, inplace=True)
374+
375+
self.assertAlmostEqual(binds[0], qc.data[0].operation.params[0])
376+
364377
def test_bind_parameters_custom_definition_global_phase(self):
365378
"""Test that a custom gate with a parametrized `global_phase` is assigned correctly."""
366379
x = Parameter("x")

0 commit comments

Comments
 (0)