Source code for qolumbina.programs.pauli_rotations.polynomial_pauli_rotations

# 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