From 4d12377d252ce13e808a6e6c55c3f105df91501e Mon Sep 17 00:00:00 2001 From: Raynel Sanchez Date: Sun, 25 Aug 2024 12:14:54 -0400 Subject: [PATCH 1/3] Initial: implement fixed capacity constructor for DAGCircuit - Implement `DAGCircuit` with `with_capacity` to create an initially allocated instance, for rust only. - Implement `with_capacity` for `BitData` and `IndexInterner` that creates an initally allocated instance of each type. - Other small tweaks and fixes. - Add num_edges optional argument. If known, use `num_edges` argument to create a `DAGCircuit` with the exact number of edges. - Leverage usage of new method in `copy_empty_like`. - Use `..Default::default()` for `op_names` and `calibrations` fields in `DAGCircuit::with_capacity()` --- crates/circuit/src/bit_data.rs | 9 ++++ crates/circuit/src/dag_circuit.rs | 71 ++++++++++++++++++++++++++++++- crates/circuit/src/interner.rs | 7 +++ 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/crates/circuit/src/bit_data.rs b/crates/circuit/src/bit_data.rs index 0c0b20a02522..6a8e4af6b920 100644 --- a/crates/circuit/src/bit_data.rs +++ b/crates/circuit/src/bit_data.rs @@ -95,6 +95,15 @@ where } } + pub fn with_capacity(py: Python<'_>, description: String, capacity: usize) -> Self { + BitData { + description, + bits: Vec::with_capacity(capacity), + indices: HashMap::with_capacity(capacity), + cached: PyList::empty_bound(py).unbind(), + } + } + /// Gets the number of bits. pub fn len(&self) -> usize { self.bits.len() diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 4e1cf88092f8..a27927eea8f8 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -1556,7 +1556,14 @@ def _format(operand): /// DAGCircuit: An empty copy of self. #[pyo3(signature = (*, vars_mode="alike"))] fn copy_empty_like(&self, py: Python, vars_mode: &str) -> PyResult { - let mut target_dag = DAGCircuit::new(py)?; + let mut target_dag = DAGCircuit::with_capacity( + py, + self.num_qubits(), + self.num_clbits(), + Some(self.num_vars()), + None, + None, + )?; target_dag.name = self.name.as_ref().map(|n| n.clone_ref(py)); target_dag.global_phase = self.global_phase.clone(); target_dag.duration = self.duration.as_ref().map(|d| d.clone_ref(py)); @@ -6157,6 +6164,68 @@ impl DAGCircuit { } Ok(()) } + + /// Alternative constructor, builds a DAGCircuit with a fixed capacity. + /// + /// # Arguments: + /// - `py`: Python GIL token + /// - `num_qubits`: Number of qubits in the circuit + /// - `num_clbits`: Number of classical bits in the circuit. + /// - `num_vars`: (Optional) number of variables in the circuit. + /// - `num_ops`: (Optional) number of operations in the circuit. + /// - `num_edges`: (Optional) If known, number of edges in the circuit. + pub fn with_capacity( + py: Python, + num_qubits: usize, + num_clbits: usize, + num_ops: Option, + num_vars: Option, + num_edges: Option, + ) -> PyResult { + let num_ops: usize = num_ops.unwrap_or_default(); + let num_vars = num_vars.unwrap_or_default(); + let num_edges = num_edges.unwrap_or( + num_qubits + // 1 edge between the input node and the output node or 1st op node. + num_clbits + // 1 edge between the input node and the output node or 1st op node. + num_vars + // 1 edge between the input node and the output node or 1st op node. + num_ops, // In Average there will be 3 edges (2 qubits and 1 clbit, or 3 qubits) per op_node. + ); + + let num_nodes = num_qubits * 2 + // One input + One output node per qubit + num_clbits * 2 + // One input + One output node per clbit + num_vars * 2 + // One input + output node per variable + num_ops; + + Ok(Self { + name: None, + metadata: Some(PyDict::new_bound(py).unbind().into()), + calibrations: HashMap::default(), + dag: StableDiGraph::with_capacity(num_nodes, num_edges), + qregs: PyDict::new_bound(py).unbind(), + cregs: PyDict::new_bound(py).unbind(), + qargs_cache: IndexedInterner::with_capacity(num_qubits), + cargs_cache: IndexedInterner::with_capacity(num_clbits), + qubits: BitData::with_capacity(py, "qubits".to_string(), num_qubits), + clbits: BitData::with_capacity(py, "clbits".to_string(), num_clbits), + global_phase: Param::Float(0.), + duration: None, + unit: "dt".to_string(), + qubit_locations: PyDict::new_bound(py).unbind(), + 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), + op_names: IndexMap::default(), + control_flow_module: PyControlFlowModule::new(py)?, + vars_info: HashMap::with_capacity(num_vars), + vars_by_type: [ + PySet::empty_bound(py)?.unbind(), + PySet::empty_bound(py)?.unbind(), + PySet::empty_bound(py)?.unbind(), + ], + }) + } } /// Add to global phase. Global phase can only be Float or ParameterExpression so this diff --git a/crates/circuit/src/interner.rs b/crates/circuit/src/interner.rs index 007256422e25..69a459fe7f95 100644 --- a/crates/circuit/src/interner.rs +++ b/crates/circuit/src/interner.rs @@ -97,6 +97,13 @@ where pub fn new() -> Self { Self(Default::default()) } + + pub fn with_capacity(capacity: usize) -> Self { + Self { + entries: Vec::with_capacity(capacity), + index_lookup: HashMap::with_capacity(capacity), + } + } } impl Interner From fec6da41c1a52956d9c9eba77e4064c0afeaaa7e Mon Sep 17 00:00:00 2001 From: Raynel Sanchez Date: Tue, 27 Aug 2024 15:08:50 -0400 Subject: [PATCH 2/3] Fix: Adapt to #13033 --- crates/circuit/src/dag_circuit.rs | 4 ++-- crates/circuit/src/interner.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index a27927eea8f8..6a80ddabd378 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -6203,8 +6203,8 @@ impl DAGCircuit { dag: StableDiGraph::with_capacity(num_nodes, num_edges), qregs: PyDict::new_bound(py).unbind(), cregs: PyDict::new_bound(py).unbind(), - qargs_cache: IndexedInterner::with_capacity(num_qubits), - cargs_cache: IndexedInterner::with_capacity(num_clbits), + qargs_interner: Interner::with_capacity(num_qubits), + 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), global_phase: Param::Float(0.), diff --git a/crates/circuit/src/interner.rs b/crates/circuit/src/interner.rs index 69a459fe7f95..1ad50b5150fe 100644 --- a/crates/circuit/src/interner.rs +++ b/crates/circuit/src/interner.rs @@ -99,10 +99,10 @@ where } pub fn with_capacity(capacity: usize) -> Self { - Self { - entries: Vec::with_capacity(capacity), - index_lookup: HashMap::with_capacity(capacity), - } + Self(IndexSet::with_capacity_and_hasher( + capacity, + ::ahash::RandomState::new(), + )) } } From 37e129eb5824cb6cfa913528b108253dd5fc2055 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez Date: Tue, 3 Sep 2024 16:43:45 -0400 Subject: [PATCH 3/3] Fix: Re-arrange the function arguments as per @mtreinish's review. - The order now follows: qubits, clbits, vars, num_ops, edges. As per [Matthew's comment](https://github.com/Qiskit/qiskit/pull/12975#discussion_r1742426093). --- 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 1e159235c989..90a4c75408fa 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -6177,8 +6177,8 @@ impl DAGCircuit { py: Python, num_qubits: usize, num_clbits: usize, - num_ops: Option, num_vars: Option, + num_ops: Option, num_edges: Option, ) -> PyResult { let num_ops: usize = num_ops.unwrap_or_default();