From 013c7fce5ea62f5f17f4d2135fb94bde71336699 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Wed, 23 Oct 2024 13:45:38 -0400 Subject: [PATCH 01/15] Represent Var with BitData. Co-authored-by: John Lapeyre --- crates/accelerate/src/commutation_analysis.rs | 2 +- crates/circuit/src/dag_circuit.rs | 420 ++++++++---------- crates/circuit/src/dot_utils.rs | 2 +- crates/circuit/src/lib.rs | 36 ++ 4 files changed, 228 insertions(+), 232 deletions(-) diff --git a/crates/accelerate/src/commutation_analysis.rs b/crates/accelerate/src/commutation_analysis.rs index a29c648a5f81..07266191fe45 100644 --- a/crates/accelerate/src/commutation_analysis.rs +++ b/crates/accelerate/src/commutation_analysis.rs @@ -61,7 +61,7 @@ pub(crate) fn analyze_commutations_inner( for qubit in 0..dag.num_qubits() { let wire = Wire::Qubit(Qubit(qubit as u32)); - for current_gate_idx in dag.nodes_on_wire(py, &wire, false) { + for current_gate_idx in dag.nodes_on_wire(&wire, false) { // get the commutation set associated with the current wire, or create a new // index set containing the current gate let commutation_entry = commutation_set diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index e8f32e5dafee..86829eb76137 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -10,7 +10,7 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -use std::hash::{Hash, Hasher}; +use std::hash::Hash; use ahash::RandomState; use smallvec::SmallVec; @@ -29,7 +29,7 @@ use crate::interner::{Interned, Interner}; use crate::operations::{Operation, OperationRef, Param, PyInstruction, StandardGate}; use crate::packed_instruction::{PackedInstruction, PackedOperation}; use crate::rustworkx_core_vnext::isomorphism; -use crate::{BitType, Clbit, Qubit, TupleLikeArg}; +use crate::{BitType, Clbit, Qubit, TupleLikeArg, Var}; use hashbrown::{HashMap, HashSet}; use indexmap::IndexMap; @@ -79,8 +79,8 @@ pub enum NodeType { QubitOut(Qubit), ClbitIn(Clbit), ClbitOut(Clbit), - VarIn(PyObject), - VarOut(PyObject), + VarIn(Var), + VarOut(Var), Operation(PackedInstruction), } @@ -97,45 +97,21 @@ impl NodeType { } } -#[derive(Clone, Debug)] +#[derive(Hash, Eq, PartialEq, Clone, Debug)] pub enum Wire { Qubit(Qubit), Clbit(Clbit), - Var(PyObject), -} - -impl PartialEq for Wire { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Wire::Qubit(q1), Wire::Qubit(q2)) => q1 == q2, - (Wire::Clbit(c1), Wire::Clbit(c2)) => c1 == c2, - (Wire::Var(v1), Wire::Var(v2)) => { - v1.is(v2) || Python::with_gil(|py| v1.bind(py).eq(v2).unwrap()) - } - _ => false, - } - } -} - -impl Eq for Wire {} - -impl Hash for Wire { - fn hash(&self, state: &mut H) { - match self { - Self::Qubit(qubit) => qubit.hash(state), - Self::Clbit(clbit) => clbit.hash(state), - Self::Var(var) => Python::with_gil(|py| var.bind(py).hash().unwrap().hash(state)), - } - } + Var(Var), } impl Wire { fn to_pickle(&self, py: Python) -> PyObject { match self { - Self::Qubit(bit) => (0, bit.0.into_py(py)).into_py(py), - Self::Clbit(bit) => (1, bit.0.into_py(py)).into_py(py), - Self::Var(var) => (2, var.clone_ref(py)).into_py(py), + Self::Qubit(bit) => (0, bit.0.into_py(py)), + Self::Clbit(bit) => (1, bit.0.into_py(py)), + Self::Var(var) => (2, var.0.into_py(py)), } + .into_py(py) } fn from_pickle(b: &Bound) -> PyResult { @@ -146,84 +122,13 @@ impl Wire { } else if wire_type == 1 { Ok(Self::Clbit(Clbit(tuple.get_item(1)?.extract()?))) } else if wire_type == 2 { - Ok(Self::Var(tuple.get_item(1)?.unbind())) + Ok(Self::Var(Var(tuple.get_item(1)?.extract()?))) } else { Err(PyTypeError::new_err("Invalid wire type")) } } } -// TODO: Remove me. -// This is a temporary map type used to store a mapping of -// Var to NodeIndex to hold us over until Var is ported to -// Rust. Currently, we need this because PyObject cannot be -// used as the key to an IndexMap. -// -// Once we've got Var ported, Wire should also become Hash + Eq -// and we can consider combining input/output nodes maps. -#[derive(Clone, Debug)] -struct _VarIndexMap { - dict: Py, -} - -impl _VarIndexMap { - pub fn new(py: Python) -> Self { - Self { - dict: PyDict::new_bound(py).unbind(), - } - } - - pub fn keys(&self, py: Python) -> impl Iterator { - self.dict - .bind(py) - .keys() - .into_iter() - .map(|k| k.unbind()) - .collect::>() - .into_iter() - } - - pub fn contains_key(&self, py: Python, key: &PyObject) -> bool { - self.dict.bind(py).contains(key).unwrap() - } - - pub fn get(&self, py: Python, key: &PyObject) -> Option { - self.dict - .bind(py) - .get_item(key) - .unwrap() - .map(|v| NodeIndex::new(v.extract().unwrap())) - } - - pub fn insert(&mut self, py: Python, key: PyObject, value: NodeIndex) { - self.dict - .bind(py) - .set_item(key, value.index().into_py(py)) - .unwrap() - } - - pub fn remove(&mut self, py: Python, key: &PyObject) -> Option { - let bound_dict = self.dict.bind(py); - let res = bound_dict - .get_item(key.clone_ref(py)) - .unwrap() - .map(|v| NodeIndex::new(v.extract().unwrap())); - let _del_result = bound_dict.del_item(key); - res - } - pub fn values<'py>(&self, py: Python<'py>) -> impl Iterator + 'py { - let values = self.dict.bind(py).values(); - values.iter().map(|x| NodeIndex::new(x.extract().unwrap())) - } - - pub fn iter<'py>(&self, py: Python<'py>) -> impl Iterator + 'py { - self.dict - .bind(py) - .iter() - .map(|(var, index)| (var.unbind(), NodeIndex::new(index.extract().unwrap()))) - } -} - /// Quantum circuit as a directed acyclic graph. /// /// There are 3 types of nodes in the graph: inputs, outputs, and operations. @@ -257,6 +162,8 @@ pub struct DAGCircuit { qubits: BitData, /// Clbits registered in the circuit. clbits: BitData, + /// Variables registered in the circuit. + vars: BitData, /// Global phase. global_phase: Param, /// Duration. @@ -281,11 +188,8 @@ pub struct DAGCircuit { /// Map from clbit to input and output nodes of the graph. clbit_io_map: Vec<[NodeIndex; 2]>, - // TODO: use IndexMap once Var is ported to Rust - /// Map from var to input nodes of the graph. - var_input_map: _VarIndexMap, - /// Map from var to output nodes of the graph. - var_output_map: _VarIndexMap, + /// Map from var to input and output nodes of the graph. + var_io_map: Vec<[NodeIndex; 2]>, /// Operation kind to count op_names: IndexMap, @@ -438,6 +342,7 @@ impl DAGCircuit { cargs_interner: Interner::new(), qubits: BitData::new(py, "qubits".to_string()), clbits: BitData::new(py, "clbits".to_string()), + vars: BitData::new(py, "vars".to_string()), global_phase: Param::Float(0.), duration: None, unit: "dt".to_string(), @@ -445,8 +350,7 @@ impl DAGCircuit { clbit_locations: PyDict::new_bound(py).unbind(), qubit_io_map: Vec::new(), clbit_io_map: Vec::new(), - var_input_map: _VarIndexMap::new(py), - var_output_map: _VarIndexMap::new(py), + var_io_map: Vec::new(), op_names: IndexMap::default(), control_flow_module: PyControlFlowModule::new(py)?, vars_info: HashMap::new(), @@ -522,10 +426,15 @@ impl DAGCircuit { self.get_node(py, indices[0])?, )?; } - for (var, index) in self.var_input_map.dict.bind(py).iter() { + for (var, indices) in self + .var_io_map + .iter() + .enumerate() + .map(|(idx, indices)| (Var::new(idx), indices)) + { out_dict.set_item( - var, - self.get_node(py, NodeIndex::new(index.extract::()?))?, + self.vars.get(var).unwrap().clone_ref(py), + self.get_node(py, indices[0])?, )?; } Ok(out_dict.unbind()) @@ -556,10 +465,15 @@ impl DAGCircuit { self.get_node(py, indices[1])?, )?; } - for (var, index) in self.var_output_map.dict.bind(py).iter() { + for (var, indices) in self + .var_io_map + .iter() + .enumerate() + .map(|(idx, indices)| (Var::new(idx), indices)) + { out_dict.set_item( - var, - self.get_node(py, NodeIndex::new(index.extract::()?))?, + self.vars.get(var).unwrap().clone_ref(py), + self.get_node(py, indices[1])?, )?; } Ok(out_dict.unbind()) @@ -589,8 +503,14 @@ impl DAGCircuit { .map(|(k, v)| (k, [v[0].index(), v[1].index()])) .collect::>(), )?; - out_dict.set_item("var_input_map", self.var_input_map.dict.clone_ref(py))?; - out_dict.set_item("var_output_map", self.var_output_map.dict.clone_ref(py))?; + out_dict.set_item( + "var_io_map", + self.var_io_map + .iter() + .enumerate() + .map(|(k, v)| (k, [v[0].index(), v[1].index()])) + .collect::>(), + )?; out_dict.set_item("op_name", self.op_names.clone())?; out_dict.set_item( "vars_info", @@ -612,6 +532,7 @@ impl DAGCircuit { out_dict.set_item("vars_by_type", self.vars_by_type.clone())?; out_dict.set_item("qubits", self.qubits.bits())?; out_dict.set_item("clbits", self.clbits.bits())?; + out_dict.set_item("vars", self.vars.bits())?; let mut nodes: Vec = Vec::with_capacity(self.dag.node_count()); for node_idx in self.dag.node_indices() { let node_data = self.get_node(py, node_idx)?; @@ -653,12 +574,6 @@ impl DAGCircuit { self.cregs = dict_state.get_item("cregs")?.unwrap().extract()?; self.global_phase = dict_state.get_item("global_phase")?.unwrap().extract()?; self.op_names = dict_state.get_item("op_name")?.unwrap().extract()?; - self.var_input_map = _VarIndexMap { - dict: dict_state.get_item("var_input_map")?.unwrap().extract()?, - }; - self.var_output_map = _VarIndexMap { - dict: dict_state.get_item("var_output_map")?.unwrap().extract()?, - }; self.vars_by_type = dict_state.get_item("vars_by_type")?.unwrap().extract()?; let binding = dict_state.get_item("vars_info")?.unwrap(); let vars_info_raw = binding.downcast::().unwrap(); @@ -689,6 +604,11 @@ impl DAGCircuit { for bit in clbits_raw.iter() { self.clbits.add(py, &bit, false)?; } + let binding = dict_state.get_item("vars")?.unwrap(); + let vars_raw = binding.downcast::().unwrap(); + for bit in vars_raw.iter() { + self.vars.add(py, &bit, false)?; + } let binding = dict_state.get_item("qubit_io_map")?.unwrap(); let qubit_index_map_raw = binding.downcast::().unwrap(); self.qubit_io_map = Vec::with_capacity(qubit_index_map_raw.len()); @@ -700,12 +620,19 @@ impl DAGCircuit { let binding = dict_state.get_item("clbit_io_map")?.unwrap(); let clbit_index_map_raw = binding.downcast::().unwrap(); self.clbit_io_map = Vec::with_capacity(clbit_index_map_raw.len()); - for (_k, v) in clbit_index_map_raw.iter() { let indices: [usize; 2] = v.extract()?; self.clbit_io_map .push([NodeIndex::new(indices[0]), NodeIndex::new(indices[1])]); } + let binding = dict_state.get_item("var_io_map")?.unwrap(); + let var_index_map_raw = binding.downcast::().unwrap(); + self.var_io_map = Vec::with_capacity(var_index_map_raw.len()); + for (_k, v) in var_index_map_raw.iter() { + let indices: [usize; 2] = v.extract()?; + self.var_io_map + .push([NodeIndex::new(indices[0]), NodeIndex::new(indices[1])]); + } // Rebuild Graph preserving index holes: let binding = dict_state.get_item("nodes")?.unwrap(); let nodes_lst = binding.downcast::()?; @@ -1190,7 +1117,7 @@ def _format(operand): let clbits: HashSet = bit_iter.collect(); let mut busy_bits = Vec::new(); for bit in clbits.iter() { - if !self.is_wire_idle(py, &Wire::Clbit(*bit))? { + if !self.is_wire_idle(&Wire::Clbit(*bit))? { busy_bits.push(self.clbits.get(*bit).unwrap()); } } @@ -1217,7 +1144,7 @@ def _format(operand): // Remove DAG in/out nodes etc. for bit in clbits.iter() { - self.remove_idle_wire(py, Wire::Clbit(*bit))?; + self.remove_idle_wire(Wire::Clbit(*bit))?; } // Copy the current clbit mapping so we can use it while remapping @@ -1398,7 +1325,7 @@ def _format(operand): let mut busy_bits = Vec::new(); for bit in qubits.iter() { - if !self.is_wire_idle(py, &Wire::Qubit(*bit))? { + if !self.is_wire_idle(&Wire::Qubit(*bit))? { busy_bits.push(self.qubits.get(*bit).unwrap()); } } @@ -1425,7 +1352,7 @@ def _format(operand): // Remove DAG in/out nodes etc. for bit in qubits.iter() { - self.remove_idle_wire(py, Wire::Qubit(*bit))?; + self.remove_idle_wire(Wire::Qubit(*bit))?; } // Copy the current qubit mapping so we can use it while remapping @@ -2151,7 +2078,7 @@ def _format(operand): let wires = (0..self.qubit_io_map.len()) .map(|idx| Wire::Qubit(Qubit::new(idx))) .chain((0..self.clbit_io_map.len()).map(|idx| Wire::Clbit(Clbit::new(idx)))) - .chain(self.var_input_map.keys(py).map(Wire::Var)); + .chain((0..self.var_io_map.len()).map(|idx| Wire::Var(Var::new(idx)))); match ignore { Some(ignore) => { // Convert the list to a Rust set. @@ -2160,7 +2087,7 @@ def _format(operand): .map(|s| s.extract()) .collect::>>()?; for wire in wires { - let nodes_found = self.nodes_on_wire(py, &wire, true).into_iter().any(|node| { + let nodes_found = self.nodes_on_wire(&wire, true).into_iter().any(|node| { let weight = self.dag.node_weight(node).unwrap(); if let NodeType::Operation(packed) = weight { !ignore_set.contains(packed.op.name()) @@ -2173,18 +2100,18 @@ def _format(operand): result.push(match wire { Wire::Qubit(qubit) => self.qubits.get(qubit).unwrap().clone_ref(py), Wire::Clbit(clbit) => self.clbits.get(clbit).unwrap().clone_ref(py), - Wire::Var(var) => var, + Wire::Var(var) => self.vars.get(var).unwrap().clone_ref(py), }); } } } None => { for wire in wires { - if self.is_wire_idle(py, &wire)? { + if self.is_wire_idle(&wire)? { result.push(match wire { Wire::Qubit(qubit) => self.qubits.get(qubit).unwrap().clone_ref(py), Wire::Clbit(clbit) => self.clbits.get(clbit).unwrap().clone_ref(py), - Wire::Var(var) => var, + Wire::Var(var) => self.vars.get(var).unwrap().clone_ref(py), }); } } @@ -2618,8 +2545,18 @@ def _format(operand): [NodeType::ClbitIn(bit1), NodeType::ClbitIn(bit2)] => Ok(bit1 == bit2), [NodeType::QubitOut(bit1), NodeType::QubitOut(bit2)] => Ok(bit1 == bit2), [NodeType::ClbitOut(bit1), NodeType::ClbitOut(bit2)] => Ok(bit1 == bit2), - [NodeType::VarIn(var1), NodeType::VarIn(var2)] => var1.bind(py).eq(var2), - [NodeType::VarOut(var1), NodeType::VarOut(var2)] => var1.bind(py).eq(var2), + [NodeType::VarIn(var1), NodeType::VarIn(var2)] => self + .vars + .get(*var1) + .unwrap() + .bind(py) + .eq(other.vars.get(*var2).unwrap()), + [NodeType::VarOut(var1), NodeType::VarOut(var2)] => self + .vars + .get(*var1) + .unwrap() + .bind(py) + .eq(other.vars.get(*var2).unwrap()), _ => Ok(false), } }; @@ -3083,7 +3020,7 @@ def _format(operand): .edges_directed(node_index, Incoming) .find(|edge| { if let Wire::Var(var) = edge.weight() { - contracted_var.eq(var).unwrap() + contracted_var.eq(self.vars.get(*var)).unwrap() } else { false } @@ -3094,7 +3031,7 @@ def _format(operand): .edges_directed(node_index, Outgoing) .find(|edge| { if let Wire::Var(var) = edge.weight() { - contracted_var.eq(var).unwrap() + contracted_var.eq(self.vars.get(*var)).unwrap() } else { false } @@ -3103,7 +3040,7 @@ def _format(operand): self.dag.add_edge( pred.source(), succ.target(), - Wire::Var(contracted_var.unbind()), + Wire::Var(self.vars.find(&contracted_var).unwrap()), ); } @@ -3452,7 +3389,11 @@ def _format(operand): let (additional_clbits, additional_vars) = self.additional_wires(py, new_op.operation.view(), new_op.extra_attrs.condition())?; new_wires.extend(additional_clbits.iter().map(|x| Wire::Clbit(*x))); - new_wires.extend(additional_vars.iter().map(|x| Wire::Var(x.clone_ref(py)))); + new_wires.extend( + additional_vars + .iter() + .map(|x| Wire::Var(self.vars.find(x.bind(py)).unwrap())), + ); if old_packed.op.num_qubits() != new_op.operation.num_qubits() || old_packed.op.num_clbits() != new_op.operation.num_clbits() @@ -3612,11 +3553,11 @@ def _format(operand): non_classical = true; } NodeType::VarIn(v) => { - let var_in = new_dag.var_input_map.get(py, v).unwrap(); + let var_in = new_dag.var_io_map[v.index()][0]; node_map.insert(*node, var_in); } NodeType::VarOut(v) => { - let var_out = new_dag.var_output_map.get(py, v).unwrap(); + let var_out = new_dag.var_io_map[v.index()][1]; node_map.insert(*node, var_out); } NodeType::Operation(pi) => { @@ -3670,12 +3611,11 @@ def _format(operand): .add_edge(*in_node, *out_node, Wire::Clbit(clbit)); } } - for (var, in_node) in new_dag.var_input_map.iter(py) { + for (var_index, &[in_node, out_node]) in new_dag.var_io_map.iter().enumerate() { if new_dag.dag.edges(in_node).next().is_none() { - let out_node = new_dag.var_output_map.get(py, &var).unwrap(); new_dag .dag - .add_edge(in_node, out_node, Wire::Var(var.clone_ref(py))); + .add_edge(in_node, out_node, Wire::Var(Var::new(var_index))); } } if remove_idle_qubits { @@ -3845,7 +3785,7 @@ def _format(operand): match edge.weight() { Wire::Qubit(qubit) => self.qubits.get(*qubit).unwrap(), Wire::Clbit(clbit) => self.clbits.get(*clbit).unwrap(), - Wire::Var(var) => var, + Wire::Var(var) => self.vars.get(*var).unwrap(), }, )) } @@ -4279,7 +4219,7 @@ def _format(operand): /// Return a list of op nodes in the first layer of this dag. #[pyo3(name = "front_layer")] fn py_front_layer(&self, py: Python) -> PyResult> { - let native_front_layer = self.front_layer(py); + let native_front_layer = self.front_layer(); let front_layer_list = PyList::empty_bound(py); for node in native_front_layer { front_layer_list.append(self.get_node(py, node)?)?; @@ -4306,7 +4246,7 @@ def _format(operand): #[pyo3(signature = (*, vars_mode="captures"))] fn layers(&self, py: Python, vars_mode: &str) -> PyResult> { let layer_list = PyList::empty_bound(py); - let mut graph_layers = self.multigraph_layers(py); + let mut graph_layers = self.multigraph_layers(); if graph_layers.next().is_none() { return Ok(PyIterator::from_bound_object(&layer_list)?.into()); } @@ -4406,7 +4346,7 @@ def _format(operand): /// Yield layers of the multigraph. #[pyo3(name = "multigraph_layers")] fn py_multigraph_layers(&self, py: Python) -> PyResult> { - let graph_layers = self.multigraph_layers(py).map(|layer| -> Vec { + let graph_layers = self.multigraph_layers().map(|layer| -> Vec { layer .into_iter() .filter_map(|index| self.get_node(py, index).ok()) @@ -4521,10 +4461,8 @@ def _format(operand): self.qubits.find(wire).map(Wire::Qubit) } else if wire.is_instance(imports::CLBIT.get_bound(py))? { self.clbits.find(wire).map(Wire::Clbit) - } else if self.var_input_map.contains_key(py, &wire.clone().unbind()) { - Some(Wire::Var(wire.clone().unbind())) } else { - None + self.vars.find(wire).map(Wire::Var) } .ok_or_else(|| { DAGCircuitError::new_err(format!( @@ -4534,7 +4472,7 @@ def _format(operand): })?; let nodes = self - .nodes_on_wire(py, &wire, only_ops) + .nodes_on_wire(&wire, only_ops) .into_iter() .map(|n| self.get_node(py, n)) .collect::>>()?; @@ -4879,7 +4817,7 @@ def _format(operand): match wire.weight() { Wire::Qubit(qubit) => self.qubits.get(*qubit).unwrap(), Wire::Clbit(clbit) => self.clbits.get(*clbit).unwrap(), - Wire::Var(var) => var, + Wire::Var(var) => self.vars.get(*var).unwrap(), }, ) .into_py(py) @@ -4897,7 +4835,7 @@ def _format(operand): match wire.weight() { Wire::Qubit(qubit) => self.qubits.get(*qubit).unwrap(), Wire::Clbit(clbit) => self.clbits.get(*clbit).unwrap(), - Wire::Var(var) => var, + Wire::Var(var) => self.vars.get(*var).unwrap(), }, ) .into_py(py) @@ -4911,7 +4849,7 @@ def _format(operand): .map(|wire| match wire.weight() { Wire::Qubit(qubit) => self.qubits.get(*qubit).unwrap(), Wire::Clbit(clbit) => self.clbits.get(*clbit).unwrap(), - Wire::Var(var) => var, + Wire::Var(var) => self.vars.get(*var).unwrap(), }) .collect() } @@ -4922,7 +4860,7 @@ def _format(operand): .map(|wire| match wire.weight() { Wire::Qubit(qubit) => self.qubits.get(*qubit).unwrap(), Wire::Clbit(clbit) => self.clbits.get(*clbit).unwrap(), - Wire::Var(var) => var, + Wire::Var(var) => self.vars.get(*var).unwrap(), }) .collect() } @@ -4942,7 +4880,7 @@ def _format(operand): let weight = match e.weight() { Wire::Qubit(q) => self.qubits.get(*q).unwrap(), Wire::Clbit(c) => self.clbits.get(*c).unwrap(), - Wire::Var(v) => v, + Wire::Var(v) => self.vars.get(*v).unwrap(), }; if edge_checker.call1((weight,))?.extract::()? { result.push(self.get_node(py, e.target())?); @@ -4959,7 +4897,7 @@ def _format(operand): match wire { Wire::Qubit(qubit) => self.qubits.get(*qubit).to_object(py), Wire::Clbit(clbit) => self.clbits.get(*clbit).to_object(py), - Wire::Var(var) => var.clone_ref(py), + Wire::Var(var) => self.vars.get(*var).to_object(py), } }) .collect() @@ -5003,6 +4941,12 @@ impl DAGCircuit { &self.clbits } + /// Returns an immutable view of the Variable wires registered in the circuit + #[inline(always)] + pub fn vars(&self) -> &BitData { + &self.vars + } + /// Return an iterator of gate runs with non-conditional op nodes of given names pub fn collect_runs( &self, @@ -5161,11 +5105,12 @@ impl DAGCircuit { .iter() .map(|c| self.clbit_io_map.get(c.index()).map(|x| x[1]).unwrap()), ) - .chain( - vars.iter() - .flatten() - .map(|v| self.var_output_map.get(py, v).unwrap()), - ) + .chain(vars.iter().flatten().map(|v| { + self.var_io_map + .get(self.vars.find(v.bind(py)).unwrap().index()) + .map(|x| x[1]) + .unwrap() + })) .collect(); for output_node in output_nodes { @@ -5226,7 +5171,7 @@ impl DAGCircuit { .collect(); if let Some(vars) = vars { for var in vars { - input_nodes.push(self.var_input_map.get(py, &var).unwrap()); + input_nodes.push(self.var_io_map[self.vars.find(var.bind(py)).unwrap().index()][0]); } } @@ -5422,7 +5367,7 @@ impl DAGCircuit { .any(|x| self.op_names.contains_key(&x.to_string())) } - fn is_wire_idle(&self, py: Python, wire: &Wire) -> PyResult { + fn is_wire_idle(&self, wire: &Wire) -> PyResult { let (input_node, output_node) = match wire { Wire::Qubit(qubit) => ( self.qubit_io_map[qubit.index()][0], @@ -5433,8 +5378,8 @@ impl DAGCircuit { self.clbit_io_map[clbit.index()][1], ), Wire::Var(var) => ( - self.var_input_map.get(py, var).unwrap(), - self.var_output_map.get(py, var).unwrap(), + self.var_io_map[var.index()][0], + self.var_io_map[var.index()][1], ), }; @@ -5569,9 +5514,12 @@ impl DAGCircuit { /// /// This adds a pair of in and out nodes connected by an edge. /// + /// Returns: + /// The input and output node indices of the added wire, respectively. + /// /// Raises: /// DAGCircuitError: if trying to add duplicate wire - fn add_wire(&mut self, py: Python, wire: Wire) -> PyResult<()> { + fn add_wire(&mut self, wire: Wire) -> PyResult<(NodeIndex, NodeIndex)> { let (in_node, out_node) = match wire { Wire::Qubit(qubit) => { if (qubit.index()) >= self.qubit_io_map.len() { @@ -5593,33 +5541,31 @@ impl DAGCircuit { Err(DAGCircuitError::new_err("classical wire already exists!")) } } - Wire::Var(ref var) => { - if self.var_input_map.contains_key(py, var) - || self.var_output_map.contains_key(py, var) - { + Wire::Var(var) => { + if var.index() >= self.var_io_map.len() { + let in_node = self.dag.add_node(NodeType::VarIn(var)); + let out_node = self.dag.add_node(NodeType::VarOut(var)); + self.var_io_map.push([in_node, out_node]); + Ok((in_node, out_node)) + } else { return Err(DAGCircuitError::new_err("var wire already exists!")); } - let in_node = self.dag.add_node(NodeType::VarIn(var.clone_ref(py))); - let out_node = self.dag.add_node(NodeType::VarOut(var.clone_ref(py))); - self.var_input_map.insert(py, var.clone_ref(py), in_node); - self.var_output_map.insert(py, var.clone_ref(py), out_node); - Ok((in_node, out_node)) } }?; self.dag.add_edge(in_node, out_node, wire); - Ok(()) + Ok((in_node, out_node)) } /// Get the nodes on the given wire. /// /// Note: result is empty if the wire is not in the DAG. - pub fn nodes_on_wire(&self, py: Python, wire: &Wire, only_ops: bool) -> Vec { + pub fn nodes_on_wire(&self, wire: &Wire, only_ops: bool) -> Vec { let mut nodes = Vec::new(); let mut current_node = match wire { Wire::Qubit(qubit) => self.qubit_io_map.get(qubit.index()).map(|x| x[0]), Wire::Clbit(clbit) => self.clbit_io_map.get(clbit.index()).map(|x| x[0]), - Wire::Var(var) => self.var_input_map.get(py, var), + Wire::Var(var) => self.var_io_map.get(var.index()).map(|x| x[0]), }; while let Some(node) = current_node { @@ -5644,14 +5590,11 @@ impl DAGCircuit { nodes } - fn remove_idle_wire(&mut self, py: Python, wire: Wire) -> PyResult<()> { + fn remove_idle_wire(&mut self, wire: Wire) -> PyResult<()> { let [in_node, out_node] = match wire { Wire::Qubit(qubit) => self.qubit_io_map[qubit.index()], Wire::Clbit(clbit) => self.clbit_io_map[clbit.index()], - Wire::Var(var) => [ - self.var_input_map.remove(py, &var).unwrap(), - self.var_output_map.remove(py, &var).unwrap(), - ], + Wire::Var(var) => self.var_io_map[var.index()], }; self.dag.remove_node(in_node); self.dag.remove_node(out_node); @@ -5670,7 +5613,7 @@ impl DAGCircuit { }, )?, )?; - self.add_wire(py, Wire::Qubit(qubit))?; + self.add_wire(Wire::Qubit(qubit))?; Ok(qubit) } @@ -5686,7 +5629,7 @@ impl DAGCircuit { }, )?, )?; - self.add_wire(py, Wire::Clbit(clbit))?; + self.add_wire(Wire::Clbit(clbit))?; Ok(clbit) } @@ -5755,7 +5698,7 @@ impl DAGCircuit { } else if wire.is_instance(imports::CLBIT.get_bound(py))? { NodeType::ClbitIn(self.clbits.find(wire).unwrap()) } else { - NodeType::VarIn(wire.clone().unbind()) + NodeType::VarIn(self.vars.find(wire).unwrap()) } } else if let Ok(out_node) = b.downcast::() { let out_node = out_node.borrow(); @@ -5765,7 +5708,7 @@ impl DAGCircuit { } else if wire.is_instance(imports::CLBIT.get_bound(py))? { NodeType::ClbitOut(self.clbits.find(wire).unwrap()) } else { - NodeType::VarIn(wire.clone().unbind()) + NodeType::VarIn(self.vars.find(wire).unwrap()) } } else if let Ok(op_node) = b.downcast::() { let op_node = op_node.borrow(); @@ -5843,12 +5786,16 @@ impl DAGCircuit { )? .into_any() } - NodeType::VarIn(var) => { - Py::new(py, DAGInNode::new(py, id, var.clone_ref(py)))?.into_any() - } - NodeType::VarOut(var) => { - Py::new(py, DAGOutNode::new(py, id, var.clone_ref(py)))?.into_any() - } + NodeType::VarIn(var) => Py::new( + py, + DAGInNode::new(py, id, self.vars.get(*var).unwrap().clone_ref(py)), + )? + .into_any(), + NodeType::VarOut(var) => Py::new( + py, + DAGOutNode::new(py, id, self.vars.get(*var).unwrap().clone_ref(py)), + )? + .into_any(), }; Ok(dag_node) } @@ -5933,10 +5880,10 @@ impl DAGCircuit { } /// Returns an iterator over a list layers of the `DAGCircuit``. - pub fn multigraph_layers(&self, py: Python) -> impl Iterator> + '_ { + pub fn multigraph_layers(&self) -> impl Iterator> + '_ { let mut first_layer: Vec<_> = self.qubit_io_map.iter().map(|x| x[0]).collect(); first_layer.extend(self.clbit_io_map.iter().map(|x| x[0])); - first_layer.extend(self.var_input_map.values(py)); + first_layer.extend(self.var_io_map.iter().map(|x| x[0])); // A DAG is by definition acyclical, therefore unwrapping the layer should never fail. layers(&self.dag, first_layer).map(|layer| match layer { Ok(layer) => layer, @@ -5945,17 +5892,19 @@ impl DAGCircuit { } /// Returns an iterator over the first layer of the `DAGCircuit``. - pub fn front_layer<'a>(&'a self, py: Python) -> Box + 'a> { - let mut graph_layers = self.multigraph_layers(py); + pub fn front_layer(&self) -> impl Iterator { + let mut graph_layers = self.multigraph_layers(); graph_layers.next(); let next_layer = graph_layers.next(); match next_layer { Some(layer) => Box::new(layer.into_iter().filter(|node| { matches!(self.dag.node_weight(*node).unwrap(), NodeType::Operation(_)) - })), - None => Box::new(vec![].into_iter()), + })) + .collect(), + None => vec![], } + .into_iter() } fn substitute_node_with_subgraph( @@ -6043,7 +5992,7 @@ impl DAGCircuit { .any(|edge| match edge.weight() { Wire::Qubit(qubit) => !qubit_map.contains_key(qubit), Wire::Clbit(clbit) => !clbit_map.contains_key(clbit), - Wire::Var(var) => !bound_var_map.contains(var).unwrap(), + Wire::Var(var) => !bound_var_map.contains(other.vars.get(*var)).unwrap(), }), _ => false, } @@ -6107,7 +6056,11 @@ impl DAGCircuit { match edge.weight() { Wire::Qubit(qubit) => Wire::Qubit(qubit_map[qubit]), Wire::Clbit(clbit) => Wire::Clbit(clbit_map[clbit]), - Wire::Var(var) => Wire::Var(bound_var_map.get_item(var)?.unwrap().unbind()), + Wire::Var(var) => Wire::Var( + self.vars + .find(&bound_var_map.get_item(other.vars.get(*var))?.unwrap()) + .unwrap(), + ), }, ); } @@ -6127,9 +6080,13 @@ impl DAGCircuit { .clbit_io_map .get(reverse_clbit_map[&clbit].index()) .map(|x| x[0]), - Wire::Var(ref var) => { - let index = &reverse_var_map.get_item(var)?.unwrap().unbind(); - other.var_input_map.get(py, index) + Wire::Var(var) => { + let index = other + .vars + .find(&reverse_var_map.get_item(self.vars.get(var))?.unwrap()) + .unwrap() + .index(); + other.var_io_map.get(index).map(|x| x[0]) } }; let old_index = @@ -6163,9 +6120,13 @@ impl DAGCircuit { .clbit_io_map .get(reverse_clbit_map[&clbit].index()) .map(|x| x[1]), - Wire::Var(ref var) => { - let index = &reverse_var_map.get_item(var)?.unwrap().unbind(); - other.var_output_map.get(py, index) + Wire::Var(var) => { + let index = other + .vars + .find(&reverse_var_map.get_item(self.vars.get(var))?.unwrap()) + .unwrap() + .index(); + other.var_io_map.get(index).map(|x| x[1]) } }; let old_index = @@ -6210,16 +6171,9 @@ impl DAGCircuit { "cannot add var as its name shadows an existing var", )); } - let in_node = NodeType::VarIn(var.clone().unbind()); - let out_node = NodeType::VarOut(var.clone().unbind()); - let in_index = self.dag.add_node(in_node); - let out_index = self.dag.add_node(out_node); - self.dag - .add_edge(in_index, out_index, Wire::Var(var.clone().unbind())); - self.var_input_map - .insert(py, var.clone().unbind(), in_index); - self.var_output_map - .insert(py, var.clone().unbind(), out_index); + + let var_idx = self.vars.add(py, var, true)?; + let (in_index, out_index) = self.add_wire(Wire::Var(var_idx))?; self.vars_by_type[type_ as usize] .bind(py) .add(var.clone().unbind())?; @@ -6269,7 +6223,8 @@ impl DAGCircuit { } } for v in vars { - if !self.var_output_map.contains_key(py, &v) { + let var_idx = self.vars.find(v.bind(py)).unwrap(); + if !self.var_io_map.len() - 1 < var_idx.index() { return Err(DAGCircuitError::new_err(format!( "var {} not found in output map", v @@ -6322,6 +6277,7 @@ impl DAGCircuit { cargs_interner: Interner::with_capacity(num_clbits), qubits: BitData::with_capacity(py, "qubits".to_string(), num_qubits), clbits: BitData::with_capacity(py, "clbits".to_string(), num_clbits), + vars: BitData::with_capacity(py, "vars".to_string(), num_vars), global_phase: Param::Float(0.), duration: None, unit: "dt".to_string(), @@ -6329,8 +6285,7 @@ impl DAGCircuit { clbit_locations: PyDict::new_bound(py).unbind(), qubit_io_map: Vec::with_capacity(num_qubits), clbit_io_map: Vec::with_capacity(num_clbits), - var_input_map: _VarIndexMap::new(py), - var_output_map: _VarIndexMap::new(py), + var_io_map: Vec::with_capacity(num_vars), op_names: IndexMap::default(), control_flow_module: PyControlFlowModule::new(py)?, vars_info: HashMap::with_capacity(num_vars), @@ -6690,7 +6645,8 @@ impl DAGCircuit { } else { // If the var is not in the last nodes collection, the edge between the output node and its predecessor. // Then, store the predecessor's NodeIndex in the last nodes collection. - let output_node = self.var_output_map.get(py, var).unwrap(); + let var_idx = self.vars.find(var.bind(py)).unwrap(); + let output_node = self.var_io_map.get(var_idx.index()).unwrap()[1]; let (edge_id, predecessor_node) = self .dag .edges_directed(output_node, Incoming) @@ -6707,8 +6663,11 @@ impl DAGCircuit { if var_last_node == new_node { continue; } - self.dag - .add_edge(var_last_node, new_node, Wire::Var(var.clone_ref(py))); + self.dag.add_edge( + var_last_node, + new_node, + Wire::Var(self.vars.find(var.bind(py)).unwrap()), + ); } } @@ -6727,7 +6686,8 @@ impl DAGCircuit { // Add the output_nodes back to vars for item in vars_last_nodes.items() { let (var, node): (PyObject, usize) = item.extract()?; - let output_node = self.var_output_map.get(py, &var).unwrap(); + let var = self.vars.find(var.bind(py)).unwrap(); + let output_node = self.var_io_map.get(var.index()).unwrap()[1]; self.dag .add_edge(NodeIndex::new(node), output_node, Wire::Var(var)); } diff --git a/crates/circuit/src/dot_utils.rs b/crates/circuit/src/dot_utils.rs index 8558535788a0..c31488e92c81 100644 --- a/crates/circuit/src/dot_utils.rs +++ b/crates/circuit/src/dot_utils.rs @@ -59,7 +59,7 @@ where let edge_weight = match edge.weight() { Wire::Qubit(qubit) => dag.qubits().get(*qubit).unwrap(), Wire::Clbit(clbit) => dag.clbits().get(*clbit).unwrap(), - Wire::Var(var) => var, + Wire::Var(var) => dag.vars().get(*var).unwrap(), }; writeln!( file, diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs index 8705d52d1aa7..4955475e3507 100644 --- a/crates/circuit/src/lib.rs +++ b/crates/circuit/src/lib.rs @@ -79,6 +79,30 @@ impl Clbit { } } +// Note: Var is meant to be opaque outside of this crate, i.e. +// users have no business creating them directly and should instead +// get them from the containing circuit. +#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] +pub struct Var(pub(crate) BitType); + +impl Var { + /// Construct a new [Var] object from a usize. if you have a u32 you can + /// create a [Var] object directly with `Var(0u32)`. This will panic + /// if the `usize` index exceeds `u32::MAX`. + #[inline(always)] + pub fn new(index: usize) -> Self { + Var(index + .try_into() + .unwrap_or_else(|_| panic!("Index value '{}' exceeds the maximum bit width!", index))) + } + + /// Get the index of the [Var] + #[inline(always)] + pub fn index(&self) -> usize { + self.0 as usize + } +} + pub struct TupleLikeArg<'py> { value: Bound<'py, PyTuple>, } @@ -122,6 +146,18 @@ impl From for BitType { } } +impl From for Var { + fn from(value: BitType) -> Self { + Var(value) + } +} + +impl From for BitType { + fn from(value: Var) -> Self { + value.0 + } +} + pub fn circuit(m: &Bound) -> PyResult<()> { m.add_class::()?; m.add_class::()?; From 72170a5a462df2fb6f0d4845d264633e2dcd7aa7 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Fri, 25 Oct 2024 11:59:33 -0400 Subject: [PATCH 02/15] Construct PyDict directly where possible in __getstate__. --- crates/circuit/src/dag_circuit.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 86829eb76137..fc80f976fd86 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -493,7 +493,7 @@ impl DAGCircuit { .iter() .enumerate() .map(|(k, v)| (k, [v[0].index(), v[1].index()])) - .collect::>(), + .into_py_dict_bound(py), )?; out_dict.set_item( "clbit_io_map", @@ -501,7 +501,7 @@ impl DAGCircuit { .iter() .enumerate() .map(|(k, v)| (k, [v[0].index(), v[1].index()])) - .collect::>(), + .into_py_dict_bound(py), )?; out_dict.set_item( "var_io_map", @@ -509,7 +509,7 @@ impl DAGCircuit { .iter() .enumerate() .map(|(k, v)| (k, [v[0].index(), v[1].index()])) - .collect::>(), + .into_py_dict_bound(py), )?; out_dict.set_item("op_name", self.op_names.clone())?; out_dict.set_item( @@ -527,7 +527,7 @@ impl DAGCircuit { ), ) }) - .collect::>(), + .into_py_dict_bound(py), )?; out_dict.set_item("vars_by_type", self.vars_by_type.clone())?; out_dict.set_item("qubits", self.qubits.bits())?; From 1254ba177b670464664b8ee0e95cb44d7e12acd1 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Fri, 25 Oct 2024 15:31:41 -0400 Subject: [PATCH 03/15] Avoid collecting to Vec unnecessarily. --- crates/circuit/src/dag_circuit.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index fc80f976fd86..bc2aeaa11b88 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -5892,19 +5892,14 @@ impl DAGCircuit { } /// Returns an iterator over the first layer of the `DAGCircuit``. - pub fn front_layer(&self) -> impl Iterator { + pub fn front_layer(&self) -> impl Iterator + '_ { let mut graph_layers = self.multigraph_layers(); graph_layers.next(); - - let next_layer = graph_layers.next(); - match next_layer { - Some(layer) => Box::new(layer.into_iter().filter(|node| { - matches!(self.dag.node_weight(*node).unwrap(), NodeType::Operation(_)) - })) - .collect(), - None => vec![], - } - .into_iter() + graph_layers + .next() + .into_iter() + .flatten() + .filter(|node| matches!(self.dag.node_weight(*node).unwrap(), NodeType::Operation(_))) } fn substitute_node_with_subgraph( From 8219418e5eacc16002cf8582649b893c8247ce7f Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Mon, 28 Oct 2024 11:53:29 -0400 Subject: [PATCH 04/15] Move Var to DAGCircuit. --- crates/circuit/src/dag_circuit.rs | 63 ++++++++++++++++++++++++++++--- crates/circuit/src/lib.rs | 36 ------------------ 2 files changed, 57 insertions(+), 42 deletions(-) diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index bc2aeaa11b88..1f4bb2d0f02b 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -29,7 +29,7 @@ use crate::interner::{Interned, Interner}; use crate::operations::{Operation, OperationRef, Param, PyInstruction, StandardGate}; use crate::packed_instruction::{PackedInstruction, PackedOperation}; use crate::rustworkx_core_vnext::isomorphism; -use crate::{BitType, Clbit, Qubit, TupleLikeArg, Var}; +use crate::{BitType, Clbit, Qubit, TupleLikeArg}; use hashbrown::{HashMap, HashSet}; use indexmap::IndexMap; @@ -73,6 +73,47 @@ use std::cell::OnceCell; static CONTROL_FLOW_OP_NAMES: [&str; 4] = ["for_loop", "while_loop", "if_else", "switch_case"]; static SEMANTIC_EQ_SYMMETRIC: [&str; 4] = ["barrier", "swap", "break_loop", "continue_loop"]; +/// An opaque key type that identifies a variable within a [DAGCircuit]. +/// +/// When a new variable is added to the DAG, it is associated internally +/// with one of these keys. When enumerating DAG nodes and edges, you can +/// retrieve the associated variable instance via [DAGCircuit::get_var]. +/// +/// These keys are [Eq], but this is semantically valid only for keys +/// from the same [DAGCircuit] instance. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct Var(BitType); + +impl Var { + /// Construct a new [Var] object from a usize. if you have a u32 you can + /// create a [Var] object directly with `Var(0u32)`. This will panic + /// if the `usize` index exceeds `u32::MAX`. + #[inline(always)] + fn new(index: usize) -> Self { + Var(index + .try_into() + .unwrap_or_else(|_| panic!("Index value '{}' exceeds the maximum bit width!", index))) + } + + /// Get the index of the [Var] + #[inline(always)] + fn index(&self) -> usize { + self.0 as usize + } +} + +impl From for Var { + fn from(value: BitType) -> Self { + Var(value) + } +} + +impl From for BitType { + fn from(value: Var) -> Self { + value.0 + } +} + #[derive(Clone, Debug)] pub enum NodeType { QubitIn(Qubit), @@ -4684,7 +4725,8 @@ def _format(operand): "cannot add inputs to a circuit with captures", )); } - self.add_var(py, var, DAGVarType::Input) + self.add_var(py, var, DAGVarType::Input)?; + Ok(()) } /// Add a captured variable to the circuit. @@ -4700,7 +4742,8 @@ def _format(operand): "cannot add captures to a circuit with inputs", )); } - self.add_var(py, var, DAGVarType::Capture) + self.add_var(py, var, DAGVarType::Capture)?; + Ok(()) } /// Add a declared local variable to the circuit. @@ -4708,7 +4751,8 @@ def _format(operand): /// Args: /// var: the variable to add. fn add_declared_var(&mut self, py: Python, var: &Bound) -> PyResult<()> { - self.add_var(py, var, DAGVarType::Declare) + self.add_var(py, var, DAGVarType::Declare)?; + Ok(()) } /// Total number of classical variables tracked by the circuit. @@ -6148,7 +6192,14 @@ impl DAGCircuit { Ok(out_map) } - fn add_var(&mut self, py: Python, var: &Bound, type_: DAGVarType) -> PyResult<()> { + /// Retrieve a variable given its unique [Var] key within the DAG. + /// + /// The provided [Var] must be from this [DAGCircuit]. + pub fn get_var(&self, py: Python, var: Var) -> Option> { + self.vars.get(var).map(|v| v.bind(py).clone()) + } + + fn add_var(&mut self, py: Python, var: &Bound, type_: DAGVarType) -> PyResult { // The setup of the initial graph structure between an "in" and an "out" node is the same as // the bit-related `_add_wire`, but this logically needs to do different bookkeeping around // tracking the properties @@ -6181,7 +6232,7 @@ impl DAGCircuit { out_node: out_index, }, ); - Ok(()) + Ok(var_idx) } fn check_op_addition(&self, py: Python, inst: &PackedInstruction) -> PyResult<()> { diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs index 4955475e3507..8705d52d1aa7 100644 --- a/crates/circuit/src/lib.rs +++ b/crates/circuit/src/lib.rs @@ -79,30 +79,6 @@ impl Clbit { } } -// Note: Var is meant to be opaque outside of this crate, i.e. -// users have no business creating them directly and should instead -// get them from the containing circuit. -#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] -pub struct Var(pub(crate) BitType); - -impl Var { - /// Construct a new [Var] object from a usize. if you have a u32 you can - /// create a [Var] object directly with `Var(0u32)`. This will panic - /// if the `usize` index exceeds `u32::MAX`. - #[inline(always)] - pub fn new(index: usize) -> Self { - Var(index - .try_into() - .unwrap_or_else(|_| panic!("Index value '{}' exceeds the maximum bit width!", index))) - } - - /// Get the index of the [Var] - #[inline(always)] - pub fn index(&self) -> usize { - self.0 as usize - } -} - pub struct TupleLikeArg<'py> { value: Bound<'py, PyTuple>, } @@ -146,18 +122,6 @@ impl From for BitType { } } -impl From for Var { - fn from(value: BitType) -> Self { - Var(value) - } -} - -impl From for BitType { - fn from(value: Var) -> Self { - value.0 - } -} - pub fn circuit(m: &Bound) -> PyResult<()> { m.add_class::()?; m.add_class::()?; From 2eab355d6349255e55c69f38f047bb5a884012cc Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Mon, 28 Oct 2024 12:47:59 -0400 Subject: [PATCH 05/15] Fix lifetime. --- crates/circuit/src/dag_circuit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 1f4bb2d0f02b..0cea697e6867 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -6195,7 +6195,7 @@ impl DAGCircuit { /// Retrieve a variable given its unique [Var] key within the DAG. /// /// The provided [Var] must be from this [DAGCircuit]. - pub fn get_var(&self, py: Python, var: Var) -> Option> { + pub fn get_var<'py>(&self, py: Python<'py>, var: Var) -> Option> { self.vars.get(var).map(|v| v.bind(py).clone()) } From fbe75e8e75f02d913851ef62b6eb5691e4646e14 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Mon, 28 Oct 2024 16:27:56 -0400 Subject: [PATCH 06/15] Limit BitType visibility to pub(crate). By limiting its visibility to the `qiskit-circuit` crate, we can hide the storage type used by Qubit and Clbit as an implementation detail of the circuit crate (also done in this PR). As a result of this change, the accelerate crate (and future crates) are forced to use the `::new` and `::index` methods of `Qubit` and `Clbit`, which deal in terms of usize. --- .../src/barrier_before_final_measurement.rs | 2 +- .../basis_translator/compose_transforms.rs | 2 +- crates/accelerate/src/check_map.rs | 5 +- .../src/circuit_library/pauli_feature_map.rs | 8 +- .../src/circuit_library/quantum_volume.rs | 2 +- crates/accelerate/src/commutation_analysis.rs | 2 +- .../src/commutation_cancellation.rs | 4 +- crates/accelerate/src/commutation_checker.rs | 67 +- .../src/euler_one_qubit_decomposer.rs | 4 +- crates/accelerate/src/gate_direction.rs | 4 +- crates/accelerate/src/synthesis/linear/pmh.rs | 2 +- .../synthesis/linear_phase/cz_depth_lnn.rs | 2 +- crates/accelerate/src/two_qubit_decompose.rs | 42 +- crates/accelerate/src/unitary_compose.rs | 2 +- crates/accelerate/src/unitary_synthesis.rs | 29 +- crates/circuit/src/bit_data.rs | 1 + crates/circuit/src/lib.rs | 4 +- crates/circuit/src/operations.rs | 574 ++++++++++++------ 18 files changed, 491 insertions(+), 265 deletions(-) diff --git a/crates/accelerate/src/barrier_before_final_measurement.rs b/crates/accelerate/src/barrier_before_final_measurement.rs index b42be53f231c..da024b1e43eb 100644 --- a/crates/accelerate/src/barrier_before_final_measurement.rs +++ b/crates/accelerate/src/barrier_before_final_measurement.rs @@ -83,7 +83,7 @@ pub fn barrier_before_final_measurements( #[cfg(not(feature = "cache_pygates"))] instruction: new_barrier.unbind(), }; - let qargs: Vec = (0..dag.num_qubits() as u32).map(Qubit).collect(); + let qargs: Vec = (0..dag.num_qubits()).map(Qubit::new).collect(); #[cfg(feature = "cache_pygates")] { dag.apply_operation_back( diff --git a/crates/accelerate/src/basis/basis_translator/compose_transforms.rs b/crates/accelerate/src/basis/basis_translator/compose_transforms.rs index e4498af0f8a5..8ebb4b579488 100644 --- a/crates/accelerate/src/basis/basis_translator/compose_transforms.rs +++ b/crates/accelerate/src/basis/basis_translator/compose_transforms.rs @@ -72,7 +72,7 @@ pub(super) fn compose_transforms<'a>( .collect::>(), ))?; let gate_obj: OperationFromPython = gate.extract()?; - let qubits: Vec = (0..dag.num_qubits() as u32).map(Qubit).collect(); + let qubits: Vec = (0..dag.num_qubits()).map(Qubit::new).collect(); dag.apply_operation_back( py, gate_obj.operation, diff --git a/crates/accelerate/src/check_map.rs b/crates/accelerate/src/check_map.rs index 4d4702d18b49..4373a52214eb 100644 --- a/crates/accelerate/src/check_map.rs +++ b/crates/accelerate/src/check_map.rs @@ -72,7 +72,10 @@ fn recurse<'py>( { return Ok(Some(( inst.op.name().to_string(), - [qubits[0].0, qubits[1].0], + [ + qubits[0].index().try_into().unwrap(), + qubits[1].index().try_into().unwrap(), + ], ))); } } diff --git a/crates/accelerate/src/circuit_library/pauli_feature_map.rs b/crates/accelerate/src/circuit_library/pauli_feature_map.rs index 6fa88d187182..a5cf6dda838b 100644 --- a/crates/accelerate/src/circuit_library/pauli_feature_map.rs +++ b/crates/accelerate/src/circuit_library/pauli_feature_map.rs @@ -60,7 +60,7 @@ fn pauli_evolution( // Get pairs of (pauli, qubit) that are active, i.e. that are not the identity. Note that // the rest of the code also works if there are only identities, in which case we will // effectively return an empty iterator. - let qubits = indices.iter().map(|i| Qubit(*i)).collect_vec(); + let qubits = indices.iter().map(|i| Qubit::new(*i as usize)).collect_vec(); let binding = pauli.to_lowercase(); // lowercase for convenience let active_paulis = binding .as_str() @@ -217,11 +217,11 @@ pub fn pauli_feature_map( } fn _get_h_layer(feature_dimension: u32) -> impl Iterator { - (0..feature_dimension).map(|i| { + (0..feature_dimension as usize).map(|i| { ( StandardGate::HGate.into(), smallvec![], - vec![Qubit(i)], + vec![Qubit::new(i)], vec![] as Vec, ) }) @@ -346,7 +346,7 @@ fn _get_barrier(py: Python, feature_dimension: u32) -> PyResult { Ok(( barrier_inst.into(), smallvec![], - (0..feature_dimension).map(Qubit).collect(), + (0..feature_dimension as usize).map(Qubit::new).collect(), vec![] as Vec, )) } diff --git a/crates/accelerate/src/circuit_library/quantum_volume.rs b/crates/accelerate/src/circuit_library/quantum_volume.rs index e17357e7cec2..44157d80917a 100644 --- a/crates/accelerate/src/circuit_library/quantum_volume.rs +++ b/crates/accelerate/src/circuit_library/quantum_volume.rs @@ -111,7 +111,7 @@ pub fn quantum_volume( ) -> PyResult { let width = num_qubits as usize / 2; let num_unitaries = width * depth; - let mut permutation: Vec = (0..num_qubits).map(Qubit).collect(); + let mut permutation: Vec = (0..num_qubits as usize).map(Qubit::new).collect(); let kwargs = PyDict::new_bound(py); kwargs.set_item(intern!(py, "num_qubits"), 2)?; diff --git a/crates/accelerate/src/commutation_analysis.rs b/crates/accelerate/src/commutation_analysis.rs index 07266191fe45..a4dff4064b9d 100644 --- a/crates/accelerate/src/commutation_analysis.rs +++ b/crates/accelerate/src/commutation_analysis.rs @@ -59,7 +59,7 @@ pub(crate) fn analyze_commutations_inner( let mut node_indices: NodeIndices = HashMap::new(); for qubit in 0..dag.num_qubits() { - let wire = Wire::Qubit(Qubit(qubit as u32)); + let wire = Wire::Qubit(Qubit::new(qubit)); for current_gate_idx in dag.nodes_on_wire(&wire, false) { // get the commutation set associated with the current wire, or create a new diff --git a/crates/accelerate/src/commutation_cancellation.rs b/crates/accelerate/src/commutation_cancellation.rs index 8e4e9281e103..ce2a47659641 100644 --- a/crates/accelerate/src/commutation_cancellation.rs +++ b/crates/accelerate/src/commutation_cancellation.rs @@ -100,8 +100,8 @@ pub(crate) fn cancel_commutations( let (commutation_set, node_indices) = analyze_commutations_inner(py, dag, commutation_checker)?; let mut cancellation_sets: HashMap> = HashMap::new(); - (0..dag.num_qubits() as u32).for_each(|qubit| { - let wire = Qubit(qubit); + (0..dag.num_qubits()).for_each(|qubit| { + let wire = Qubit::new(qubit); if let Some(wire_commutation_set) = commutation_set.get(&Wire::Qubit(wire)) { for (com_set_idx, com_set) in wire_commutation_set.iter().enumerate() { if let Some(&nd) = com_set.first() { diff --git a/crates/accelerate/src/commutation_checker.rs b/crates/accelerate/src/commutation_checker.rs index 005e37ecc375..f97b7dd5abdc 100644 --- a/crates/accelerate/src/commutation_checker.rs +++ b/crates/accelerate/src/commutation_checker.rs @@ -23,13 +23,12 @@ use pyo3::intern; use pyo3::prelude::*; use pyo3::types::{IntoPyDict, PyBool, PyDict, PySequence, PyTuple}; -use qiskit_circuit::bit_data::BitData; use qiskit_circuit::circuit_instruction::{ExtraInstructionAttributes, OperationFromPython}; use qiskit_circuit::dag_node::DAGOpNode; use qiskit_circuit::imports::QI_OPERATOR; use qiskit_circuit::operations::OperationRef::{Gate as PyGateType, Operation as PyOperationType}; use qiskit_circuit::operations::{Operation, OperationRef, Param}; -use qiskit_circuit::{BitType, Clbit, Qubit}; +use qiskit_circuit::{Clbit, Qubit}; use crate::unitary_compose; use crate::QiskitError; @@ -43,24 +42,40 @@ static SUPPORTED_OP: Lazy> = Lazy::new(|| { ]) }); -fn get_bits( +fn get_bits( py: Python, + new_bit: F, bits1: &Bound, bits2: &Bound, ) -> PyResult<(Vec, Vec)> where - T: From + Copy, - BitType: From, + F: Fn(usize) -> T, { - let mut bitdata: BitData = BitData::new(py, "bits".to_string()); - + let bits = PyDict::new_bound(py); + let mut index = 0usize; for bit in bits1.iter().chain(bits2.iter()) { - bitdata.add(py, &bit, false)?; + if bits.contains(&bit)? { + continue; + } + bits.set_item(bit, index)?; + index += 1; } Ok(( - bitdata.map_bits(bits1)?.collect(), - bitdata.map_bits(bits2)?.collect(), + bits1 + .iter() + .map(|b| { + bits.get_item(b) + .map(|b| new_bit(b.unwrap().extract().unwrap())) + }) + .collect::>>()?, + bits2 + .iter() + .map(|b| { + bits.get_item(b) + .map(|b| new_bit(b.unwrap().extract().unwrap())) + }) + .collect::>>()?, )) } @@ -106,13 +121,15 @@ impl CommutationChecker { op2: &DAGOpNode, max_num_qubits: u32, ) -> PyResult { - let (qargs1, qargs2) = get_bits::( + let (qargs1, qargs2) = get_bits( py, + Qubit::new, op1.instruction.qubits.bind(py), op2.instruction.qubits.bind(py), )?; - let (cargs1, cargs2) = get_bits::( + let (cargs1, cargs2) = get_bits( py, + Clbit::new, op1.instruction.clbits.bind(py), op2.instruction.clbits.bind(py), )?; @@ -155,8 +172,8 @@ impl CommutationChecker { let cargs2 = cargs2.map_or_else(|| Ok(PyTuple::empty_bound(py)), PySequenceMethods::to_tuple)?; - let (qargs1, qargs2) = get_bits::(py, &qargs1, &qargs2)?; - let (cargs1, cargs2) = get_bits::(py, &cargs1, &cargs2)?; + let (qargs1, qargs2) = get_bits(py, Qubit::new, &qargs1, &qargs2)?; + let (cargs1, cargs2) = get_bits(py, Clbit::new, &cargs1, &cargs2)?; self.commute_inner( py, @@ -379,15 +396,15 @@ impl CommutationChecker { .enumerate() .map(|(i, q)| (q, Qubit::new(i))), ); - let mut num_qubits = first_qargs.len() as u32; + let mut num_qubits = first_qargs.len(); for q in second_qargs { if !qarg.contains_key(q) { - qarg.insert(q, Qubit(num_qubits)); + qarg.insert(q, Qubit::new(num_qubits)); num_qubits += 1; } } - let first_qarg: Vec = Vec::from_iter((0..first_qargs.len() as u32).map(Qubit)); + let first_qarg: Vec = Vec::from_iter((0..first_qargs.len()).map(Qubit::new)); let second_qarg: Vec = second_qargs.iter().map(|q| qarg[q]).collect(); if first_qarg.len() > second_qarg.len() { @@ -418,7 +435,7 @@ impl CommutationChecker { 2 => Ok(unitary_compose::commute_2q( &first_mat.view(), &second_mat.view(), - &[Qubit(0), Qubit(1)], + &[Qubit::new(0), Qubit::new(1)], rtol, atol, )), @@ -436,7 +453,7 @@ impl CommutationChecker { // 2. This code here expands the first op to match the second -- hence we always // match the operator sizes. // This whole extension logic could be avoided since we know the second one is larger. - let extra_qarg2 = num_qubits - first_qarg.len() as u32; + let extra_qarg2: u32 = (num_qubits as u32) - first_qarg.len() as u32; let first_mat = if extra_qarg2 > 0 { let id_op = Array2::::eye(usize::pow(2, extra_qarg2)); kron(&id_op, &first_mat) @@ -642,9 +659,9 @@ impl<'py> FromPyObject<'py> for CommutationLibraryEntry { let dict = b.downcast::()?; let mut ret = hashbrown::HashMap::with_capacity(dict.len()); for (k, v) in dict { - let raw_key: SmallVec<[Option; 2]> = k.extract()?; + let raw_key: SmallVec<[Option; 2]> = k.extract()?; let v: bool = v.extract()?; - let key = raw_key.into_iter().map(|key| key.map(Qubit)).collect(); + let key = raw_key.into_iter().map(|key| key.map(Qubit::new)).collect(); ret.insert(key, v); } Ok(CommutationLibraryEntry::QubitMapping(ret)) @@ -659,7 +676,7 @@ impl ToPyObject for CommutationLibraryEntry { .iter() .map(|(k, v)| { ( - PyTuple::new_bound(py, k.iter().map(|q| q.map(|t| t.0))), + PyTuple::new_bound(py, k.iter().map(|q| q.map(|t| t.index()))), PyBool::new_bound(py, *v), ) }) @@ -680,7 +697,7 @@ type CommutationCacheEntry = HashMap; fn commutation_entry_to_pydict(py: Python, entry: &CommutationCacheEntry) -> PyResult> { let out_dict = PyDict::new_bound(py); for (k, v) in entry.iter() { - let qubits = PyTuple::new_bound(py, k.0.iter().map(|q| q.map(|t| t.0))); + let qubits = PyTuple::new_bound(py, k.0.iter().map(|q| q.map(|t| t.index()))); let params0 = PyTuple::new_bound(py, k.1 .0.iter().map(|pk| pk.0)); let params1 = PyTuple::new_bound(py, k.1 .1.iter().map(|pk| pk.0)); out_dict.set_item( @@ -695,7 +712,7 @@ fn commutation_cache_entry_from_pydict(dict: &Bound) -> PyResult = raw_key.1 .0; let params1: SmallVec<_> = raw_key.1 .1; let v: bool = v.extract()?; @@ -705,7 +722,7 @@ fn commutation_cache_entry_from_pydict(dict: &Bound) -> PyResult; 2]>, + SmallVec<[Option; 2]>, (SmallVec<[ParameterKey; 3]>, SmallVec<[ParameterKey; 3]>), ); diff --git a/crates/accelerate/src/euler_one_qubit_decomposer.rs b/crates/accelerate/src/euler_one_qubit_decomposer.rs index b5ed6014faaa..1dd472a805c3 100644 --- a/crates/accelerate/src/euler_one_qubit_decomposer.rs +++ b/crates/accelerate/src/euler_one_qubit_decomposer.rs @@ -911,7 +911,7 @@ pub fn unitary_to_circuit( ( gate, params.into_iter().map(Param::Float).collect(), - smallvec![Qubit(0)], + smallvec![Qubit::new(0)], ) }), Param::Float(seq.global_phase), @@ -1090,7 +1090,7 @@ pub(crate) fn optimize_1q_gates_decomposition( None => raw_run.len() as f64, }; let qubit: PhysicalQubit = if let NodeType::Operation(inst) = &dag.dag()[raw_run[0]] { - PhysicalQubit::new(dag.get_qargs(inst.qubits)[0].0) + PhysicalQubit::new(dag.get_qargs(inst.qubits)[0].index().try_into().unwrap()) } else { unreachable!("nodes in runs will always be op nodes") }; diff --git a/crates/accelerate/src/gate_direction.rs b/crates/accelerate/src/gate_direction.rs index 3143a11a22a2..3c048b33dece 100644 --- a/crates/accelerate/src/gate_direction.rs +++ b/crates/accelerate/src/gate_direction.rs @@ -60,8 +60,8 @@ fn py_check_with_coupling_map( fn py_check_with_target(py: Python, dag: &DAGCircuit, target: &Target) -> PyResult { let target_check = |inst: &PackedInstruction, op_args: &[Qubit]| -> bool { let qargs = smallvec![ - PhysicalQubit::new(op_args[0].0), - PhysicalQubit::new(op_args[1].0) + PhysicalQubit::new(op_args[0].index().try_into().unwrap()), + PhysicalQubit::new(op_args[1].index().try_into().unwrap()) ]; target.instruction_supported(inst.op.name(), Some(&qargs)) diff --git a/crates/accelerate/src/synthesis/linear/pmh.rs b/crates/accelerate/src/synthesis/linear/pmh.rs index d7283fd580e4..81baec9f260e 100644 --- a/crates/accelerate/src/synthesis/linear/pmh.rs +++ b/crates/accelerate/src/synthesis/linear/pmh.rs @@ -187,7 +187,7 @@ pub fn synth_cnot_count_full_pmh( ( StandardGate::CXGate, smallvec![], - smallvec![Qubit(ctrl as u32), Qubit(target as u32)], + smallvec![Qubit::new(ctrl), Qubit::new(target)], ) }); diff --git a/crates/accelerate/src/synthesis/linear_phase/cz_depth_lnn.rs b/crates/accelerate/src/synthesis/linear_phase/cz_depth_lnn.rs index df01c1ed6fa8..1666ac44a6da 100644 --- a/crates/accelerate/src/synthesis/linear_phase/cz_depth_lnn.rs +++ b/crates/accelerate/src/synthesis/linear_phase/cz_depth_lnn.rs @@ -101,7 +101,7 @@ fn _append_phase_gate(pat_val: usize, gates: &mut LnnGatesVec, qubit: usize) { 3 => StandardGate::SGate, _ => unreachable!(), // unreachable as we have modulo 4 }; - gates.push((gate, smallvec![], smallvec![Qubit(qubit as u32)])); + gates.push((gate, smallvec![], smallvec![Qubit::new(qubit)])); } } diff --git a/crates/accelerate/src/two_qubit_decompose.rs b/crates/accelerate/src/two_qubit_decompose.rs index fb8c58baab9d..9e692a848892 100644 --- a/crates/accelerate/src/two_qubit_decompose.rs +++ b/crates/accelerate/src/two_qubit_decompose.rs @@ -528,16 +528,24 @@ impl TwoQubitWeylDecomposition { ) -> PyResult<()> { match self.specialization { Specialization::MirrorControlledEquiv => { - sequence.push_standard_gate(StandardGate::SwapGate, &[], &[Qubit(0), Qubit(1)])?; + sequence.push_standard_gate( + StandardGate::SwapGate, + &[], + &[Qubit::new(0), Qubit::new(1)], + )?; sequence.push_standard_gate( StandardGate::RZZGate, &[Param::Float((PI4 - self.c) * 2.)], - &[Qubit(0), Qubit(1)], + &[Qubit::new(0), Qubit::new(1)], )?; *global_phase += PI4 } Specialization::SWAPEquiv => { - sequence.push_standard_gate(StandardGate::SwapGate, &[], &[Qubit(0), Qubit(1)])?; + sequence.push_standard_gate( + StandardGate::SwapGate, + &[], + &[Qubit::new(0), Qubit::new(1)], + )?; *global_phase -= 3. * PI / 4. } _ => { @@ -545,21 +553,21 @@ impl TwoQubitWeylDecomposition { sequence.push_standard_gate( StandardGate::RXXGate, &[Param::Float(-self.a * 2.)], - &[Qubit(0), Qubit(1)], + &[Qubit::new(0), Qubit::new(1)], )?; } if !simplify || self.b.abs() > atol { sequence.push_standard_gate( StandardGate::RYYGate, &[Param::Float(-self.b * 2.)], - &[Qubit(0), Qubit(1)], + &[Qubit::new(0), Qubit::new(1)], )?; } if !simplify || self.c.abs() > atol { sequence.push_standard_gate( StandardGate::RZZGate, &[Param::Float(-self.c * 2.)], - &[Qubit(0), Qubit(1)], + &[Qubit::new(0), Qubit::new(1)], )?; } } @@ -1173,7 +1181,7 @@ impl TwoQubitWeylDecomposition { gate_sequence.push_standard_gate( gate.0, &gate.1.into_iter().map(Param::Float).collect::>(), - &[Qubit(0)], + &[Qubit::new(0)], )? } global_phase += c2r.global_phase; @@ -1190,7 +1198,7 @@ impl TwoQubitWeylDecomposition { gate_sequence.push_standard_gate( gate.0, &gate.1.into_iter().map(Param::Float).collect::>(), - &[Qubit(1)], + &[Qubit::new(1)], )? } global_phase += c2l.global_phase; @@ -1213,7 +1221,7 @@ impl TwoQubitWeylDecomposition { gate_sequence.push_standard_gate( gate.0, &gate.1.into_iter().map(Param::Float).collect::>(), - &[Qubit(0)], + &[Qubit::new(0)], )? } global_phase += c2r.global_phase; @@ -1230,7 +1238,7 @@ impl TwoQubitWeylDecomposition { gate_sequence.push_standard_gate( gate.0, &gate.1.into_iter().map(Param::Float).collect::>(), - &[Qubit(1)], + &[Qubit::new(1)], )? } gate_sequence.set_global_phase(py, Param::Float(global_phase))?; @@ -2197,12 +2205,12 @@ impl TwoQubitBasisDecomposer { Some(gate) => ( gate, params.into_iter().map(Param::Float).collect(), - qubits.into_iter().map(|x| Qubit(x.into())).collect(), + qubits.into_iter().map(|x| Qubit::new(x.into())).collect(), ), None => ( std_kak_gate, kak_gate.params.clone(), - qubits.into_iter().map(|x| Qubit(x.into())).collect(), + qubits.into_iter().map(|x| Qubit::new(x.into())).collect(), ), }), Param::Float(sequence.global_phase), @@ -2218,13 +2226,13 @@ impl TwoQubitBasisDecomposer { Some(gate) => Ok(( PackedOperation::from_standard(gate), params.into_iter().map(Param::Float).collect(), - qubits.into_iter().map(|x| Qubit(x.into())).collect(), + qubits.into_iter().map(|x| Qubit::new(x.into())).collect(), Vec::new(), )), None => Ok(( kak_gate.operation.clone(), kak_gate.params.clone(), - qubits.into_iter().map(|x| Qubit(x.into())).collect(), + qubits.into_iter().map(|x| Qubit::new(x.into())).collect(), Vec::new(), )), }), @@ -2285,8 +2293,10 @@ fn two_qubit_decompose_up_to_diagonal( .map(|(gate, param_floats, qubit_index)| { let params: SmallVec<[Param; 3]> = param_floats.into_iter().map(Param::Float).collect(); - let qubits: SmallVec<[Qubit; 2]> = - qubit_index.into_iter().map(|x| Qubit(x as u32)).collect(); + let qubits: SmallVec<[Qubit; 2]> = qubit_index + .into_iter() + .map(|x| Qubit::new(x.into())) + .collect(); (gate.unwrap_or(StandardGate::CXGate), params, qubits) }), Param::Float(circ_seq.global_phase + phase), diff --git a/crates/accelerate/src/unitary_compose.rs b/crates/accelerate/src/unitary_compose.rs index 7c4da99dd7d2..cfdb0ab2b512 100644 --- a/crates/accelerate/src/unitary_compose.rs +++ b/crates/accelerate/src/unitary_compose.rs @@ -186,7 +186,7 @@ pub fn commute_2q( rtol: f64, atol: f64, ) -> bool { - let rev = qargs[0].0 == 1; + let rev = qargs[0].index() == 1; for i in 0..4usize { for j in 0..4usize { // We compute AB and BA separately, to enable checking the relative difference diff --git a/crates/accelerate/src/unitary_synthesis.rs b/crates/accelerate/src/unitary_synthesis.rs index 1931f1e97b2b..288c2ead0c78 100644 --- a/crates/accelerate/src/unitary_synthesis.rs +++ b/crates/accelerate/src/unitary_synthesis.rs @@ -116,7 +116,7 @@ fn apply_synth_dag( let synth_qargs = synth_dag.get_qargs(out_packed_instr.qubits); let mapped_qargs: Vec = synth_qargs .iter() - .map(|qarg| out_qargs[qarg.0 as usize]) + .map(|qarg| out_qargs[qarg.index()]) .collect(); out_packed_instr.qubits = out_dag.qargs_interner.insert(&mapped_qargs); out_dag.push_back(py, out_packed_instr)?; @@ -254,7 +254,7 @@ fn py_run_main_loop( let new_ids = dag .get_qargs(packed_instr.qubits) .iter() - .map(|qarg| qubit_indices[qarg.0 as usize]) + .map(|qarg| qubit_indices[qarg.index()]) .collect_vec(); let res = py_run_main_loop( py, @@ -303,11 +303,14 @@ fn py_run_main_loop( // Run 1q synthesis [2, 2] => { let qubit = dag.get_qargs(packed_instr.qubits)[0]; - let target_basis_set = get_target_basis_set(target, PhysicalQubit::new(qubit.0)); + let target_basis_set = get_target_basis_set( + target, + PhysicalQubit::new(qubit.index().try_into().unwrap()), + ); let sequence = unitary_to_gate_sequence_inner( unitary.view(), &target_basis_set, - qubit.0 as usize, + qubit.index(), None, true, None, @@ -341,8 +344,8 @@ fn py_run_main_loop( let out_qargs = dag.get_qargs(packed_instr.qubits); // "ref_qubits" is used to access properties in the target. It accounts for control flow mapping. let ref_qubits: &[PhysicalQubit; 2] = &[ - PhysicalQubit::new(qubit_indices[out_qargs[0].0 as usize] as u32), - PhysicalQubit::new(qubit_indices[out_qargs[1].0 as usize] as u32), + PhysicalQubit::new(qubit_indices[out_qargs[0].index()] as u32), + PhysicalQubit::new(qubit_indices[out_qargs[1].index()] as u32), ]; let apply_original_op = |out_dag: &mut DAGCircuit| -> PyResult<()> { out_dag.push_back(py, packed_instr.clone())?; @@ -491,7 +494,7 @@ fn run_2q_unitary_synthesis( let inst_qubits = synth_dag .get_qargs(inst.qubits) .iter() - .map(|q| ref_qubits[q.0 as usize]) + .map(|q| ref_qubits[q.index()]) .collect(); ( inst.op.name().to_string(), @@ -836,8 +839,8 @@ fn preferred_direction( edge_set.insert(tuple); } } - let zero_one = edge_set.contains(&(qubits[0].0 as usize, qubits[1].0 as usize)); - let one_zero = edge_set.contains(&(qubits[1].0 as usize, qubits[0].0 as usize)); + let zero_one = edge_set.contains(&(qubits[0].index(), qubits[1].index())); + let one_zero = edge_set.contains(&(qubits[1].index(), qubits[0].index())); match (zero_one, one_zero) { (true, false) => Some(true), @@ -1001,12 +1004,12 @@ fn synth_su4_dag( match preferred_direction { None => Ok(synth_dag), Some(preferred_dir) => { - let mut synth_direction: Option> = None; + let mut synth_direction: Option> = None; for node in synth_dag.topological_op_nodes()? { let inst = &synth_dag.dag()[node].unwrap_operation(); if inst.op.num_qubits() == 2 { let qargs = synth_dag.get_qargs(inst.qubits); - synth_direction = Some(vec![qargs[0].0, qargs[1].0]); + synth_direction = Some(vec![qargs[0].index(), qargs[1].index()]); } } match synth_direction { @@ -1065,14 +1068,14 @@ fn reversed_synth_su4_dag( }; let mut target_dag = synth_dag.copy_empty_like(py, "alike")?; - let flip_bits: [Qubit; 2] = [Qubit(1), Qubit(0)]; + let flip_bits: [Qubit; 2] = [Qubit::new(1), Qubit::new(0)]; for node in synth_dag.topological_op_nodes()? { let mut inst = synth_dag.dag()[node].unwrap_operation().clone(); let qubits: Vec = synth_dag .qargs_interner() .get(inst.qubits) .iter() - .map(|x| flip_bits[x.0 as usize]) + .map(|x| flip_bits[x.index()]) .collect(); inst.qubits = target_dag.qargs_interner.insert_owned(qubits); target_dag.push_back(py, inst)?; diff --git a/crates/circuit/src/bit_data.rs b/crates/circuit/src/bit_data.rs index 56a2560385f2..9b1364b4886c 100644 --- a/crates/circuit/src/bit_data.rs +++ b/crates/circuit/src/bit_data.rs @@ -197,6 +197,7 @@ where bit ))); } + // TODO: looks like a bug where `idx` is wrong if not strict and already exists Ok(idx.into()) } diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs index 8705d52d1aa7..ca597099156c 100644 --- a/crates/circuit/src/lib.rs +++ b/crates/circuit/src/lib.rs @@ -32,9 +32,9 @@ mod rustworkx_core_vnext; use pyo3::prelude::*; use pyo3::types::{PySequence, PyTuple}; -pub type BitType = u32; +pub(crate) type BitType = u32; #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq, FromPyObject)] -pub struct Qubit(pub BitType); +pub struct Qubit(pub(crate) BitType); impl Qubit { /// Construct a new Qubit object from a usize, if you have a u32 you can diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index 67edb3aa0adc..d0a6e70e3bff 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -1004,7 +1004,7 @@ impl Operation for StandardGate { [( Self::UGate, smallvec![Param::Float(PI / 2.), FLOAT_ZERO, Param::Float(PI)], - smallvec![Qubit(0)], + smallvec![Qubit::new(0)], )], FLOAT_ZERO, ) @@ -1020,7 +1020,7 @@ impl Operation for StandardGate { [( Self::UGate, smallvec![Param::Float(PI), FLOAT_ZERO, Param::Float(PI)], - smallvec![Qubit(0)], + smallvec![Qubit::new(0)], )], FLOAT_ZERO, ) @@ -1039,7 +1039,7 @@ impl Operation for StandardGate { Param::Float(PI / 2.), Param::Float(PI / 2.), ], - smallvec![Qubit(0)], + smallvec![Qubit::new(0)], )], FLOAT_ZERO, ) @@ -1055,7 +1055,7 @@ impl Operation for StandardGate { [( Self::PhaseGate, smallvec![Param::Float(PI)], - smallvec![Qubit(0)], + smallvec![Qubit::new(0)], )], FLOAT_ZERO, ) @@ -1070,7 +1070,7 @@ impl Operation for StandardGate { [( Self::UGate, smallvec![FLOAT_ZERO, FLOAT_ZERO, params[0].clone()], - smallvec![Qubit(0)], + smallvec![Qubit::new(0)], )], FLOAT_ZERO, ) @@ -1086,7 +1086,7 @@ impl Operation for StandardGate { CircuitData::from_standard_gates( py, 1, - [(Self::UGate, defparams, smallvec![Qubit(0)])], + [(Self::UGate, defparams, smallvec![Qubit::new(0)])], FLOAT_ZERO, ) .expect("Unexpected Qiskit python bug"), @@ -1101,7 +1101,7 @@ impl Operation for StandardGate { [( Self::RGate, smallvec![theta.clone(), FLOAT_ZERO], - smallvec![Qubit(0)], + smallvec![Qubit::new(0)], )], FLOAT_ZERO, ) @@ -1117,7 +1117,7 @@ impl Operation for StandardGate { [( Self::RGate, smallvec![theta.clone(), Param::Float(PI / 2.)], - smallvec![Qubit(0)], + smallvec![Qubit::new(0)], )], FLOAT_ZERO, ) @@ -1133,7 +1133,7 @@ impl Operation for StandardGate { [( Self::PhaseGate, smallvec![theta.clone()], - smallvec![Qubit(0)], + smallvec![Qubit::new(0)], )], multiply_param(theta, -0.5, py), ) @@ -1148,7 +1148,7 @@ impl Operation for StandardGate { [( Self::PhaseGate, smallvec![Param::Float(PI / 2.)], - smallvec![Qubit(0)], + smallvec![Qubit::new(0)], )], FLOAT_ZERO, ) @@ -1163,7 +1163,7 @@ impl Operation for StandardGate { [( Self::PhaseGate, smallvec![Param::Float(-PI / 2.)], - smallvec![Qubit(0)], + smallvec![Qubit::new(0)], )], FLOAT_ZERO, ) @@ -1176,9 +1176,9 @@ impl Operation for StandardGate { py, 1, [ - (Self::SdgGate, smallvec![], smallvec![Qubit(0)]), - (Self::HGate, smallvec![], smallvec![Qubit(0)]), - (Self::SdgGate, smallvec![], smallvec![Qubit(0)]), + (Self::SdgGate, smallvec![], smallvec![Qubit::new(0)]), + (Self::HGate, smallvec![], smallvec![Qubit::new(0)]), + (Self::SdgGate, smallvec![], smallvec![Qubit::new(0)]), ], FLOAT_ZERO, ) @@ -1191,9 +1191,9 @@ impl Operation for StandardGate { py, 1, [ - (Self::SGate, smallvec![], smallvec![Qubit(0)]), - (Self::HGate, smallvec![], smallvec![Qubit(0)]), - (Self::SGate, smallvec![], smallvec![Qubit(0)]), + (Self::SGate, smallvec![], smallvec![Qubit::new(0)]), + (Self::HGate, smallvec![], smallvec![Qubit::new(0)]), + (Self::SGate, smallvec![], smallvec![Qubit::new(0)]), ], FLOAT_ZERO, ) @@ -1208,7 +1208,7 @@ impl Operation for StandardGate { [( Self::PhaseGate, smallvec![Param::Float(PI / 4.)], - smallvec![Qubit(0)], + smallvec![Qubit::new(0)], )], FLOAT_ZERO, ) @@ -1223,7 +1223,7 @@ impl Operation for StandardGate { [( Self::PhaseGate, smallvec![Param::Float(-PI / 4.)], - smallvec![Qubit(0)], + smallvec![Qubit::new(0)], )], FLOAT_ZERO, ) @@ -1239,7 +1239,7 @@ impl Operation for StandardGate { [( Self::PhaseGate, params.iter().cloned().collect(), - smallvec![Qubit(0)], + smallvec![Qubit::new(0)], )], FLOAT_ZERO, ) @@ -1254,7 +1254,7 @@ impl Operation for StandardGate { [( Self::UGate, smallvec![Param::Float(PI / 2.), params[0].clone(), params[1].clone()], - smallvec![Qubit(0)], + smallvec![Qubit::new(0)], )], FLOAT_ZERO, ) @@ -1269,7 +1269,7 @@ impl Operation for StandardGate { [( Self::UGate, params.iter().cloned().collect(), - smallvec![Qubit(0)], + smallvec![Qubit::new(0)], )], FLOAT_ZERO, ) @@ -1277,8 +1277,8 @@ impl Operation for StandardGate { ) }), Self::CHGate => Python::with_gil(|py| -> Option { - let q1 = smallvec![Qubit(1)]; - let q0_1 = smallvec![Qubit(0), Qubit(1)]; + let q1 = smallvec![Qubit::new(1)]; + let q0_1 = smallvec![Qubit::new(0), Qubit::new(1)]; Some( CircuitData::from_standard_gates( py, @@ -1300,8 +1300,8 @@ impl Operation for StandardGate { Self::CXGate => None, Self::CYGate => Python::with_gil(|py| -> Option { - let q1 = smallvec![Qubit(1)]; - let q0_1 = smallvec![Qubit(0), Qubit(1)]; + let q1 = smallvec![Qubit::new(1)]; + let q0_1 = smallvec![Qubit::new(0), Qubit::new(1)]; Some( CircuitData::from_standard_gates( py, @@ -1317,8 +1317,8 @@ impl Operation for StandardGate { ) }), Self::CZGate => Python::with_gil(|py| -> Option { - let q1 = smallvec![Qubit(1)]; - let q0_1 = smallvec![Qubit(0), Qubit(1)]; + let q1 = smallvec![Qubit::new(1)]; + let q0_1 = smallvec![Qubit::new(0), Qubit::new(1)]; Some( CircuitData::from_standard_gates( py, @@ -1339,8 +1339,16 @@ impl Operation for StandardGate { py, 2, [ - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), - (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(0)]), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(1)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(1), Qubit::new(0)], + ), ], FLOAT_ZERO, ) @@ -1356,13 +1364,13 @@ impl Operation for StandardGate { ( Self::RZXGate, smallvec![Param::Float(PI / 4.)], - smallvec![Qubit(0), Qubit(1)], + smallvec![Qubit::new(0), Qubit::new(1)], ), - (Self::XGate, smallvec![], smallvec![Qubit(0)]), + (Self::XGate, smallvec![], smallvec![Qubit::new(0)]), ( Self::RZXGate, smallvec![Param::Float(-PI / 4.)], - smallvec![Qubit(0), Qubit(1)], + smallvec![Qubit::new(0), Qubit::new(1)], ), ], FLOAT_ZERO, @@ -1376,9 +1384,21 @@ impl Operation for StandardGate { py, 2, [ - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), - (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(0)]), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(1)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(1), Qubit::new(0)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(1)], + ), ], FLOAT_ZERO, ) @@ -1391,12 +1411,20 @@ impl Operation for StandardGate { py, 2, [ - (Self::SGate, smallvec![], smallvec![Qubit(0)]), - (Self::SGate, smallvec![], smallvec![Qubit(1)]), - (Self::HGate, smallvec![], smallvec![Qubit(0)]), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), - (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(0)]), - (Self::HGate, smallvec![], smallvec![Qubit(1)]), + (Self::SGate, smallvec![], smallvec![Qubit::new(0)]), + (Self::SGate, smallvec![], smallvec![Qubit::new(1)]), + (Self::HGate, smallvec![], smallvec![Qubit::new(0)]), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(1)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(1), Qubit::new(0)], + ), + (Self::HGate, smallvec![], smallvec![Qubit::new(1)]), ], FLOAT_ZERO, ) @@ -1404,9 +1432,9 @@ impl Operation for StandardGate { ) }), Self::CPhaseGate => Python::with_gil(|py| -> Option { - let q0 = smallvec![Qubit(0)]; - let q1 = smallvec![Qubit(1)]; - let q0_1 = smallvec![Qubit(0), Qubit(1)]; + let q0 = smallvec![Qubit::new(0)]; + let q1 = smallvec![Qubit::new(1)]; + let q0_1 = smallvec![Qubit::new(0), Qubit::new(1)]; Some( CircuitData::from_standard_gates( py, @@ -1445,9 +1473,13 @@ impl Operation for StandardGate { ( Self::PhaseGate, smallvec![Param::Float(PI / 2.)], - smallvec![Qubit(1)], + smallvec![Qubit::new(1)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(1)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), ( Self::UGate, smallvec![ @@ -1455,9 +1487,13 @@ impl Operation for StandardGate { Param::Float(0.0), Param::Float(0.0) ], - smallvec![Qubit(1)], + smallvec![Qubit::new(1)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(1)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), ( Self::UGate, smallvec![ @@ -1465,7 +1501,7 @@ impl Operation for StandardGate { Param::Float(-PI / 2.), Param::Float(0.0) ], - smallvec![Qubit(1)], + smallvec![Qubit::new(1)], ), ], Param::Float(0.0), @@ -1483,15 +1519,23 @@ impl Operation for StandardGate { ( Self::RYGate, smallvec![multiply_param(theta, 0.5, py)], - smallvec![Qubit(1)], + smallvec![Qubit::new(1)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(1)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), ( Self::RYGate, smallvec![multiply_param(theta, -0.5, py)], - smallvec![Qubit(1)], + smallvec![Qubit::new(1)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(1)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), ], Param::Float(0.0), ) @@ -1508,15 +1552,23 @@ impl Operation for StandardGate { ( Self::RZGate, smallvec![multiply_param(theta, 0.5, py)], - smallvec![Qubit(1)], + smallvec![Qubit::new(1)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(1)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), ( Self::RZGate, smallvec![multiply_param(theta, -0.5, py)], - smallvec![Qubit(1)], + smallvec![Qubit::new(1)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(1)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), ], Param::Float(0.0), ) @@ -1524,9 +1576,9 @@ impl Operation for StandardGate { ) }), Self::CSGate => Python::with_gil(|py| -> Option { - let q0 = smallvec![Qubit(0)]; - let q1 = smallvec![Qubit(1)]; - let q0_1 = smallvec![Qubit(0), Qubit(1)]; + let q0 = smallvec![Qubit::new(0)]; + let q1 = smallvec![Qubit::new(1)]; + let q0_1 = smallvec![Qubit::new(0), Qubit::new(1)]; Some( CircuitData::from_standard_gates( py, @@ -1548,9 +1600,9 @@ impl Operation for StandardGate { ) }), Self::CSdgGate => Python::with_gil(|py| -> Option { - let q0 = smallvec![Qubit(0)]; - let q1 = smallvec![Qubit(1)]; - let q0_1 = smallvec![Qubit(0), Qubit(1)]; + let q0 = smallvec![Qubit::new(0)]; + let q1 = smallvec![Qubit::new(1)]; + let q0_1 = smallvec![Qubit::new(0), Qubit::new(1)]; Some( CircuitData::from_standard_gates( py, @@ -1572,8 +1624,8 @@ impl Operation for StandardGate { ) }), Self::CSXGate => Python::with_gil(|py| -> Option { - let q1 = smallvec![Qubit(1)]; - let q0_1 = smallvec![Qubit(0), Qubit(1)]; + let q1 = smallvec![Qubit::new(1)]; + let q0_1 = smallvec![Qubit::new(0), Qubit::new(1)]; Some( CircuitData::from_standard_gates( py, @@ -1612,19 +1664,23 @@ impl Operation for StandardGate { ( Self::PhaseGate, smallvec![params[3].clone()], - smallvec![Qubit(0)], + smallvec![Qubit::new(0)], ), ( Self::PhaseGate, smallvec![param_second_p], - smallvec![Qubit(0)], + smallvec![Qubit::new(0)], ), ( Self::PhaseGate, smallvec![param_third_p], - smallvec![Qubit(1)], + smallvec![Qubit::new(1)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(1)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), ( Self::UGate, smallvec![ @@ -1632,9 +1688,13 @@ impl Operation for StandardGate { FLOAT_ZERO, param_first_u ], - smallvec![Qubit(1)], + smallvec![Qubit::new(1)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(1)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), ( Self::UGate, smallvec![ @@ -1642,7 +1702,7 @@ impl Operation for StandardGate { params[1].clone(), FLOAT_ZERO ], - smallvec![Qubit(1)], + smallvec![Qubit::new(1)], ), ], FLOAT_ZERO, @@ -1659,19 +1719,27 @@ impl Operation for StandardGate { ( Self::U1Gate, smallvec![multiply_param(¶ms[0], 0.5, py)], - smallvec![Qubit(0)], + smallvec![Qubit::new(0)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(1)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), ( Self::U1Gate, smallvec![multiply_param(¶ms[0], -0.5, py)], - smallvec![Qubit(1)], + smallvec![Qubit::new(1)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(1)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), ( Self::U1Gate, smallvec![multiply_param(¶ms[0], 0.5, py)], - smallvec![Qubit(1)], + smallvec![Qubit::new(1)], ), ], FLOAT_ZERO, @@ -1700,13 +1768,21 @@ impl Operation for StandardGate { py, 2, [ - (Self::U1Gate, smallvec![param_first_u1], smallvec![Qubit(0)]), + ( + Self::U1Gate, + smallvec![param_first_u1], + smallvec![Qubit::new(0)], + ), ( Self::U1Gate, smallvec![param_second_u1], - smallvec![Qubit(1)], + smallvec![Qubit::new(1)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(1)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), ( Self::U3Gate, smallvec![ @@ -1714,9 +1790,13 @@ impl Operation for StandardGate { FLOAT_ZERO, param_first_u3 ], - smallvec![Qubit(1)], + smallvec![Qubit::new(1)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(1)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), ( Self::U3Gate, smallvec![ @@ -1724,7 +1804,7 @@ impl Operation for StandardGate { params[1].clone(), FLOAT_ZERO ], - smallvec![Qubit(1)], + smallvec![Qubit::new(1)], ), ], FLOAT_ZERO, @@ -1733,9 +1813,9 @@ impl Operation for StandardGate { ) }), Self::RXXGate => Python::with_gil(|py| -> Option { - let q0 = smallvec![Qubit(0)]; - let q1 = smallvec![Qubit(1)]; - let q0_q1 = smallvec![Qubit(0), Qubit(1)]; + let q0 = smallvec![Qubit::new(0)]; + let q1 = smallvec![Qubit::new(1)]; + let q0_q1 = smallvec![Qubit::new(0), Qubit::new(1)]; let theta = ¶ms[0]; Some( CircuitData::from_standard_gates( @@ -1756,9 +1836,9 @@ impl Operation for StandardGate { ) }), Self::RYYGate => Python::with_gil(|py| -> Option { - let q0 = smallvec![Qubit(0)]; - let q1 = smallvec![Qubit(1)]; - let q0_q1 = smallvec![Qubit(0), Qubit(1)]; + let q0 = smallvec![Qubit::new(0)]; + let q1 = smallvec![Qubit::new(1)]; + let q0_q1 = smallvec![Qubit::new(0), Qubit::new(1)]; let theta = ¶ms[0]; Some( CircuitData::from_standard_gates( @@ -1779,8 +1859,8 @@ impl Operation for StandardGate { ) }), Self::RZZGate => Python::with_gil(|py| -> Option { - let q1 = smallvec![Qubit(1)]; - let q0_q1 = smallvec![Qubit(0), Qubit(1)]; + let q1 = smallvec![Qubit::new(1)]; + let q0_q1 = smallvec![Qubit::new(0), Qubit::new(1)]; let theta = ¶ms[0]; Some( CircuitData::from_standard_gates( @@ -1797,8 +1877,8 @@ impl Operation for StandardGate { ) }), Self::RZXGate => Python::with_gil(|py| -> Option { - let q1 = smallvec![Qubit(1)]; - let q0_q1 = smallvec![Qubit(0), Qubit(1)]; + let q1 = smallvec![Qubit::new(1)]; + let q0_q1 = smallvec![Qubit::new(0), Qubit::new(1)]; let theta = ¶ms[0]; Some( CircuitData::from_standard_gates( @@ -1817,9 +1897,9 @@ impl Operation for StandardGate { ) }), Self::XXMinusYYGate => Python::with_gil(|py| -> Option { - let q0 = smallvec![Qubit(0)]; - let q1 = smallvec![Qubit(1)]; - let q0_1 = smallvec![Qubit(0), Qubit(1)]; + let q0 = smallvec![Qubit::new(0)]; + let q1 = smallvec![Qubit::new(1)]; + let q0_1 = smallvec![Qubit::new(0), Qubit::new(1)]; let theta = ¶ms[0]; let beta = ¶ms[1]; Some( @@ -1860,9 +1940,9 @@ impl Operation for StandardGate { ) }), Self::XXPlusYYGate => Python::with_gil(|py| -> Option { - let q0 = smallvec![Qubit(0)]; - let q1 = smallvec![Qubit(1)]; - let q1_0 = smallvec![Qubit(1), Qubit(0)]; + let q0 = smallvec![Qubit::new(0)]; + let q1 = smallvec![Qubit::new(1)]; + let q1_0 = smallvec![Qubit::new(1), Qubit::new(0)]; let theta = ¶ms[0]; let beta = ¶ms[1]; Some( @@ -1899,12 +1979,12 @@ impl Operation for StandardGate { ) }), Self::CCXGate => Python::with_gil(|py| -> Option { - let q0 = smallvec![Qubit(0)]; - let q1 = smallvec![Qubit(1)]; - let q2 = smallvec![Qubit(2)]; - let q0_1 = smallvec![Qubit(0), Qubit(1)]; - let q0_2 = smallvec![Qubit(0), Qubit(2)]; - let q1_2 = smallvec![Qubit(1), Qubit(2)]; + let q0 = smallvec![Qubit::new(0)]; + let q1 = smallvec![Qubit::new(1)]; + let q2 = smallvec![Qubit::new(2)]; + let q0_1 = smallvec![Qubit::new(0), Qubit::new(1)]; + let q0_2 = smallvec![Qubit::new(0), Qubit::new(2)]; + let q1_2 = smallvec![Qubit::new(1), Qubit::new(2)]; Some( CircuitData::from_standard_gates( py, @@ -1938,13 +2018,13 @@ impl Operation for StandardGate { py, 3, [ - (Self::HGate, smallvec![], smallvec![Qubit(2)]), + (Self::HGate, smallvec![], smallvec![Qubit::new(2)]), ( Self::CCXGate, smallvec![], - smallvec![Qubit(0), Qubit(1), Qubit(2)], + smallvec![Qubit::new(0), Qubit::new(1), Qubit::new(2)], ), - (Self::HGate, smallvec![], smallvec![Qubit(2)]), + (Self::HGate, smallvec![], smallvec![Qubit::new(2)]), ], FLOAT_ZERO, ) @@ -1957,13 +2037,21 @@ impl Operation for StandardGate { py, 3, [ - (Self::CXGate, smallvec![], smallvec![Qubit(2), Qubit(1)]), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(2), Qubit::new(1)], + ), ( Self::CCXGate, smallvec![], - smallvec![Qubit(0), Qubit(1), Qubit(2)], + smallvec![Qubit::new(0), Qubit::new(1), Qubit::new(2)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(2), Qubit::new(1)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(2), Qubit(1)]), ], FLOAT_ZERO, ) @@ -1972,9 +2060,9 @@ impl Operation for StandardGate { }), Self::RCCXGate => Python::with_gil(|py| -> Option { - let q2 = smallvec![Qubit(2)]; - let q0_2 = smallvec![Qubit(0), Qubit(2)]; - let q1_2 = smallvec![Qubit(1), Qubit(2)]; + let q2 = smallvec![Qubit::new(2)]; + let q0_2 = smallvec![Qubit::new(0), Qubit::new(2)]; + let q1_2 = smallvec![Qubit::new(1), Qubit::new(2)]; Some( CircuitData::from_standard_gates( py, @@ -2005,97 +2093,153 @@ impl Operation for StandardGate { py, 4, [ - (Self::HGate, smallvec![], smallvec![Qubit(3)]), + (Self::HGate, smallvec![], smallvec![Qubit::new(3)]), ( Self::PhaseGate, smallvec![Param::Float(PI / 8.)], - smallvec![Qubit(0)], + smallvec![Qubit::new(0)], ), ( Self::PhaseGate, smallvec![Param::Float(PI / 8.)], - smallvec![Qubit(1)], + smallvec![Qubit::new(1)], ), ( Self::PhaseGate, smallvec![Param::Float(PI / 8.)], - smallvec![Qubit(2)], + smallvec![Qubit::new(2)], ), ( Self::PhaseGate, smallvec![Param::Float(PI / 8.)], - smallvec![Qubit(3)], + smallvec![Qubit::new(3)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(1)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), ( Self::PhaseGate, smallvec![Param::Float(-PI / 8.)], - smallvec![Qubit(1)], + smallvec![Qubit::new(1)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(1)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(1), Qubit::new(2)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), - (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(2)]), ( Self::PhaseGate, smallvec![Param::Float(-PI / 8.)], - smallvec![Qubit(2)], + smallvec![Qubit::new(2)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(2)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(2)]), ( Self::PhaseGate, smallvec![Param::Float(PI / 8.)], - smallvec![Qubit(2)], + smallvec![Qubit::new(2)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(1), Qubit::new(2)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(2)]), ( Self::PhaseGate, smallvec![Param::Float(-PI / 8.)], - smallvec![Qubit(2)], + smallvec![Qubit::new(2)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(2)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(2), Qubit::new(3)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(2)]), - (Self::CXGate, smallvec![], smallvec![Qubit(2), Qubit(3)]), ( Self::PhaseGate, smallvec![Param::Float(-PI / 8.)], - smallvec![Qubit(3)], + smallvec![Qubit::new(3)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(1), Qubit::new(3)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(3)]), ( Self::PhaseGate, smallvec![Param::Float(PI / 8.)], - smallvec![Qubit(3)], + smallvec![Qubit::new(3)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(2), Qubit::new(3)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(2), Qubit(3)]), ( Self::PhaseGate, smallvec![Param::Float(-PI / 8.)], - smallvec![Qubit(3)], + smallvec![Qubit::new(3)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(3)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(3)]), ( Self::PhaseGate, smallvec![Param::Float(PI / 8.)], - smallvec![Qubit(3)], + smallvec![Qubit::new(3)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(2), Qubit::new(3)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(2), Qubit(3)]), ( Self::PhaseGate, smallvec![Param::Float(-PI / 8.)], - smallvec![Qubit(3)], + smallvec![Qubit::new(3)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(1), Qubit::new(3)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(3)]), ( Self::PhaseGate, smallvec![Param::Float(PI / 8.)], - smallvec![Qubit(3)], + smallvec![Qubit::new(3)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(2), Qubit::new(3)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(2), Qubit(3)]), ( Self::PhaseGate, smallvec![Param::Float(-PI / 8.)], - smallvec![Qubit(3)], + smallvec![Qubit::new(3)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(3)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(3)]), - (Self::HGate, smallvec![], smallvec![Qubit(3)]), + (Self::HGate, smallvec![], smallvec![Qubit::new(3)]), ], FLOAT_ZERO, ) @@ -2109,61 +2253,85 @@ impl Operation for StandardGate { py, 4, [ - (Self::HGate, smallvec![], smallvec![Qubit(3)]), + (Self::HGate, smallvec![], smallvec![Qubit::new(3)]), ( Self::CU1Gate, smallvec![Param::Float(PI / 8.)], - smallvec![Qubit(0), Qubit(3)], + smallvec![Qubit::new(0), Qubit::new(3)], ), - (Self::HGate, smallvec![], smallvec![Qubit(3)]), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), - (Self::HGate, smallvec![], smallvec![Qubit(3)]), + (Self::HGate, smallvec![], smallvec![Qubit::new(3)]), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(1)], + ), + (Self::HGate, smallvec![], smallvec![Qubit::new(3)]), ( Self::CU1Gate, smallvec![Param::Float(-PI / 8.)], - smallvec![Qubit(1), Qubit(3)], + smallvec![Qubit::new(1), Qubit::new(3)], ), - (Self::HGate, smallvec![], smallvec![Qubit(3)]), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), - (Self::HGate, smallvec![], smallvec![Qubit(3)]), + (Self::HGate, smallvec![], smallvec![Qubit::new(3)]), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(1)], + ), + (Self::HGate, smallvec![], smallvec![Qubit::new(3)]), ( Self::CU1Gate, smallvec![Param::Float(PI / 8.)], - smallvec![Qubit(1), Qubit(3)], + smallvec![Qubit::new(1), Qubit::new(3)], + ), + (Self::HGate, smallvec![], smallvec![Qubit::new(3)]), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(1), Qubit::new(2)], ), - (Self::HGate, smallvec![], smallvec![Qubit(3)]), - (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(2)]), - (Self::HGate, smallvec![], smallvec![Qubit(3)]), + (Self::HGate, smallvec![], smallvec![Qubit::new(3)]), ( Self::CU1Gate, smallvec![Param::Float(-PI / 8.)], - smallvec![Qubit(2), Qubit(3)], + smallvec![Qubit::new(2), Qubit::new(3)], ), - (Self::HGate, smallvec![], smallvec![Qubit(3)]), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(2)]), - (Self::HGate, smallvec![], smallvec![Qubit(3)]), + (Self::HGate, smallvec![], smallvec![Qubit::new(3)]), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(2)], + ), + (Self::HGate, smallvec![], smallvec![Qubit::new(3)]), ( Self::CU1Gate, smallvec![Param::Float(PI / 8.)], - smallvec![Qubit(2), Qubit(3)], + smallvec![Qubit::new(2), Qubit::new(3)], ), - (Self::HGate, smallvec![], smallvec![Qubit(3)]), - (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(2)]), - (Self::HGate, smallvec![], smallvec![Qubit(3)]), + (Self::HGate, smallvec![], smallvec![Qubit::new(3)]), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(1), Qubit::new(2)], + ), + (Self::HGate, smallvec![], smallvec![Qubit::new(3)]), ( Self::CU1Gate, smallvec![Param::Float(-PI / 8.)], - smallvec![Qubit(2), Qubit(3)], + smallvec![Qubit::new(2), Qubit::new(3)], + ), + (Self::HGate, smallvec![], smallvec![Qubit::new(3)]), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(2)], ), - (Self::HGate, smallvec![], smallvec![Qubit(3)]), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(2)]), - (Self::HGate, smallvec![], smallvec![Qubit(3)]), + (Self::HGate, smallvec![], smallvec![Qubit::new(3)]), ( Self::CU1Gate, smallvec![Param::Float(PI / 8.)], - smallvec![Qubit(2), Qubit(3)], + smallvec![Qubit::new(2), Qubit::new(3)], ), - (Self::HGate, smallvec![], smallvec![Qubit(3)]), + (Self::HGate, smallvec![], smallvec![Qubit::new(3)]), ], FLOAT_ZERO, ) @@ -2179,68 +2347,92 @@ impl Operation for StandardGate { ( Self::U2Gate, smallvec![FLOAT_ZERO, Param::Float(PI)], - smallvec![Qubit(3)], + smallvec![Qubit::new(3)], ), ( Self::U1Gate, smallvec![Param::Float(PI / 4.)], - smallvec![Qubit(3)], + smallvec![Qubit::new(3)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(2), Qubit::new(3)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(2), Qubit(3)]), ( Self::U1Gate, smallvec![Param::Float(-PI / 4.)], - smallvec![Qubit(3)], + smallvec![Qubit::new(3)], ), ( Self::U2Gate, smallvec![FLOAT_ZERO, Param::Float(PI)], - smallvec![Qubit(3)], + smallvec![Qubit::new(3)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(3)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(3)]), ( Self::U1Gate, smallvec![Param::Float(PI / 4.)], - smallvec![Qubit(3)], + smallvec![Qubit::new(3)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(1), Qubit::new(3)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(3)]), ( Self::U1Gate, smallvec![Param::Float(-PI / 4.)], - smallvec![Qubit(3)], + smallvec![Qubit::new(3)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(0), Qubit::new(3)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(3)]), ( Self::U1Gate, smallvec![Param::Float(PI / 4.)], - smallvec![Qubit(3)], + smallvec![Qubit::new(3)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(1), Qubit::new(3)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(3)]), ( Self::U1Gate, smallvec![Param::Float(-PI / 4.)], - smallvec![Qubit(3)], + smallvec![Qubit::new(3)], ), ( Self::U2Gate, smallvec![FLOAT_ZERO, Param::Float(PI)], - smallvec![Qubit(3)], + smallvec![Qubit::new(3)], ), ( Self::U1Gate, smallvec![Param::Float(PI / 4.)], - smallvec![Qubit(3)], + smallvec![Qubit::new(3)], + ), + ( + Self::CXGate, + smallvec![], + smallvec![Qubit::new(2), Qubit::new(3)], ), - (Self::CXGate, smallvec![], smallvec![Qubit(2), Qubit(3)]), ( Self::U1Gate, smallvec![Param::Float(-PI / 4.)], - smallvec![Qubit(3)], + smallvec![Qubit::new(3)], ), ( Self::U2Gate, smallvec![FLOAT_ZERO, Param::Float(PI)], - smallvec![Qubit(3)], + smallvec![Qubit::new(3)], ), ], FLOAT_ZERO, From 698c226a82b8782229bf40024b426008a5910a0e Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Mon, 28 Oct 2024 18:19:10 -0400 Subject: [PATCH 07/15] Make BitData private to qiskit-circuit. The BitData type was only ever intended as an implementation detail of CircuitData and DAGCircuit. As such, its visibility has been downgraded from pub to pub(crate). By limiting visibility of BitData to the circuit crate, we can fully encapsulate the storage type of Qubit, Clbit, and dagcircuit::Var. We also encourage the accelerate crate to avoid writing code which depends on conversion to and from the Python bit representations (there were only two existing places where BitData was being used in accelerate, and it'd be nice to avoid any more. --- .../src/circuit_library/pauli_feature_map.rs | 5 +- crates/accelerate/src/commutation_analysis.rs | 4 +- .../accelerate/src/convert_2q_block_matrix.rs | 152 +++++++++--------- crates/circuit/src/bit_data.rs | 2 +- crates/circuit/src/circuit_data.rs | 48 +++++- crates/circuit/src/converters.rs | 39 +---- crates/circuit/src/dag_circuit.rs | 20 ++- crates/circuit/src/lib.rs | 2 +- 8 files changed, 144 insertions(+), 128 deletions(-) diff --git a/crates/accelerate/src/circuit_library/pauli_feature_map.rs b/crates/accelerate/src/circuit_library/pauli_feature_map.rs index a5cf6dda838b..53115d0307b6 100644 --- a/crates/accelerate/src/circuit_library/pauli_feature_map.rs +++ b/crates/accelerate/src/circuit_library/pauli_feature_map.rs @@ -60,7 +60,10 @@ fn pauli_evolution( // Get pairs of (pauli, qubit) that are active, i.e. that are not the identity. Note that // the rest of the code also works if there are only identities, in which case we will // effectively return an empty iterator. - let qubits = indices.iter().map(|i| Qubit::new(*i as usize)).collect_vec(); + let qubits = indices + .iter() + .map(|i| Qubit::new(*i as usize)) + .collect_vec(); let binding = pauli.to_lowercase(); // lowercase for convenience let active_paulis = binding .as_str() diff --git a/crates/accelerate/src/commutation_analysis.rs b/crates/accelerate/src/commutation_analysis.rs index a4dff4064b9d..590b9101bda9 100644 --- a/crates/accelerate/src/commutation_analysis.rs +++ b/crates/accelerate/src/commutation_analysis.rs @@ -153,7 +153,7 @@ pub(crate) fn analyze_commutations( // we know all wires are of type Wire::Qubit, since in analyze_commutations_inner // we only iterater over the qubits let py_wire = match wire { - Wire::Qubit(q) => dag.qubits().get(q).unwrap().to_object(py), + Wire::Qubit(q) => dag.get_qubit(py, q).unwrap(), _ => return Err(PyValueError::new_err("Unexpected wire type.")), }; @@ -176,7 +176,7 @@ pub(crate) fn analyze_commutations( // Then we add the {(node, wire): index} dictionary for ((node_index, wire), index) in node_indices { let py_wire = match wire { - Wire::Qubit(q) => dag.qubits().get(q).unwrap().to_object(py), + Wire::Qubit(q) => dag.get_qubit(py, q).unwrap(), _ => return Err(PyValueError::new_err("Unexpected wire type.")), }; out_dict.set_item((dag.get_node(py, node_index)?, py_wire), index)?; diff --git a/crates/accelerate/src/convert_2q_block_matrix.rs b/crates/accelerate/src/convert_2q_block_matrix.rs index dc4d0b77c4a7..45e37515cd7b 100644 --- a/crates/accelerate/src/convert_2q_block_matrix.rs +++ b/crates/accelerate/src/convert_2q_block_matrix.rs @@ -10,104 +10,98 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -use pyo3::intern; use pyo3::prelude::*; use pyo3::types::PyDict; use pyo3::wrap_pyfunction; use pyo3::Python; use num_complex::Complex64; -use numpy::ndarray::linalg::kron; -use numpy::ndarray::{aview2, Array2, ArrayView2}; -use numpy::{IntoPyArray, PyArray2, PyReadonlyArray2}; -use smallvec::SmallVec; +use numpy::ndarray::{Array2, ArrayView2}; +use numpy::PyArray2; +use pyo3::exceptions::PyNotImplementedError; -use qiskit_circuit::bit_data::BitData; -use qiskit_circuit::circuit_instruction::CircuitInstruction; use qiskit_circuit::dag_node::DAGOpNode; -use qiskit_circuit::gate_matrix::ONE_QUBIT_IDENTITY; -use qiskit_circuit::imports::QI_OPERATOR; -use qiskit_circuit::operations::Operation; -use crate::QiskitError; - -fn get_matrix_from_inst<'py>( - py: Python<'py>, - inst: &'py CircuitInstruction, -) -> PyResult> { - if let Some(mat) = inst.operation.matrix(&inst.params) { - Ok(mat) - } else if inst.operation.try_standard_gate().is_some() { - Err(QiskitError::new_err( - "Parameterized gates can't be consolidated", - )) - } else { - Ok(QI_OPERATOR - .get_bound(py) - .call1((inst.get_operation(py)?,))? - .getattr(intern!(py, "data"))? - .extract::>()? - .as_array() - .to_owned()) - } -} +// fn get_matrix_from_inst<'py>( +// py: Python<'py>, +// inst: &'py CircuitInstruction, +// ) -> PyResult> { +// if let Some(mat) = inst.operation.matrix(&inst.params) { +// Ok(mat) +// } else if inst.operation.try_standard_gate().is_some() { +// Err(QiskitError::new_err( +// "Parameterized gates can't be consolidated", +// )) +// } else { +// Ok(QI_OPERATOR +// .get_bound(py) +// .call1((inst.get_operation(py)?,))? +// .getattr(intern!(py, "data"))? +// .extract::>()? +// .as_array() +// .to_owned()) +// } +// } /// Return the matrix Operator resulting from a block of Instructions. #[pyfunction] #[pyo3(text_signature = "(op_list, /")] pub fn blocks_to_matrix( - py: Python, - op_list: Vec>, - block_index_map_dict: &Bound, + _py: Python, + _op_list: Vec>, + _block_index_map_dict: &Bound, ) -> PyResult>> { + Err(PyNotImplementedError::new_err( + "needs rebase on https://github.com/Qiskit/qiskit/pull/13368", + )) // Build a BitData in block_index_map_dict order. block_index_map_dict is a dict of bits to // indices mapping the order of the qargs in the block. There should only be 2 entries since // there are only 2 qargs here (e.g. `{Qubit(): 0, Qubit(): 1}`) so we need to ensure that // we added the qubits to bit data in the correct index order. - let mut index_map: Vec = (0..block_index_map_dict.len()).map(|_| py.None()).collect(); - for bit_tuple in block_index_map_dict.items() { - let (bit, index): (PyObject, usize) = bit_tuple.extract()?; - index_map[index] = bit; - } - let mut bit_map: BitData = BitData::new(py, "qargs".to_string()); - for bit in index_map { - bit_map.add(py, bit.bind(py), true)?; - } - let identity = aview2(&ONE_QUBIT_IDENTITY); - let first_node = &op_list[0]; - let input_matrix = get_matrix_from_inst(py, &first_node.instruction)?; - let mut matrix: Array2 = match bit_map - .map_bits(first_node.instruction.qubits.bind(py).iter())? - .collect::>() - .as_slice() - { - [0] => kron(&identity, &input_matrix), - [1] => kron(&input_matrix, &identity), - [0, 1] => input_matrix, - [1, 0] => change_basis(input_matrix.view()), - [] => Array2::eye(4), - _ => unreachable!(), - }; - for node in op_list.into_iter().skip(1) { - let op_matrix = get_matrix_from_inst(py, &node.instruction)?; - let q_list = bit_map - .map_bits(node.instruction.qubits.bind(py).iter())? - .map(|x| x as u8) - .collect::>(); - - let result = match q_list.as_slice() { - [0] => Some(kron(&identity, &op_matrix)), - [1] => Some(kron(&op_matrix, &identity)), - [1, 0] => Some(change_basis(op_matrix.view())), - [] => Some(Array2::eye(4)), - _ => None, - }; - matrix = match result { - Some(result) => result.dot(&matrix), - None => op_matrix.dot(&matrix), - }; - } - Ok(matrix.into_pyarray_bound(py).unbind()) + // let mut index_map: Vec = (0..block_index_map_dict.len()).map(|_| py.None()).collect(); + // for bit_tuple in block_index_map_dict.items() { + // let (bit, index): (PyObject, usize) = bit_tuple.extract()?; + // index_map[index] = bit; + // } + // let mut bit_map: BitData = BitData::new(py, "qargs".to_string()); + // for bit in index_map { + // bit_map.add(py, bit.bind(py), true)?; + // } + // let identity = aview2(&ONE_QUBIT_IDENTITY); + // let first_node = &op_list[0]; + // let input_matrix = get_matrix_from_inst(py, &first_node.instruction)?; + // let mut matrix: Array2 = match bit_map + // .map_bits(first_node.instruction.qubits.bind(py).iter())? + // .collect::>() + // .as_slice() + // { + // [0] => kron(&identity, &input_matrix), + // [1] => kron(&input_matrix, &identity), + // [0, 1] => input_matrix, + // [1, 0] => change_basis(input_matrix.view()), + // [] => Array2::eye(4), + // _ => unreachable!(), + // }; + // for node in op_list.into_iter().skip(1) { + // let op_matrix = get_matrix_from_inst(py, &node.instruction)?; + // let q_list = bit_map + // .map_bits(node.instruction.qubits.bind(py).iter())? + // .map(|x| x as u8) + // .collect::>(); + // + // let result = match q_list.as_slice() { + // [0] => Some(kron(&identity, &op_matrix)), + // [1] => Some(kron(&op_matrix, &identity)), + // [1, 0] => Some(change_basis(op_matrix.view())), + // [] => Some(Array2::eye(4)), + // _ => None, + // }; + // matrix = match result { + // Some(result) => result.dot(&matrix), + // None => op_matrix.dot(&matrix), + // }; + // } + // Ok(matrix.into_pyarray_bound(py).unbind()) } /// Switches the order of qubits in a two qubit operation. diff --git a/crates/circuit/src/bit_data.rs b/crates/circuit/src/bit_data.rs index 9b1364b4886c..7a6fb9f9a846 100644 --- a/crates/circuit/src/bit_data.rs +++ b/crates/circuit/src/bit_data.rs @@ -71,7 +71,7 @@ impl PartialEq for BitAsKey { impl Eq for BitAsKey {} #[derive(Clone, Debug)] -pub struct BitData { +pub(crate) struct BitData { /// The public field name (i.e. `qubits` or `clbits`). description: String, /// Registered Python bits. diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs index 7d2d99181e02..449e34d85d58 100644 --- a/crates/circuit/src/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -32,6 +32,7 @@ use pyo3::pybacked::PyBackedStr; use pyo3::types::{IntoPyDict, PyDict, PyList, PySet, PyTuple, PyType}; use pyo3::{import_exception, intern, PyTraverseError, PyVisit}; +use crate::dag_circuit::DAGCircuit; use hashbrown::{HashMap, HashSet}; use indexmap::IndexMap; use smallvec::SmallVec; @@ -929,9 +930,48 @@ impl CircuitData { Ok(res) } + pub fn from_dag(py: Python, dag: &DAGCircuit, copy_operations: bool) -> PyResult { + use crate::dag_circuit::NodeType; + Self::from_packed_instructions( + py, + dag.qubits().clone(), + dag.clbits().clone(), + dag.qargs_interner().clone(), + dag.cargs_interner().clone(), + dag.topological_op_nodes()?.map(|node_index| { + let NodeType::Operation(ref instr) = dag.dag()[node_index] else { + unreachable!( + "The received node from topological_op_nodes() is not an Operation node." + ) + }; + if copy_operations { + let op = instr.op.py_deepcopy(py, None)?; + Ok(PackedInstruction { + op, + qubits: instr.qubits, + clbits: instr.clbits, + params: Some(Box::new( + instr + .params_view() + .iter() + .map(|param| param.clone_ref(py)) + .collect(), + )), + extra_attrs: instr.extra_attrs.clone(), + #[cfg(feature = "cache_pygates")] + py_op: OnceCell::new(), + }) + } else { + Ok(instr.clone()) + } + }), + dag.get_global_phase(), + ) + } + /// A constructor for CircuitData from an iterator of PackedInstruction objects /// - /// This is tpically useful when iterating over a CircuitData or DAGCircuit + /// This is typically useful when iterating over a CircuitData or DAGCircuit /// to construct a new CircuitData from the iterator of PackedInstructions. As /// such it requires that you have `BitData` and `Interner` objects to run. If /// you just wish to build a circuit data from an iterator of instructions @@ -956,7 +996,7 @@ impl CircuitData { /// of the operation while iterating for constructing the new `CircuitData`. An /// example of this use case is in `qiskit_circuit::converters::dag_to_circuit`. /// * global_phase: The global phase value to use for the new circuit. - pub fn from_packed_instructions( + pub(crate) fn from_packed_instructions( py: Python, qubits: BitData, clbits: BitData, @@ -1254,12 +1294,12 @@ impl CircuitData { } /// Returns an immutable view of the Qubits registered in the circuit - pub fn qubits(&self) -> &BitData { + pub(crate) fn qubits(&self) -> &BitData { &self.qubits } /// Returns an immutable view of the Classical bits registered in the circuit - pub fn clbits(&self) -> &BitData { + pub(crate) fn clbits(&self) -> &BitData { &self.clbits } diff --git a/crates/circuit/src/converters.rs b/crates/circuit/src/converters.rs index 37ba83ae5173..a4ddeeb9c6f2 100644 --- a/crates/circuit/src/converters.rs +++ b/crates/circuit/src/converters.rs @@ -21,8 +21,7 @@ use pyo3::{ }; use crate::circuit_data::CircuitData; -use crate::dag_circuit::{DAGCircuit, NodeType}; -use crate::packed_instruction::PackedInstruction; +use crate::dag_circuit::DAGCircuit; /// An extractable representation of a QuantumCircuit reserved only for /// conversion purposes. @@ -96,41 +95,7 @@ pub fn dag_to_circuit( dag: &DAGCircuit, copy_operations: bool, ) -> PyResult { - CircuitData::from_packed_instructions( - py, - dag.qubits().clone(), - dag.clbits().clone(), - dag.qargs_interner().clone(), - dag.cargs_interner().clone(), - dag.topological_op_nodes()?.map(|node_index| { - let NodeType::Operation(ref instr) = dag.dag()[node_index] else { - unreachable!( - "The received node from topological_op_nodes() is not an Operation node." - ) - }; - if copy_operations { - let op = instr.op.py_deepcopy(py, None)?; - Ok(PackedInstruction { - op, - qubits: instr.qubits, - clbits: instr.clbits, - params: Some(Box::new( - instr - .params_view() - .iter() - .map(|param| param.clone_ref(py)) - .collect(), - )), - extra_attrs: instr.extra_attrs.clone(), - #[cfg(feature = "cache_pygates")] - py_op: OnceCell::new(), - }) - } else { - Ok(instr.clone()) - } - }), - dag.get_global_phase(), - ) + CircuitData::from_dag(py, dag, copy_operations) } pub fn converters(m: &Bound) -> PyResult<()> { diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 0cea697e6867..5fbe9e46584f 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -4975,19 +4975,19 @@ impl DAGCircuit { /// Returns an immutable view of the Qubits registered in the circuit #[inline(always)] - pub fn qubits(&self) -> &BitData { + pub(crate) fn qubits(&self) -> &BitData { &self.qubits } /// Returns an immutable view of the Classical bits registered in the circuit #[inline(always)] - pub fn clbits(&self) -> &BitData { + pub(crate) fn clbits(&self) -> &BitData { &self.clbits } /// Returns an immutable view of the Variable wires registered in the circuit #[inline(always)] - pub fn vars(&self) -> &BitData { + pub(crate) fn vars(&self) -> &BitData { &self.vars } @@ -6192,6 +6192,20 @@ impl DAGCircuit { Ok(out_map) } + /// Retrieve a Python qubit given its [Qubit] within the DAG. + /// + /// The provided [Qubit] must be from this [DAGCircuit]. + pub fn get_qubit<'py>(&self, py: Python<'py>, qubit: Qubit) -> Option> { + self.qubits.get(qubit).map(|v| v.bind(py).clone()) + } + + /// Retrieve a Python clbit given its [Clbit] within the DAG. + /// + /// The provided [Clbit] must be from this [DAGCircuit]. + pub fn get_clbit<'py>(&self, py: Python<'py>, clbit: Clbit) -> Option> { + self.clbits.get(clbit).map(|v| v.bind(py).clone()) + } + /// Retrieve a variable given its unique [Var] key within the DAG. /// /// The provided [Var] must be from this [DAGCircuit]. diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs index ca597099156c..6592374fcf85 100644 --- a/crates/circuit/src/lib.rs +++ b/crates/circuit/src/lib.rs @@ -10,7 +10,7 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -pub mod bit_data; +mod bit_data; pub mod circuit_data; pub mod circuit_instruction; pub mod converters; From 536c19897ac124bdcef8189840516b099de61fb0 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Tue, 29 Oct 2024 17:13:25 -0400 Subject: [PATCH 08/15] Make Qubit and Clbit inner type private. Also removes From and Into implementations for bit types (and DAGCircuit's Var type) to improve data encapsulation outside of the circuit crate. In its place, we now have WireIndex which is private to the circuit crate, which we implement for our wire types and use as a bound for types used with BitData going forward. --- crates/accelerate/src/check_map.rs | 17 ++++---- crates/circuit/src/bit_data.rs | 51 ++++++++++++----------- crates/circuit/src/dag_circuit.rs | 38 +++++------------ crates/circuit/src/lib.rs | 66 ++++++++++++++---------------- 4 files changed, 77 insertions(+), 95 deletions(-) diff --git a/crates/accelerate/src/check_map.rs b/crates/accelerate/src/check_map.rs index 4373a52214eb..0665d92b880a 100644 --- a/crates/accelerate/src/check_map.rs +++ b/crates/accelerate/src/check_map.rs @@ -24,16 +24,16 @@ use qiskit_circuit::Qubit; fn recurse<'py>( py: Python<'py>, dag: &'py DAGCircuit, - edge_set: &'py HashSet<[u32; 2]>, + edge_set: &'py HashSet<[usize; 2]>, wire_map: Option<&'py [Qubit]>, -) -> PyResult> { +) -> PyResult> { let check_qubits = |qubits: &[Qubit]| -> bool { match wire_map { Some(wire_map) => { let mapped_bits = [wire_map[qubits[0].index()], wire_map[qubits[1].index()]]; - edge_set.contains(&[mapped_bits[0].into(), mapped_bits[1].into()]) + edge_set.contains(&[mapped_bits[0].index(), mapped_bits[1].index()]) } - None => edge_set.contains(&[qubits[0].into(), qubits[1].into()]), + None => edge_set.contains(&[qubits[0].index(), qubits[1].index()]), } }; for node in dag.op_nodes(false) { @@ -72,10 +72,7 @@ fn recurse<'py>( { return Ok(Some(( inst.op.name().to_string(), - [ - qubits[0].index().try_into().unwrap(), - qubits[1].index().try_into().unwrap(), - ], + [qubits[0].index(), qubits[1].index()], ))); } } @@ -87,8 +84,8 @@ fn recurse<'py>( pub fn check_map( py: Python, dag: &DAGCircuit, - edge_set: HashSet<[u32; 2]>, -) -> PyResult> { + edge_set: HashSet<[usize; 2]>, +) -> PyResult> { recurse(py, dag, &edge_set, None) } diff --git a/crates/circuit/src/bit_data.rs b/crates/circuit/src/bit_data.rs index 7a6fb9f9a846..bcfdd8196ba3 100644 --- a/crates/circuit/src/bit_data.rs +++ b/crates/circuit/src/bit_data.rs @@ -10,7 +10,6 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -use crate::BitType; use hashbrown::HashMap; use pyo3::exceptions::{PyKeyError, PyRuntimeError, PyValueError}; use pyo3::prelude::*; @@ -70,6 +69,26 @@ impl PartialEq for BitAsKey { impl Eq for BitAsKey {} +/// An intentionally crate-private trait that must be implemented +/// for types used with [BitData]. +/// +/// Because this is private to the crate, implementing it does +/// not automatically make the implementing type convertible to +/// and from a [usize]. This is handy for types like +/// [crate::dagcircuit::Var] which are intended to serve as opaque +/// keys to access variable data managed by a +/// [crate::dagcircuit::DAGCircuit]. +pub(crate) trait WireIndex { + fn new(index: usize) -> Self; + fn index(&self) -> usize; +} + +/// An internal structure used to track the mapping between +/// wire indices and Python bits. +/// +/// This is very much only intended as a short-term method of +/// tracking Python bits, and should not be exposed beyond +/// this crate. #[derive(Clone, Debug)] pub(crate) struct BitData { /// The public field name (i.e. `qubits` or `clbits`). @@ -84,8 +103,7 @@ pub(crate) struct BitData { impl BitData where - T: From + Copy, - BitType: From, + T: WireIndex + Copy, { pub fn new(py: Python<'_>, description: String) -> Self { BitData { @@ -96,7 +114,7 @@ where } } - pub fn with_capacity(py: Python<'_>, description: String, capacity: usize) -> Self { + pub fn with_capacity(py: Python, description: String, capacity: usize) -> Self { BitData { description, bits: Vec::with_capacity(capacity), @@ -168,7 +186,7 @@ where /// bit index. #[inline] pub fn get(&self, index: T) -> Option<&PyObject> { - self.bits.get(>::from(index) as usize) + self.bits.get(index.index()) } /// Adds a new Python bit. @@ -178,17 +196,8 @@ where format!("This circuit's {} list has become out of sync with the circuit data. Did something modify it?", self.description) )); } - let idx: BitType = self.bits.len().try_into().map_err(|_| { - PyRuntimeError::new_err(format!( - "The number of {} in the circuit has exceeded the maximum capacity", - self.description - )) - })?; - if self - .indices - .try_insert(BitAsKey::new(bit), idx.into()) - .is_ok() - { + let idx = T::new(self.bits.len()); + if self.indices.try_insert(BitAsKey::new(bit), idx).is_ok() { self.bits.push(bit.into_py(py)); self.cached.bind(py).append(bit)?; } else if strict { @@ -198,17 +207,14 @@ where ))); } // TODO: looks like a bug where `idx` is wrong if not strict and already exists - Ok(idx.into()) + Ok(idx) } pub fn remove_indices(&mut self, py: Python, indices: I) -> PyResult<()> where I: IntoIterator, { - let mut indices_sorted: Vec = indices - .into_iter() - .map(|i| >::from(i) as usize) - .collect(); + let mut indices_sorted: Vec = indices.into_iter().map(|i| i.index()).collect(); indices_sorted.sort(); for index in indices_sorted.into_iter().rev() { @@ -218,8 +224,7 @@ where } // Update indices. for (i, bit) in self.bits.iter().enumerate() { - self.indices - .insert(BitAsKey::new(bit.bind(py)), (i as BitType).into()); + self.indices.insert(BitAsKey::new(bit.bind(py)), T::new(i)); } Ok(()) } diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 5fbe9e46584f..a0f5d96a611e 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -15,7 +15,7 @@ use std::hash::Hash; use ahash::RandomState; use smallvec::SmallVec; -use crate::bit_data::BitData; +use crate::bit_data::{BitData, WireIndex}; use crate::circuit_data::CircuitData; use crate::circuit_instruction::{ CircuitInstruction, ExtraInstructionAttributes, OperationFromPython, @@ -29,7 +29,7 @@ use crate::interner::{Interned, Interner}; use crate::operations::{Operation, OperationRef, Param, PyInstruction, StandardGate}; use crate::packed_instruction::{PackedInstruction, PackedOperation}; use crate::rustworkx_core_vnext::isomorphism; -use crate::{BitType, Clbit, Qubit, TupleLikeArg}; +use crate::{Clbit, Qubit, TupleLikeArg}; use hashbrown::{HashMap, HashSet}; use indexmap::IndexMap; @@ -82,38 +82,22 @@ static SEMANTIC_EQ_SYMMETRIC: [&str; 4] = ["barrier", "swap", "break_loop", "con /// These keys are [Eq], but this is semantically valid only for keys /// from the same [DAGCircuit] instance. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub struct Var(BitType); +pub struct Var(u32); -impl Var { - /// Construct a new [Var] object from a usize. if you have a u32 you can - /// create a [Var] object directly with `Var(0u32)`. This will panic - /// if the `usize` index exceeds `u32::MAX`. - #[inline(always)] +// Note that the implementation is private to this crate +// because the visibility of WireIndex is only pub(crate). +// This is intentional, since it prevents users from creating +// a Var, which should only be done by DAGCircuit. +impl WireIndex for Var { fn new(index: usize) -> Self { - Var(index - .try_into() - .unwrap_or_else(|_| panic!("Index value '{}' exceeds the maximum bit width!", index))) + Var(index.try_into().expect("Variable storage exceeded.")) } - /// Get the index of the [Var] - #[inline(always)] fn index(&self) -> usize { self.0 as usize } } -impl From for Var { - fn from(value: BitType) -> Self { - Var(value) - } -} - -impl From for BitType { - fn from(value: Var) -> Self { - value.0 - } -} - #[derive(Clone, Debug)] pub enum NodeType { QubitIn(Qubit), @@ -942,10 +926,10 @@ def _format(operand): } params.push(p.to_object(py)); } - let qubits: Vec = self + let qubits: Vec = self .qubits .map_bits(node.instruction.qubits.bind(py).iter())? - .map(|bit| bit.0) + .map(|bit| bit.index()) .collect(); let qubits = PyTuple::new_bound(py, qubits); let params = PyTuple::new_bound(py, params); diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs index 6592374fcf85..3dbe4e09526d 100644 --- a/crates/circuit/src/lib.rs +++ b/crates/circuit/src/lib.rs @@ -29,17 +29,17 @@ pub mod util; mod rustworkx_core_vnext; +use crate::bit_data::WireIndex; use pyo3::prelude::*; use pyo3::types::{PySequence, PyTuple}; -pub(crate) type BitType = u32; #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq, FromPyObject)] -pub struct Qubit(pub(crate) BitType); +pub struct Qubit(u32); impl Qubit { - /// Construct a new Qubit object from a usize, if you have a u32 you can - /// create a `Qubit` object directly with `Qubit(0u32)`. This will panic - /// if the `usize` index exceeds `u32::MAX`. + /// Construct a new [Qubit] from a usize. + /// + /// This will panic if the `usize` index exceeds `u32::MAX`. #[inline(always)] pub fn new(index: usize) -> Self { Qubit( @@ -49,20 +49,30 @@ impl Qubit { ) } - /// Convert a Qubit to a usize + /// Convert a [Qubit] to a usize. #[inline(always)] pub fn index(&self) -> usize { self.0 as usize } } +impl WireIndex for Qubit { + fn new(index: usize) -> Self { + Qubit::new(index) + } + + fn index(&self) -> usize { + self.index() + } +} + #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] -pub struct Clbit(pub BitType); +pub struct Clbit(u32); impl Clbit { - /// Construct a new Clbit object from a usize. if you have a u32 you can - /// create a `Clbit` object directly with `Clbit(0u32)`. This will panic - /// if the `usize` index exceeds `u32::MAX`. + /// Construct a new [Clbit] from a usize. + /// + /// This will panic if the `usize` index exceeds `u32::MAX`. #[inline(always)] pub fn new(index: usize) -> Self { Clbit( @@ -72,13 +82,23 @@ impl Clbit { ) } - /// Convert a Clbit to a usize + /// Convert a [Clbit] to a usize. #[inline(always)] pub fn index(&self) -> usize { self.0 as usize } } +impl WireIndex for Clbit { + fn new(index: usize) -> Self { + Clbit::new(index) + } + + fn index(&self) -> usize { + self.index() + } +} + pub struct TupleLikeArg<'py> { value: Bound<'py, PyTuple>, } @@ -98,30 +118,6 @@ impl<'py> FromPyObject<'py> for TupleLikeArg<'py> { } } -impl From for Qubit { - fn from(value: BitType) -> Self { - Qubit(value) - } -} - -impl From for BitType { - fn from(value: Qubit) -> Self { - value.0 - } -} - -impl From for Clbit { - fn from(value: BitType) -> Self { - Clbit(value) - } -} - -impl From for BitType { - fn from(value: Clbit) -> Self { - value.0 - } -} - pub fn circuit(m: &Bound) -> PyResult<()> { m.add_class::()?; m.add_class::()?; From 9d364d44056a2a0ea6f752c29ee5c4f4792a1fff Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Mon, 11 Nov 2024 12:43:21 -0500 Subject: [PATCH 09/15] Backup --- .../accelerate/src/circuit_library/blocks.rs | 16 +++---- .../src/circuit_library/entanglement.rs | 44 +++++++++---------- crates/accelerate/src/circuit_library/iqp.rs | 6 +-- .../src/circuit_library/multi_local.rs | 10 ++--- .../src/circuit_library/pauli_evolution.rs | 30 ++++++------- .../src/synthesis/evolution/pauli_network.rs | 16 +++---- crates/circuit/src/circuit_data.rs | 2 +- 7 files changed, 62 insertions(+), 62 deletions(-) diff --git a/crates/accelerate/src/circuit_library/blocks.rs b/crates/accelerate/src/circuit_library/blocks.rs index 80add611abb1..746279cdad6e 100644 --- a/crates/accelerate/src/circuit_library/blocks.rs +++ b/crates/accelerate/src/circuit_library/blocks.rs @@ -65,7 +65,7 @@ impl BlockOperation { #[pyclass] pub struct Block { pub operation: BlockOperation, - pub num_qubits: u32, + pub num_qubits: usize, pub num_parameters: usize, } @@ -76,7 +76,7 @@ impl Block { pub fn from_standard_gate(gate: StandardGate) -> Self { Block { operation: BlockOperation::Standard { gate }, - num_qubits: gate.num_qubits(), + num_qubits: gate.num_qubits() as usize, num_parameters: gate.num_params() as usize, } } @@ -98,7 +98,7 @@ impl Block { operation: BlockOperation::PyCustom { builder: builder.to_object(py), }, - num_qubits: num_qubits as u32, + num_qubits: num_qubits as usize, num_parameters: num_parameters as usize, }; @@ -108,11 +108,11 @@ impl Block { // We introduce typedefs to make the types more legible. We can understand the hierarchy // as follows: -// Connection: Vec -- indices that the multi-qubit gate acts on +// Connection: Vec -- indices that the multi-qubit gate acts on // BlockEntanglement: Vec -- entanglement for single block // LayerEntanglement: Vec -- entanglements for all blocks in the layer // Entanglement: Vec -- entanglement for every layer -type BlockEntanglement = Vec>; +type BlockEntanglement = Vec>; pub(super) type LayerEntanglement = Vec; /// Represent the entanglement in an n-local circuit. @@ -127,7 +127,7 @@ pub struct Entanglement { impl Entanglement { /// Create an entanglement from the input of an n_local circuit. pub fn from_py( - num_qubits: u32, + num_qubits: usize, reps: usize, entanglement: &Bound, entanglement_blocks: &[&Block], @@ -158,7 +158,7 @@ impl Entanglement { } fn unpack_entanglement( - num_qubits: u32, + num_qubits: usize, layer: usize, entanglement: &Bound, entanglement_blocks: &[&Block], @@ -166,7 +166,7 @@ fn unpack_entanglement( entanglement_blocks .iter() .zip(entanglement.iter()) - .map(|(block, ent)| -> PyResult>> { + .map(|(block, ent)| -> PyResult>> { get_entanglement(num_qubits, block.num_qubits, &ent, layer)?.collect() }) .collect() diff --git a/crates/accelerate/src/circuit_library/entanglement.rs b/crates/accelerate/src/circuit_library/entanglement.rs index 2168414cc4b0..543791b828f5 100644 --- a/crates/accelerate/src/circuit_library/entanglement.rs +++ b/crates/accelerate/src/circuit_library/entanglement.rs @@ -22,13 +22,13 @@ use crate::QiskitError; /// Get all-to-all entanglement. For 4 qubits and block size 2 we have: /// [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)] -fn full(num_qubits: u32, block_size: u32) -> impl Iterator> { +fn full(num_qubits: usize, block_size: usize) -> impl Iterator> { (0..num_qubits).combinations(block_size as usize) } /// Get a linear entanglement structure. For ``n`` qubits and block size ``m`` we have: /// [(0..m-1), (1..m), (2..m+1), ..., (n-m..n-1)] -fn linear(num_qubits: u32, block_size: u32) -> impl DoubleEndedIterator> { +fn linear(num_qubits: usize, block_size: usize) -> impl DoubleEndedIterator> { (0..num_qubits - block_size + 1) .map(move |start_index| (start_index..start_index + block_size).collect()) } @@ -37,7 +37,7 @@ fn linear(num_qubits: u32, block_size: u32) -> impl DoubleEndedIterator impl Iterator> { +fn reverse_linear(num_qubits: usize, block_size: usize) -> impl Iterator> { linear(num_qubits, block_size).rev() } @@ -45,7 +45,7 @@ fn reverse_linear(num_qubits: u32, block_size: u32) -> impl Iterator Box>> { +fn circular(num_qubits: usize, block_size: usize) -> Box>> { if block_size == 1 || num_qubits == block_size { Box::new(linear(num_qubits, block_size)) } else { @@ -61,7 +61,7 @@ fn circular(num_qubits: u32, block_size: u32) -> Box impl Iterator> { +fn pairwise(num_qubits: usize) -> impl Iterator> { // for Python-folks (like me): pairwise is equal to linear[::2] + linear[1::2] linear(num_qubits, 2) .step_by(2) @@ -77,13 +77,13 @@ fn pairwise(num_qubits: u32) -> impl Iterator> { /// Offset 2 -> [(0,1,2), (1,2,3), (2,3,0), (3,0,1)] /// ... fn shift_circular_alternating( - num_qubits: u32, - block_size: u32, + num_qubits: usize, + block_size: usize, offset: usize, -) -> Box>> { +) -> Box>> { // index at which we split the circular iterator -- we use Python-like indexing here, // and define ``split`` as equivalent to a Python index of ``-offset`` - let split = (num_qubits - (offset as u32 % num_qubits)) % num_qubits; + let split = (num_qubits - (offset % num_qubits)) % num_qubits; let shifted = circular(num_qubits, block_size) .skip(split as usize) .chain(circular(num_qubits, block_size).take(split as usize)); @@ -109,11 +109,11 @@ fn shift_circular_alternating( /// The entangler map using mode ``entanglement`` to scatter a block of ``block_size`` /// qubits on ``num_qubits`` qubits. pub fn get_entanglement_from_str( - num_qubits: u32, - block_size: u32, + num_qubits: usize, + block_size: usize, entanglement: &str, offset: usize, -) -> PyResult>>> { +) -> PyResult>>> { if num_qubits == 0 || block_size == 0 { return Ok(Box::new(std::iter::empty())); } @@ -157,11 +157,11 @@ pub fn get_entanglement_from_str( /// The entangler map using mode ``entanglement`` to scatter a block of ``block_size`` /// qubits on ``num_qubits`` qubits. pub fn get_entanglement<'a>( - num_qubits: u32, - block_size: u32, + num_qubits: usize, + block_size: usize, entanglement: &'a Bound, offset: usize, -) -> PyResult>> + 'a>> { +) -> PyResult>> + 'a>> { // unwrap the callable, if it is one let entanglement = if entanglement.is_callable() { entanglement.call1((offset,))? @@ -191,10 +191,10 @@ pub fn get_entanglement<'a>( fn _check_entanglement_list<'a>( list: Bound<'a, PyList>, - block_size: u32, -) -> PyResult>> + 'a>> { + block_size: usize, +) -> PyResult>> + 'a>> { let entanglement_iter = list.iter().map(move |el| { - let connections: Vec = el.extract()?; + let connections: Vec = el.extract()?; if connections.len() != block_size as usize { return Err(QiskitError::new_err(format!( @@ -235,14 +235,14 @@ fn _check_entanglement_list<'a>( #[pyo3(signature = (num_qubits, block_size, entanglement, offset=0))] pub fn get_entangler_map<'py>( py: Python<'py>, - num_qubits: u32, - block_size: u32, + num_qubits: usize, + block_size: usize, entanglement: &Bound, offset: usize, ) -> PyResult>> { - // The entanglement is Result>>>, so there's two + // The entanglement is Result>>>, so there's two // levels of errors we must handle: the outer error is handled by the outer match statement, - // and the inner (Result>) is handled upon the PyTuple creation. + // and the inner (Result>) is handled upon the PyTuple creation. match get_entanglement(num_qubits, block_size, entanglement, offset) { Ok(entanglement) => entanglement .into_iter() diff --git a/crates/accelerate/src/circuit_library/iqp.rs b/crates/accelerate/src/circuit_library/iqp.rs index 4cb931f8c228..72f57eb90982 100644 --- a/crates/accelerate/src/circuit_library/iqp.rs +++ b/crates/accelerate/src/circuit_library/iqp.rs @@ -36,7 +36,7 @@ fn iqp( // The initial and final Hadamard layer. let h_layer = - (0..num_qubits).map(|i| (StandardGate::HGate, smallvec![], smallvec![Qubit(i as u32)])); + (0..num_qubits).map(|i| (StandardGate::HGate, smallvec![], smallvec![Qubit::new(i)])); // The circuit interactions are powers of the CSGate, which is implemented by calling // the CPhaseGate with angles of Pi/2 times the power. The gate powers are given by the @@ -49,7 +49,7 @@ fn iqp( ( StandardGate::CPhaseGate, smallvec![Param::Float(PI2 * value as f64)], - smallvec![Qubit(i as u32), Qubit(j as u32)], + smallvec![Qubit::new(i), Qubit::new(j)], ) }) }); @@ -64,7 +64,7 @@ fn iqp( ( StandardGate::PhaseGate, smallvec![Param::Float(PI8 * value as f64)], - smallvec![Qubit(i as u32)], + smallvec![Qubit::new(i)], ) }); diff --git a/crates/accelerate/src/circuit_library/multi_local.rs b/crates/accelerate/src/circuit_library/multi_local.rs index dd75ebbcc5e8..c543f8327f81 100644 --- a/crates/accelerate/src/circuit_library/multi_local.rs +++ b/crates/accelerate/src/circuit_library/multi_local.rs @@ -52,7 +52,7 @@ type Instruction = ( /// An iterator for the rotation instructions. fn rotation_layer<'a>( py: Python<'a>, - num_qubits: u32, + num_qubits: usize, rotation_blocks: &'a [&'a Block], parameters: Vec>>, skipped_qubits: &'a HashSet, @@ -62,7 +62,7 @@ fn rotation_layer<'a>( .zip(parameters) .flat_map(move |(block, block_params)| { (0..num_qubits) - .step_by(block.num_qubits as usize) + .step_by(block.num_qubits) .filter(move |start_idx| { skipped_qubits.is_disjoint(&HashSet::from_iter( *start_idx..(*start_idx + block.num_qubits), @@ -78,7 +78,7 @@ fn rotation_layer<'a>( bound_op, bound_params, (0..block.num_qubits) - .map(|i| Qubit(start_idx + i)) + .map(|i| Qubit::new(start_idx + i)) .collect(), vec![] as Vec, )) @@ -122,7 +122,7 @@ fn entanglement_layer<'a>( Ok(( bound_op, bound_params, - indices.iter().map(|i| Qubit(*i)).collect(), + indices.iter().map(|i| Qubit::new(*i)).collect(), vec![] as Vec, )) }) @@ -298,7 +298,7 @@ impl MaybeBarrier { let inst = ( py_inst.into(), smallvec![], - (0..num_qubits).map(Qubit).collect(), + (0..num_qubits).map(Qubit::new).collect(), vec![] as Vec, ); diff --git a/crates/accelerate/src/circuit_library/pauli_evolution.rs b/crates/accelerate/src/circuit_library/pauli_evolution.rs index 3c5164314c08..75a7ee07cff2 100644 --- a/crates/accelerate/src/circuit_library/pauli_evolution.rs +++ b/crates/accelerate/src/circuit_library/pauli_evolution.rs @@ -46,7 +46,7 @@ type Instruction = ( /// A pointer to an iterator over standard instructions. pub fn pauli_evolution<'a>( pauli: &'a str, - indices: Vec, + indices: Vec, time: Param, phase_gate: bool, do_fountain: bool, @@ -58,7 +58,7 @@ pub fn pauli_evolution<'a>( .chars() .zip(indices) .filter(|(pauli, _)| *pauli != 'i'); - let (paulis, indices): (Vec, Vec) = active.unzip(); + let (paulis, indices): (Vec, Vec) = active.unzip(); match (phase_gate, indices.len()) { (_, 0) => Box::new(std::iter::empty()), @@ -79,10 +79,10 @@ pub fn pauli_evolution<'a>( /// multiplied by a factor of 2. fn single_qubit_evolution( pauli: char, - index: u32, + index: usize, time: Param, ) -> impl Iterator { - let qubit: SmallVec<[Qubit; 2]> = smallvec![Qubit(index)]; + let qubit: SmallVec<[Qubit; 2]> = smallvec![Qubit::new(index)]; let param: SmallVec<[Param; 3]> = smallvec![time]; std::iter::once(match pauli { @@ -101,10 +101,10 @@ fn single_qubit_evolution( /// multi-qubit evolution is called. fn two_qubit_evolution<'a>( pauli: Vec, - indices: Vec, + indices: Vec, time: Param, ) -> Box + 'a> { - let qubits: SmallVec<[Qubit; 2]> = smallvec![Qubit(indices[0]), Qubit(indices[1])]; + let qubits: SmallVec<[Qubit; 2]> = smallvec![Qubit::new(indices[0]), Qubit::new(indices[1])]; let param: SmallVec<[Param; 3]> = smallvec![time.clone()]; let paulistring: String = pauli.iter().collect(); @@ -122,14 +122,14 @@ fn two_qubit_evolution<'a>( /// Implement a multi-qubit Pauli evolution. See ``pauli_evolution`` detailed docs. fn multi_qubit_evolution( pauli: Vec, - indices: Vec, + indices: Vec, time: Param, phase_gate: bool, do_fountain: bool, ) -> impl Iterator { let active_paulis: Vec<(char, Qubit)> = pauli .into_iter() - .zip(indices.into_iter().map(Qubit)) + .zip(indices.into_iter().map(Qubit::new)) .collect(); // get the basis change: x -> HGate, y -> SXdgGate, z -> nothing @@ -227,7 +227,7 @@ pub fn py_pauli_evolution( let py = sparse_paulis.py(); let num_paulis = sparse_paulis.len(); let mut paulis: Vec = Vec::with_capacity(num_paulis); - let mut indices: Vec> = Vec::with_capacity(num_paulis); + let mut indices: Vec> = Vec::with_capacity(num_paulis); let mut times: Vec = Vec::with_capacity(num_paulis); let mut global_phase = Param::Float(0.0); let mut modified_phase = false; // keep track of whether we modified the phase @@ -245,10 +245,10 @@ pub fn py_pauli_evolution( paulis.push(pauli); times.push(time); // note we do not multiply by 2 here, this is done Python side! - indices.push(tuple.get_item(1)?.extract::>()?) + indices.push(tuple.get_item(1)?.extract::>()?) } - let barrier = get_barrier(py, num_qubits as u32); + let barrier = get_barrier(py, num_qubits); let evos = paulis.iter().enumerate().zip(indices).zip(times).flat_map( |(((i, pauli), qubits), time)| { @@ -274,7 +274,7 @@ pub fn py_pauli_evolution( global_phase = multiply_param(&global_phase, -1.0, py); } - CircuitData::from_packed_operations(py, num_qubits as u32, 0, evos, global_phase) + CircuitData::from_packed_operations(py, num_qubits, 0, evos, global_phase) } /// Build a CX chain over the active qubits. E.g. with q_1 inactive, this would return @@ -329,13 +329,13 @@ fn cx_fountain( })) } -fn get_barrier(py: Python, num_qubits: u32) -> Instruction { +fn get_barrier(py: Python, num_qubits: usize) -> Instruction { let barrier_cls = imports::BARRIER.get_bound(py); let barrier = barrier_cls .call1((num_qubits,)) .expect("Could not create Barrier Python-side"); let barrier_inst = PyInstruction { - qubits: num_qubits, + qubits: num_qubits.try_into().unwrap(), clbits: 0, params: 0, op_name: "barrier".to_string(), @@ -345,7 +345,7 @@ fn get_barrier(py: Python, num_qubits: u32) -> Instruction { ( barrier_inst.into(), smallvec![], - (0..num_qubits).map(Qubit).collect(), + (0..num_qubits).map(Qubit::new).collect(), vec![], ) } diff --git a/crates/accelerate/src/synthesis/evolution/pauli_network.rs b/crates/accelerate/src/synthesis/evolution/pauli_network.rs index b5a73262aae8..9b8953b128e4 100644 --- a/crates/accelerate/src/synthesis/evolution/pauli_network.rs +++ b/crates/accelerate/src/synthesis/evolution/pauli_network.rs @@ -52,37 +52,37 @@ fn to_qiskit_clifford_gate(rustiq_gate: &CliffordGate) -> QiskitGate { CliffordGate::CNOT(i, j) => ( StandardGate::CXGate, smallvec![], - smallvec![Qubit(*i as u32), Qubit(*j as u32)], + smallvec![Qubit::new(*i), Qubit::new(*j)], ), CliffordGate::CZ(i, j) => ( StandardGate::CZGate, smallvec![], - smallvec![Qubit(*i as u32), Qubit(*j as u32)], + smallvec![Qubit::new(*i as u32), Qubit::new(*j as u32)], ), CliffordGate::H(i) => ( StandardGate::HGate, smallvec![], - smallvec![Qubit(*i as u32)], + smallvec![Qubit::new(*i as u32)], ), CliffordGate::S(i) => ( StandardGate::SGate, smallvec![], - smallvec![Qubit(*i as u32)], + smallvec![Qubit::new(*i as u32)], ), CliffordGate::Sd(i) => ( StandardGate::SdgGate, smallvec![], - smallvec![Qubit(*i as u32)], + smallvec![Qubit::new(*i as u32)], ), CliffordGate::SqrtX(i) => ( StandardGate::SXGate, smallvec![], - smallvec![Qubit(*i as u32)], + smallvec![Qubit::new(*i as u32)], ), CliffordGate::SqrtXd(i) => ( StandardGate::SXdgGate, smallvec![], - smallvec![Qubit(*i as u32)], + smallvec![Qubit::new(*i as u32)], ), } } @@ -110,7 +110,7 @@ fn qiskit_rotation_gate(py: Python, paulis: &PauliSet, i: usize, angle: &Param) false => angle.clone(), true => multiply_param(angle, -1.0, py), }; - return (standard_gate, smallvec![param], smallvec![Qubit(q as u32)]); + return (standard_gate, smallvec![param], smallvec![Qubit::new(q as u32)]); } } unreachable!("The pauli rotation is guaranteed to be a single-qubit rotation.") diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs index 21fdee405982..378bf05b68c9 100644 --- a/crates/circuit/src/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -1063,7 +1063,7 @@ impl CircuitData { /// * global_phase: The global phase to use for the circuit pub fn from_standard_gates( py: Python, - num_qubits: u32, + num_qubits: usize, instructions: I, global_phase: Param, ) -> PyResult From 2e319e414a3f5c78af4a185d36ccad435e9c2ab7 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Mon, 11 Nov 2024 16:11:44 -0500 Subject: [PATCH 10/15] Change Rust circuit API to use usize bit indices. --- .../basis/basis_translator/basis_search.rs | 2 +- .../basis_translator/compose_transforms.rs | 2 +- .../src/basis/basis_translator/mod.rs | 16 +++---- .../accelerate/src/circuit_library/blocks.rs | 2 +- .../src/circuit_library/entanglement.rs | 8 ++-- crates/accelerate/src/circuit_library/iqp.rs | 8 ++-- .../src/circuit_library/multi_local.rs | 12 ++--- .../src/circuit_library/parameter_ledger.rs | 16 +++---- .../src/circuit_library/pauli_evolution.rs | 2 +- .../src/circuit_library/pauli_feature_map.rs | 22 ++++----- .../src/circuit_library/quantum_volume.rs | 6 +-- crates/accelerate/src/consolidate_blocks.rs | 4 +- crates/accelerate/src/equivalence.rs | 10 ++-- crates/accelerate/src/gate_direction.rs | 23 +++------ .../accelerate/src/remove_identity_equiv.rs | 4 +- .../accelerate/src/synthesis/clifford/mod.rs | 4 +- .../src/synthesis/clifford/utils.rs | 6 +-- .../src/synthesis/evolution/pauli_network.rs | 16 +++---- crates/accelerate/src/synthesis/linear/pmh.rs | 2 +- .../src/synthesis/linear_phase/mod.rs | 2 +- .../src/synthesis/multi_controlled/mcmt.rs | 4 +- .../src/synthesis/permutation/mod.rs | 8 ++-- .../accelerate/src/target_transpiler/mod.rs | 18 +++---- crates/accelerate/src/unitary_synthesis.rs | 2 +- crates/circuit/src/circuit_data.rs | 8 ++-- crates/circuit/src/dag_circuit.rs | 6 +-- crates/circuit/src/dag_node.rs | 4 +- crates/circuit/src/operations.rs | 48 +++++++++---------- crates/circuit/src/packed_instruction.rs | 4 +- 29 files changed, 130 insertions(+), 139 deletions(-) diff --git a/crates/accelerate/src/basis/basis_translator/basis_search.rs b/crates/accelerate/src/basis/basis_translator/basis_search.rs index 4686ba9c4c3f..d238691547df 100644 --- a/crates/accelerate/src/basis/basis_translator/basis_search.rs +++ b/crates/accelerate/src/basis/basis_translator/basis_search.rs @@ -78,7 +78,7 @@ pub(crate) fn basis_search( equivs: vec![], key: Key { name: "key".to_string(), - num_qubits: u32::MAX, + num_qubits: usize::MAX, }, }); diff --git a/crates/accelerate/src/basis/basis_translator/compose_transforms.rs b/crates/accelerate/src/basis/basis_translator/compose_transforms.rs index 574e29d0fc64..603d031d5b2e 100644 --- a/crates/accelerate/src/basis/basis_translator/compose_transforms.rs +++ b/crates/accelerate/src/basis/basis_translator/compose_transforms.rs @@ -26,7 +26,7 @@ use smallvec::SmallVec; use crate::equivalence::CircuitFromPython; // Custom types -pub type GateIdentifier = (String, u32); +pub type GateIdentifier = (String, usize); pub type BasisTransformIn = (SmallVec<[Param; 3]>, CircuitFromPython); pub type BasisTransformOut = (SmallVec<[Param; 3]>, DAGCircuit); diff --git a/crates/accelerate/src/basis/basis_translator/mod.rs b/crates/accelerate/src/basis/basis_translator/mod.rs index c900f80beff4..bf8e5434539b 100644 --- a/crates/accelerate/src/basis/basis_translator/mod.rs +++ b/crates/accelerate/src/basis/basis_translator/mod.rs @@ -295,7 +295,7 @@ fn extract_basis_target( // true for > 2q ops too (so for 4q operations we need to check for 3q, 2q, // and 1q operations in the same manner) let physical_qargs: SmallVec<[PhysicalQubit; 2]> = - qargs.iter().map(|x| PhysicalQubit(x.0)).collect(); + qargs.iter().map(|x| PhysicalQubit(x.index() as u32)).collect(); let physical_qargs_as_set: HashSet = HashSet::from_iter(physical_qargs.iter().copied()); if qargs_with_non_global_operation.contains_key(&Some(physical_qargs)) @@ -374,7 +374,7 @@ fn extract_basis_target_circ( // true for > 2q ops too (so for 4q operations we need to check for 3q, 2q, // and 1q operations in the same manner) let physical_qargs: SmallVec<[PhysicalQubit; 2]> = - qargs.iter().map(|x| PhysicalQubit(x.0)).collect(); + qargs.iter().map(|x| PhysicalQubit(x.index() as u32)).collect(); let physical_qargs_as_set: HashSet = HashSet::from_iter(physical_qargs.iter().copied()); if qargs_with_non_global_operation.contains_key(&Some(physical_qargs)) @@ -510,7 +510,7 @@ fn apply_translation( continue; } let node_qarg_as_physical: Option = - Some(node_qarg.iter().map(|x| PhysicalQubit(x.0)).collect()); + Some(node_qarg.iter().map(|x| PhysicalQubit(x.index() as u32)).collect()); if qargs_with_non_global_operation.contains_key(&node_qarg_as_physical) && qargs_with_non_global_operation[&node_qarg_as_physical].contains(node_obj.op.name()) { @@ -563,7 +563,7 @@ fn apply_translation( let unique_qargs: Option = if qubit_set.is_empty() { None } else { - Some(qubit_set.iter().map(|x| PhysicalQubit(x.0)).collect()) + Some(qubit_set.iter().map(|x| PhysicalQubit(x.index() as u32)).collect()) }; if extra_inst_map.contains_key(&unique_qargs) { replace_node( @@ -614,12 +614,12 @@ fn replace_node( let new_qubits: Vec = target_dag .get_qargs(inner_node.qubits) .iter() - .map(|qubit| old_qargs[qubit.0 as usize]) + .map(|qubit| old_qargs[qubit.index()]) .collect(); let new_clbits: Vec = target_dag .get_cargs(inner_node.clbits) .iter() - .map(|clbit| old_cargs[clbit.0 as usize]) + .map(|clbit| old_cargs[clbit.index()]) .collect(); let new_op = if inner_node.op.try_standard_gate().is_none() { inner_node.op.py_copy(py)? @@ -675,12 +675,12 @@ fn replace_node( let new_qubits: Vec = target_dag .get_qargs(inner_node.qubits) .iter() - .map(|qubit| old_qargs[qubit.0 as usize]) + .map(|qubit| old_qargs[qubit.index()]) .collect(); let new_clbits: Vec = target_dag .get_cargs(inner_node.clbits) .iter() - .map(|clbit| old_cargs[clbit.0 as usize]) + .map(|clbit| old_cargs[clbit.index()]) .collect(); let new_op = if inner_node.op.try_standard_gate().is_none() { inner_node.op.py_copy(py)? diff --git a/crates/accelerate/src/circuit_library/blocks.rs b/crates/accelerate/src/circuit_library/blocks.rs index 746279cdad6e..7cb8c689ffdb 100644 --- a/crates/accelerate/src/circuit_library/blocks.rs +++ b/crates/accelerate/src/circuit_library/blocks.rs @@ -76,7 +76,7 @@ impl Block { pub fn from_standard_gate(gate: StandardGate) -> Self { Block { operation: BlockOperation::Standard { gate }, - num_qubits: gate.num_qubits() as usize, + num_qubits: gate.num_qubits(), num_parameters: gate.num_params() as usize, } } diff --git a/crates/accelerate/src/circuit_library/entanglement.rs b/crates/accelerate/src/circuit_library/entanglement.rs index 543791b828f5..3d8c0495e1b3 100644 --- a/crates/accelerate/src/circuit_library/entanglement.rs +++ b/crates/accelerate/src/circuit_library/entanglement.rs @@ -23,7 +23,7 @@ use crate::QiskitError; /// Get all-to-all entanglement. For 4 qubits and block size 2 we have: /// [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)] fn full(num_qubits: usize, block_size: usize) -> impl Iterator> { - (0..num_qubits).combinations(block_size as usize) + (0..num_qubits).combinations(block_size) } /// Get a linear entanglement structure. For ``n`` qubits and block size ``m`` we have: @@ -85,8 +85,8 @@ fn shift_circular_alternating( // and define ``split`` as equivalent to a Python index of ``-offset`` let split = (num_qubits - (offset % num_qubits)) % num_qubits; let shifted = circular(num_qubits, block_size) - .skip(split as usize) - .chain(circular(num_qubits, block_size).take(split as usize)); + .skip(split) + .chain(circular(num_qubits, block_size).take(split)); if offset % 2 == 0 { Box::new(shifted) } else { @@ -196,7 +196,7 @@ fn _check_entanglement_list<'a>( let entanglement_iter = list.iter().map(move |el| { let connections: Vec = el.extract()?; - if connections.len() != block_size as usize { + if connections.len() != block_size { return Err(QiskitError::new_err(format!( "Entanglement {:?} does not match block size {}", connections, block_size diff --git a/crates/accelerate/src/circuit_library/iqp.rs b/crates/accelerate/src/circuit_library/iqp.rs index 72f57eb90982..fc465cb61c5a 100644 --- a/crates/accelerate/src/circuit_library/iqp.rs +++ b/crates/accelerate/src/circuit_library/iqp.rs @@ -76,8 +76,8 @@ fn iqp( } /// This generates a random symmetric integer matrix with values in [0,7]. -fn generate_random_interactions(num_qubits: u32, seed: Option) -> Array2 { - let num_qubits = num_qubits as usize; +fn generate_random_interactions(num_qubits: usize, seed: Option) -> Array2 { + let num_qubits = num_qubits; let mut rng = match seed { Some(seed) => Pcg64Mcg::seed_from_u64(seed), None => Pcg64Mcg::from_entropy(), @@ -142,7 +142,7 @@ pub fn py_iqp(py: Python, interactions: PyReadonlyArray2) -> PyResult) -> PyResult) -> PyResult { +pub fn py_random_iqp(py: Python, num_qubits: usize, seed: Option) -> PyResult { let interactions = generate_random_interactions(num_qubits, seed); let view = interactions.view(); let instructions = iqp(view); diff --git a/crates/accelerate/src/circuit_library/multi_local.rs b/crates/accelerate/src/circuit_library/multi_local.rs index c543f8327f81..0c692400bfd4 100644 --- a/crates/accelerate/src/circuit_library/multi_local.rs +++ b/crates/accelerate/src/circuit_library/multi_local.rs @@ -55,7 +55,7 @@ fn rotation_layer<'a>( num_qubits: usize, rotation_blocks: &'a [&'a Block], parameters: Vec>>, - skipped_qubits: &'a HashSet, + skipped_qubits: &'a HashSet, ) -> impl Iterator> + 'a { rotation_blocks .iter() @@ -158,7 +158,7 @@ fn entanglement_layer<'a>( #[allow(clippy::too_many_arguments)] pub fn n_local( py: Python, - num_qubits: u32, + num_qubits: usize, rotation_blocks: &[&Block], entanglement_blocks: &[&Block], entanglement: &Entanglement, @@ -184,7 +184,7 @@ pub fn n_local( // Compute the qubits that are skipped in the rotation layer. If this is set, // we skip qubits that do not appear in any of the entanglement layers. let skipped_qubits = if skip_unentangled_qubits { - let active: HashSet<&u32> = + let active: HashSet<&usize> = HashSet::from_iter(entanglement.iter().flatten().flatten().flatten()); HashSet::from_iter((0..num_qubits).filter(|i| !active.contains(i))) } else { @@ -231,7 +231,7 @@ pub fn n_local( #[allow(clippy::too_many_arguments)] pub fn py_n_local( py: Python, - num_qubits: u32, + num_qubits: usize, rotation_blocks: Vec>, entanglement_blocks: Vec>, entanglement: &Bound, @@ -280,14 +280,14 @@ struct MaybeBarrier { } impl MaybeBarrier { - fn new(py: Python, num_qubits: u32, insert_barriers: bool) -> PyResult { + fn new(py: Python, num_qubits: usize, insert_barriers: bool) -> PyResult { if !insert_barriers { Ok(Self { barrier: None }) } else { let barrier_cls = imports::BARRIER.get_bound(py); let py_barrier = barrier_cls.call1((num_qubits,))?; let py_inst = PyInstruction { - qubits: num_qubits, + qubits: num_qubits.try_into().unwrap(), clbits: 0, params: 0, op_name: "barrier".to_string(), diff --git a/crates/accelerate/src/circuit_library/parameter_ledger.rs b/crates/accelerate/src/circuit_library/parameter_ledger.rs index 457034850196..0c56f41f57bb 100644 --- a/crates/accelerate/src/circuit_library/parameter_ledger.rs +++ b/crates/accelerate/src/circuit_library/parameter_ledger.rs @@ -46,8 +46,8 @@ pub(super) struct ParameterLedger { parameter_vector: Vec, // all parameters rotation_indices: Vec, // indices where rotation blocks start entangle_indices: Vec, - rotations: Vec<(u32, usize)>, // (#blocks per layer, #params per block) for each block - entanglements: Vec>, // this might additionally change per layer + rotations: Vec<(usize, usize)>, // (#blocks per layer, #params per block) for each block + entanglements: Vec>, // this might additionally change per layer } impl ParameterLedger { @@ -57,7 +57,7 @@ impl ParameterLedger { #[allow(clippy::too_many_arguments)] pub(super) fn from_nlocal( py: Python, - num_qubits: u32, + num_qubits: usize, reps: usize, entanglement: &Entanglement, rotation_blocks: &[&Block], @@ -73,24 +73,24 @@ impl ParameterLedger { // compute the number of parameters used for the rotation layers let mut num_rotation_params_per_layer: usize = 0; - let mut rotations: Vec<(u32, usize)> = Vec::new(); + let mut rotations: Vec<(usize, usize)> = Vec::new(); for block in rotation_blocks { let num_blocks = num_qubits / block.num_qubits; rotations.push((num_blocks, block.num_parameters)); - num_rotation_params_per_layer += (num_blocks as usize) * block.num_parameters; + num_rotation_params_per_layer += num_blocks * block.num_parameters; } // compute the number of parameters used for the entanglement layers let mut num_entangle_params_per_layer: Vec = Vec::with_capacity(reps); - let mut entanglements: Vec> = Vec::with_capacity(reps); + let mut entanglements: Vec> = Vec::with_capacity(reps); for this_entanglement in entanglement.iter() { - let mut this_entanglements: Vec<(u32, usize)> = Vec::new(); + let mut this_entanglements: Vec<(usize, usize)> = Vec::new(); let mut this_num_params: usize = 0; for (block, block_entanglement) in entanglement_blocks.iter().zip(this_entanglement) { let num_blocks = block_entanglement.len(); this_num_params += num_blocks * block.num_parameters; - this_entanglements.push((num_blocks as u32, block.num_parameters)); + this_entanglements.push((num_blocks, block.num_parameters)); } num_entangle_params_per_layer.push(this_num_params); entanglements.push(this_entanglements); diff --git a/crates/accelerate/src/circuit_library/pauli_evolution.rs b/crates/accelerate/src/circuit_library/pauli_evolution.rs index 75a7ee07cff2..5f481731442d 100644 --- a/crates/accelerate/src/circuit_library/pauli_evolution.rs +++ b/crates/accelerate/src/circuit_library/pauli_evolution.rs @@ -219,7 +219,7 @@ fn multi_qubit_evolution( #[pyfunction] #[pyo3(name = "pauli_evolution", signature = (num_qubits, sparse_paulis, insert_barriers=false, do_fountain=false))] pub fn py_pauli_evolution( - num_qubits: i64, + num_qubits: usize, sparse_paulis: &Bound, insert_barriers: bool, do_fountain: bool, diff --git a/crates/accelerate/src/circuit_library/pauli_feature_map.rs b/crates/accelerate/src/circuit_library/pauli_feature_map.rs index a68af11545d5..85a7382fdd6e 100644 --- a/crates/accelerate/src/circuit_library/pauli_feature_map.rs +++ b/crates/accelerate/src/circuit_library/pauli_feature_map.rs @@ -54,7 +54,7 @@ type Instruction = ( #[allow(clippy::too_many_arguments)] pub fn pauli_feature_map( py: Python, - feature_dimension: u32, + feature_dimension: usize, parameters: Bound, reps: usize, entanglement: Option<&Bound>, @@ -124,8 +124,8 @@ pub fn pauli_feature_map( ) } -fn _get_h_layer(feature_dimension: u32) -> impl Iterator { - (0..feature_dimension as usize).map(|i| { +fn _get_h_layer(feature_dimension: usize) -> impl Iterator { + (0..feature_dimension).map(|i| { ( StandardGate::HGate.into(), smallvec![], @@ -138,7 +138,7 @@ fn _get_h_layer(feature_dimension: u32) -> impl Iterator { #[allow(clippy::too_many_arguments)] fn _get_evolution_layer<'a>( py: Python<'a>, - feature_dimension: u32, + feature_dimension: usize, rep: usize, alpha: f64, parameter_vector: &'a [Param], @@ -149,7 +149,7 @@ fn _get_evolution_layer<'a>( let mut insts: Vec = Vec::new(); for pauli in pauli_strings { - let block_size = pauli.len() as u32; + let block_size = pauli.len(); let entanglement = entanglement::get_entanglement(feature_dimension, block_size, entanglement, rep)?; @@ -158,7 +158,7 @@ fn _get_evolution_layer<'a>( let active_parameters: Vec = indices .clone() .iter() - .map(|i| parameter_vector[*i as usize].clone()) + .map(|i| parameter_vector[*i].clone()) .collect(); let angle = match data_map_func { @@ -214,7 +214,7 @@ fn _default_reduce(py: Python, parameters: Vec) -> Param { /// PyString->String, followed by a check whether the feature dimension is large enough /// for the Pauli (e.g. we cannot implement a "zzz" Pauli on a 2 qubit circuit). fn _get_paulis( - feature_dimension: u32, + feature_dimension: usize, paulis: Option<&Bound>, ) -> PyResult> { let default_pauli: Vec = if feature_dimension == 1 { @@ -231,7 +231,7 @@ fn _get_paulis( .map(|el| { // Get the string and check whether it fits the feature dimension let as_string = (*el.downcast::()?).to_string(); - if as_string.len() > feature_dimension as usize { + if as_string.len() > feature_dimension { Err(QiskitError::new_err(format!( "feature_dimension ({}) smaller than the Pauli ({})", feature_dimension, as_string @@ -246,11 +246,11 @@ fn _get_paulis( } /// Get a barrier object from Python space. -fn _get_barrier(py: Python, feature_dimension: u32) -> PyResult { +fn _get_barrier(py: Python, feature_dimension: usize) -> PyResult { let barrier_cls = imports::BARRIER.get_bound(py); let barrier = barrier_cls.call1((feature_dimension,))?; let barrier_inst = PyInstruction { - qubits: feature_dimension, + qubits: feature_dimension.try_into().unwrap(), clbits: 0, params: 0, op_name: "barrier".to_string(), @@ -260,7 +260,7 @@ fn _get_barrier(py: Python, feature_dimension: u32) -> PyResult { Ok(( barrier_inst.into(), smallvec![], - (0..feature_dimension as usize).map(Qubit::new).collect(), + (0..feature_dimension).map(Qubit::new).collect(), vec![] as Vec, )) } diff --git a/crates/accelerate/src/circuit_library/quantum_volume.rs b/crates/accelerate/src/circuit_library/quantum_volume.rs index 44157d80917a..990f6573b0a5 100644 --- a/crates/accelerate/src/circuit_library/quantum_volume.rs +++ b/crates/accelerate/src/circuit_library/quantum_volume.rs @@ -105,13 +105,13 @@ const UNITARY_PER_SEED: usize = 50; #[pyo3(signature=(num_qubits, depth, seed=None))] pub fn quantum_volume( py: Python, - num_qubits: u32, + num_qubits: usize, depth: usize, seed: Option, ) -> PyResult { - let width = num_qubits as usize / 2; + let width = num_qubits / 2; let num_unitaries = width * depth; - let mut permutation: Vec = (0..num_qubits as usize).map(Qubit::new).collect(); + let mut permutation: Vec = (0..num_qubits).map(Qubit::new).collect(); let kwargs = PyDict::new_bound(py); kwargs.set_item(intern!(py, "num_qubits"), 2)?; diff --git a/crates/accelerate/src/consolidate_blocks.rs b/crates/accelerate/src/consolidate_blocks.rs index 1edd592ce877..7528d3e6f511 100644 --- a/crates/accelerate/src/consolidate_blocks.rs +++ b/crates/accelerate/src/consolidate_blocks.rs @@ -39,7 +39,7 @@ fn is_supported( ) -> bool { match target { Some(target) => { - let physical_qargs = qargs.iter().map(|bit| PhysicalQubit(bit.0)).collect(); + let physical_qargs = qargs.iter().map(|bit| PhysicalQubit(bit.index() as u32)).collect(); target.instruction_supported(name, Some(&physical_qargs)) } None => match basis_gates { @@ -147,7 +147,7 @@ pub(crate) fn consolidate_blocks( .collect(); let circuit_data = CircuitData::from_packed_operations( py, - block_qargs.len() as u32, + block_qargs.len(), 0, block.iter().map(|node| { let inst = dag.dag()[*node].unwrap_operation(); diff --git a/crates/accelerate/src/equivalence.rs b/crates/accelerate/src/equivalence.rs index dfa338fba45a..a5181d2be60e 100644 --- a/crates/accelerate/src/equivalence.rs +++ b/crates/accelerate/src/equivalence.rs @@ -56,14 +56,14 @@ pub struct Key { #[pyo3(get)] pub name: String, #[pyo3(get)] - pub num_qubits: u32, + pub num_qubits: usize, } #[pymethods] impl Key { #[new] #[pyo3(signature = (name, num_qubits))] - fn new(name: String, num_qubits: u32) -> Self { + fn new(name: String, num_qubits: usize) -> Self { Self { name, num_qubits } } @@ -77,7 +77,7 @@ impl Key { slf.to_string() } - fn __getnewargs__(slf: PyRef) -> (Bound, u32) { + fn __getnewargs__(slf: PyRef) -> (Bound, usize) { ( PyString::new_bound(slf.py(), slf.name.as_str()), slf.num_qubits, @@ -739,8 +739,8 @@ fn raise_if_param_mismatch( fn raise_if_shape_mismatch(gate: &PackedOperation, circuit: &CircuitFromPython) -> PyResult<()> { let op_ref = gate.view(); - if op_ref.num_qubits() != circuit.0.num_qubits() as u32 - || op_ref.num_clbits() != circuit.0.num_clbits() as u32 + if op_ref.num_qubits() != circuit.0.num_qubits() + || op_ref.num_clbits() != circuit.0.num_clbits() { return Err(CircuitError::new_err(format!( "Cannot add equivalence between circuit and gate \ diff --git a/crates/accelerate/src/gate_direction.rs b/crates/accelerate/src/gate_direction.rs index a7831da60961..5b8b68d0b2a1 100755 --- a/crates/accelerate/src/gate_direction.rs +++ b/crates/accelerate/src/gate_direction.rs @@ -14,7 +14,6 @@ use crate::nlayout::PhysicalQubit; use crate::target_transpiler::exceptions::TranspilerError; use crate::target_transpiler::Target; use hashbrown::HashSet; -use pyo3::intern; use pyo3::prelude::*; use pyo3::types::PyTuple; use qiskit_circuit::operations::OperationRef; @@ -207,8 +206,8 @@ fn py_fix_direction_target( ) -> PyResult { let target_check = |inst: &PackedInstruction, op_args: &[Qubit]| -> bool { let qargs = smallvec![ - PhysicalQubit::new(op_args[0].0), - PhysicalQubit::new(op_args[1].0) + PhysicalQubit::new(op_args[0].index() as u32), + PhysicalQubit::new(op_args[1].index() as u32) ]; // Take this path so Target can check for exact match of the parameterized gate's angle @@ -389,7 +388,7 @@ fn has_calibration_for_op_node( packed_inst: &PackedInstruction, qargs: &[Qubit], ) -> PyResult { - let py_args = PyTuple::new_bound(py, dag.qubits().map_indices(qargs)); + let py_args = PyTuple::new_bound(py, qargs.iter().map(|q| dag.get_qubit(py, *q).unwrap())); let dag_op_node = Py::new( py, @@ -441,23 +440,15 @@ fn replace_dag( // // TODO: replace this once we have a Rust version of QuantumRegister #[inline] -fn add_qreg(py: Python, dag: &mut DAGCircuit, num_qubits: u32) -> PyResult> { +fn add_qreg(py: Python, dag: &mut DAGCircuit, num_qubits: usize) -> PyResult> { + let first_bit = dag.num_qubits(); let qreg = imports::QUANTUM_REGISTER .get_bound(py) .call1((num_qubits,))?; dag.add_qreg(py, &qreg)?; - let mut qargs = Vec::new(); - - for i in 0..num_qubits { - let qubit = qreg.call_method1(intern!(py, "__getitem__"), (i,))?; - qargs.push( - dag.qubits() - .find(&qubit) - .expect("Qubit should have been stored in the DAGCircuit"), - ); - } - Ok(qargs) + // We are adding brand new bits, so we know exactly the indices that got added. + Ok((first_bit..(first_bit + num_qubits)).map(Qubit::new).collect()) } #[inline] diff --git a/crates/accelerate/src/remove_identity_equiv.rs b/crates/accelerate/src/remove_identity_equiv.rs index a3eb921628e2..f2eb78e28038 100644 --- a/crates/accelerate/src/remove_identity_equiv.rs +++ b/crates/accelerate/src/remove_identity_equiv.rs @@ -44,7 +44,7 @@ fn remove_identity_equiv( let qargs: Vec = dag .get_qargs(inst.qubits) .iter() - .map(|x| PhysicalQubit::new(x.0)) + .map(|x| PhysicalQubit::new(x.index() as u32)) .collect(); let error_rate = target.get_error(inst.op.name(), qargs.as_slice()); match error_rate { @@ -61,7 +61,7 @@ fn remove_identity_equiv( let qargs: Vec = dag .get_qargs(inst.qubits) .iter() - .map(|x| PhysicalQubit::new(x.0)) + .map(|x| PhysicalQubit::new(x.index() as u32)) .collect(); let error_rate = target.get_error(inst.op.name(), qargs.as_slice()); match error_rate { diff --git a/crates/accelerate/src/synthesis/clifford/mod.rs b/crates/accelerate/src/synthesis/clifford/mod.rs index dae85de4972d..2be59f8ff245 100644 --- a/crates/accelerate/src/synthesis/clifford/mod.rs +++ b/crates/accelerate/src/synthesis/clifford/mod.rs @@ -41,7 +41,7 @@ fn synth_clifford_greedy(py: Python, clifford: PyReadonlyArray2) -> PyResu GreedyCliffordSynthesis::new(tableau.view()).map_err(QiskitError::new_err)?; let (num_qubits, clifford_gates) = greedy_synthesis.run().map_err(QiskitError::new_err)?; - CircuitData::from_standard_gates(py, num_qubits as u32, clifford_gates, Param::Float(0.0)) + CircuitData::from_standard_gates(py, num_qubits, clifford_gates, Param::Float(0.0)) } /// Generate a random Clifford tableau. @@ -77,7 +77,7 @@ fn synth_clifford_bm(py: Python, clifford: PyReadonlyArray2) -> PyResult) -> PyResult<()> { diff --git a/crates/accelerate/src/synthesis/clifford/utils.rs b/crates/accelerate/src/synthesis/clifford/utils.rs index 19cfcfaa49fd..29ff4b198db5 100644 --- a/crates/accelerate/src/synthesis/clifford/utils.rs +++ b/crates/accelerate/src/synthesis/clifford/utils.rs @@ -252,15 +252,15 @@ impl Clifford { Ok(()) } StandardGate::SdgGate => { - clifford.append_sdg(qubits[0].0 as usize); + clifford.append_sdg(qubits[0].index()); Ok(()) } StandardGate::SXGate => { - clifford.append_sx(qubits[0].0 as usize); + clifford.append_sx(qubits[0].index()); Ok(()) } StandardGate::SXdgGate => { - clifford.append_sxdg(qubits[0].0 as usize); + clifford.append_sxdg(qubits[0].index()); Ok(()) } StandardGate::HGate => { diff --git a/crates/accelerate/src/synthesis/evolution/pauli_network.rs b/crates/accelerate/src/synthesis/evolution/pauli_network.rs index 9b8953b128e4..4d13a8f657ee 100644 --- a/crates/accelerate/src/synthesis/evolution/pauli_network.rs +++ b/crates/accelerate/src/synthesis/evolution/pauli_network.rs @@ -57,32 +57,32 @@ fn to_qiskit_clifford_gate(rustiq_gate: &CliffordGate) -> QiskitGate { CliffordGate::CZ(i, j) => ( StandardGate::CZGate, smallvec![], - smallvec![Qubit::new(*i as u32), Qubit::new(*j as u32)], + smallvec![Qubit::new(*i), Qubit::new(*j)], ), CliffordGate::H(i) => ( StandardGate::HGate, smallvec![], - smallvec![Qubit::new(*i as u32)], + smallvec![Qubit::new(*i)], ), CliffordGate::S(i) => ( StandardGate::SGate, smallvec![], - smallvec![Qubit::new(*i as u32)], + smallvec![Qubit::new(*i)], ), CliffordGate::Sd(i) => ( StandardGate::SdgGate, smallvec![], - smallvec![Qubit::new(*i as u32)], + smallvec![Qubit::new(*i)], ), CliffordGate::SqrtX(i) => ( StandardGate::SXGate, smallvec![], - smallvec![Qubit::new(*i as u32)], + smallvec![Qubit::new(*i)], ), CliffordGate::SqrtXd(i) => ( StandardGate::SXdgGate, smallvec![], - smallvec![Qubit::new(*i as u32)], + smallvec![Qubit::new(*i)], ), } } @@ -110,7 +110,7 @@ fn qiskit_rotation_gate(py: Python, paulis: &PauliSet, i: usize, angle: &Param) false => angle.clone(), true => multiply_param(angle, -1.0, py), }; - return (standard_gate, smallvec![param], smallvec![Qubit::new(q as u32)]); + return (standard_gate, smallvec![param], smallvec![Qubit::new(q)]); } } unreachable!("The pauli rotation is guaranteed to be a single-qubit rotation.") @@ -372,5 +372,5 @@ pub fn pauli_network_synthesis_inner( } } - CircuitData::from_standard_gates(py, num_qubits as u32, gates, global_phase) + CircuitData::from_standard_gates(py, num_qubits, gates, global_phase) } diff --git a/crates/accelerate/src/synthesis/linear/pmh.rs b/crates/accelerate/src/synthesis/linear/pmh.rs index 81baec9f260e..007c0ea28a6e 100644 --- a/crates/accelerate/src/synthesis/linear/pmh.rs +++ b/crates/accelerate/src/synthesis/linear/pmh.rs @@ -191,5 +191,5 @@ pub fn synth_cnot_count_full_pmh( ) }); - CircuitData::from_standard_gates(py, num_qubits as u32, instructions, Param::Float(0.0)) + CircuitData::from_standard_gates(py, num_qubits, instructions, Param::Float(0.0)) } diff --git a/crates/accelerate/src/synthesis/linear_phase/mod.rs b/crates/accelerate/src/synthesis/linear_phase/mod.rs index fd95985e1025..4751a403c06b 100644 --- a/crates/accelerate/src/synthesis/linear_phase/mod.rs +++ b/crates/accelerate/src/synthesis/linear_phase/mod.rs @@ -37,7 +37,7 @@ pub(crate) mod cz_depth_lnn; fn synth_cz_depth_line_mr(py: Python, mat: PyReadonlyArray2) -> PyResult { let view = mat.as_array(); let (num_qubits, lnn_gates) = cz_depth_lnn::synth_cz_depth_line_mr_inner(view); - CircuitData::from_standard_gates(py, num_qubits as u32, lnn_gates, Param::Float(0.0)) + CircuitData::from_standard_gates(py, num_qubits, lnn_gates, Param::Float(0.0)) } pub fn linear_phase(m: &Bound) -> PyResult<()> { diff --git a/crates/accelerate/src/synthesis/multi_controlled/mcmt.rs b/crates/accelerate/src/synthesis/multi_controlled/mcmt.rs index 80128b077e7d..6a79d171fed3 100644 --- a/crates/accelerate/src/synthesis/multi_controlled/mcmt.rs +++ b/crates/accelerate/src/synthesis/multi_controlled/mcmt.rs @@ -146,7 +146,7 @@ pub fn mcmt_v_chain( if num_ctrl_qubits == 1 { CircuitData::from_packed_operations( py, - num_qubits as u32, + num_qubits, 0, flip_control_state .clone() @@ -164,7 +164,7 @@ pub fn mcmt_v_chain( CircuitData::from_packed_operations( py, - num_qubits as u32, + num_qubits, 0, flip_control_state .clone() diff --git a/crates/accelerate/src/synthesis/permutation/mod.rs b/crates/accelerate/src/synthesis/permutation/mod.rs index 2cc0b02f2c66..39a59ecd6940 100644 --- a/crates/accelerate/src/synthesis/permutation/mod.rs +++ b/crates/accelerate/src/synthesis/permutation/mod.rs @@ -49,7 +49,7 @@ pub fn _synth_permutation_basic(py: Python, pattern: PyArrayLike1) -> PyRes let num_qubits = view.len(); CircuitData::from_standard_gates( py, - num_qubits as u32, + num_qubits, utils::get_ordered_swap(&view).iter().map(|(i, j)| { ( StandardGate::SwapGate, @@ -72,7 +72,7 @@ fn _synth_permutation_acg(py: Python, pattern: PyArrayLike1) -> PyResult PyResult { let mut gates = LnnGatesVec::new(); _append_reverse_permutation_lnn_kms(&mut gates, num_qubits); - CircuitData::from_standard_gates(py, num_qubits as u32, gates, Param::Float(0.0)) + CircuitData::from_standard_gates(py, num_qubits, gates, Param::Float(0.0)) } pub fn permutation(m: &Bound) -> PyResult<()> { diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 8eb9f385a672..c13acb7c5430 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -83,7 +83,7 @@ impl ToPyObject for TargetOperation { impl TargetOperation { /// Gets the number of qubits of a [TargetOperation], will panic if the operation is [TargetOperation::Variadic]. - pub fn num_qubits(&self) -> u32 { + pub fn num_qubits(&self) -> usize { match &self { Self::Normal(normal) => normal.operation.num_qubits(), Self::Variadic(_) => { @@ -176,7 +176,7 @@ pub(crate) struct Target { gate_map: GateMap, #[pyo3(get)] _gate_name_map: IndexMap, - global_operations: IndexMap, RandomState>, + global_operations: IndexMap, RandomState>, qarg_gate_map: NullableIndexMap>>, non_global_strict_basis: Option>, non_global_basis: Option>, @@ -324,7 +324,7 @@ impl Target { properties.keys().map(|qargs| qargs.cloned()).collect(); for qarg in property_keys { if let Some(qarg) = qarg.as_ref() { - if qarg.len() != inst_num_qubits as usize { + if qarg.len() != inst_num_qubits { return Err(TranspilerError::new_err(format!( "The number of qubits for {name} does not match\ the number of qubits in the properties dictionary: {:?}", @@ -596,14 +596,14 @@ impl Target { if gate_map_name.contains_key(None) { let qubit_comparison = self._gate_name_map[op_name].num_qubits(); - return Ok(qubit_comparison == _qargs.len() as u32 + return Ok(qubit_comparison == _qargs.len() && _qargs.iter().all(|x| { x.index() < self.num_qubits.unwrap_or_default() })); } } else { let qubit_comparison = obj.num_qubits(); - return Ok(qubit_comparison == _qargs.len() as u32 + return Ok(qubit_comparison == _qargs.len() && _qargs.iter().all(|x| { x.index() < self.num_qubits.unwrap_or_default() })); @@ -881,7 +881,7 @@ impl Target { self.global_operations = state .get_item("global_operations")? .unwrap() - .extract::, RandomState>>()?; + .extract::, RandomState>>()?; self.qarg_gate_map = NullableIndexMap::from_iter( state .get_item("qarg_gate_map")? @@ -1059,7 +1059,7 @@ impl Target { } } if let Some(qargs) = qargs.as_ref() { - if let Some(global_gates) = self.global_operations.get(&(qargs.len() as u32)) { + if let Some(global_gates) = self.global_operations.get(&(qargs.len())) { res.extend(global_gates.iter().map(|key| key.as_str())) } } @@ -1169,7 +1169,7 @@ impl Target { } TargetOperation::Normal(obj) => { let qubit_comparison = obj.operation.num_qubits(); - return qubit_comparison == _qargs.len() as u32 + return qubit_comparison == _qargs.len() && _qargs.iter().all(|qarg| { qarg.index() < self.num_qubits.unwrap_or_default() }); @@ -1188,7 +1188,7 @@ impl Target { } TargetOperation::Normal(obj) => { let qubit_comparison = obj.operation.num_qubits(); - return qubit_comparison == _qargs.len() as u32 + return qubit_comparison == _qargs.len() && _qargs.iter().all(|qarg| { qarg.index() < self.num_qubits.unwrap_or_default() }); diff --git a/crates/accelerate/src/unitary_synthesis.rs b/crates/accelerate/src/unitary_synthesis.rs index 467e9d8d438f..2d8ad73309c1 100644 --- a/crates/accelerate/src/unitary_synthesis.rs +++ b/crates/accelerate/src/unitary_synthesis.rs @@ -290,7 +290,7 @@ fn py_run_main_loop( }; } if !(packed_instr.op.name() == "unitary" - && packed_instr.op.num_qubits() >= min_qubits as u32) + && packed_instr.op.num_qubits() >= min_qubits) { out_dag.push_back(py, packed_instr)?; continue; diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs index 378bf05b68c9..230a352598a7 100644 --- a/crates/circuit/src/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -906,8 +906,8 @@ impl CircuitData { /// * global_phase: The global phase to use for the circuit pub fn from_packed_operations( py: Python, - num_qubits: u32, - num_clbits: u32, + num_qubits: usize, + num_clbits: usize, instructions: I, global_phase: Param, ) -> PyResult @@ -1099,8 +1099,8 @@ impl CircuitData { /// Build an empty CircuitData object with an initially allocated instruction capacity pub fn with_capacity( py: Python, - num_qubits: u32, - num_clbits: u32, + num_qubits: usize, + num_clbits: usize, instruction_capacity: usize, global_phase: Param, ) -> PyResult { diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index b7f55a1ca4cb..d5e9838cde5f 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -3235,8 +3235,8 @@ def _format(operand): &mut self.dag[*new_node_index] { new_inst.op = PyInstruction { - qubits: old_op.num_qubits(), - clbits: old_op.num_clbits(), + qubits: old_op.num_qubits() as u32, + clbits: old_op.num_clbits() as u32, params: old_op.num_params(), control_flow: old_op.control_flow(), op_name: old_op.name().to_string(), @@ -6848,7 +6848,7 @@ impl DAGCircuit { let py_op = op.extract::()?; - if py_op.operation.num_qubits() as usize != block_qargs.len() { + if py_op.operation.num_qubits() != block_qargs.len() { return Err(DAGCircuitError::new_err(format!( "Number of qubits in the replacement operation ({}) is not equal to the number of qubits in the block ({})!", py_op.operation.num_qubits(), block_qargs.len() ))); diff --git a/crates/circuit/src/dag_node.rs b/crates/circuit/src/dag_node.rs index 6c3a2d15fbad..657ac3ae4b95 100644 --- a/crates/circuit/src/dag_node.rs +++ b/crates/circuit/src/dag_node.rs @@ -316,12 +316,12 @@ impl DAGOpNode { } #[getter] - fn num_qubits(&self) -> u32 { + fn num_qubits(&self) -> usize { self.instruction.operation.num_qubits() } #[getter] - fn num_clbits(&self) -> u32 { + fn num_clbits(&self) -> usize { self.instruction.operation.num_clbits() } diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index ced9901e68b0..cc65a2468287 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -160,8 +160,8 @@ impl<'py> Iterator for ParamParameterIter<'py> { /// needed for something to be addable to the circuit struct pub trait Operation { fn name(&self) -> &str; - fn num_qubits(&self) -> u32; - fn num_clbits(&self) -> u32; + fn num_qubits(&self) -> usize; + fn num_clbits(&self) -> usize; fn num_params(&self) -> u32; fn control_flow(&self) -> bool; fn blocks(&self) -> Vec; @@ -194,7 +194,7 @@ impl<'a> Operation for OperationRef<'a> { } } #[inline] - fn num_qubits(&self) -> u32 { + fn num_qubits(&self) -> usize { match self { Self::Standard(standard) => standard.num_qubits(), Self::Gate(gate) => gate.num_qubits(), @@ -203,7 +203,7 @@ impl<'a> Operation for OperationRef<'a> { } } #[inline] - fn num_clbits(&self) -> u32 { + fn num_clbits(&self) -> usize { match self { Self::Standard(standard) => standard.num_clbits(), Self::Gate(gate) => gate.num_clbits(), @@ -349,7 +349,7 @@ impl ToPyObject for StandardGate { } } -static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] = [ +static STANDARD_GATE_NUM_QUBITS: [usize; STANDARD_GATE_SIZE] = [ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0-9 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 10-19 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 20-29 @@ -367,7 +367,7 @@ static STANDARD_GATE_NUM_PARAMS: [u32; STANDARD_GATE_SIZE] = [ 0, 0, // 50-51 ]; -static STANDARD_GATE_NUM_CTRL_QUBITS: [u32; STANDARD_GATE_SIZE] = [ +static STANDARD_GATE_NUM_CTRL_QUBITS: [usize; STANDARD_GATE_SIZE] = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0-9 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 10-19 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, // 20-29 @@ -479,7 +479,7 @@ impl StandardGate { } } - pub fn num_ctrl_qubits(&self) -> u32 { + pub fn num_ctrl_qubits(&self) -> usize { STANDARD_GATE_NUM_CTRL_QUBITS[*self as usize] } @@ -701,17 +701,17 @@ impl StandardGate { } #[getter] - pub fn get_num_qubits(&self) -> u32 { + pub fn get_num_qubits(&self) -> usize { self.num_qubits() } #[getter] - pub fn get_num_ctrl_qubits(&self) -> u32 { + pub fn get_num_ctrl_qubits(&self) -> usize { self.num_ctrl_qubits() } #[getter] - pub fn get_num_clbits(&self) -> u32 { + pub fn get_num_clbits(&self) -> usize { self.num_clbits() } @@ -760,11 +760,11 @@ impl Operation for StandardGate { STANDARD_GATE_NAME[*self as usize] } - fn num_qubits(&self) -> u32 { + fn num_qubits(&self) -> usize { STANDARD_GATE_NUM_QUBITS[*self as usize] } - fn num_clbits(&self) -> u32 { + fn num_clbits(&self) -> usize { 0 } @@ -2563,11 +2563,11 @@ impl Operation for PyInstruction { fn name(&self) -> &str { self.op_name.as_str() } - fn num_qubits(&self) -> u32 { - self.qubits + fn num_qubits(&self) -> usize { + self.qubits as usize } - fn num_clbits(&self) -> u32 { - self.clbits + fn num_clbits(&self) -> usize { + self.clbits as usize } fn num_params(&self) -> u32 { self.params @@ -2649,11 +2649,11 @@ impl Operation for PyGate { fn name(&self) -> &str { self.op_name.as_str() } - fn num_qubits(&self) -> u32 { - self.qubits + fn num_qubits(&self) -> usize { + self.qubits as usize } - fn num_clbits(&self) -> u32 { - self.clbits + fn num_clbits(&self) -> usize { + self.clbits as usize } fn num_params(&self) -> u32 { self.params @@ -2728,11 +2728,11 @@ impl Operation for PyOperation { fn name(&self) -> &str { self.op_name.as_str() } - fn num_qubits(&self) -> u32 { - self.qubits + fn num_qubits(&self) -> usize { + self.qubits as usize } - fn num_clbits(&self) -> u32 { - self.clbits + fn num_clbits(&self) -> usize { + self.clbits as usize } fn num_params(&self) -> u32 { self.params diff --git a/crates/circuit/src/packed_instruction.rs b/crates/circuit/src/packed_instruction.rs index af72b3226a7c..61a9338bee03 100644 --- a/crates/circuit/src/packed_instruction.rs +++ b/crates/circuit/src/packed_instruction.rs @@ -379,11 +379,11 @@ impl Operation for PackedOperation { } } #[inline] - fn num_qubits(&self) -> u32 { + fn num_qubits(&self) -> usize { self.view().num_qubits() } #[inline] - fn num_clbits(&self) -> u32 { + fn num_clbits(&self) -> usize { self.view().num_clbits() } #[inline] From 4189d64a327e37ec21e533da805d884cd5685ed3 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Mon, 11 Nov 2024 16:19:42 -0500 Subject: [PATCH 11/15] Change Rust circuit API to use usize param indices. --- .../accelerate/src/circuit_library/blocks.rs | 10 ++++---- crates/circuit/src/dag_circuit.rs | 2 +- crates/circuit/src/operations.rs | 24 +++++++++---------- crates/circuit/src/packed_instruction.rs | 2 +- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/crates/accelerate/src/circuit_library/blocks.rs b/crates/accelerate/src/circuit_library/blocks.rs index 7cb8c689ffdb..b411d7591c74 100644 --- a/crates/accelerate/src/circuit_library/blocks.rs +++ b/crates/accelerate/src/circuit_library/blocks.rs @@ -77,7 +77,7 @@ impl Block { Block { operation: BlockOperation::Standard { gate }, num_qubits: gate.num_qubits(), - num_parameters: gate.num_params() as usize, + num_parameters: gate.num_params(), } } @@ -85,8 +85,8 @@ impl Block { #[pyo3(signature = (num_qubits, num_parameters, builder,))] pub fn from_callable( py: Python, - num_qubits: i64, - num_parameters: i64, + num_qubits: usize, + num_parameters: usize, builder: &Bound, ) -> PyResult { if !builder.is_callable() { @@ -98,8 +98,8 @@ impl Block { operation: BlockOperation::PyCustom { builder: builder.to_object(py), }, - num_qubits: num_qubits as usize, - num_parameters: num_parameters as usize, + num_qubits, + num_parameters, }; Ok(block) diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index d5e9838cde5f..b169f0a9ec20 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -3237,7 +3237,7 @@ def _format(operand): new_inst.op = PyInstruction { qubits: old_op.num_qubits() as u32, clbits: old_op.num_clbits() as u32, - params: old_op.num_params(), + params: old_op.num_params() as u32, control_flow: old_op.control_flow(), op_name: old_op.name().to_string(), instruction: new_op.clone().unbind(), diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index cc65a2468287..141c3f500e9a 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -162,7 +162,7 @@ pub trait Operation { fn name(&self) -> &str; fn num_qubits(&self) -> usize; fn num_clbits(&self) -> usize; - fn num_params(&self) -> u32; + fn num_params(&self) -> usize; fn control_flow(&self) -> bool; fn blocks(&self) -> Vec; fn matrix(&self, params: &[Param]) -> Option>; @@ -212,7 +212,7 @@ impl<'a> Operation for OperationRef<'a> { } } #[inline] - fn num_params(&self) -> u32 { + fn num_params(&self) -> usize { match self { Self::Standard(standard) => standard.num_params(), Self::Gate(gate) => gate.num_params(), @@ -358,7 +358,7 @@ static STANDARD_GATE_NUM_QUBITS: [usize; STANDARD_GATE_SIZE] = [ 4, 4, // 50-51 ]; -static STANDARD_GATE_NUM_PARAMS: [u32; STANDARD_GATE_SIZE] = [ +static STANDARD_GATE_NUM_PARAMS: [usize; STANDARD_GATE_SIZE] = [ 1, 0, 0, 0, 0, 0, 1, 2, 1, 1, // 0-9 1, 0, 0, 0, 0, 0, 0, 3, 1, 2, // 10-19 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, // 20-29 @@ -688,7 +688,7 @@ impl StandardGate { .map(|x| x.into_pyarray_bound(py).into()) } - pub fn _num_params(&self) -> u32 { + pub fn _num_params(&self) -> usize { self.num_params() } @@ -716,7 +716,7 @@ impl StandardGate { } #[getter] - pub fn get_num_params(&self) -> u32 { + pub fn get_num_params(&self) -> usize { self.num_params() } @@ -768,7 +768,7 @@ impl Operation for StandardGate { 0 } - fn num_params(&self) -> u32 { + fn num_params(&self) -> usize { STANDARD_GATE_NUM_PARAMS[*self as usize] } @@ -2569,8 +2569,8 @@ impl Operation for PyInstruction { fn num_clbits(&self) -> usize { self.clbits as usize } - fn num_params(&self) -> u32 { - self.params + fn num_params(&self) -> usize { + self.params as usize } fn control_flow(&self) -> bool { self.control_flow @@ -2655,8 +2655,8 @@ impl Operation for PyGate { fn num_clbits(&self) -> usize { self.clbits as usize } - fn num_params(&self) -> u32 { - self.params + fn num_params(&self) -> usize { + self.params as usize } fn control_flow(&self) -> bool { false @@ -2734,8 +2734,8 @@ impl Operation for PyOperation { fn num_clbits(&self) -> usize { self.clbits as usize } - fn num_params(&self) -> u32 { - self.params + fn num_params(&self) -> usize { + self.params as usize } fn control_flow(&self) -> bool { false diff --git a/crates/circuit/src/packed_instruction.rs b/crates/circuit/src/packed_instruction.rs index 61a9338bee03..c84f5d9ee075 100644 --- a/crates/circuit/src/packed_instruction.rs +++ b/crates/circuit/src/packed_instruction.rs @@ -387,7 +387,7 @@ impl Operation for PackedOperation { self.view().num_clbits() } #[inline] - fn num_params(&self) -> u32 { + fn num_params(&self) -> usize { self.view().num_params() } #[inline] From 1a02579aeaa236fdf023855e32cce363bc96ebbc Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Mon, 11 Nov 2024 16:20:02 -0500 Subject: [PATCH 12/15] Run formatting. --- .../src/basis/basis_translator/mod.rs | 27 ++++++++++++++----- crates/accelerate/src/consolidate_blocks.rs | 5 +++- crates/accelerate/src/gate_direction.rs | 4 ++- .../src/synthesis/evolution/pauli_network.rs | 18 +++---------- crates/accelerate/src/unitary_synthesis.rs | 4 +-- 5 files changed, 31 insertions(+), 27 deletions(-) diff --git a/crates/accelerate/src/basis/basis_translator/mod.rs b/crates/accelerate/src/basis/basis_translator/mod.rs index bf8e5434539b..3e2f73140dc2 100644 --- a/crates/accelerate/src/basis/basis_translator/mod.rs +++ b/crates/accelerate/src/basis/basis_translator/mod.rs @@ -294,8 +294,10 @@ fn extract_basis_target( // single qubit operation for (1,) as valid. This pattern also holds // true for > 2q ops too (so for 4q operations we need to check for 3q, 2q, // and 1q operations in the same manner) - let physical_qargs: SmallVec<[PhysicalQubit; 2]> = - qargs.iter().map(|x| PhysicalQubit(x.index() as u32)).collect(); + let physical_qargs: SmallVec<[PhysicalQubit; 2]> = qargs + .iter() + .map(|x| PhysicalQubit(x.index() as u32)) + .collect(); let physical_qargs_as_set: HashSet = HashSet::from_iter(physical_qargs.iter().copied()); if qargs_with_non_global_operation.contains_key(&Some(physical_qargs)) @@ -373,8 +375,10 @@ fn extract_basis_target_circ( // single qubit operation for (1,) as valid. This pattern also holds // true for > 2q ops too (so for 4q operations we need to check for 3q, 2q, // and 1q operations in the same manner) - let physical_qargs: SmallVec<[PhysicalQubit; 2]> = - qargs.iter().map(|x| PhysicalQubit(x.index() as u32)).collect(); + let physical_qargs: SmallVec<[PhysicalQubit; 2]> = qargs + .iter() + .map(|x| PhysicalQubit(x.index() as u32)) + .collect(); let physical_qargs_as_set: HashSet = HashSet::from_iter(physical_qargs.iter().copied()); if qargs_with_non_global_operation.contains_key(&Some(physical_qargs)) @@ -509,8 +513,12 @@ fn apply_translation( } continue; } - let node_qarg_as_physical: Option = - Some(node_qarg.iter().map(|x| PhysicalQubit(x.index() as u32)).collect()); + let node_qarg_as_physical: Option = Some( + node_qarg + .iter() + .map(|x| PhysicalQubit(x.index() as u32)) + .collect(), + ); if qargs_with_non_global_operation.contains_key(&node_qarg_as_physical) && qargs_with_non_global_operation[&node_qarg_as_physical].contains(node_obj.op.name()) { @@ -563,7 +571,12 @@ fn apply_translation( let unique_qargs: Option = if qubit_set.is_empty() { None } else { - Some(qubit_set.iter().map(|x| PhysicalQubit(x.index() as u32)).collect()) + Some( + qubit_set + .iter() + .map(|x| PhysicalQubit(x.index() as u32)) + .collect(), + ) }; if extra_inst_map.contains_key(&unique_qargs) { replace_node( diff --git a/crates/accelerate/src/consolidate_blocks.rs b/crates/accelerate/src/consolidate_blocks.rs index 7528d3e6f511..61dcc41ddfb8 100644 --- a/crates/accelerate/src/consolidate_blocks.rs +++ b/crates/accelerate/src/consolidate_blocks.rs @@ -39,7 +39,10 @@ fn is_supported( ) -> bool { match target { Some(target) => { - let physical_qargs = qargs.iter().map(|bit| PhysicalQubit(bit.index() as u32)).collect(); + let physical_qargs = qargs + .iter() + .map(|bit| PhysicalQubit(bit.index() as u32)) + .collect(); target.instruction_supported(name, Some(&physical_qargs)) } None => match basis_gates { diff --git a/crates/accelerate/src/gate_direction.rs b/crates/accelerate/src/gate_direction.rs index 5b8b68d0b2a1..fec066aa68d6 100755 --- a/crates/accelerate/src/gate_direction.rs +++ b/crates/accelerate/src/gate_direction.rs @@ -448,7 +448,9 @@ fn add_qreg(py: Python, dag: &mut DAGCircuit, num_qubits: usize) -> PyResult QiskitGate { smallvec![], smallvec![Qubit::new(*i), Qubit::new(*j)], ), - CliffordGate::H(i) => ( - StandardGate::HGate, - smallvec![], - smallvec![Qubit::new(*i)], - ), - CliffordGate::S(i) => ( - StandardGate::SGate, - smallvec![], - smallvec![Qubit::new(*i)], - ), + CliffordGate::H(i) => (StandardGate::HGate, smallvec![], smallvec![Qubit::new(*i)]), + CliffordGate::S(i) => (StandardGate::SGate, smallvec![], smallvec![Qubit::new(*i)]), CliffordGate::Sd(i) => ( StandardGate::SdgGate, smallvec![], smallvec![Qubit::new(*i)], ), - CliffordGate::SqrtX(i) => ( - StandardGate::SXGate, - smallvec![], - smallvec![Qubit::new(*i)], - ), + CliffordGate::SqrtX(i) => (StandardGate::SXGate, smallvec![], smallvec![Qubit::new(*i)]), CliffordGate::SqrtXd(i) => ( StandardGate::SXdgGate, smallvec![], diff --git a/crates/accelerate/src/unitary_synthesis.rs b/crates/accelerate/src/unitary_synthesis.rs index 2d8ad73309c1..434dc9556fec 100644 --- a/crates/accelerate/src/unitary_synthesis.rs +++ b/crates/accelerate/src/unitary_synthesis.rs @@ -289,9 +289,7 @@ fn py_run_main_loop( py_op: new_node.unbind().into(), }; } - if !(packed_instr.op.name() == "unitary" - && packed_instr.op.num_qubits() >= min_qubits) - { + if !(packed_instr.op.name() == "unitary" && packed_instr.op.num_qubits() >= min_qubits) { out_dag.push_back(py, packed_instr)?; continue; } From 4909d5935c8256487756b70130ff6beff4a4ef5e Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Mon, 11 Nov 2024 16:55:15 -0500 Subject: [PATCH 13/15] Add PhysicalQubit::from_bit and From impls. --- .../accelerate/src/euler_one_qubit_decomposer.rs | 2 +- crates/accelerate/src/gate_direction.rs | 11 ++--------- crates/accelerate/src/gates_in_basis.rs | 4 +++- crates/accelerate/src/nlayout.rs | 14 ++++++++++++++ crates/accelerate/src/remove_identity_equiv.rs | 6 ++++-- crates/accelerate/src/unitary_synthesis.rs | 5 +---- 6 files changed, 25 insertions(+), 17 deletions(-) diff --git a/crates/accelerate/src/euler_one_qubit_decomposer.rs b/crates/accelerate/src/euler_one_qubit_decomposer.rs index 0207f0f2aed1..a825054937e7 100644 --- a/crates/accelerate/src/euler_one_qubit_decomposer.rs +++ b/crates/accelerate/src/euler_one_qubit_decomposer.rs @@ -1090,7 +1090,7 @@ pub(crate) fn optimize_1q_gates_decomposition( None => raw_run.len() as f64, }; let qubit: PhysicalQubit = if let NodeType::Operation(inst) = &dag.dag()[raw_run[0]] { - PhysicalQubit::new(dag.get_qargs(inst.qubits)[0].index().try_into().unwrap()) + PhysicalQubit::from_bit(dag.get_qargs(inst.qubits)[0]) } else { unreachable!("nodes in runs will always be op nodes") }; diff --git a/crates/accelerate/src/gate_direction.rs b/crates/accelerate/src/gate_direction.rs index fec066aa68d6..73344c4485da 100755 --- a/crates/accelerate/src/gate_direction.rs +++ b/crates/accelerate/src/gate_direction.rs @@ -10,7 +10,6 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -use crate::nlayout::PhysicalQubit; use crate::target_transpiler::exceptions::TranspilerError; use crate::target_transpiler::Target; use hashbrown::HashSet; @@ -75,10 +74,7 @@ fn py_check_direction_coupling_map( #[pyo3(name = "check_gate_direction_target")] fn py_check_direction_target(py: Python, dag: &DAGCircuit, target: &Target) -> PyResult { let target_check = |inst: &PackedInstruction, op_args: &[Qubit]| -> bool { - let qargs = smallvec![ - PhysicalQubit::new(op_args[0].index().try_into().unwrap()), - PhysicalQubit::new(op_args[1].index().try_into().unwrap()) - ]; + let qargs = smallvec![op_args[0].into(), op_args[1].into()]; target.instruction_supported(inst.op.name(), Some(&qargs)) }; @@ -205,10 +201,7 @@ fn py_fix_direction_target( target: &Target, ) -> PyResult { let target_check = |inst: &PackedInstruction, op_args: &[Qubit]| -> bool { - let qargs = smallvec![ - PhysicalQubit::new(op_args[0].index() as u32), - PhysicalQubit::new(op_args[1].index() as u32) - ]; + let qargs = smallvec![op_args[0].into(), op_args[1].into()]; // Take this path so Target can check for exact match of the parameterized gate's angle if let OperationRef::Standard(std_gate) = inst.op.view() { diff --git a/crates/accelerate/src/gates_in_basis.rs b/crates/accelerate/src/gates_in_basis.rs index 81dc5cd0284a..1ca54224c424 100644 --- a/crates/accelerate/src/gates_in_basis.rs +++ b/crates/accelerate/src/gates_in_basis.rs @@ -75,7 +75,9 @@ fn any_gate_missing_from_target(dag: &DAGCircuit, target: &Target) -> PyResult = HashMap::from_iter( - (0..dag.num_qubits()).map(|i| (Qubit::new(i), PhysicalQubit::new(i.try_into().unwrap()))), + (0..dag.num_qubits()) + .map(Qubit::new) + .map(|q| (q, PhysicalQubit::from_bit(q))), ); // Process the DAG. diff --git a/crates/accelerate/src/nlayout.rs b/crates/accelerate/src/nlayout.rs index dcd6e71fafa8..8d0e958607c7 100644 --- a/crates/accelerate/src/nlayout.rs +++ b/crates/accelerate/src/nlayout.rs @@ -15,6 +15,8 @@ use pyo3::types::PyList; use hashbrown::HashMap; +use qiskit_circuit::Qubit; + /// A newtype for the different categories of qubits used within layouts. This is to enforce /// significantly more type safety when dealing with mixtures of physical and virtual qubits, as we /// typically are when dealing with layouts. In Rust space, `NLayout` only works in terms of the @@ -32,11 +34,23 @@ macro_rules! qubit_newtype { Self(val) } #[inline] + pub fn from_bit(val: Qubit) -> Self { + // TODO: move physical and virtual bit types to the circuit crate + // This will always work since Qubit is never bigger than a u32. + Self(val.index() as u32) + } + #[inline] pub fn index(&self) -> usize { self.0 as usize } } + impl From for $id { + fn from(value: Qubit) -> Self { + Self::from_bit(value) + } + } + impl pyo3::IntoPy for $id { fn into_py(self, py: Python<'_>) -> PyObject { self.0.into_py(py) diff --git a/crates/accelerate/src/remove_identity_equiv.rs b/crates/accelerate/src/remove_identity_equiv.rs index f2eb78e28038..aba590131b21 100644 --- a/crates/accelerate/src/remove_identity_equiv.rs +++ b/crates/accelerate/src/remove_identity_equiv.rs @@ -44,7 +44,8 @@ fn remove_identity_equiv( let qargs: Vec = dag .get_qargs(inst.qubits) .iter() - .map(|x| PhysicalQubit::new(x.index() as u32)) + .cloned() + .map(PhysicalQubit::from_bit) .collect(); let error_rate = target.get_error(inst.op.name(), qargs.as_slice()); match error_rate { @@ -61,7 +62,8 @@ fn remove_identity_equiv( let qargs: Vec = dag .get_qargs(inst.qubits) .iter() - .map(|x| PhysicalQubit::new(x.index() as u32)) + .cloned() + .map(PhysicalQubit::from_bit) .collect(); let error_rate = target.get_error(inst.op.name(), qargs.as_slice()); match error_rate { diff --git a/crates/accelerate/src/unitary_synthesis.rs b/crates/accelerate/src/unitary_synthesis.rs index 434dc9556fec..52eff72b3139 100644 --- a/crates/accelerate/src/unitary_synthesis.rs +++ b/crates/accelerate/src/unitary_synthesis.rs @@ -301,10 +301,7 @@ fn py_run_main_loop( // Run 1q synthesis [2, 2] => { let qubit = dag.get_qargs(packed_instr.qubits)[0]; - let target_basis_set = get_target_basis_set( - target, - PhysicalQubit::new(qubit.index().try_into().unwrap()), - ); + let target_basis_set = get_target_basis_set(target, PhysicalQubit::from_bit(qubit)); let sequence = unitary_to_gate_sequence_inner( unitary.view(), &target_basis_set, From 9a4a781be9b3fde57fb6c176a35524998e4b9b25 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Mon, 11 Nov 2024 17:28:46 -0500 Subject: [PATCH 14/15] Also fix uses of PhysicalQubit and VirtualQubit. --- .../src/basis/basis_translator/mod.rs | 18 ++++++++---------- crates/accelerate/src/consolidate_blocks.rs | 5 +---- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/crates/accelerate/src/basis/basis_translator/mod.rs b/crates/accelerate/src/basis/basis_translator/mod.rs index 3e2f73140dc2..9395e84cc8f9 100644 --- a/crates/accelerate/src/basis/basis_translator/mod.rs +++ b/crates/accelerate/src/basis/basis_translator/mod.rs @@ -294,10 +294,8 @@ fn extract_basis_target( // single qubit operation for (1,) as valid. This pattern also holds // true for > 2q ops too (so for 4q operations we need to check for 3q, 2q, // and 1q operations in the same manner) - let physical_qargs: SmallVec<[PhysicalQubit; 2]> = qargs - .iter() - .map(|x| PhysicalQubit(x.index() as u32)) - .collect(); + let physical_qargs: SmallVec<[PhysicalQubit; 2]> = + qargs.iter().cloned().map(PhysicalQubit::from_bit).collect(); let physical_qargs_as_set: HashSet = HashSet::from_iter(physical_qargs.iter().copied()); if qargs_with_non_global_operation.contains_key(&Some(physical_qargs)) @@ -375,10 +373,8 @@ fn extract_basis_target_circ( // single qubit operation for (1,) as valid. This pattern also holds // true for > 2q ops too (so for 4q operations we need to check for 3q, 2q, // and 1q operations in the same manner) - let physical_qargs: SmallVec<[PhysicalQubit; 2]> = qargs - .iter() - .map(|x| PhysicalQubit(x.index() as u32)) - .collect(); + let physical_qargs: SmallVec<[PhysicalQubit; 2]> = + qargs.iter().cloned().map(PhysicalQubit::from_bit).collect(); let physical_qargs_as_set: HashSet = HashSet::from_iter(physical_qargs.iter().copied()); if qargs_with_non_global_operation.contains_key(&Some(physical_qargs)) @@ -516,7 +512,8 @@ fn apply_translation( let node_qarg_as_physical: Option = Some( node_qarg .iter() - .map(|x| PhysicalQubit(x.index() as u32)) + .cloned() + .map(PhysicalQubit::from_bit) .collect(), ); if qargs_with_non_global_operation.contains_key(&node_qarg_as_physical) @@ -574,7 +571,8 @@ fn apply_translation( Some( qubit_set .iter() - .map(|x| PhysicalQubit(x.index() as u32)) + .cloned() + .map(PhysicalQubit::from_bit) .collect(), ) }; diff --git a/crates/accelerate/src/consolidate_blocks.rs b/crates/accelerate/src/consolidate_blocks.rs index 61dcc41ddfb8..acef323204c0 100644 --- a/crates/accelerate/src/consolidate_blocks.rs +++ b/crates/accelerate/src/consolidate_blocks.rs @@ -39,10 +39,7 @@ fn is_supported( ) -> bool { match target { Some(target) => { - let physical_qargs = qargs - .iter() - .map(|bit| PhysicalQubit(bit.index() as u32)) - .collect(); + let physical_qargs = qargs.iter().cloned().map(PhysicalQubit::from_bit).collect(); target.instruction_supported(name, Some(&physical_qargs)) } None => match basis_gates { From 060ae31d6f7abaf28a5db3bd2a44d95a63c16d35 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Mon, 11 Nov 2024 17:37:36 -0500 Subject: [PATCH 15/15] Fix merge issue. --- crates/circuit/src/circuit_data.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs index 2b020892941a..493ccc9d0a3e 100644 --- a/crates/circuit/src/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -977,7 +977,7 @@ impl CircuitData { )), extra_attrs: instr.extra_attrs.clone(), #[cfg(feature = "cache_pygates")] - py_op: OnceCell::new(), + py_op: OnceLock::new(), }) } else { Ok(instr.clone())