Skip to content

Commit

Permalink
Merge branch 'main' into qasm3/symbol-table
Browse files Browse the repository at this point in the history
  • Loading branch information
jlapeyre authored Jul 19, 2024
2 parents 92dfc19 + ec1f42c commit 74802fd
Show file tree
Hide file tree
Showing 14 changed files with 265,200 additions and 54 deletions.
8 changes: 4 additions & 4 deletions Cargo.lock

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

68 changes: 38 additions & 30 deletions qiskit/circuit/library/standard_gates/x.py
Original file line number Diff line number Diff line change
Expand Up @@ -952,8 +952,8 @@ class C4XGate(SingletonControlledGate):
of the relative phase version of c3x, the rc3x [2].
References:
[1] Barenco et al., 1995. https://arxiv.org/pdf/quant-ph/9503016.pdf
[2] Maslov, 2015. https://arxiv.org/abs/1508.03273
1. Barenco et al., 1995. https://arxiv.org/pdf/quant-ph/9503016.pdf
2. Maslov, 2015. https://arxiv.org/abs/1508.03273
"""

def __init__(
Expand Down Expand Up @@ -1320,9 +1320,14 @@ def _define(self):
class MCXRecursive(MCXGate):
"""Implement the multi-controlled X gate using recursion.
Using a single ancilla qubit, the multi-controlled X gate is recursively split onto
four sub-registers. This is done until we reach the 3- or 4-controlled X gate since
for these we have a concrete implementation that do not require ancillas.
Using a single clean ancilla qubit, the multi-controlled X gate is split into
four sub-registers, each one of them uses the V-chain method.
The method is based on Lemma 9 of [2], first shown in Lemma 7.3 of [1].
References:
1. Barenco et al., 1995. https://arxiv.org/pdf/quant-ph/9503016.pdf
2. Iten et al., 2015. https://arxiv.org/abs/1501.06911
"""

def __init__(
Expand Down Expand Up @@ -1378,32 +1383,35 @@ def _define(self):
qc._append(C4XGate(), q[:], [])
self.definition = qc
else:
for instr, qargs, cargs in self._recurse(q[:-1], q_ancilla=q[-1]):
qc._append(instr, qargs, cargs)
self.definition = qc
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=[],
)

def _recurse(self, q, q_ancilla=None):
# recursion stop
if len(q) == 4:
return [(C3XGate(), q[:], [])]
if len(q) == 5:
return [(C4XGate(), q[:], [])]
if len(q) < 4:
raise AttributeError("Something went wrong in the recursion, have less than 4 qubits.")

# recurse
num_ctrl_qubits = len(q) - 1
middle = ceil(num_ctrl_qubits / 2)
first_half = [*q[:middle], q_ancilla]
second_half = [*q[middle:num_ctrl_qubits], q_ancilla, q[num_ctrl_qubits]]

rule = []
rule += self._recurse(first_half, q_ancilla=q[middle])
rule += self._recurse(second_half, q_ancilla=q[middle - 1])
rule += self._recurse(first_half, q_ancilla=q[middle])
rule += self._recurse(second_half, q_ancilla=q[middle - 1])

return rule
self.definition = qc


class MCXVChain(MCXGate):
Expand Down
26 changes: 22 additions & 4 deletions qiskit/primitives/backend_estimator_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class _PreprocessedData:


class BackendEstimatorV2(BaseEstimatorV2):
"""Evaluates expectation values for provided quantum circuit and observable combinations
r"""Evaluates expectation values for provided quantum circuit and observable combinations.
The :class:`~.BackendEstimatorV2` class is a generic implementation of the
:class:`~.BaseEstimatorV2` interface that is used to wrap a :class:`~.BackendV2`
Expand All @@ -87,7 +87,19 @@ class BackendEstimatorV2(BaseEstimatorV2):
precludes doing any provider- or backend-specific optimizations.
This class does not perform any measurement or gate mitigation, and, presently, is only
compatible with Pauli-based observables.
compatible with Pauli-based observables. More formally, given an observable of the type
:math:`O=\sum_{i=1}^Na_iP_i`, where :math:`a_i` is a complex number and :math:`P_i` is a
Pauli operator, the estimator calculates the expectation :math:`\mathbb{E}(P_i)` of each
:math:`P_i` and finally calculates the expectation value of :math:`O` as
:math:`\mathbb{E}(O)=\sum_{i=1}^Na_i\mathbb{E}(P_i)`. The reported ``std`` is calculated
as
.. math::
\frac{\sum_{i=1}^{n}|a_i|\sqrt{\textrm{Var}\big(P_i\big)}}{\sqrt{N}}\:,
where :math:`\textrm{Var}(P_i)` is the variance of :math:`P_i`, :math:`N=O(\epsilon^{-2})` is
the number of shots, and :math:`\epsilon` is the target precision [1].
Each tuple of ``(circuit, observables, <optional> parameter values, <optional> precision)``,
called an estimator primitive unified bloc (PUB), produces its own array-based result. The
Expand All @@ -104,6 +116,12 @@ class BackendEstimatorV2(BaseEstimatorV2):
* ``seed_simulator``: The seed to use in the simulator. If None, a random seed will be used.
Default: None.
**Reference:**
[1] O. Crawford, B. van Straaten, D. Wang, T. Parks, E. Campbell, St. Brierley,
Efficient quantum measurement of Pauli operators in the presence of finite sampling error.
`Quantum 5, 385 <https://doi.org/10.22331/q-2021-01-20-385>`_
"""

def __init__(
Expand Down Expand Up @@ -254,8 +272,8 @@ def _postprocess_pub(
for pauli, coeff in bc_obs[index].items():
expval, variance = expval_map[param_index, pauli]
evs[index] += expval * coeff
variances[index] += variance * coeff**2
stds = np.sqrt(variances / shots)
variances[index] += np.abs(coeff) * variance**0.5
stds = variances / np.sqrt(shots)
data_bin = DataBin(evs=evs, stds=stds, shape=evs.shape)
return PubResult(data_bin, metadata={"target_precision": pub.precision})

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
fixes:
- |
Changes the way in which the :class:`.BackendEstimatorV2` class calculates the ``std`` to ensure that
it matches the correct formula.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
features_synthesis:
- |
:class:`.MCXRecursive` with :math:`k` control qubits and a single clean auxiliary qubit
now requires at most :math:`16k-8` CX gates.
84 changes: 84 additions & 0 deletions test/benchmarks/circuit_construction.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,16 @@
# pylint: disable=missing-docstring,invalid-name,no-member
# pylint: disable=attribute-defined-outside-init

import os
import itertools

from qiskit.quantum_info import random_clifford
from qiskit import QuantumRegister, QuantumCircuit
from qiskit.circuit import Parameter
from qiskit.circuit.library import EfficientSU2, QuantumVolume
from .utils import dtc_unitary, multi_control_circuit

SEED = 12345


def build_circuit(width, gates):
Expand Down Expand Up @@ -96,3 +102,81 @@ def time_bind_params(self, _, __, ___):
# TODO: write more complete benchmarks of assign_parameters
# that test more of the input formats / combinations
self.circuit.assign_parameters({x: 3.14 for x in self.params})


class ParamaterizedDifferentCircuit:
param_names = ["circuit_size", "num_qubits"]
params = ([10, 50, 100], [10, 50, 150])

def time_QV100_build(self, circuit_size, num_qubits):
"""Measures an SDKs ability to build a 100Q
QV circit from scratch.
"""
return QuantumVolume(circuit_size, num_qubits, seed=SEED)

def time_DTC100_set_build(self, circuit_size, num_qubits):
"""Measures an SDKs ability to build a set
of 100Q DTC circuits out to 100 layers of
the underlying unitary
"""
max_cycles = circuit_size
initial_state = QuantumCircuit(num_qubits)
dtc_circuit = dtc_unitary(num_qubits, g=0.95, seed=SEED)

circs = [initial_state]
for tt in range(max_cycles):
qc = circs[tt].compose(dtc_circuit)
circs.append(qc)
result = circs[-1]

return result


class MultiControl:
param_names = ["width"]
params = [10, 16, 20]

def time_multi_control_circuit(self, width):
"""Measures an SDKs ability to build a circuit
with a multi-controlled X-gate
"""
out = multi_control_circuit(width)
return out


class ParameterizedCirc:
param_names = ["num_qubits"]
params = [5, 10, 16]

def time_param_circSU2_100_build(self, num_qubits):
"""Measures an SDKs ability to build a
parameterized efficient SU2 circuit with circular entanglement
over 100Q utilizing 4 repetitions. This will yield a
circuit with 1000 parameters
"""
out = EfficientSU2(num_qubits, reps=4, entanglement="circular", flatten=True)
out._build()
return out


class QasmImport:
def time_QV100_qasm2_import(self):
"""QASM import of QV100 circuit"""
self.qasm_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "qasm"))

out = QuantumCircuit.from_qasm_file(os.path.join(self.qasm_path, "qv_N100_12345.qasm"))
ops = out.count_ops()
assert ops.get("rz", 0) == 120000
assert ops.get("rx", 0) == 80000
assert ops.get("cx", 0) == 15000
return ops


class CliffordSynthesis:
param_names = ["num_qubits"]
params = [10, 50, 100]

def time_clifford_synthesis(self, num_qubits):
cliff = random_clifford(num_qubits)
qc = cliff.to_circuit()
return qc
Loading

0 comments on commit 74802fd

Please sign in to comment.