Ejemplo n.º 1
0
    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 build(self, qc, q, q_ancillas=None, params=None):
        """Build the circuit."""
        pwlr = PiecewiseLinearPauliRotations(num_state_qubits=self.num_state_qubits,
                                             breakpoints=self.breakpoints,
                                             slopes=self.slopes,
                                             offsets=self.offsets,
                                             basis=self.basis).to_instruction()

        qr = [q[i] for i in self.i_state] + [q[self.i_target]]
        if q_ancillas:
            # pylint:disable=unnecessary-comprehension
            qr += [qi for qi in q_ancillas[:self.required_ancillas()]]
        qc.append(pwlr, qr)
Ejemplo n.º 3
0
    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)
Ejemplo n.º 4
0
    def __init__(self,
                 num_state_qubits: int,
                 min_state_value: float,
                 max_state_value: float,
                 breakpoints: Union[List[float], np.ndarray],
                 slopes: Union[List[float], np.ndarray],
                 offsets: Union[List[float], np.ndarray],
                 f_min: float,
                 f_max: float,
                 c_approx: float,
                 i_state: Optional[int] = None,
                 i_objective: Optional[int] = None) -> None:
        r"""
        Args:
            num_state_qubits: number of qubits to represent the state
            min_state_value : lower bound of values to be represented by state qubits
            max_state_value: upper bound of values to be represented by state qubits
            breakpoints: breakpoints of piecewise linear function
            slopes: slopes of linear segments
            offsets: offset of linear segments
            f_min: minimal value of resulting function
                           (required for normalization of amplitude)
            f_max: maximal value of resulting function
                           (required for normalization of amplitude)
            c_approx: approximating factor (linear segments are approximated by
                              contracting rotation
                              around pi/4, where sin\^2() is locally linear)
            i_state: indices of qubits that represent the state
            i_objective: index of target qubit to apply the rotation to
        """
        super().__init__(num_state_qubits + 1)

        self.num_state_qubits = num_state_qubits
        self.min_state_value = min_state_value
        self.max_state_value = max_state_value

        # sort breakpoints
        i_sort = np.argsort(breakpoints)
        breakpoints = np.array(breakpoints)[i_sort]
        slopes = np.array(slopes)[i_sort]
        offsets = np.array(offsets)[i_sort]

        # drop breakpoints and corresponding values below min_state_value or above max_state_value
        for i in reversed(range(len(breakpoints))):
            if breakpoints[i] <= (self.min_state_value - 1e-6) or \
                    breakpoints[i] >= (self.max_state_value + 1e-6):
                breakpoints = np.delete(breakpoints, i)
                slopes = np.delete(slopes, i)
                offsets = np.delete(offsets, i)

        # make sure the minimal value is included in the breakpoints
        min_value_included = False
        for point in breakpoints:
            if np.isclose(point, min_state_value):
                min_value_included = True
                break
        if not min_value_included:
            breakpoints = np.append(min_state_value, breakpoints)
            slopes = np.append(0, slopes)
            offsets = np.append(0, offsets)

        # store parameters
        self._breakpoints = breakpoints
        self._slopes = slopes
        self._offsets = offsets
        self._f_min = f_min
        self._f_max = f_max
        self._c_approx = c_approx

        # get and store qubit indices
        self.i_state = None
        if i_state is not None:
            self.i_state = i_state
        else:
            self.i_state = list(range(num_state_qubits))

        self.i_objective = None
        if i_objective is not None:
            self.i_objective = i_objective
        else:
            self.i_objective = num_state_qubits

        # map breakpoints, slopes, and offsets such that they fit {0, ..., 2^n-1}
        lower = min_state_value
        upper = max_state_value
        self._mapped_breakpoints = []
        self._mapped_slopes = []
        self._mapped_offsets = []
        for i, point in enumerate(breakpoints):
            mapped_breakpoint = (point - lower) / (upper - lower) * (2**num_state_qubits - 1)
            if mapped_breakpoint <= 2**num_state_qubits - 1:
                self._mapped_breakpoints += [mapped_breakpoint]

                # factor (upper - lower) / (2^n - 1) is for the scaling of x to [l,u]
                # note that the +l for mapping to [l,u] is already included in
                # the offsets given as parameters
                self._mapped_slopes += [slopes[i] * (upper - lower) / (2**num_state_qubits - 1)]
                self._mapped_offsets += [offsets[i]]
        self._mapped_breakpoints = np.array(self._mapped_breakpoints)
        self._mapped_slopes = np.array(self._mapped_slopes)
        self._mapped_offsets = np.array(self._mapped_offsets)

        # approximate linear behavior by scaling and contracting around pi/4
        if len(self._mapped_breakpoints):  # pylint: disable=len-as-condition
            self._slope_angles = np.zeros(len(breakpoints))
            self._offset_angles = np.pi / 4 * (1 - c_approx) * np.ones(len(breakpoints))
            for i in range(len(breakpoints)):
                self._slope_angles[i] = \
                    np.pi * c_approx * self._mapped_slopes[i] / 2 / (f_max - f_min)
                self._offset_angles[i] += \
                    np.pi * c_approx * (self._mapped_offsets[i] - f_min) / 2 / (f_max - f_min)

            # multiply by 2 since Y-rotation uses theta/2 as angle
            self._slope_angles = 2 * self._slope_angles
            self._offset_angles = 2 * self._offset_angles

            # create piecewise linear Y rotation
            self._pwl_ry = PiecewiseLinearPauliRotations(
                num_state_qubits,
                self._mapped_breakpoints,
                self._slope_angles,
                self._offset_angles
            )

        else:
            self.offset_angle = 0
            self.slope_angle = 0

            # create piecewise linear Y rotation
            self._pwl_ry = None
Ejemplo n.º 5
0
class UnivariatePiecewiseLinearObjective(CircuitFactory):
    r"""Univariate Piecewise Linear Objective Function.

    This objective function applies controlled Y-rotation to the target qubit, where the
    control qubits represent integer value, and rotation approximates a piecewise
    linear function of the amplitude f:

    .. math::

        |x\rangle |0\rangle \mapsto |x\rangle (\sqrt(1 - f(x))|0\rangle + sqrt(f(x))|1\rangle )

    """

    def __init__(self,
                 num_state_qubits: int,
                 min_state_value: float,
                 max_state_value: float,
                 breakpoints: Union[List[float], np.ndarray],
                 slopes: Union[List[float], np.ndarray],
                 offsets: Union[List[float], np.ndarray],
                 f_min: float,
                 f_max: float,
                 c_approx: float,
                 i_state: Optional[int] = None,
                 i_objective: Optional[int] = None) -> None:
        r"""
        Args:
            num_state_qubits: number of qubits to represent the state
            min_state_value : lower bound of values to be represented by state qubits
            max_state_value: upper bound of values to be represented by state qubits
            breakpoints: breakpoints of piecewise linear function
            slopes: slopes of linear segments
            offsets: offset of linear segments
            f_min: minimal value of resulting function
                           (required for normalization of amplitude)
            f_max: maximal value of resulting function
                           (required for normalization of amplitude)
            c_approx: approximating factor (linear segments are approximated by
                              contracting rotation
                              around pi/4, where sin\^2() is locally linear)
            i_state: indices of qubits that represent the state
            i_objective: index of target qubit to apply the rotation to
        """
        super().__init__(num_state_qubits + 1)

        self.num_state_qubits = num_state_qubits
        self.min_state_value = min_state_value
        self.max_state_value = max_state_value

        # sort breakpoints
        i_sort = np.argsort(breakpoints)
        breakpoints = np.array(breakpoints)[i_sort]
        slopes = np.array(slopes)[i_sort]
        offsets = np.array(offsets)[i_sort]

        # drop breakpoints and corresponding values below min_state_value or above max_state_value
        for i in reversed(range(len(breakpoints))):
            if breakpoints[i] <= (self.min_state_value - 1e-6) or \
                    breakpoints[i] >= (self.max_state_value + 1e-6):
                breakpoints = np.delete(breakpoints, i)
                slopes = np.delete(slopes, i)
                offsets = np.delete(offsets, i)

        # make sure the minimal value is included in the breakpoints
        min_value_included = False
        for point in breakpoints:
            if np.isclose(point, min_state_value):
                min_value_included = True
                break
        if not min_value_included:
            breakpoints = np.append(min_state_value, breakpoints)
            slopes = np.append(0, slopes)
            offsets = np.append(0, offsets)

        # store parameters
        self._breakpoints = breakpoints
        self._slopes = slopes
        self._offsets = offsets
        self._f_min = f_min
        self._f_max = f_max
        self._c_approx = c_approx

        # get and store qubit indices
        self.i_state = None
        if i_state is not None:
            self.i_state = i_state
        else:
            self.i_state = list(range(num_state_qubits))

        self.i_objective = None
        if i_objective is not None:
            self.i_objective = i_objective
        else:
            self.i_objective = num_state_qubits

        # map breakpoints, slopes, and offsets such that they fit {0, ..., 2^n-1}
        lower = min_state_value
        upper = max_state_value
        self._mapped_breakpoints = []
        self._mapped_slopes = []
        self._mapped_offsets = []
        for i, point in enumerate(breakpoints):
            mapped_breakpoint = (point - lower) / (upper - lower) * (2**num_state_qubits - 1)
            if mapped_breakpoint <= 2**num_state_qubits - 1:
                self._mapped_breakpoints += [mapped_breakpoint]

                # factor (upper - lower) / (2^n - 1) is for the scaling of x to [l,u]
                # note that the +l for mapping to [l,u] is already included in
                # the offsets given as parameters
                self._mapped_slopes += [slopes[i] * (upper - lower) / (2**num_state_qubits - 1)]
                self._mapped_offsets += [offsets[i]]
        self._mapped_breakpoints = np.array(self._mapped_breakpoints)
        self._mapped_slopes = np.array(self._mapped_slopes)
        self._mapped_offsets = np.array(self._mapped_offsets)

        # approximate linear behavior by scaling and contracting around pi/4
        if len(self._mapped_breakpoints):  # pylint: disable=len-as-condition
            self._slope_angles = np.zeros(len(breakpoints))
            self._offset_angles = np.pi / 4 * (1 - c_approx) * np.ones(len(breakpoints))
            for i in range(len(breakpoints)):
                self._slope_angles[i] = \
                    np.pi * c_approx * self._mapped_slopes[i] / 2 / (f_max - f_min)
                self._offset_angles[i] += \
                    np.pi * c_approx * (self._mapped_offsets[i] - f_min) / 2 / (f_max - f_min)

            # multiply by 2 since Y-rotation uses theta/2 as angle
            self._slope_angles = 2 * self._slope_angles
            self._offset_angles = 2 * self._offset_angles

            # create piecewise linear Y rotation
            self._pwl_ry = PiecewiseLinearPauliRotations(
                num_state_qubits,
                self._mapped_breakpoints,
                self._slope_angles,
                self._offset_angles
            )

        else:
            self.offset_angle = 0
            self.slope_angle = 0

            # create piecewise linear Y rotation
            self._pwl_ry = None

    def value_to_estimation(self, value):
        """ value to estimation """
        if self._c_approx < 1:
            # map normalized value back to estimation
            estimator = value - 1 / 2 + np.pi / 4 * self._c_approx
            estimator *= 2 / np.pi / self._c_approx
            estimator *= self._f_max - self._f_min
            estimator += self._f_min
            return estimator
        else:
            return value

    def required_ancillas(self):
        """ requires ancillas """
        return self._pwl_ry.num_ancilla_qubits

    def build(self, qc, q, q_ancillas=None, params=None):
        """ build """
        q_state = [q[i] for i in self.i_state]
        q_objective = q[self.i_objective]

        # apply piecewise linear rotation
        qubits = q_state[:] + [q_objective]
        if q_ancillas:
            qubits += q_ancillas[:self.required_ancillas()]

        qc.append(self._pwl_ry.to_instruction(), qubits)