# The original version of the following code is sourced from Qiskit circuit library:
# https://github.com/Qiskit/qiskit/blob/stable/2.3/qiskit/circuit/library/arithmetic/polynomial_pauli_rotations.py#L266-L388
#
# Original repository link:
# https://github.com/Qiskit/qiskit/tree/f14e0b29a484795034447ea5bfb637fe845c194f
#
# This program is adapted for use as a benchmark in controlled software testing experiments.
# Modifications made to the original code include (for Apache License 2.0):
# - Refactored to unify the inherent class as QuantumCircuit:
# - _define() -> _build()
# - Modified __init__() to call QuantumCircuit's __init__()
# - self.definition replaced with self.compose(...) to build the circuit
#
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""Polynomially controlled Pauli-rotations."""
from __future__ import annotations
from itertools import product
from qiskit.circuit import QuantumRegister, QuantumCircuit, Gate
from qiskit.circuit.exceptions import CircuitError
# ---------- benchmark registration ----------
from ..benchmark_registry import register_benchmark
from pathlib import Path
@register_benchmark(
Path(__file__).stem,
family=Path(__file__).resolve().parent.name,
description="A circuit implementing polynomial Pauli rotations.",
class_name="PolynomialPauliRotations",
source={
"repo": "https://github.com/Qiskit/qiskit/tree/f14e0b29a484795034447ea5bfb637fe845c194f",
"file": "qiskit/circuit/library/arithmetic/polynomial_pauli_rotations.py",
"sdk": "Qiskit",
"available_doc": True
},
testability_refactoring=[
"Structure reorganization"
]
)
def create_polynomial_pauli_rotations(
num_state_qubits: int,
coeffs: list[float] | None = None,
basis: str = "Y"
):
return PolynomialPauliRotations(
num_state_qubits=num_state_qubits,
coeffs=coeffs,
basis=basis
)
[docs]
class PolynomialPauliRotations(QuantumCircuit):
r"""
A circuit implementing polynomial Pauli rotations.
"""
def __init__(
self,
num_state_qubits: int,
coeffs: list[float] | None = None,
basis: str = "Y",
label: str | None = None,
) -> None:
r"""
Args:
num_state_qubits: The number of qubits representing the state, i.e., :math:`n`.
coeffs: The coefficients of the polynomial. ``coeffs[i]`` is the coefficient of the
i-th power of x. Defaults to linear: ``[0, 1]``.
basis: The type of Pauli rotation (``'X'``, ``'Y'``, ``'Z'``). The default is ``'Y'``.
label: Optional label for the circuit.
"""
self.coeffs = coeffs or [0, 1]
self.basis = basis.lower()
super().__init__(num_state_qubits + 1, name="PolyPauli" or label) # Modification here
self._build() # Modification here to build the circuit
[docs]
def binomial_coefficients(self, n: int) -> dict[tuple[int, int], int]:
"""Return a dictionary of binomial coefficients
Based-on/forked from sympy's binomial_coefficients() function [#]_.
.. [#] https://github.com/sympy/sympy/blob/sympy-1.5.1/sympy/ntheory/multinomial.py
"""
data = {(0, n): 1, (n, 0): 1}
temp = 1
for k in range(1, n // 2 + 1):
temp = (temp * (n - k + 1)) // k
data[k, n - k] = data[n - k, k] = temp
return data
[docs]
def large_coefficients_iter(self, m: int, n: int):
"""Return an iterator of multinomial coefficients
Based-on/forked from sympy's multinomial_coefficients_iterator() function [#]_.
.. [#] https://github.com/sympy/sympy/blob/sympy-1.5.1/sympy/ntheory/multinomial.py
"""
if m < 2 * n or n == 1:
coefficients = self.multinomial_coefficients(m, n)
for key, value in coefficients.items():
yield (key, value)
else:
coefficients = self.multinomial_coefficients(n, n)
coefficients_dict = {}
for key, value in coefficients.items():
coefficients_dict[tuple(filter(None, key))] = value
coefficients = coefficients_dict
temp = [n] + [0] * (m - 1)
temp_a = tuple(temp)
b = tuple(filter(None, temp_a))
yield (temp_a, coefficients[b])
if n:
j = 0 # j will be the leftmost nonzero position
else:
j = m
# enumerate tuples in co-lex order
while j < m - 1:
# compute next tuple
temp_j = temp[j]
if j:
temp[j] = 0
temp[0] = temp_j
if temp_j > 1:
temp[j + 1] += 1
j = 0
else:
j += 1
temp[j] += 1
temp[0] -= 1
temp_a = tuple(temp)
b = tuple(filter(None, temp_a))
yield (temp_a, coefficients[b])
[docs]
def multinomial_coefficients(self, m: int, n: int):
"""Return an iterator of multinomial coefficients
Based-on/forked from sympy's multinomial_coefficients() function [#]_.
.. [#] https://github.com/sympy/sympy/blob/sympy-1.5.1/sympy/ntheory/multinomial.py
"""
if not m:
if n:
return {}
return {(): 1}
if m == 2:
return self.binomial_coefficients(n)
if m >= 2 * n and n > 1:
return dict(self.large_coefficients_iter(m, n))
if n:
j = 0
else:
j = m
temp = [n] + [0] * (m - 1)
res = {tuple(temp): 1}
while j < m - 1:
temp_j = temp[j]
if j:
temp[j] = 0
temp[0] = temp_j
if temp_j > 1:
temp[j + 1] += 1
j = 0
start = 1
v = 0
else:
j += 1
start = j + 1
v = res[tuple(temp)]
temp[j] += 1
for k in range(start, m):
if temp[k]:
temp[k] -= 1
v += res[tuple(temp)]
temp[k] += 1
temp[0] -= 1
res[tuple(temp)] = (v * temp_j) // (n - temp[0])
return res
def _build(self):
circuit = QuantumCircuit(self.num_qubits)
qr_state = circuit.qubits[: self.num_qubits - 1]
qr_target = circuit.qubits[-1]
rotation_coeffs = self.get_rotation_coefficients()
if self.basis == "x":
circuit.rx(self.coeffs[0], qr_target)
elif self.basis == "y":
circuit.ry(self.coeffs[0], qr_target)
else:
circuit.rz(self.coeffs[0], qr_target)
for c in rotation_coeffs:
qr_control = []
for i, _ in enumerate(c):
if c[i] > 0:
qr_control.append(qr_state[i])
# apply controlled rotations
if len(qr_control) > 1:
if self.basis == "x":
circuit.mcrx(rotation_coeffs[c], qr_control, qr_target)
elif self.basis == "y":
circuit.mcry(rotation_coeffs[c], qr_control, qr_target)
else:
circuit.mcrz(rotation_coeffs[c], qr_control, qr_target)
elif len(qr_control) == 1:
if self.basis == "x":
circuit.crx(rotation_coeffs[c], qr_control[0], qr_target)
elif self.basis == "y":
circuit.cry(rotation_coeffs[c], qr_control[0], qr_target)
else:
circuit.crz(rotation_coeffs[c], qr_control[0], qr_target)
# self.definition = circuit
# Use compose to build the circuit into this QuantumCircuit instead
self.compose(circuit, inplace=True)
[docs]
def get_rotation_coefficients(self) -> dict[tuple[int, ...], float]:
"""Compute the coefficient of each monomial.
Returns:
A dictionary with pairs ``{control_state: rotation angle}`` where ``control_state``
is a tuple of ``0`` or ``1`` bits.
"""
# determine the control states
num_state_qubits = self.num_qubits - 1
degree = len(self.coeffs) - 1
all_combinations = list(product([0, 1], repeat=num_state_qubits))
valid_combinations = []
for combination in all_combinations:
if 0 < sum(combination) <= degree:
valid_combinations += [combination]
rotation_coeffs = {control_state: 0.0 for control_state in valid_combinations}
# compute the coefficients for the control states
for i, coeff in enumerate(self.coeffs[1:]):
i += 1 # since we skip the first element we need to increase i by one
# iterate over the multinomial coefficients
for comb, num_combs in self.multinomial_coefficients(num_state_qubits, i).items():
control_state: tuple[int, ...] = ()
power = 1
for j, qubit in enumerate(comb):
if qubit > 0: # means we control on qubit i
control_state += (1,)
power *= 2 ** (j * qubit)
else:
control_state += (0,)
# Add angle
rotation_coeffs[control_state] += coeff * num_combs * power
return rotation_coeffs