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 13, 2024
2 parents 3f24bfd + 61adcf9 commit 3c765fd
Show file tree
Hide file tree
Showing 18 changed files with 481 additions and 220 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ ndarray = "^0.15.6"
numpy = "0.21.0"
smallvec = "1.13"
thiserror = "1.0"
ahash = "0.8.11"

# Most of the crates don't need the feature `extension-module`, since only `qiskit-pyext` builds an
# actual C extension (the feature disables linking in `libpython`, which is forbidden in Python
Expand Down
2 changes: 1 addition & 1 deletion crates/accelerate/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ numpy.workspace = true
rand = "0.8"
rand_pcg = "0.3"
rand_distr = "0.4.3"
ahash = "0.8.11"
ahash.workspace = true
num-traits = "0.2"
num-complex.workspace = true
num-bigint.workspace = true
Expand Down
5 changes: 3 additions & 2 deletions crates/accelerate/src/synthesis/clifford/greedy_synthesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
// copyright notice, and modified files need to carry a notice indicating
// that they have been altered from the originals.

use ahash::RandomState;
use indexmap::IndexSet;
use ndarray::{s, ArrayView2};
use smallvec::smallvec;
Expand Down Expand Up @@ -102,7 +103,7 @@ pub struct GreedyCliffordSynthesis<'a> {
symplectic_matrix: SymplecticMatrix,

/// Unprocessed qubits.
unprocessed_qubits: IndexSet<usize>,
unprocessed_qubits: IndexSet<usize, RandomState>,
}

impl GreedyCliffordSynthesis<'_> {
Expand All @@ -121,7 +122,7 @@ impl GreedyCliffordSynthesis<'_> {
smat: tableau.slice(s![.., 0..2 * num_qubits]).to_owned(),
};

let unprocessed_qubits: IndexSet<usize> = (0..num_qubits).collect();
let unprocessed_qubits = (0..num_qubits).collect();

Ok(GreedyCliffordSynthesis {
tableau,
Expand Down
72 changes: 71 additions & 1 deletion crates/circuit/src/circuit_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::circuit_instruction::{CircuitInstruction, OperationFromPython};
use crate::imports::{ANNOTATED_OPERATION, CLBIT, QUANTUM_CIRCUIT, QUBIT};
use crate::interner::{IndexedInterner, Interner, InternerKey};
use crate::operations::{Operation, OperationRef, Param, StandardGate};
use crate::packed_instruction::PackedInstruction;
use crate::packed_instruction::{PackedInstruction, PackedOperation};
use crate::parameter_table::{ParameterTable, ParameterTableError, ParameterUse, ParameterUuid};
use crate::slice::{PySequenceIndex, SequenceIndex};
use crate::{Clbit, Qubit};
Expand Down Expand Up @@ -104,6 +104,71 @@ pub struct CircuitData {
}

impl CircuitData {
/// An alternate constructor to build a new `CircuitData` from an iterator
/// of packed operations. This can be used to build a circuit from a sequence
/// of `PackedOperation` without needing to involve Python.
///
/// This can be connected with the Python space
/// QuantumCircuit.from_circuit_data() constructor to build a full
/// QuantumCircuit from Rust.
///
/// # Arguments
///
/// * py: A GIL handle this is needed to instantiate Qubits in Python space
/// * num_qubits: The number of qubits in the circuit. These will be created
/// in Python as loose bits without a register.
/// * num_clbits: The number of classical bits in the circuit. These will be created
/// in Python as loose bits without a register.
/// * instructions: An iterator of the (packed operation, params, qubits, clbits) to
/// add to the circuit
/// * global_phase: The global phase to use for the circuit
pub fn from_packed_operations<I>(
py: Python,
num_qubits: u32,
num_clbits: u32,
instructions: I,
global_phase: Param,
) -> PyResult<Self>
where
I: IntoIterator<
Item = (
PackedOperation,
SmallVec<[Param; 3]>,
Vec<Qubit>,
Vec<Clbit>,
),
>,
{
let instruction_iter = instructions.into_iter();
let mut res = Self::with_capacity(
py,
num_qubits,
num_clbits,
instruction_iter.size_hint().0,
global_phase,
)?;
for (operation, params, qargs, cargs) in instruction_iter {
let qubits = (&mut res.qargs_interner)
.intern(InternerKey::Value(qargs))?
.index;
let clbits = (&mut res.cargs_interner)
.intern(InternerKey::Value(cargs))?
.index;
let params = (!params.is_empty()).then(|| Box::new(params));
res.data.push(PackedInstruction {
op: operation,
qubits,
clbits,
params,
extra_attrs: None,
#[cfg(feature = "cache_pygates")]
py_op: RefCell::new(None),
});
res.track_instruction_parameters(py, res.data.len() - 1)?;
}
Ok(res)
}

/// An alternate constructor to build a new `CircuitData` from an iterator
/// of standard gates. This can be used to build a circuit from a sequence
/// of standard gates, such as for a `StandardGate` definition or circuit
Expand Down Expand Up @@ -1284,6 +1349,11 @@ impl CircuitData {
}
Ok(())
}

/// Retrieves the python `Param` object based on its `ParameterUuid`.
pub fn get_parameter_by_uuid(&self, uuid: ParameterUuid) -> Option<&Py<PyAny>> {
self.param_table.py_parameter_by_uuid(uuid)
}
}

/// Helper struct for `assign_parameters` to allow use of `Param::extract_no_coerce` in
Expand Down
5 changes: 5 additions & 0 deletions crates/circuit/src/parameter_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,11 @@ impl ParameterTable {
.map(|uuid| &self.by_uuid[uuid].object)
}

/// Lookup the Python parameter object by uuid.
pub fn py_parameter_by_uuid(&self, uuid: ParameterUuid) -> Option<&Py<PyAny>> {
self.by_uuid.get(&uuid).map(|param| &param.object)
}

/// 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() {
Expand Down
1 change: 1 addition & 0 deletions crates/qasm3/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ pyo3.workspace = true
indexmap.workspace = true
hashbrown.workspace = true
oq3_semantics = "0.6.0"
ahash.workspace = true
5 changes: 4 additions & 1 deletion crates/qasm3/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
use pyo3::prelude::*;
use pyo3::types::{PySequence, PyString, PyTuple};

use ahash::RandomState;

use hashbrown::HashMap;
use indexmap::IndexMap;

Expand Down Expand Up @@ -190,8 +192,9 @@ impl BuilderState {
let qubits = if let Some(asg_qubits) = barrier.qubits().as_ref() {
// We want any deterministic order for easier circuit reproducibility in Python space,
// and to include each seen qubit once. This simply maintains insertion order.
let mut qubits = IndexMap::<*const ::pyo3::ffi::PyObject, Py<PyAny>>::with_capacity(
let mut qubits = IndexMap::<*const ::pyo3::ffi::PyObject, Py<PyAny>, RandomState>::with_capacity_and_hasher(
asg_qubits.len(),
RandomState::default()
);
for qarg in asg_qubits.iter() {
let qarg = expr::expect_gate_operand(qarg)?;
Expand Down
138 changes: 16 additions & 122 deletions qiskit/circuit/library/standard_gates/x.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"""X, CX, CCX and multi-controlled X gates."""
from __future__ import annotations
from typing import Optional, Union, Type
from math import ceil, pi
from math import pi
import numpy
from qiskit.circuit.controlledgate import ControlledGate
from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate, stdlib_singleton_key
Expand Down Expand Up @@ -1371,47 +1371,12 @@ def inverse(self, annotated: bool = False):

def _define(self):
"""Define the MCX gate using recursion."""
# pylint: disable=cyclic-import
from qiskit.circuit.quantumcircuit import QuantumCircuit

q = QuantumRegister(self.num_qubits, name="q")
qc = QuantumCircuit(q, name=self.name)
if self.num_qubits == 4:
qc._append(C3XGate(), q[:], [])
self.definition = qc
elif self.num_qubits == 5:
qc._append(C4XGate(), q[:], [])
self.definition = qc
else:
num_ctrl_qubits = len(q) - 1
q_ancilla = q[-1]
q_target = q[-2]
middle = ceil(num_ctrl_qubits / 2)
first_half = [*q[:middle]]
second_half = [*q[middle : num_ctrl_qubits - 1], q_ancilla]

qc._append(
MCXVChain(num_ctrl_qubits=len(first_half), dirty_ancillas=True),
qargs=[*first_half, q_ancilla, *q[middle : middle + len(first_half) - 2]],
cargs=[],
)
qc._append(
MCXVChain(num_ctrl_qubits=len(second_half), dirty_ancillas=True),
qargs=[*second_half, q_target, *q[: len(second_half) - 2]],
cargs=[],
)
qc._append(
MCXVChain(num_ctrl_qubits=len(first_half), dirty_ancillas=True),
qargs=[*first_half, q_ancilla, *q[middle : middle + len(first_half) - 2]],
cargs=[],
)
qc._append(
MCXVChain(num_ctrl_qubits=len(second_half), dirty_ancillas=True),
qargs=[*second_half, q_target, *q[: len(second_half) - 2]],
cargs=[],
)
# pylint: disable=cyclic-import
from qiskit.synthesis.multi_controlled import synth_mcx_1_clean_b95

self.definition = qc
qc = synth_mcx_1_clean_b95(self.num_ctrl_qubits)
self.definition = qc


class MCXVChain(MCXGate):
Expand Down Expand Up @@ -1513,92 +1478,21 @@ def get_num_ancilla_qubits(num_ctrl_qubits: int, mode: str = "v-chain"):

def _define(self):
"""Define the MCX gate using a V-chain of CX gates."""
# pylint: disable=cyclic-import
from qiskit.circuit.quantumcircuit import QuantumCircuit

q = QuantumRegister(self.num_qubits, name="q")
qc = QuantumCircuit(q, name=self.name)
q_controls = q[: self.num_ctrl_qubits]
q_target = q[self.num_ctrl_qubits]
q_ancillas = q[self.num_ctrl_qubits + 1 :]

if self._dirty_ancillas:
if self.num_ctrl_qubits < 3:
qc.mcx(q_controls, q_target)
elif not self._relative_phase and self.num_ctrl_qubits == 3:
qc._append(C3XGate(), [*q_controls, q_target], [])
else:
num_ancillas = self.num_ctrl_qubits - 2
targets = [q_target] + q_ancillas[:num_ancillas][::-1]

for j in range(2):
for i in range(self.num_ctrl_qubits): # action part
if i < self.num_ctrl_qubits - 2:
if targets[i] != q_target or self._relative_phase:
# gate cancelling

# cancel rightmost gates of action part
# with leftmost gates of reset part
if self._relative_phase and targets[i] == q_target and j == 1:
qc.cx(q_ancillas[num_ancillas - i - 1], targets[i])
qc.t(targets[i])
qc.cx(q_controls[self.num_ctrl_qubits - i - 1], targets[i])
qc.tdg(targets[i])
qc.h(targets[i])
else:
qc.h(targets[i])
qc.t(targets[i])
qc.cx(q_controls[self.num_ctrl_qubits - i - 1], targets[i])
qc.tdg(targets[i])
qc.cx(q_ancillas[num_ancillas - i - 1], targets[i])
else:
controls = [
q_controls[self.num_ctrl_qubits - i - 1],
q_ancillas[num_ancillas - i - 1],
]

qc.ccx(controls[0], controls[1], targets[i])
else:
# implements an optimized toffoli operation
# up to a diagonal gate, akin to lemma 6 of arXiv:1501.06911
qc.h(targets[i])
qc.t(targets[i])
qc.cx(q_controls[self.num_ctrl_qubits - i - 2], targets[i])
qc.tdg(targets[i])
qc.cx(q_controls[self.num_ctrl_qubits - i - 1], targets[i])
qc.t(targets[i])
qc.cx(q_controls[self.num_ctrl_qubits - i - 2], targets[i])
qc.tdg(targets[i])
qc.h(targets[i])

break

for i in range(num_ancillas - 1): # reset part
qc.cx(q_ancillas[i], q_ancillas[i + 1])
qc.t(q_ancillas[i + 1])
qc.cx(q_controls[2 + i], q_ancillas[i + 1])
qc.tdg(q_ancillas[i + 1])
qc.h(q_ancillas[i + 1])

if self._action_only:
qc.ccx(q_controls[-1], q_ancillas[-1], q_target)

break
else:
qc.rccx(q_controls[0], q_controls[1], q_ancillas[0])
i = 0
for j in range(2, self.num_ctrl_qubits - 1):
qc.rccx(q_controls[j], q_ancillas[i], q_ancillas[i + 1])
# pylint: disable=cyclic-import
from qiskit.synthesis.multi_controlled import synth_mcx_n_dirty_i15

i += 1

qc.ccx(q_controls[-1], q_ancillas[i], q_target)

for j in reversed(range(2, self.num_ctrl_qubits - 1)):
qc.rccx(q_controls[j], q_ancillas[i - 1], q_ancillas[i])
qc = synth_mcx_n_dirty_i15(
self.num_ctrl_qubits,
self._relative_phase,
self._action_only,
)

i -= 1
else: # use clean ancillas
# pylint: disable=cyclic-import
from qiskit.synthesis.multi_controlled import synth_mcx_n_clean_m15

qc.rccx(q_controls[0], q_controls[1], q_ancillas[i])
qc = synth_mcx_n_clean_m15(self.num_ctrl_qubits)

self.definition = qc
8 changes: 7 additions & 1 deletion qiskit/qasm3/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,13 @@ def build_program(self):
if builtin in _BUILTIN_GATES:
# It's built into the langauge; we don't need to re-add it.
continue
self.symbols.register_gate_without_definition(builtin, None)
try:
self.symbols.register_gate_without_definition(builtin, None)
except QASM3ExporterError as exc:
raise QASM3ExporterError(
f"Cannot use '{builtin}' as a basis gate for the reason in the prior exception."
" Consider renaming the gate if needed, or omitting this basis gate if not."
) from exc

header = ast.Header(ast.Version("3.0"), list(self.build_includes()))

Expand Down
12 changes: 12 additions & 0 deletions qiskit/synthesis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,13 @@
.. autofunction:: two_qubit_cnot_decompose
Multi Controlled Synthesis
==========================
.. autofunction:: synth_mcx_n_dirty_i15
.. autofunction:: synth_mcx_n_clean_m15
.. autofunction:: synth_mcx_1_clean_b95
"""

from .evolution import (
Expand Down Expand Up @@ -173,3 +180,8 @@
two_qubit_cnot_decompose,
TwoQubitWeylDecomposition,
)
from .multi_controlled.mcx_with_ancillas_synth import (
synth_mcx_n_dirty_i15,
synth_mcx_n_clean_m15,
synth_mcx_1_clean_b95,
)
Loading

0 comments on commit 3c765fd

Please sign in to comment.