diff --git a/qiskit/circuit/library/__init__.py b/qiskit/circuit/library/__init__.py index 1dbf8641c353..e8ac42c863c9 100644 --- a/qiskit/circuit/library/__init__.py +++ b/qiskit/circuit/library/__init__.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2020. +# (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 @@ -28,7 +28,47 @@ InnerProduct Permutation XOR + + +Arithmetic Circuits +=================== + +Functional Pauli Rotations +++++++++++++++++++++++++++ + +.. autosummary:: + :toctree: ../stubs/ + + FunctionalPauliRotations + LinearPauliRotations + PolynomialPauliRotations + PiecewiseLinearPauliRotations + +Adders +++++++ + +.. autosummary:: + :toctree: ../stubs/ + + WeightedAdder + +Comparators ++++++++++++ + +.. autosummary:: + :toctree: ../stubs/ + + IntegerComparator + """ from .boolean_logic import Permutation, XOR, InnerProduct +from .arithmetic import ( + FunctionalPauliRotations, + LinearPauliRotations, + PiecewiseLinearPauliRotations, + PolynomialPauliRotations, + IntegerComparator, + WeightedAdder +) diff --git a/qiskit/circuit/library/arithmetic/__init__.py b/qiskit/circuit/library/arithmetic/__init__.py new file mode 100644 index 000000000000..b91ceab563d1 --- /dev/null +++ b/qiskit/circuit/library/arithmetic/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 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. + +"""The arithmetic circuit library.""" + +from .functional_pauli_rotations import FunctionalPauliRotations +from .integer_comparator import IntegerComparator +from .linear_pauli_rotations import LinearPauliRotations +from .piecewise_linear_pauli_rotations import PiecewiseLinearPauliRotations +from .polynomial_pauli_rotations import PolynomialPauliRotations +from .weighted_adder import WeightedAdder diff --git a/qiskit/circuit/library/arithmetic/functional_pauli_rotations.py b/qiskit/circuit/library/arithmetic/functional_pauli_rotations.py new file mode 100644 index 000000000000..2c12422a5920 --- /dev/null +++ b/qiskit/circuit/library/arithmetic/functional_pauli_rotations.py @@ -0,0 +1,141 @@ +# -*- 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. + +"""Base class for functional Pauli rotations.""" + +from typing import Optional, List, Tuple + +from abc import ABC, abstractmethod +from qiskit.circuit import QuantumCircuit, Qubit, Clbit, Instruction + + +class FunctionalPauliRotations(QuantumCircuit, ABC): + """Base class for functional Pauli rotations.""" + + def __init__(self, + num_state_qubits: Optional[int] = None, + basis: str = 'Y', + name: str = 'F') -> None: + r"""Create a new functional Pauli rotation circuit. + + Args: + num_state_qubits: The number of qubits representing the state :math:`|x\rangle`. + basis: The kind of Pauli rotation to use. Must be 'X', 'Y' or 'Z'. + name: The name of the circuit object. + """ + super().__init__(name=name) + + # define internal parameters + self._num_state_qubits = None + self._basis = None + + # store parameters + self.num_state_qubits = num_state_qubits + self.basis = basis + + @property + def basis(self) -> str: + """The kind of Pauli rotation to be used. + + Set the basis to 'X', 'Y' or 'Z' for controlled-X, -Y, or -Z rotations respectively. + + Returns: + The kind of Pauli rotation used in controlled rotation. + """ + return self._basis + + @basis.setter + def basis(self, basis: str) -> None: + """Set the kind of Pauli rotation to be used. + + Args: + basis: The Pauli rotation to be used. + + Raises: + ValueError: The provided basis in not X, Y or Z. + """ + basis = basis.lower() + if self._basis is None or basis != self._basis: + if basis not in ['x', 'y', 'z']: + raise ValueError('The provided basis must be X, Y or Z, not {}'.format(basis)) + self._invalidate() + self._basis = basis + + @property + def num_state_qubits(self) -> int: + r"""The number of state qubits representing the state :math:`|x\rangle`. + + Returns: + The number of state qubits. + """ + return self._num_state_qubits + + @num_state_qubits.setter + def num_state_qubits(self, num_state_qubits: Optional[int]) -> None: + """Set the number of state qubits. + + Note that this may change the underlying quantum register, if the number of state qubits + changes. + + Args: + num_state_qubits: The new number of qubits. + """ + if self._num_state_qubits is None or num_state_qubits != self._num_state_qubits: + self._invalidate() + self._num_state_qubits = num_state_qubits + + self._reset_registers(num_state_qubits) + + def _invalidate(self) -> None: + """Invalidate the current build of the circuit.""" + self._data = None + + @abstractmethod + def _reset_registers(self, num_state_qubits: Optional[int]) -> None: + """Reset the registers according to the new number of state qubits. + + Args: + num_state_qubits: The new number of qubits. + """ + raise NotImplementedError + + @property + def num_ancilla_qubits(self) -> int: + """The minimum number of ancilla qubits in the circuit. + + Returns: + The minimal number of ancillas required. + """ + return 0 + + @abstractmethod + def _configuration_is_valid(self, raise_on_failure: bool = True) -> bool: + raise NotImplementedError + + @abstractmethod + def _build(self): + # if data is populated we already built the circuit + if self._data: + return + + # check if the current configuration is valid and re-initiate the circuit data + _ = self._configuration_is_valid() + self._data = [] + + @property + def data(self) -> List[Tuple[Instruction, List[Qubit], List[Clbit]]]: + """Get the circuit definition.""" + if self._data is None: + self._build() + return super().data diff --git a/qiskit/circuit/library/arithmetic/integer_comparator.py b/qiskit/circuit/library/arithmetic/integer_comparator.py new file mode 100644 index 000000000000..4f460d9f4080 --- /dev/null +++ b/qiskit/circuit/library/arithmetic/integer_comparator.py @@ -0,0 +1,249 @@ +# -*- 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. + +"""Integer Comparator.""" + +from typing import List, Optional +import numpy as np + +from qiskit.circuit import QuantumCircuit, QuantumRegister +from qiskit.circuit.exceptions import CircuitError + + +class IntegerComparator(QuantumCircuit): + r"""Integer Comparator. + + Operator compares basis states :math:`|i\rangle_n` against a classically given integer + :math:`L` of fixed value and flips a target qubit if :math:`i \geq L` + (or :math:`<` depending on parameters): + + .. math:: + + |i\rangle_n |0\rangle \mapsto |i\rangle_n |i \geq L\rangle + + This operation is based on two's complement implementation of binary subtraction but only + uses carry bits and no actual result bits. If the most significant carry bit + (the results bit) is 1, the :math:`\geq` condition is ``True`` otherwise it is ``False``. + """ + + def __init__(self, num_state_qubits: Optional[int] = None, + value: Optional[int] = None, + geq: bool = True, + name: str = 'cmp') -> None: + """Create a new fixed value comparator circuit. + + Args: + num_state_qubits: Number of state qubits. If this is set it will determine the number + of qubits required for the circuit. + value: The fixed value to compare with. + geq: If True, evaluate a ``>=`` condition, else ``<``. + name: Name of the circuit. + """ + super().__init__(name=name) + + self._data = None + self._value = None + self._geq = None + self._num_state_qubits = None + + self.value = value + self.geq = geq + self.num_state_qubits = num_state_qubits + + @property + def value(self) -> int: + """The value to compare the qubit register to. + + Returns: + The value against which the value of the qubit register is compared. + """ + return self._value + + @value.setter + def value(self, value: int) -> None: + if value != self._value: + self._invalidate() + self._value = value + + @property + def geq(self) -> bool: + """Return whether the comparator compares greater or less equal. + + Returns: + True, if the comparator compares ``>=``, False if ``<``. + """ + return self._geq + + @geq.setter + def geq(self, geq: bool) -> None: + """Set whether the comparator compares greater or less equal. + + Args: + geq: If True, the comparator compares ``>=``, if False ``<``. + """ + if geq != self._geq: + self._invalidate() + self._geq = geq + + @property + def num_state_qubits(self) -> int: + """The number of qubits encoding the state for the comparison. + + Returns: + The number of state qubits. + """ + return self._num_state_qubits + + @num_state_qubits.setter + def num_state_qubits(self, num_state_qubits: Optional[int]) -> None: + """Set the number of state qubits. + + Note that this will change the quantum registers. + + Args: + num_state_qubits: The new number of state qubits. + """ + if self._num_state_qubits is None or num_state_qubits != self._num_state_qubits: + self._invalidate() # reset data + self._num_state_qubits = num_state_qubits + + if num_state_qubits: + # set the new qubit registers + qr_state = QuantumRegister(self.num_state_qubits, name='state') + q_compare = QuantumRegister(1, name='compare') + + self.qregs = [qr_state, q_compare] + + if self.num_ancilla_qubits > 0: + qr_ancilla = QuantumRegister(self.num_ancilla_qubits, name='ancilla') + self.qregs += [qr_ancilla] + + @property + def num_ancilla_qubits(self) -> int: + """The number of ancilla qubits used. + + Returns: + The number of ancillas in the circuit. + """ + return self._num_state_qubits - 1 + + def _get_twos_complement(self) -> List[int]: + """Returns the 2's complement of ``self.value`` as array. + + Returns: + The 2's complement of ``self.value``. + """ + twos_complement = pow(2, self.num_state_qubits) - int(np.ceil(self.value)) + twos_complement = '{0:b}'.format(twos_complement).rjust(self.num_state_qubits, '0') + twos_complement = \ + [1 if twos_complement[i] == '1' else 0 for i in reversed(range(len(twos_complement)))] + return twos_complement + + @property + def data(self): + if self._data is None: + self._build() + return self._data + + def _invalidate(self) -> None: + """Invalidate the current build of the circuit.""" + self._data = None + + def _configuration_is_valid(self, raise_on_failure: bool = True) -> bool: + """Check if the current configuration is valid.""" + valid = True + + if self._num_state_qubits is None: + valid = False + if raise_on_failure: + raise AttributeError('Number of state qubits is not set.') + + if self._value is None: + valid = False + if raise_on_failure: + raise AttributeError('No comparison value set.') + + required_num_qubits = 2 * self.num_state_qubits + if self.num_qubits != required_num_qubits: + valid = False + if raise_on_failure: + raise CircuitError('Number of qubits does not match required number of qubits.') + + return valid + + def _build(self) -> None: + """Build the comparator circuit.""" + # set the register + if self._data: + return + + _ = self._configuration_is_valid() + + self._data = [] + + qr_state = self.qubits[:self.num_state_qubits] + q_compare = self.qubits[self.num_state_qubits] + qr_ancilla = self.qubits[self.num_state_qubits + 1:] + + if self.value <= 0: # condition always satisfied for non-positive values + if self._geq: # otherwise the condition is never satisfied + self.x(q_compare) + # condition never satisfied for values larger than or equal to 2^n + elif self.value < pow(2, self.num_state_qubits): + + if self.num_state_qubits > 1: + twos = self._get_twos_complement() + for i in range(self.num_state_qubits): + if i == 0: + if twos[i] == 1: + self.cx(qr_state[i], qr_ancilla[i]) + elif i < self.num_state_qubits - 1: + if twos[i] == 1: + self.OR([qr_state[i], qr_ancilla[i - 1]], qr_ancilla[i], None) + else: + self.ccx(qr_state[i], qr_ancilla[i - 1], qr_ancilla[i]) + else: + if twos[i] == 1: + # OR needs the result argument as qubit not register, thus + # access the index [0] + self.OR([qr_state[i], qr_ancilla[i - 1]], q_compare, None) + else: + self.ccx(qr_state[i], qr_ancilla[i - 1], q_compare) + + # flip result bit if geq flag is false + if not self._geq: + self.x(q_compare) + + # uncompute ancillas state + for i in reversed(range(self.num_state_qubits-1)): + if i == 0: + if twos[i] == 1: + self.cx(qr_state[i], qr_ancilla[i]) + else: + if twos[i] == 1: + self.OR([qr_state[i], qr_ancilla[i - 1]], qr_ancilla[i], None) + else: + self.ccx(qr_state[i], qr_ancilla[i - 1], qr_ancilla[i]) + else: + + # num_state_qubits == 1 and value == 1: + self.cx(qr_state[0], q_compare) + + # flip result bit if geq flag is false + if not self._geq: + self.x(q_compare) + + else: + if not self._geq: # otherwise the condition is never satisfied + self.x(q_compare) diff --git a/qiskit/circuit/library/arithmetic/linear_pauli_rotations.py b/qiskit/circuit/library/arithmetic/linear_pauli_rotations.py new file mode 100644 index 000000000000..5438fe30bd76 --- /dev/null +++ b/qiskit/circuit/library/arithmetic/linear_pauli_rotations.py @@ -0,0 +1,178 @@ +# -*- 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. + +"""Linearly-controlled X, Y or Z rotation.""" + +from typing import Optional + +from qiskit.circuit import QuantumRegister +from qiskit.circuit.exceptions import CircuitError + +from .functional_pauli_rotations import FunctionalPauliRotations + + +class LinearPauliRotations(FunctionalPauliRotations): + r"""Linearly-controlled X, Y or Z rotation. + + For a register of state qubits :math:`|x\rangle`, a target qubit :math:`|0\rangle` and the + basis ``'Y'`` this circuit acts as: + + .. parsed-literal:: + + q_0: ─────────────────────────■───────── ... ────────────────────── + │ + . + │ + q_(n-1): ─────────────────────────┼───────── ... ───────────■────────── + ┌────────────┐ ┌───────┴───────┐ ┌─────────┴─────────┐ + q_n: ─┤ RY(offset) ├──┤ RY(2^0 slope) ├ ... ┤ RY(2^(n-1) slope) ├ + └────────────┘ └───────────────┘ └───────────────────┘ + + This can for example be used to approximate linear functions, with :math:`a/2 =` ``slope`` + and :math:`b/2 =` ``offset`` and the basis ``'Y'``: + + .. math:: + + |x\rangle |0\rangle \mapsto \cos(ax + b)|x\rangle|0\rangle + \sin(ax + b)|x\rangle |1\rangle + + Since for small arguments :math:`\sin(x) \approx x` this operator can be used to approximate + linear functions. + """ + + def __init__(self, + num_state_qubits: Optional[int] = None, + slope: float = 1, + offset: float = 0, + basis: str = 'Y', + name: str = 'LinRot') -> None: + r"""Create a new linear rotation circuit. + + Args: + num_state_qubits: The number of qubits representing the state :math:`|x\rangle`. + slope: The slope of the controlled rotation. + offset: The offset of the controlled rotation. + basis: The type of Pauli rotation ('X', 'Y', 'Z'). + name: The name of the circuit object. + """ + super().__init__(num_state_qubits=num_state_qubits, basis=basis, name=name) + + # define internal parameters + self._slope = None + self._offset = None + + # store parameters + self.slope = slope + self.offset = offset + + @property + def slope(self) -> 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._slope + + @slope.setter + def slope(self, slope: float) -> None: + """Set the multiplicative factor of the rotation angles. + + Args: + The slope of the rotation angles. + """ + if self._slope is None or slope != self._slope: + self._invalidate() + self._slope = slope + + @property + def offset(self) -> float: + """The angle of the single qubit offset rotation on the target qubit. + + Before applying the controlled rotations, a single rotation of angle ``offset`` is + applied to the target qubit. + + Returns: + The offset angle. + """ + return self._offset + + @offset.setter + def offset(self, offset: float) -> None: + """Set the angle for the offset rotation on the target qubit. + + Args: + offset: The offset rotation angle. + """ + if self._offset is None or offset != self._offset: + self._invalidate() + self._offset = offset + + def _reset_registers(self, num_state_qubits: Optional[int]) -> None: + """Set the number of state qubits. + + Note that this changes the underlying quantum register, if the number of state qubits + changes. + + Args: + num_state_qubits: The new number of 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') + self.qregs = [qr_state, qr_target] + else: + self.qregs = [] + + def _configuration_is_valid(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 _build(self): + # check if we have to rebuild and if the configuration is valid + super()._build() + + # build the circuit + qr_state = self.qubits[:self.num_state_qubits] + qr_target = self.qubits[self.num_state_qubits] + + if self.basis == 'x': + self.rx(self.offset, qr_target) + elif self.basis == 'y': + self.ry(self.offset, qr_target) + else: # 'Z': + self.rz(self.offset, qr_target) + + for i, q_i in enumerate(qr_state): + if self.basis == 'x': + self.crx(self.slope * pow(2, i), q_i, qr_target) + elif self.basis == 'y': + self.cry(self.slope * pow(2, i), q_i, qr_target) + else: # 'Z' + self.crz(self.slope * pow(2, i), q_i, qr_target) diff --git a/qiskit/circuit/library/arithmetic/piecewise_linear_pauli_rotations.py b/qiskit/circuit/library/arithmetic/piecewise_linear_pauli_rotations.py new file mode 100644 index 000000000000..398e23e5acc1 --- /dev/null +++ b/qiskit/circuit/library/arithmetic/piecewise_linear_pauli_rotations.py @@ -0,0 +1,275 @@ +# -*- 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. + +"""Piecewise-linearly-controlled rotation.""" + +from typing import List, Optional +import numpy as np + +from qiskit.circuit import QuantumRegister +from qiskit.circuit.exceptions import CircuitError + +from .functional_pauli_rotations import FunctionalPauliRotations +from .linear_pauli_rotations import LinearPauliRotations +from .integer_comparator import IntegerComparator + + +class PiecewiseLinearPauliRotations(FunctionalPauliRotations): + r"""Piecewise-linearly-controlled Pauli rotations. + + For a piecewise linear (not necessarily continuous) function :math:`f(x)`, which is defined + through breakpoints, slopes and offsets as follows. + Suppose the breakpoints :math:`(x_0, ..., x_J)` are a subset of :math:`[0, 2^n-1]`, where + :math:`n` is the number of state qubits. Further on, denote the corresponding slopes and + offsets by :math:`a_j` and :math:`b_j` respectively. + Then f(x) is defined as: + + .. math:: + + f(x) = \begin{cases} + 0, x < x_0 \\ + a_j (x - x_j) + b_j, x_j \leq x < x_{j+1} + \end{cases} + + where we implicitly assume :math:`x_{J+1} = 2^n`. + """ + + def __init__(self, + num_state_qubits: Optional[int] = None, + breakpoints: Optional[List[int]] = None, + slopes: Optional[List[float]] = None, + offsets: Optional[List[float]] = None, + basis: str = 'Y', + name: str = 'pw_lin') -> None: + """Construct piecewise-linearly-controlled Pauli rotations. + + Args: + num_state_qubits: The number of qubits representing the state. + breakpoints: The breakpoints to define the piecewise-linear function. + Defaults to ``[0]``. + slopes: The slopes for different segments of the piecewise-linear function. + Defaults to ``[1]``. + offsets: The offsets for different segments of the piecewise-linear function. + Defaults to ``[0]``. + basis: The type of Pauli rotation (``'X'``, ``'Y'``, ``'Z'``). + name: The name of the circuit. + """ + # store parameters + self._breakpoints = breakpoints if breakpoints is not None else [0] + self._slopes = slopes if slopes is not None else [1] + self._offsets = offsets if offsets is not None else [0] + + super().__init__(num_state_qubits=num_state_qubits, basis=basis, name=name) + + @property + def breakpoints(self) -> List[int]: + """The breakpoints of the piecewise linear function. + + The function is linear in the intervals ``[point_i, point_{i+1}]`` where the last + point implicitely is ``2**(num_state_qubits + 1)``. + """ + return self._breakpoints + + @breakpoints.setter + def breakpoints(self, breakpoints: List[int]) -> None: + """Set the breakpoints. + + Args: + breakpoints: The new breakpoints. + """ + self._invalidate() + self._breakpoints = breakpoints + + if self.num_state_qubits and breakpoints: + self._reset_registers(self.num_state_qubits) + + @property + def slopes(self) -> List[int]: + """The breakpoints of the piecewise linear function. + + The function is linear in the intervals ``[point_i, point_{i+1}]`` where the last + point implicitely is ``2**(num_state_qubits + 1)``. + """ + return self._slopes + + @slopes.setter + def slopes(self, slopes: List[float]) -> None: + """Set the slopes. + + Args: + slopes: The new slopes. + """ + self._invalidate() + self._slopes = slopes + + @property + def offsets(self) -> List[float]: + """The breakpoints of the piecewise linear function. + + The function is linear in the intervals ``[point_i, point_{i+1}]`` where the last + point implicitely is ``2**(num_state_qubits + 1)``. + """ + return self._offsets + + @offsets.setter + def offsets(self, offsets: List[float]) -> None: + """Set the offsets. + + Args: + offsets: The new offsets. + """ + self._invalidate() + self._offsets = offsets + + @property + def mapped_slopes(self) -> List[float]: + """The slopes mapped to the internal representation. + + Returns: + The mapped slopes. + """ + mapped_slopes = np.zeros_like(self.slopes) + for i, slope in enumerate(self.slopes): + mapped_slopes[i] = slope - sum(mapped_slopes[:i]) + + return mapped_slopes + + @property + def mapped_offsets(self) -> List[float]: + """The offsets mapped to the internal representation. + + Returns: + The mapped offsets. + """ + mapped_offsets = np.zeros_like(self.offsets) + for i, (offset, slope, point) in enumerate(zip(self.offsets, + self.slopes, + self.breakpoints)): + mapped_offsets[i] = offset - slope * point - sum(mapped_offsets[:i]) + + return mapped_offsets + + @property + def contains_zero_breakpoint(self) -> bool: + """Whether 0 is the first breakpoint. + + Returns: + True, if 0 is the first breakpoint, otherwise False. + """ + return np.isclose(0, self.breakpoints[0]) + + def evaluate(self, x: float) -> float: + """Classically evaluate the piecewise linear rotation. + + Args: + x: Value to be evaluated at. + + Returns: + Value of piecewise linear function at x. + """ + + y = (x >= self.breakpoints[0]) * (x * self.mapped_slopes[0] + self.mapped_offsets[0]) + for i in range(1, len(self.breakpoints)): + y = y + (x >= self.breakpoints[i]) * (x * self.mapped_slopes[i] + + self.mapped_offsets[i]) + + return y + + @property + def num_ancilla_qubits(self) -> int: + """The number of ancilla qubits. + + Returns: + The number of ancilla qubits in the circuit. + """ + num_ancilla_qubits = self.num_state_qubits - 1 + len(self.breakpoints) + if self.contains_zero_breakpoint: + num_ancilla_qubits -= 1 + return num_ancilla_qubits + + def _configuration_is_valid(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)) + + if len(self.breakpoints) != len(self.slopes) or len(self.breakpoints) != len(self.offsets): + valid = False + if raise_on_failure: + raise ValueError('Mismatching sizes of breakpoints, slopes and offsets.') + + return valid + + def _reset_registers(self, num_state_qubits: Optional[int]) -> None: + if num_state_qubits: + qr_state = QuantumRegister(num_state_qubits) + qr_target = QuantumRegister(1) + self.qregs = [qr_state, qr_target] + + if self.num_ancilla_qubits > 0: + qr_ancilla = QuantumRegister(self.num_ancilla_qubits) + self.qregs += [qr_ancilla] + else: + self.qregs = [] + + 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:] + + # apply comparators and controlled linear rotations + for i, point in enumerate(self.breakpoints): + if i == 0 and self.contains_zero_breakpoint: + # apply rotation + lin_r = LinearPauliRotations(num_state_qubits=self.num_state_qubits, + slope=self.mapped_slopes[i], + offset=self.mapped_offsets[i], + basis=self.basis) + self.append(lin_r.to_gate(), qr_state[:] + qr_target) + + else: + if self.contains_zero_breakpoint: + i_compare = i - 1 + else: + i_compare = i + + # apply Comparator + comp = IntegerComparator(num_state_qubits=self.num_state_qubits, value=point) + qr = qr_state[:] + [qr_ancilla[i_compare]] # add ancilla as compare qubit + qr_remaining_ancilla = qr_ancilla[i_compare + 1:] # take remaining ancillas + + self.append(comp.to_gate(), + qr[:] + qr_remaining_ancilla[:comp.num_ancilla_qubits]) + + # apply controlled rotation + lin_r = LinearPauliRotations(num_state_qubits=self.num_state_qubits, + slope=self.mapped_slopes[i], + offset=self.mapped_offsets[i], + basis=self.basis) + self.append(lin_r.to_gate().control(), + [qr_ancilla[i_compare]] + qr_state[:] + qr_target) + + # uncompute comparator + self.append(comp.to_gate().inverse(), + qr[:] + qr_remaining_ancilla[:comp.num_ancilla_qubits]) diff --git a/qiskit/circuit/library/arithmetic/polynomial_pauli_rotations.py b/qiskit/circuit/library/arithmetic/polynomial_pauli_rotations.py new file mode 100644 index 000000000000..490914395233 --- /dev/null +++ b/qiskit/circuit/library/arithmetic/polynomial_pauli_rotations.py @@ -0,0 +1,247 @@ +# -*- 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. + +"""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 + + +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 _configuration_is_valid(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) diff --git a/qiskit/circuit/library/arithmetic/weighted_adder.py b/qiskit/circuit/library/arithmetic/weighted_adder.py new file mode 100644 index 000000000000..afdeb28abdc3 --- /dev/null +++ b/qiskit/circuit/library/arithmetic/weighted_adder.py @@ -0,0 +1,329 @@ +# -*- 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. + +"""Compute the weighted sum of qubit states.""" + +from typing import List, Optional +import numpy as np + +from qiskit.circuit import QuantumCircuit, QuantumRegister + + +class WeightedAdder(QuantumCircuit): + r"""A circuit to compute the weighted sum of qubit registers. + + Given :math:`n` qubit basis states :math:`q_0, \ldots, q_{n-1} \in \{0, 1\}` and non-negative + integer weights :math:`\lambda_0, \ldots, \lambda_{n-1}`, this circuit performs the operation + + .. math:: + + |q_0 \ldots q_{n-1}\rangle |0\rangle_s + \mapsto |q_0 \ldots q_{n-1}\rangle |\sum_{j=0}^{n-1} \lambda_j q_j\rangle_s + + where :math:`s` is the number of sum qubits required. + This can be computed as + + .. math:: + + s = 1 + \left\lfloor \log_2\left( \sum_{j=0}^{n-1} \lambda_j \right) \right\rfloor + + or :math:`s = 1` if the sum of the weights is 0 (then the expression in the logarithm is + invalid). + + For qubits in a circuit diagram, the first weight applies to the upper-most qubit. + For an example where the state of 4 qubits is added into a sum register, the circuit can + be schematically drawn as + + .. parsed-literal:: + + ┌────────┐ + state_0: ┤0 ├ | state_0 * weights[0] + │ │ | + state_1: ┤1 ├ | + state_1 * weights[1] + │ │ | + state_2: ┤2 ├ | + state_2 * weights[2] + │ │ | + state_3: ┤3 ├ | + state_3 * weights[3] + │ │ + sum_0: ┤4 ├ | + │ Adder │ | + sum_1: ┤5 ├ | = sum_0 * 2^0 + sum_1 * 2^1 + sum_2 * 2^2 + │ │ | + sum_2: ┤6 ├ | + │ │ + carry_0: ┤7 ├ + │ │ + carry_1: ┤8 ├ + │ │ + control_0: ┤9 ├ + └────────┘ + """ + + def __init__(self, + num_state_qubits: Optional[int] = None, + weights: Optional[List[int]] = None, + name: str = 'adder') -> None: + """Computes the weighted sum controlled by state qubits. + + Args: + num_state_qubits: The number of state qubits. + weights: List of weights, one for each state qubit. If none are provided they + default to 1 for every qubit. + name: The name of the circuit. + """ + super().__init__(name=name) + + self._weights = None + self._num_state_qubits = None + + self.weights = weights + self.num_state_qubits = num_state_qubits + + @property + def num_sum_qubits(self) -> int: + """The number of sum qubits in the circuit. + + Returns: + The number of qubits needed to represent the weighted sum of the qubits. + """ + if sum(self.weights) > 0: + return int(np.floor(np.log2(sum(self.weights))) + 1) + return 1 + + @property + def weights(self) -> List[int]: + """The weights for the qubit states. + + Returns: + The weight for the qubit states. + """ + if self._weights: + return self._weights + if self.num_state_qubits: + return [1] * self.num_state_qubits + return None + + @weights.setter + def weights(self, weights: List[int]) -> None: + """Set the weights for summing the qubit states. + + Args: + weights: The new weights. + + Raises: + ValueError: If not all weights are close to an integer. + """ + if weights: + for i, weight in enumerate(weights): + if not np.isclose(weight, np.round(weight)): + raise ValueError('Non-integer weights are not supported!') + weights[i] = np.round(weight) + + self._invalidate() + self._weights = weights + self._reset_registers() + + @property + def num_state_qubits(self) -> int: + """The number of qubits to be summed. + + Returns: + The number of state qubits. + """ + return self._num_state_qubits + + @num_state_qubits.setter + def num_state_qubits(self, num_state_qubits: int) -> None: + """Set the number of state qubits. + + Args: + num_state_qubits: The new number of state qubits. + """ + if self._num_state_qubits is None or num_state_qubits != self._num_state_qubits: + self._invalidate() + self._num_state_qubits = num_state_qubits + self._reset_registers() + + def _invalidate(self) -> None: + """Invalidate the current build of the circuit.""" + self._data = None + + def _reset_registers(self): + if self.num_state_qubits: + qr_state = QuantumRegister(self.num_state_qubits, name='state') + qr_sum = QuantumRegister(self.num_sum_qubits, name='sum') + self.qregs = [qr_state, qr_sum] + if self.num_carry_qubits > 0: + qr_carry = QuantumRegister(self.num_carry_qubits, name='carry') + self.qregs += [qr_carry] + + if self.num_control_qubits > 0: + qr_control = QuantumRegister(self.num_control_qubits, name='control') + self.qregs += [qr_control] + else: + self.qregs = [] + + @property + def num_carry_qubits(self) -> int: + """The number of carry qubits required to compute the sum. + + Note that this is not necessarily equal to the number of ancilla qubits, these can + be queried using ``num_ancilla_qubits``. + + Returns: + The number of carry qubits required to compute the sum. + """ + return self.num_sum_qubits - 1 + + @property + def num_control_qubits(self) -> int: + """The number of additional control qubits required. + + Note that the total number of ancilla qubits can be obtained by calling the + method ``num_ancilla_qubits``. + + Returns: + The number of additional control qubits required (0 or 1). + """ + return int(self.num_sum_qubits > 2) + + @property + def num_ancilla_qubits(self) -> int: + """The number of ancilla qubits required to implement the weighted sum. + + Returns: + The number of ancilla qubits in the circuit. + """ + return self.num_carry_qubits + self.num_control_qubits + + @property + def data(self): + if self._data is None: + self._build() + return super().data + + def _configuration_is_valid(self, raise_on_failure=True): + valid = True + if self._num_state_qubits is None: + valid = False + if raise_on_failure: + raise AttributeError('The number of state qubits has not been set.') + + if self._num_state_qubits != len(self.weights): + valid = False + if raise_on_failure: + raise ValueError('Mismatching number of state qubits and weights.') + + return valid + + def _build(self): + if self._data: + return + + _ = self._configuration_is_valid() + + self._data = [] + + num_result_qubits = self.num_state_qubits + self.num_sum_qubits + + qr_state = self.qubits[:self.num_state_qubits] + qr_sum = self.qubits[self.num_state_qubits:num_result_qubits] + qr_carry = self.qubits[num_result_qubits:num_result_qubits + self.num_carry_qubits] + qr_control = self.qubits[num_result_qubits + self.num_carry_qubits:] + + # loop over state qubits and corresponding weights + for i, weight in enumerate(self.weights): + # only act if non-trivial weight + if np.isclose(weight, 0): + continue + + # get state control qubit + q_state = qr_state[i] + + # get bit representation of current weight + weight_binary = '{0:b}'.format(int(weight)).rjust(self.num_sum_qubits, '0')[::-1] + + # loop over bits of current weight and add them to sum and carry registers + for j, bit in enumerate(weight_binary): + if bit == '1': + if self.num_sum_qubits == 1: + self.cx(q_state, qr_sum[j]) + elif j == 0: + # compute (q_sum[0] + 1) into (q_sum[0], q_carry[0]) + # - controlled by q_state[i] + self.ccx(q_state, qr_sum[j], qr_carry[j]) + self.cx(q_state, qr_sum[j]) + elif j == self.num_sum_qubits - 1: + # compute (q_sum[j] + q_carry[j-1] + 1) into (q_sum[j]) + # - controlled by q_state[i] / last qubit, + # no carry needed by construction + self.cx(q_state, qr_sum[j]) + self.ccx(q_state, qr_carry[j - 1], qr_sum[j]) + else: + # compute (q_sum[j] + q_carry[j-1] + 1) into (q_sum[j], q_carry[j]) + # - controlled by q_state[i] + self.x(qr_sum[j]) + self.x(qr_carry[j - 1]) + self.mct([q_state, qr_sum[j], qr_carry[j - 1]], qr_carry[j], qr_control) + self.cx(q_state, qr_carry[j]) + self.x(qr_sum[j]) + self.x(qr_carry[j - 1]) + self.cx(q_state, qr_sum[j]) + self.ccx(q_state, qr_carry[j - 1], qr_sum[j]) + else: + if self.num_sum_qubits == 1: + pass # nothing to do, since nothing to add + elif j == 0: + pass # nothing to do, since nothing to add + elif j == self.num_sum_qubits-1: + # compute (q_sum[j] + q_carry[j-1]) into (q_sum[j]) + # - controlled by q_state[i] / last qubit, + # no carry needed by construction + self.ccx(q_state, qr_carry[j - 1], qr_sum[j]) + else: + # compute (q_sum[j] + q_carry[j-1]) into (q_sum[j], q_carry[j]) + # - controlled by q_state[i] + self.mct([q_state, qr_sum[j], qr_carry[j - 1]], qr_carry[j], qr_control) + self.ccx(q_state, qr_carry[j - 1], qr_sum[j]) + + # uncompute carry qubits + for j in reversed(range(len(weight_binary))): + bit = weight_binary[j] + if bit == '1': + if self.num_sum_qubits == 1: + pass + elif j == 0: + self.x(qr_sum[j]) + self.ccx(q_state, qr_sum[j], qr_carry[j]) + self.x(qr_sum[j]) + elif j == self.num_sum_qubits - 1: + pass + else: + self.x(qr_carry[j - 1]) + self.mct([q_state, qr_sum[j], qr_carry[j - 1]], qr_carry[j], qr_control) + self.cx(q_state, qr_carry[j]) + self.x(qr_carry[j - 1]) + else: + if self.num_sum_qubits == 1: + pass + elif j == 0: + pass + elif j == self.num_sum_qubits - 1: + pass + else: + # compute (q_sum[j] + q_carry[j-1]) into (q_sum[j], q_carry[j]) + # - controlled by q_state[i] + self.x(qr_sum[j]) + self.mct([q_state, qr_sum[j], qr_carry[j - 1]], qr_carry[j], qr_control) + self.x(qr_sum[j]) diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index fc58377fe978..6b26862c37bf 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -1144,7 +1144,7 @@ def copy(self, name=None): cpy = copy.copy(self) instr_instances = {id(instr): instr - for instr, _, __ in self._data} + for instr, _, __ in self.data} instr_copies = {id_: instr.copy() for id_, instr in instr_instances.items()} @@ -1157,7 +1157,7 @@ def copy(self, name=None): } cpy._data = [(instr_copies[id(inst)], qargs.copy(), cargs.copy()) - for inst, qargs, cargs in self._data] + for inst, qargs, cargs in self.data] if name: cpy.name = name diff --git a/qiskit/extensions/standard/__init__.py b/qiskit/extensions/standard/__init__.py index 9fb8782cf666..6c98fecb6094 100644 --- a/qiskit/extensions/standard/__init__.py +++ b/qiskit/extensions/standard/__init__.py @@ -41,6 +41,7 @@ from .z import ZGate, CZGate # to be converted to gates +from .boolean_logical_gates import logical_or, logical_and from .multi_control_toffoli_gate import mct from .multi_control_rotation_gates import mcrx, mcry, mcrz diff --git a/qiskit/extensions/standard/boolean_logical_gates.py b/qiskit/extensions/standard/boolean_logical_gates.py new file mode 100644 index 000000000000..8bd2887b997c --- /dev/null +++ b/qiskit/extensions/standard/boolean_logical_gates.py @@ -0,0 +1,131 @@ +# -*- 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. +""" +The Boolean Logical AND and OR Gates. +""" + +import logging +import numpy as np + +from qiskit.circuit import QuantumCircuit, QuantumRegister, Qubit +from qiskit.circuit.exceptions import CircuitError +from qiskit.qasm import pi + + +logger = logging.getLogger(__name__) + +# pylint: disable=expression-not-assigned + + +def _logical_and(circuit, variable_register, flags, target_qubit, ancillary_register, mct_mode): + if flags is not None: + zvf = list(zip(variable_register, flags)) + ctl_bits = [v for v, f in zvf if f] + anc_bits = None + if ancillary_register: + anc_bits = [ancillary_register[idx] for idx in range(np.count_nonzero(flags) - 2)] + + [circuit.u3(pi, 0, pi, v) for v, f in zvf if f < 0] + circuit.mct(ctl_bits, target_qubit, anc_bits, mode=mct_mode) + [circuit.u3(pi, 0, pi, v) for v, f in zvf if f < 0] + + +def _logical_or(circuit, qr_variables, flags, qb_target, qr_ancillae, mct_mode): + circuit.u3(pi, 0, pi, qb_target) + if flags is not None: + zvf = list(zip(qr_variables, flags)) + ctl_bits = [v for v, f in zvf if f] + anc_bits = \ + [qr_ancillae[idx] for idx in range(np.count_nonzero(flags) - 2)] \ + if qr_ancillae else None + + [circuit.u3(pi, 0, pi, v) for v, f in zvf if f > 0] + circuit.mct(ctl_bits, qb_target, anc_bits, mode=mct_mode) + [circuit.u3(pi, 0, pi, v) for v, f in zvf if f > 0] + + +def _do_checks(flags, qr_variables, qb_target, qr_ancillae, circuit): + # check flags + if flags is None: + flags = [1 for i in range(len(qr_variables))] + else: + if len(flags) > len(qr_variables): + raise CircuitError('`flags` cannot be longer than `qr_variables`.') + + # check variables + # TODO: improve the check + if isinstance(qr_variables, (QuantumRegister, list)): + variable_qubits = [qb for qb, i in zip(qr_variables, flags) if not i == 0] + else: + raise ValueError('A QuantumRegister or list of qubits is expected for variables.') + + # check target + if isinstance(qb_target, Qubit): + target_qubit = qb_target + else: + raise ValueError('A single qubit is expected for the target.') + + # check ancilla + if qr_ancillae is None: + ancillary_qubits = [] + elif isinstance(qr_ancillae, QuantumRegister): + ancillary_qubits = list(qr_ancillae) + elif isinstance(qr_ancillae, list): + ancillary_qubits = qr_ancillae + else: + raise ValueError('An optional list of qubits or a ' + 'QuantumRegister is expected for ancillae.') + + all_qubits = variable_qubits + [target_qubit] + ancillary_qubits + + circuit._check_qargs(all_qubits) + circuit._check_dups(all_qubits) + + return flags + + +def logical_and(self, qr_variables, qb_target, qr_ancillae, flags=None, mct_mode='no-ancilla'): + """ + Build a collective conjunction (AND) circuit in place using mct. + + Args: + self (QuantumCircuit): The QuantumCircuit object to build the conjunction on. + qr_variables (QuantumRegister): The QuantumRegister holding the variable qubits. + qb_target (Qubit): The target qubit to hold the conjunction result. + qr_ancillae (QuantumRegister): The ancillary QuantumRegister for building the mct. + flags (list[int]): A list of +1/-1/0 to mark negations or omissions of qubits. + mct_mode (str): The mct building mode. + """ + flags = _do_checks(flags, qr_variables, qb_target, qr_ancillae, self) + _logical_and(self, qr_variables, flags, qb_target, qr_ancillae, mct_mode) + + +def logical_or(self, qr_variables, qb_target, qr_ancillae, flags=None, mct_mode='basic'): + """ + Build a collective disjunction (OR) circuit in place using mct. + + Args: + self (QuantumCircuit): The QuantumCircuit object to build the disjunction on. + qr_variables (QuantumRegister): The QuantumRegister holding the variable qubits. + flags (list[int]): A list of +1/-1/0 to mark negations or omissions of qubits. + qb_target (Qubit): The target qubit to hold the disjunction result. + qr_ancillae (QuantumRegister): The ancillary QuantumRegister for building the mct. + mct_mode (str): The mct building mode. + """ + flags = _do_checks(flags, qr_variables, qb_target, qr_ancillae, self) + _logical_or(self, qr_variables, flags, qb_target, qr_ancillae, mct_mode) + + +QuantumCircuit.AND = logical_and +QuantumCircuit.OR = logical_or diff --git a/test/python/circuit/test_library.py b/test/python/circuit/test_library.py index b1f562ddd23a..7ea5153073d8 100644 --- a/test/python/circuit/test_library.py +++ b/test/python/circuit/test_library.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2020. +# (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 @@ -14,10 +14,18 @@ """Test library of quantum circuits.""" -from qiskit.test import QiskitTestCase -from qiskit.circuit import QuantumCircuit +from collections import defaultdict +from ddt import ddt, data, unpack +import numpy as np + +from qiskit.test.base import QiskitTestCase +from qiskit import BasicAer, execute +from qiskit.circuit import QuantumCircuit, QuantumRegister from qiskit.circuit.exceptions import CircuitError from qiskit.circuit.library import Permutation, XOR, InnerProduct +from qiskit.circuit.library.arithmetic import (LinearPauliRotations, PolynomialPauliRotations, + IntegerComparator, PiecewiseLinearPauliRotations, + WeightedAdder) class TestBooleanLogicLibrary(QiskitTestCase): @@ -50,3 +58,449 @@ def test_inner_product(self): expected.cz(1, 4) expected.cz(2, 5) self.assertEqual(circuit, expected) + + +@ddt +class TestFunctionalPauliRotations(QiskitTestCase): + """Test the functional Pauli rotations.""" + + def assertFunctionIsCorrect(self, function_circuit, reference): + """Assert that ``function_circuit`` implements the reference function ``reference``.""" + num_state_qubits = function_circuit.num_state_qubits + num_ancilla_qubits = function_circuit.num_ancilla_qubits + circuit = QuantumCircuit(num_state_qubits + 1 + num_ancilla_qubits) + circuit.h(list(range(num_state_qubits))) + circuit.append(function_circuit.to_instruction(), list(range(circuit.num_qubits))) + + backend = BasicAer.get_backend('statevector_simulator') + statevector = execute(circuit, backend).result().get_statevector() + + probabilities = defaultdict(float) + for i, statevector_amplitude in enumerate(statevector): + i = bin(i)[2:].zfill(circuit.num_qubits)[num_ancilla_qubits:] + probabilities[i] += np.real(np.abs(statevector_amplitude) ** 2) + + unrolled_probabilities = [] + unrolled_expectations = [] + for i, probability in probabilities.items(): + x, last_qubit = int(i[1:], 2), i[0] + if last_qubit == '0': + expected_amplitude = np.cos(reference(x)) / np.sqrt(2**num_state_qubits) + else: + expected_amplitude = np.sin(reference(x)) / np.sqrt(2**num_state_qubits) + + unrolled_probabilities += [probability] + unrolled_expectations += [np.real(np.abs(expected_amplitude) ** 2)] + + np.testing.assert_almost_equal(unrolled_probabilities, unrolled_expectations) + + @data( + ([1, 0.1], 3), + ([0, 0.4, 2], 2), + ) + @unpack + def test_polynomial_function(self, coeffs, num_state_qubits): + """Test the polynomial rotation.""" + def poly(x): + res = sum(coeff * x**i for i, coeff in enumerate(coeffs)) + return res + + polynome = PolynomialPauliRotations(num_state_qubits, [2 * coeff for coeff in coeffs]) + self.assertFunctionIsCorrect(polynome, poly) + + def test_polynomial_rotations_mutability(self): + """Test the mutability of the linear rotations circuit.""" + + polynomial_rotations = PolynomialPauliRotations() + + with self.subTest(msg='missing number of state qubits'): + with self.assertRaises(AttributeError): # no state qubits set + print(polynomial_rotations.draw()) + + with self.subTest(msg='default setup, just setting number of state qubits'): + polynomial_rotations.num_state_qubits = 2 + self.assertFunctionIsCorrect(polynomial_rotations, lambda x: x / 2) + + with self.subTest(msg='setting non-default values'): + polynomial_rotations.coeffs = [0, 1.2 * 2, 0.4 * 2] + self.assertFunctionIsCorrect(polynomial_rotations, lambda x: 1.2 * x + 0.4 * x ** 2) + + with self.subTest(msg='changing of all values'): + polynomial_rotations.num_state_qubits = 4 + polynomial_rotations.coeffs = [1 * 2, 0, 0, -0.5 * 2] + self.assertFunctionIsCorrect(polynomial_rotations, lambda x: 1 - 0.5 * x**3) + + @data( + (2, 0.1, 0), + (4, -2, 2), + (1, 0, 0) + ) + @unpack + def test_linear_function(self, num_state_qubits, slope, offset): + """Test the linear rotation arithmetic circuit.""" + def linear(x): + return offset + slope * x + + linear_rotation = LinearPauliRotations(num_state_qubits, slope * 2, offset * 2) + self.assertFunctionIsCorrect(linear_rotation, linear) + + def test_linear_rotations_mutability(self): + """Test the mutability of the linear rotations circuit.""" + + linear_rotation = LinearPauliRotations() + + with self.subTest(msg='missing number of state qubits'): + with self.assertRaises(AttributeError): # no state qubits set + print(linear_rotation.draw()) + + with self.subTest(msg='default setup, just setting number of state qubits'): + linear_rotation.num_state_qubits = 2 + self.assertFunctionIsCorrect(linear_rotation, lambda x: x / 2) + + with self.subTest(msg='setting non-default values'): + linear_rotation.slope = -2.3 * 2 + linear_rotation.offset = 1 * 2 + self.assertFunctionIsCorrect(linear_rotation, lambda x: 1 - 2.3 * x) + + with self.subTest(msg='changing all values'): + linear_rotation.num_state_qubits = 4 + linear_rotation.slope = 0.2 * 2 + linear_rotation.offset = 0.1 * 2 + self.assertFunctionIsCorrect(linear_rotation, lambda x: 0.1 + 0.2 * x) + + @data( + (1, [0], [1], [0]), + (2, [0, 2], [-0.5, 1], [2, 1]), + (3, [0, 2, 5], [1, 0, -1], [0, 2, 2]), + (2, [1, 2], [1, -1], [2, 1]), + (3, [0, 1], [1, 0], [0, 1]) + ) + @unpack + def test_piecewise_linear_function(self, num_state_qubits, breakpoints, slopes, offsets): + """Test the piecewise linear rotations.""" + def pw_linear(x): + for i, point in enumerate(reversed(breakpoints)): + if x >= point: + return offsets[-(i + 1)] + slopes[-(i + 1)] * (x - point) + return 0 + + pw_linear_rotations = PiecewiseLinearPauliRotations(num_state_qubits, breakpoints, + [2 * slope for slope in slopes], + [2 * offset for offset in offsets]) + + self.assertFunctionIsCorrect(pw_linear_rotations, pw_linear) + + def test_piecewise_linear_rotations_mutability(self): + """Test the mutability of the linear rotations circuit.""" + + pw_linear_rotations = PiecewiseLinearPauliRotations() + + with self.subTest(msg='missing number of state qubits'): + with self.assertRaises(AttributeError): # no state qubits set + print(pw_linear_rotations.draw()) + + with self.subTest(msg='default setup, just setting number of state qubits'): + pw_linear_rotations.num_state_qubits = 2 + self.assertFunctionIsCorrect(pw_linear_rotations, lambda x: x / 2) + + with self.subTest(msg='setting non-default values'): + pw_linear_rotations.breakpoints = [0, 2] + pw_linear_rotations.slopes = [-1 * 2, 1 * 2] + pw_linear_rotations.offsets = [0, -1.2 * 2] + self.assertFunctionIsCorrect(pw_linear_rotations, + lambda x: -1.2 + (x - 2) if x >= 2 else -x) + + with self.subTest(msg='changing all values'): + pw_linear_rotations.num_state_qubits = 4 + pw_linear_rotations.breakpoints = [1, 3, 6] + pw_linear_rotations.slopes = [-1 * 2, 1 * 2, -0.2 * 2] + pw_linear_rotations.offsets = [0, -1.2 * 2, 2 * 2] + + def pw_linear(x): + if x >= 6: + return 2 - 0.2 * (x - 6) + if x >= 3: + return -1.2 + (x - 3) + if x >= 1: + return -(x - 1) + return 0 + + self.assertFunctionIsCorrect(pw_linear_rotations, pw_linear) + + +@ddt +class TestIntegerComparator(QiskitTestCase): + """Text Fixed Value Comparator""" + + def assertComparisonIsCorrect(self, comp, num_state_qubits, value, geq): + """Assert that the comparator output is correct.""" + qc = QuantumCircuit(comp.num_qubits) # initialize circuit + qc.h(list(range(num_state_qubits))) # set equal superposition state + qc.append(comp, list(range(comp.num_qubits))) # add comparator + + # run simulation + backend = BasicAer.get_backend('statevector_simulator') + statevector = execute(qc, backend).result().get_statevector() + for i, amplitude in enumerate(statevector): + prob = np.abs(amplitude)**2 + if prob > 1e-6: + # equal superposition + self.assertEqual(True, np.isclose(1.0, prob * 2.0**num_state_qubits)) + b_value = '{0:b}'.format(i).rjust(qc.width(), '0') + x = int(b_value[(-num_state_qubits):], 2) + comp_result = int(b_value[-num_state_qubits-1], 2) + if geq: + self.assertEqual(x >= value, comp_result == 1) + else: + self.assertEqual(x < value, comp_result == 1) + + @data( + # n, value, geq + [1, 0, True], + [1, 1, True], + [2, -1, True], + [3, 5, True], + [3, 2, True], + [3, 2, False], + [4, 6, False] + ) + @unpack + def test_fixed_value_comparator(self, num_state_qubits, value, geq): + """Test the fixed value comparator circuit.""" + # build the circuit with the comparator + comp = IntegerComparator(num_state_qubits, value, geq=geq) + self.assertComparisonIsCorrect(comp, num_state_qubits, value, geq) + + def test_mutability(self): + """Test changing the arguments of the comparator.""" + + comp = IntegerComparator() + + with self.subTest(msg='missing num state qubits and value'): + with self.assertRaises(AttributeError): + print(comp.draw()) + + comp.num_state_qubits = 2 + + with self.subTest(msg='missing value'): + with self.assertRaises(AttributeError): + print(comp.draw()) + + comp.value = 0 + comp.geq = True + + with self.subTest(msg='updating num state qubits'): + comp.num_state_qubits = 1 + self.assertComparisonIsCorrect(comp, 1, 0, True) + + with self.subTest(msg='updating the value'): + comp.num_state_qubits = 3 + comp.value = 2 + self.assertComparisonIsCorrect(comp, 3, 2, True) + + with self.subTest(msg='updating geq'): + comp.geq = False + self.assertComparisonIsCorrect(comp, 3, 2, False) + + +class TestAquaApplications(QiskitTestCase): + """Test applications of the arithmetic library in Aqua use-cases.""" + + def test_asian_barrier_spread(self): + """Test the asian barrier spread model.""" + try: + from qiskit.aqua.circuits import WeightedSumOperator, FixedValueComparator as Comparator + from qiskit.aqua.components.uncertainty_problems import ( + UnivariatePiecewiseLinearObjective as PwlObjective, + MultivariateProblem + ) + from qiskit.aqua.components.uncertainty_models import MultivariateLogNormalDistribution + except ImportError: + import warnings + warnings.warn('Qiskit Aqua is not installed, skipping the application test.') + return + + # number of qubits per dimension to represent the uncertainty + num_uncertainty_qubits = 2 + + # parameters for considered random distribution + spot_price = 2.0 # initial spot price + volatility = 0.4 # volatility of 40% + interest_rate = 0.05 # annual interest rate of 5% + time_to_maturity = 40 / 365 # 40 days to maturity + + # resulting parameters for log-normal distribution + # pylint: disable=invalid-name + mu = ((interest_rate - 0.5 * volatility**2) * time_to_maturity + np.log(spot_price)) + sigma = volatility * np.sqrt(time_to_maturity) + mean = np.exp(mu + sigma**2/2) + variance = (np.exp(sigma**2) - 1) * np.exp(2*mu + sigma**2) + stddev = np.sqrt(variance) + + # lowest and highest value considered for the spot price; in between, + # an equidistant discretization is considered. + low = np.maximum(0, mean - 3*stddev) + high = mean + 3*stddev + + # map to higher dimensional distribution + # for simplicity assuming dimensions are independent and identically distributed) + dimension = 2 + num_qubits = [num_uncertainty_qubits]*dimension + low = low * np.ones(dimension) + high = high * np.ones(dimension) + mu = mu * np.ones(dimension) + cov = sigma ** 2 * np.eye(dimension) + + # construct circuit factory + distribution = MultivariateLogNormalDistribution(num_qubits=num_qubits, + low=low, + high=high, + mu=mu, + cov=cov) + + # determine number of qubits required to represent total loss + weights = [] + for n in num_qubits: + for i in range(n): + weights += [2**i] + + num_sum_qubits = WeightedSumOperator.get_required_sum_qubits(weights) + + # create circuit factoy + agg = WeightedSumOperator(sum(num_qubits), weights) + + # set the strike price (should be within the low and the high value of the uncertainty) + strike_price_1 = 3 + strike_price_2 = 4 + + # set the barrier threshold + barrier = 2.5 + + # map strike prices and barrier threshold from [low, high] to {0, ..., 2^n-1} + max_value = 2**num_sum_qubits - 1 + low_ = low[0] + high_ = high[0] + + mapped_strike_price_1 = (strike_price_1 - dimension*low_) / \ + (high_ - low_) * (2**num_uncertainty_qubits - 1) + mapped_strike_price_2 = (strike_price_2 - dimension*low_) / \ + (high_ - low_) * (2**num_uncertainty_qubits - 1) + mapped_barrier = (barrier - low) / (high - low) * (2**num_uncertainty_qubits - 1) + + conditions = [] + for i in range(dimension): + # target dimension of random distribution and corresponding condition + conditions += [(i, Comparator(num_qubits[i], mapped_barrier[i] + 1, geq=False))] + + # set the approximation scaling for the payoff function + c_approx = 0.25 + + # setup piecewise linear objective fcuntion + breakpoints = [0, mapped_strike_price_1, mapped_strike_price_2] + slopes = [0, 1, 0] + offsets = [0, 0, mapped_strike_price_2 - mapped_strike_price_1] + f_min = 0 + f_max = mapped_strike_price_2 - mapped_strike_price_1 + bull_spread_objective = PwlObjective( + num_sum_qubits, 0, max_value, breakpoints, slopes, offsets, f_min, f_max, c_approx) + + # define overall multivariate problem + asian_barrier_spread = MultivariateProblem( + distribution, agg, bull_spread_objective, conditions=conditions) + + num_req_qubits = asian_barrier_spread.num_target_qubits + num_req_ancillas = asian_barrier_spread.required_ancillas() + + qr = QuantumRegister(num_req_qubits, name='q') + qr_ancilla = QuantumRegister(num_req_ancillas, name='q_a') + qc = QuantumCircuit(qr, qr_ancilla) + + asian_barrier_spread.build(qc, qr, qr_ancilla) + job = execute(qc, backend=BasicAer.get_backend('statevector_simulator')) + + # evaluate resulting statevector + value = 0 + for i, amplitude in enumerate(job.result().get_statevector()): + b = ('{0:0%sb}' % asian_barrier_spread.num_target_qubits).format( + i)[-asian_barrier_spread.num_target_qubits:] + prob = np.abs(amplitude)**2 + if prob > 1e-4 and b[0] == '1': + value += prob + # all other states should have zero probability due to ancilla qubits + if i > 2**num_req_qubits: + break + + # map value to original range + mapped_value = asian_barrier_spread.value_to_estimation( + value) / (2**num_uncertainty_qubits - 1) * (high_ - low_) + expected = 0.83188 + self.assertAlmostEqual(mapped_value, expected, places=5) + + +@ddt +class TestWeightedAdder(QiskitTestCase): + """Test the weighted adder circuit.""" + + def assertSummationIsCorrect(self, adder): + """Assert that ``adder`` correctly implements the summation w.r.t. its set weights.""" + + circuit = QuantumCircuit(adder.num_qubits) + circuit.h(list(range(adder.num_state_qubits))) + circuit.append(adder.to_instruction(), list(range(adder.num_qubits))) + + backend = BasicAer.get_backend('statevector_simulator') + statevector = execute(circuit, backend).result().get_statevector() + + probabilities = defaultdict(float) + for i, statevector_amplitude in enumerate(statevector): + i = bin(i)[2:].zfill(circuit.num_qubits)[adder.num_ancilla_qubits:] + probabilities[i] += np.real(np.abs(statevector_amplitude) ** 2) + + expectations = defaultdict(float) + for x in range(2**adder.num_state_qubits): + bits = np.array(list(bin(x)[2:].zfill(adder.num_state_qubits)), dtype=int) + summation = bits.dot(adder.weights[::-1]) + + entry = bin(summation)[2:].zfill(adder.num_sum_qubits) \ + + bin(x)[2:].zfill(adder.num_state_qubits) + expectations[entry] = 1 / 2 ** adder.num_state_qubits + + for state, probability in probabilities.items(): + self.assertAlmostEqual(probability, expectations[state]) + + @data( + [0], + [1, 2, 1], + [4], + ) + def test_summation(self, weights): + """Test the weighted adder on some examples.""" + adder = WeightedAdder(len(weights), weights) + self.assertSummationIsCorrect(adder) + + def test_mutability(self): + """Test the mutability of the weighted adder.""" + adder = WeightedAdder() + + with self.subTest(msg='missing number of state qubits'): + with self.assertRaises(AttributeError): + print(adder.draw()) + + with self.subTest(msg='default weights'): + adder.num_state_qubits = 3 + default_weights = 3 * [1] + self.assertListEqual(adder.weights, default_weights) + + with self.subTest(msg='specify weights'): + adder.weights = [3, 2, 1] + self.assertSummationIsCorrect(adder) + + with self.subTest(msg='mismatching number of state qubits and weights'): + with self.assertRaises(ValueError): + adder.weights = [0, 1, 2, 3] + print(adder.draw()) + + with self.subTest(msg='change all attributes'): + adder.num_state_qubits = 4 + adder.weights = [2, 0, 1, 1] + self.assertSummationIsCorrect(adder)