Skip to content

Commit

Permalink
Merge branch 'main' into oncecell-cache
Browse files Browse the repository at this point in the history
  • Loading branch information
raynelfss authored Aug 14, 2024
2 parents a19d92f + 2d3db9a commit fc315af
Show file tree
Hide file tree
Showing 21 changed files with 775 additions and 262 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ license = "Apache-2.0"
# Each crate can add on specific features freely as it inherits.
[workspace.dependencies]
bytemuck = "1.16"
indexmap.version = "2.3.0"
indexmap.version = "2.4.0"
hashbrown.version = "0.14.0"
num-bigint = "0.4"
num-complex = "0.4"
Expand Down
2 changes: 1 addition & 1 deletion crates/circuit/src/circuit_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ impl CircuitData {
/// Get a (cached) sorted list of the Python-space `Parameter` instances tracked by this circuit
/// data's parameter table.
#[getter]
pub fn get_parameters<'py>(&mut self, py: Python<'py>) -> Bound<'py, PyList> {
pub fn get_parameters<'py>(&self, py: Python<'py>) -> Bound<'py, PyList> {
self.param_table.py_parameters(py)
}

Expand Down
129 changes: 77 additions & 52 deletions crates/circuit/src/parameter_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
// copyright notice, and modified files need to carry a notice indicating
// that they have been altered from the originals.

use std::cell::OnceCell;

use hashbrown::hash_map::Entry;
use hashbrown::{HashMap, HashSet};
use thiserror::Error;
Expand Down Expand Up @@ -123,18 +125,17 @@ pub struct ParameterTable {
by_name: HashMap<PyBackedStr, ParameterUuid>,
/// Additional information on any `ParameterVector` instances that have elements in the circuit.
vectors: HashMap<VectorUuid, VectorInfo>,
/// Sort order of the parameters. This is lexicographical for most parameters, except elements
/// of a `ParameterVector` are sorted within the vector by numerical index. We calculate this
/// on demand and cache it; an empty `order` implies it is not currently calculated. We don't
/// use `Option<Vec>` so we can re-use the allocation for partial parameter bindings.
/// Cache of the sort order of the parameters. This is lexicographical for most parameters,
/// except elements of a `ParameterVector` are sorted within the vector by numerical index. We
/// calculate this on demand and cache it.
///
/// Any method that adds or a removes a parameter is responsible for invalidating this cache.
order: Vec<ParameterUuid>,
/// Any method that adds or removes a parameter needs to invalidate this.
order_cache: OnceCell<Vec<ParameterUuid>>,
/// Cache of a Python-space list of the parameter objects, in order. We only generate this
/// specifically when asked.
///
/// Any method that adds or a removes a parameter is responsible for invalidating this cache.
py_parameters: Option<Py<PyList>>,
/// Any method that adds or removes a parameter needs to invalidate this.
py_parameters_cache: OnceCell<Py<PyList>>,
}

impl ParameterTable {
Expand Down Expand Up @@ -194,8 +195,6 @@ impl ParameterTable {
None
};
self.by_name.insert(name.clone(), uuid);
self.order.clear();
self.py_parameters = None;
let mut uses = HashSet::new();
if let Some(usage) = usage {
uses.insert_unique_unchecked(usage);
Expand All @@ -206,6 +205,7 @@ impl ParameterTable {
element,
object: param_ob.clone().unbind(),
});
self.invalidate_cache();
}
}
Ok(uuid)
Expand All @@ -231,43 +231,39 @@ impl ParameterTable {
}

/// Get the (maybe cached) Python list of the sorted `Parameter` objects.
pub fn py_parameters<'py>(&mut self, py: Python<'py>) -> Bound<'py, PyList> {
if let Some(py_parameters) = self.py_parameters.as_ref() {
return py_parameters.clone_ref(py).into_bound(py);
}
self.ensure_sorted();
let out = PyList::new_bound(
py,
self.order
.iter()
.map(|uuid| self.by_uuid[uuid].object.clone_ref(py).into_bound(py)),
);
self.py_parameters = Some(out.clone().unbind());
out
pub fn py_parameters<'py>(&self, py: Python<'py>) -> Bound<'py, PyList> {
self.py_parameters_cache
.get_or_init(|| {
PyList::new_bound(
py,
self.order_cache
.get_or_init(|| self.sorted_order())
.iter()
.map(|uuid| self.by_uuid[uuid].object.bind(py).clone()),
)
.unbind()
})
.bind(py)
.clone()
}

/// Get a Python set of all tracked `Parameter` objects.
pub fn py_parameters_unsorted<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PySet>> {
PySet::new_bound(py, self.by_uuid.values().map(|info| &info.object))
}

/// Ensure that the `order` field is populated and sorted.
fn ensure_sorted(&mut self) {
// If `order` is already populated, it's sorted; it's the responsibility of the methods of
// this struct that mutate it to invalidate the cache.
if !self.order.is_empty() {
return;
}
self.order.reserve(self.by_uuid.len());
self.order.extend(self.by_uuid.keys());
self.order.sort_unstable_by_key(|uuid| {
/// Get the sorted order of the `ParameterTable`. This does not access the cache.
fn sorted_order(&self) -> Vec<ParameterUuid> {
let mut out = self.by_uuid.keys().copied().collect::<Vec<_>>();
out.sort_unstable_by_key(|uuid| {
let info = &self.by_uuid[uuid];
if let Some(vec) = info.element.as_ref() {
(&self.vectors[&vec.vector_uuid].name, vec.index)
} else {
(&info.name, 0)
}
})
});
out
}

/// Add a use of a parameter to the table.
Expand Down Expand Up @@ -310,9 +306,8 @@ impl ParameterTable {
vec_entry.remove_entry();
}
}
self.order.clear();
self.py_parameters = None;
entry.remove_entry();
self.invalidate_cache();
}
Ok(())
}
Expand All @@ -337,26 +332,28 @@ impl ParameterTable {
(vector_info.refcount > 0).then_some(vector_info)
});
}
self.order.clear();
self.py_parameters = None;
self.invalidate_cache();
Ok(info.uses)
}

/// Clear this table, yielding the Python parameter objects and their uses in sorted order.
///
/// The clearing effect is eager and not dependent on the iteration.
pub fn drain_ordered(
&'_ mut self,
) -> impl Iterator<Item = (Py<PyAny>, HashSet<ParameterUse>)> + '_ {
self.ensure_sorted();
&mut self,
) -> impl ExactSizeIterator<Item = (Py<PyAny>, HashSet<ParameterUse>)> {
let order = self
.order_cache
.take()
.unwrap_or_else(|| self.sorted_order());
let by_uuid = ::std::mem::take(&mut self.by_uuid);
self.by_name.clear();
self.vectors.clear();
self.py_parameters = None;
self.order.drain(..).map(|uuid| {
let info = self
.by_uuid
.remove(&uuid)
.expect("tracked UUIDs should be consistent");
(info.object, info.uses)
})
self.py_parameters_cache.take();
ParameterTableDrain {
order: order.into_iter(),
by_uuid,
}
}

/// Empty this `ParameterTable` of all its contents. This does not affect the capacities of the
Expand All @@ -365,8 +362,12 @@ impl ParameterTable {
self.by_uuid.clear();
self.by_name.clear();
self.vectors.clear();
self.order.clear();
self.py_parameters = None;
self.invalidate_cache();
}

fn invalidate_cache(&mut self) {
self.order_cache.take();
self.py_parameters_cache.take();
}

/// Expose the tracked data for a given parameter as directly as possible to Python space.
Expand Down Expand Up @@ -401,9 +402,33 @@ impl ParameterTable {
visit.call(&info.object)?
}
// We don't need to / can't visit the `PyBackedStr` stores.
if let Some(list) = self.py_parameters.as_ref() {
if let Some(list) = self.py_parameters_cache.get() {
visit.call(list)?
}
Ok(())
}
}

struct ParameterTableDrain {
order: ::std::vec::IntoIter<ParameterUuid>,
by_uuid: HashMap<ParameterUuid, ParameterInfo>,
}
impl Iterator for ParameterTableDrain {
type Item = (Py<PyAny>, HashSet<ParameterUse>);

fn next(&mut self) -> Option<Self::Item> {
self.order.next().map(|uuid| {
let info = self
.by_uuid
.remove(&uuid)
.expect("tracked UUIDs should be consistent");
(info.object, info.uses)
})
}

fn size_hint(&self) -> (usize, Option<usize>) {
self.order.size_hint()
}
}
impl ExactSizeIterator for ParameterTableDrain {}
impl ::std::iter::FusedIterator for ParameterTableDrain {}
6 changes: 3 additions & 3 deletions qiskit/circuit/add_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def control(
gate.definition.data[0].operation.params[0],
q_control,
q_target[bit_indices[qargs[0]]],
use_basis_gates=True,
use_basis_gates=False,
)
elif gate.name == "ry":
controlled_circ.mcry(
Expand All @@ -146,14 +146,14 @@ def control(
q_target[bit_indices[qargs[0]]],
q_ancillae,
mode="noancilla",
use_basis_gates=True,
use_basis_gates=False,
)
elif gate.name == "rz":
controlled_circ.mcrz(
gate.definition.data[0].operation.params[0],
q_control,
q_target[bit_indices[qargs[0]]],
use_basis_gates=True,
use_basis_gates=False,
)
continue
elif gate.name == "p":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ def _mcsu2_real_diagonal(
circuit.h(target)

if use_basis_gates:
circuit = transpile(circuit, basis_gates=["p", "u", "cx"])
circuit = transpile(circuit, basis_gates=["p", "u", "cx"], qubits_initially_zero=False)

return circuit

Expand Down
3 changes: 2 additions & 1 deletion qiskit/circuit/library/standard_gates/p.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,8 @@ def _define(self):
q_target = self.num_ctrl_qubits
new_target = q_target
for k in range(self.num_ctrl_qubits):
qc.mcrz(lam / (2**k), q_controls, new_target, use_basis_gates=True)
# Note: it's better *not* to run transpile recursively
qc.mcrz(lam / (2**k), q_controls, new_target, use_basis_gates=False)
new_target = q_controls.pop()
qc.p(lam / (2**self.num_ctrl_qubits), new_target)
else: # in this case type(lam) is ParameterValueType
Expand Down
3 changes: 3 additions & 0 deletions qiskit/compiler/transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def transpile( # pylint: disable=too-many-return-statements
optimization_method: Optional[str] = None,
ignore_backend_supplied_default_methods: bool = False,
num_processes: Optional[int] = None,
qubits_initially_zero: bool = True,
) -> _CircuitT:
"""Transpile one or more circuits, according to some desired transpilation targets.
Expand Down Expand Up @@ -284,6 +285,7 @@ def callback_func(**kwargs):
``num_processes`` in the user configuration file, and the ``QISKIT_NUM_PROCS``
environment variable. If set to ``None`` the system default or local user configuration
will be used.
qubits_initially_zero: Indicates whether the input circuit is zero-initialized.
Returns:
The transpiled circuit(s).
Expand Down Expand Up @@ -386,6 +388,7 @@ def callback_func(**kwargs):
init_method=init_method,
optimization_method=optimization_method,
dt=dt,
qubits_initially_zero=qubits_initially_zero,
)

out_circuits = pm.run(circuits, callback=callback, num_processes=num_processes)
Expand Down
4 changes: 3 additions & 1 deletion qiskit/synthesis/unitary/qsd.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,9 @@ def _apply_a2(circ):
from qiskit.circuit.library.generalized_gates.unitary import UnitaryGate

decomposer = two_qubit_decompose_up_to_diagonal
ccirc = transpile(circ, basis_gates=["u", "cx", "qsd2q"], optimization_level=0)
ccirc = transpile(
circ, basis_gates=["u", "cx", "qsd2q"], optimization_level=0, qubits_initially_zero=False
)
ind2q = []
# collect 2q instrs
for i, instruction in enumerate(ccirc.data):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from qiskit.circuit.delay import Delay
from qiskit.circuit.library.standard_gates import IGate, UGate, U3Gate
from qiskit.circuit.reset import Reset
from qiskit.dagcircuit import DAGCircuit, DAGNode, DAGInNode, DAGOpNode
from qiskit.dagcircuit import DAGCircuit, DAGNode, DAGInNode, DAGOpNode, DAGOutNode
from qiskit.quantum_info.operators.predicates import matrix_equal
from qiskit.synthesis.one_qubit import OneQubitEulerDecomposer
from qiskit.transpiler.exceptions import TranspilerError
Expand Down Expand Up @@ -331,8 +331,7 @@ def _pad(
if time_interval % self._alignment != 0:
raise TranspilerError(
f"Time interval {time_interval} is not divisible by alignment {self._alignment} "
f"between DAGNode {prev_node.name} on qargs {prev_node.qargs} and {next_node.name} "
f"on qargs {next_node.qargs}."
f"between {_format_node(prev_node)} and {_format_node(next_node)}."
)

if not self.__is_dd_qubit(dag.qubits.index(qubit)):
Expand Down Expand Up @@ -430,3 +429,10 @@ def _resolve_params(gate: Gate) -> tuple:
else:
params.append(p)
return tuple(params)


def _format_node(node: DAGNode) -> str:
"""Util to format the DAGNode, DAGInNode, and DAGOutNode."""
if isinstance(node, (DAGInNode, DAGOutNode)):
return f"{node.__class__.__name__} on qarg {node.wire}"
return f"DAGNode {node.name} on qargs {node.qargs}"
Loading

0 comments on commit fc315af

Please sign in to comment.