Skip to content

Commit

Permalink
Fix broken assumptions around the gate model
Browse files Browse the repository at this point in the history
The `compose` test had a now-broken assumption, because the Python-space
`is` check is no longer expected to return an identical object when a
standard gate is moved from one circuit to another and has its
components remapped as part of the `compose` operation.  This doesn't
constitute the unpleasant deep-copy that that test is preventing. A
custom gate still satisfies that, however, so we can just change the
test.

`DAGNode::set_name` could cause problems if it was called for the first
time on a `CircuitInstruction` that was for a standard gate; these would
be created as immutable instances.  Given the changes in operator
extraction to Rust space, it can now be the case that a standard gate
that comes in as mutable is unpacked into Rust space, the cache is some
time later invalidated, and then the operation is recreated immutably.
  • Loading branch information
jakelishman committed Jul 9, 2024
1 parent b5a3d9f commit 59d1fc0
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 2 deletions.
16 changes: 16 additions & 0 deletions crates/circuit/src/circuit_instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,22 @@ impl CircuitInstruction {
pub fn op(&self) -> OperationRef {
self.operation.view()
}

/// Get the Python-space operation, ensuring that it is mutable from Python space (singleton
/// gates might not necessarily satisfy this otherwise).
///
/// This returns the cached instruction if valid, and replaces the cached instruction if not.
pub fn get_operation_mut(&self, py: Python) -> PyResult<Py<PyAny>> {
let mut out = self.get_operation(py)?.into_bound(py);
if !out.getattr(intern!(py, "mutable"))?.extract::<bool>()? {
out = out.call_method0(intern!(py, "to_mutable"))?;
}
#[cfg(feature = "cache_pygates")]
{
*self.py_op.borrow_mut() = Some(out.to_object(py));
}
Ok(out.unbind())
}
}

#[pymethods]
Expand Down
2 changes: 1 addition & 1 deletion crates/circuit/src/dag_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ impl DAGOpNode {
/// Sets the Instruction name corresponding to the op for this node
#[setter]
fn set_name(&mut self, py: Python, new_name: PyObject) -> PyResult<()> {
let op = self.instruction.get_operation(py)?.into_bound(py);
let op = self.instruction.get_operation_mut(py)?.into_bound(py);
op.setattr(intern!(py, "name"), new_name)?;
self.instruction.operation = op.extract::<OperationFromPython>()?.operation;
Ok(())
Expand Down
8 changes: 7 additions & 1 deletion test/python/circuit/test_compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,8 +360,14 @@ def test_compose_copy(self):
# For standard gates a fresh copy is returned from the data list each time
self.assertEqual(forbid_copy.data[-1].operation, parametric.data[-1].operation)

class Custom(Gate):
"""Custom gate that cannot be decomposed into Rust space."""

def __init__(self):
super().__init__("mygate", 1, [])

conditional = QuantumCircuit(1, 1)
conditional.x(0).c_if(conditional.clbits[0], True)
conditional.append(Custom(), [0], []).c_if(conditional.clbits[0], True)
test = base.compose(conditional, qubits=[0], clbits=[0], copy=False)
self.assertIs(test.data[-1].operation, conditional.data[-1].operation)
self.assertEqual(test.data[-1].operation.condition, (test.clbits[0], True))
Expand Down

0 comments on commit 59d1fc0

Please sign in to comment.