Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 100 additions & 6 deletions crates/accelerate/src/target_transpiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ mod errors;
mod instruction_properties;
mod nullable_index_map;

use std::ops::Index;
use std::{ops::Index, sync::OnceLock};

use ahash::RandomState;

Expand Down Expand Up @@ -84,15 +84,55 @@ impl TargetOperation {
}
}
}

/// Creates a [TargetOperation] from an instance of [PackedOperation]
pub fn from_packed_operation(operation: PackedOperation, params: SmallVec<[Param; 3]>) -> Self {
NormalOperation::from_packed_operation(operation, params).into()
}
}

impl From<NormalOperation> for TargetOperation {
fn from(value: NormalOperation) -> Self {
TargetOperation::Normal(value)
}
}

/// Represents a Qiskit `Gate` object, keeps a reference to its Python
/// instance for caching purposes.
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct NormalOperation {
pub operation: PackedOperation,
pub params: SmallVec<[Param; 3]>,
op_object: PyObject,
op_object: OnceLock<PyResult<PyObject>>,
}

impl NormalOperation {
// Creates a python Operation type based on the operation's internal data.
#[inline]
fn create_py_op(&self, py: Python, label: Option<&str>) -> PyResult<PyObject> {
let obj = match self.operation.view() {
OperationRef::StandardGate(standard_gate) => {
standard_gate.create_py_op(py, Some(&self.params), label)?
}
OperationRef::StandardInstruction(standard_instruction) => {
standard_instruction.create_py_op(py, Some(&self.params), label)?
}
OperationRef::Gate(gate) => gate.gate.clone_ref(py),
OperationRef::Instruction(instruction) => instruction.instruction.clone_ref(py),
OperationRef::Operation(operation) => operation.operation.clone_ref(py),
OperationRef::Unitary(unitary) => unitary.create_py_op(py, label)?,
};
Ok(obj)
}

/// Creates a of [TargetOperation] from an instance of [PackedOperation]
pub fn from_packed_operation(operation: PackedOperation, params: SmallVec<[Param; 3]>) -> Self {
Self {
operation,
params,
op_object: OnceLock::new(),
}
}
}

impl<'py> IntoPyObject<'py> for NormalOperation {
Expand All @@ -101,7 +141,10 @@ impl<'py> IntoPyObject<'py> for NormalOperation {
type Error = PyErr;

fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.op_object.bind(py).clone())
match self.op_object.get_or_init(|| self.create_py_op(py, None)) {
Ok(op) => Ok(op.bind(py).clone()),
Err(err) => Err(err.clone_ref(py)),
}
}
}

Expand All @@ -111,7 +154,10 @@ impl<'a, 'py> IntoPyObject<'py> for &'a NormalOperation {
type Error = PyErr;

fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.op_object.bind_borrowed(py))
match self.op_object.get_or_init(|| self.create_py_op(py, None)) {
Ok(op) => Ok(op.bind_borrowed(py)),
Err(err) => Err(err.clone_ref(py)),
}
}
}

Expand All @@ -121,11 +167,22 @@ impl<'py> FromPyObject<'py> for NormalOperation {
Ok(Self {
operation: operation.operation,
params: operation.params,
op_object: ob.clone().unbind(),
op_object: Ok(ob.clone().unbind()).into(),
})
}
}

// Custom impl for Clone to avoid cloning the `OnceLock`.
impl Clone for NormalOperation {
fn clone(&self) -> Self {
Self {
operation: self.operation.clone(),
params: self.params.clone(),
op_object: OnceLock::new(),
}
}
}

/**
The base class for a Python ``Target`` object. Contains data representing the
constraints of a particular backend.
Expand Down Expand Up @@ -738,6 +795,21 @@ impl Target {
.unbind())
}

// TODO: Add flag for custom tests
/// Private method for development purposes only
fn _raw_operation_from_name(&self, py: Python, name: &str) -> PyResult<Py<PyAny>> {
if let Some(gate) = self._gate_name_map.get(name) {
match gate {
TargetOperation::Normal(normal_operation) => {
normal_operation.create_py_op(py, None)
}
TargetOperation::Variadic(py_op) => Ok(py_op.clone_ref(py)),
}
} else {
Ok(py.None())
}
}

// Instance attributes

/// The dt attribute.
Expand Down Expand Up @@ -1287,6 +1359,28 @@ impl Index<&str> for Target {
}
}

impl Default for Target {
fn default() -> Self {
Self {
description: None,
num_qubits: Default::default(),
dt: None,
granularity: 1,
min_length: 1,
pulse_alignment: 1,
acquire_alignment: 1,
qubit_properties: None,
concurrent_measurements: None,
gate_map: Default::default(),
_gate_name_map: Default::default(),
global_operations: Default::default(),
qarg_gate_map: Default::default(),
non_global_strict_basis: None,
non_global_basis: None,
}
}
}

// For instruction_supported
fn check_obj_params(parameters: &[Param], obj: &NormalOperation) -> bool {
for (index, param) in parameters.iter().enumerate() {
Expand Down
21 changes: 21 additions & 0 deletions test/python/transpiler/test_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
RZXGate,
CZGate,
UnitaryGate,
Barrier,
)
from qiskit.circuit import IfElseOp, ForLoopOp, WhileLoopOp, SwitchCaseOp
from qiskit.circuit.measure import Measure
Expand Down Expand Up @@ -1201,6 +1202,26 @@ def test_target_no_num_qubits_qubit_properties(self):
# Check that the Target num_qubit attribute matches the length of qubit properties
self.assertEqual(target.num_qubits, len(qubit_properties))

def test_gate_reconstruction_rust(self):
standard_gate = RXGate(3.14)
barrier = Barrier(5)
unitary = UnitaryGate([[0, 1], [1, 0]])

# Create Target and add rest of instructions.
target = Target()
target.add_instruction(standard_gate)
target.add_instruction(barrier)
target.add_instruction(unitary)

# Check the gate instances are working as expected
self.assertEqual(target.operation_from_name("rx"), target._raw_operation_from_name("rx"))
Comment thread
raynelfss marked this conversation as resolved.
Resolved
self.assertEqual(
target.operation_from_name("barrier"), target._raw_operation_from_name("barrier")
)
self.assertEqual(
target.operation_from_name("unitary"), target._raw_operation_from_name("unitary")
)


class TestGlobalVariableWidthOperations(QiskitTestCase):
def setUp(self):
Expand Down