Source code for qiskit.circuit.library.arithmetic.polynomial_pauli_rotations

# -*- coding: utf-8 -*-

# 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.

# pylint: disable=no-member

"""Polynomially controlled Pauli-rotations."""

from typing import List, Optional, Dict, Sequence

from itertools import product
from sympy.ntheory.multinomial import multinomial_coefficients

from qiskit.circuit import QuantumRegister
from qiskit.circuit.exceptions import CircuitError

from .functional_pauli_rotations import FunctionalPauliRotations


[docs]class PolynomialPauliRotations(FunctionalPauliRotations): r"""A circuit implementing polynomial Pauli rotations. For a polynomial :math`p(x)`, a basis state :math:`|i\rangle` and a target qubit :math:`|0\rangle` this operator acts as: .. math:: |i\rangle |0\rangle \mapsto \cos(p(i)) |i\rangle |0\rangle + \sin(p(i)) |i\rangle |1\rangle Let n be the number of qubits representing the state, d the degree of p(x) and q_i the qubits, where q_0 is the least significant qubit. Then for .. math:: x = \sum_{i=0}^{n-1} 2^i q_i, we can write .. math:: p(x) = \sum_{j=0}^{j=d} c_j x_j where :math:`c` are the input coefficients, ``coeffs``. """ def __init__(self, num_state_qubits: Optional[int] = None, coeffs: Optional[List[float]] = None, basis: str = 'Y', reverse: bool = False, name: str = 'poly') -> None: """Prepare an approximation to a state with amplitudes specified by a polynomial. Args: num_state_qubits: The number of qubits representing the state. 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'). reverse: If True, apply the polynomial with the reversed list of qubits (i.e. q_n as q_0, q_n-1 as q_1, etc). name: The name of the circuit. """ # set default internal parameters self._coeffs = coeffs or [0, 1] self._reverse = reverse # initialize super (after setting coeffs) super().__init__(num_state_qubits=num_state_qubits, basis=basis, name=name) @property def coeffs(self) -> List[float]: """The multiplicative factor in the rotation angle of the controlled rotations. The rotation angles are ``slope * 2^0``, ``slope * 2^1``, ... , ``slope * 2^(n-1)`` where ``n`` is the number of state qubits. Returns: The rotation angle common in all controlled rotations. """ return self._coeffs @coeffs.setter def coeffs(self, coeffs: List[float]) -> None: """Set the multiplicative factor of the rotation angles. Args: The slope of the rotation angles. """ self._invalidate() self._coeffs = coeffs # the number of ancilla's depends on the number of coefficients, so update if necessary if coeffs and self.num_state_qubits: self._reset_registers(self.num_state_qubits) @property def degree(self) -> int: """Return the degree of the polynomial, equals to the number of coefficients minus 1. Returns: The degree of the polynomial. If the coefficients have not been set, return 0. """ if self.coeffs: return len(self.coeffs) - 1 return 0 @property def reverse(self) -> bool: """Whether to apply the rotations on the reversed list of qubits. Returns: True, if the rotations are applied on the reversed list, False otherwise. """ return self._reverse @reverse.setter def reverse(self, reverse: bool) -> None: """Set to True to reverse the list of qubits. Args: reverse: If True, the rotations are applied on the reversed list. If False, then not. """ if self._reverse is None or reverse != self._reverse: self._invalidate() self._reverse = reverse @property def num_ancilla_qubits(self) -> int: """The number of ancilla qubits in this circuit. Returns: The number of ancilla qubits. """ return max(1, self.degree - 1) def _reset_registers(self, num_state_qubits): if num_state_qubits: # set new register of appropriate size qr_state = QuantumRegister(num_state_qubits, name='state') qr_target = QuantumRegister(1, name='target') qr_ancilla = QuantumRegister(self.num_ancilla_qubits, name='ancilla') self.qregs = [qr_state, qr_target, qr_ancilla] else: self.qregs = [] def _check_configuration(self, raise_on_failure: bool = True) -> bool: valid = True if self.num_state_qubits is None: valid = False if raise_on_failure: raise AttributeError('The number of qubits has not been set.') if self.num_qubits < self.num_state_qubits + 1: valid = False if raise_on_failure: raise CircuitError('Not enough qubits in the circuit, need at least ' '{}.'.format(self.num_state_qubits + 1)) return valid def _get_rotation_coefficients(self) -> Dict[Sequence[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 all_combinations = list(product([0, 1], repeat=self.num_state_qubits)) valid_combinations = [] for combination in all_combinations: if 0 < sum(combination) <= self.degree: valid_combinations += [combination] rotation_coeffs = {control_state: 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 multinomial_coefficients(self.num_state_qubits, i).items(): control_state = () 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 def _build(self): super()._build() qr_state = self.qubits[:self.num_state_qubits] qr_target = self.qubits[self.num_state_qubits] qr_ancilla = self.qubits[self.num_state_qubits + 1:] rotation_coeffs = self._get_rotation_coefficients() if self.basis == 'x': self.rx(self.coeffs[0], qr_target) elif self.basis == 'y': self.ry(self.coeffs[0], qr_target) else: self.rz(self.coeffs[0], qr_target) for c in rotation_coeffs: qr_control = [] if self.reverse: for i, _ in enumerate(c): if c[i] > 0: qr_control.append(qr_state[qr_state.size - i - 1]) else: 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': self.mcrx(rotation_coeffs[c], qr_control, qr_target, qr_ancilla) elif self.basis == 'y': self.mcry(rotation_coeffs[c], qr_control, qr_target, qr_ancilla) else: self.mcrz(rotation_coeffs[c], qr_control, qr_target, qr_ancilla) elif len(qr_control) == 1: if self.basis == 'x': self.crx(rotation_coeffs[c], qr_control[0], qr_target) elif self.basis == 'y': self.cry(rotation_coeffs[c], qr_control[0], qr_target) else: self.crz(rotation_coeffs[c], qr_control[0], qr_target)