def test_piecewise_chebyshev(self, f_x, degree, breakpoints,
                                 num_state_qubits):
        """Test the piecewise Chebyshev approximation."""
        def pw_poly(x):
            if breakpoints[0] <= x < breakpoints[-1]:
                return f_x(x)
            return np.arcsin(1)

        pw_approximation = PiecewiseChebyshev(f_x, degree, breakpoints,
                                              num_state_qubits)

        self.assertFunctionIsCorrect(pw_approximation, pw_poly)
Exemplo n.º 2
0
    def test_piecewise_chebyshev(self, f_x, degree, breakpoints, num_state_qubits):
        """Test the piecewise Chebyshev approximation."""

        def pw_poly(x):
            if breakpoints:
                if len(breakpoints) > 1:
                    start = breakpoints[0]
                    end = breakpoints[-1]
                else:
                    start = breakpoints[0]
                    end = 2**num_state_qubits
            else:
                start = 0
                end = 2**num_state_qubits
            if start <= x < end:
                return f_x(x)
            return np.arcsin(1)

        pw_approximation = PiecewiseChebyshev(f_x, degree, breakpoints, num_state_qubits)

        self.assertFunctionIsCorrect(pw_approximation, pw_poly)
Exemplo n.º 3
0
    def construct_circuit(
            self, matrix: Union[np.ndarray, QuantumCircuit],
            vector: Union[np.ndarray, QuantumCircuit]) -> QuantumCircuit:
        """Construct the HHL circuit.

        Args:
            matrix: The matrix specifying the system, i.e. A in Ax=b.
            vector: The vector specifying the right hand side of the equation in Ax=b.

        Returns:
            The HHL circuit.

        Raises:
            ValueError: If the input is not in the correct format.
            ValueError: If the type of the input matrix is not supported.
        """
        # State preparation circuit - default is qiskit
        if isinstance(vector, QuantumCircuit):
            nb = vector.num_qubits
            vector_circuit = vector
        elif isinstance(vector, np.ndarray):
            nb = int(np.log2(len(vector)))
            vector_circuit = QuantumCircuit(nb)
            vector_circuit.isometry(vector / np.linalg.norm(vector),
                                    list(range(nb)), None)

        # If state preparation is probabilistic the number of qubit flags should increase
        nf = 1

        # Hamiltonian simulation circuit - default is Trotterization
        if isinstance(matrix, QuantumCircuit):
            matrix_circuit = matrix
        elif isinstance(matrix, (list, np.ndarray)):
            if isinstance(matrix, list):
                matrix = np.array(matrix)

            if matrix.shape[0] != matrix.shape[1]:
                raise ValueError("Input matrix must be square!")
            if np.log2(matrix.shape[0]) % 1 != 0:
                raise ValueError("Input matrix dimension must be 2^n!")
            if not np.allclose(matrix, matrix.conj().T):
                raise ValueError("Input matrix must be hermitian!")
            if matrix.shape[0] != 2**vector_circuit.num_qubits:
                raise ValueError("Input vector dimension does not match input "
                                 "matrix dimension! Vector dimension: " +
                                 str(vector_circuit.num_qubits) +
                                 ". Matrix dimension: " + str(matrix.shape[0]))
            matrix_circuit = NumPyMatrix(matrix, evolution_time=2 * np.pi)
        else:
            raise ValueError(f'Invalid type for matrix: {type(matrix)}.')

        # Set the tolerance for the matrix approximation
        if hasattr(matrix_circuit, "tolerance"):
            matrix_circuit.tolerance = self._epsilon_a

        # check if the matrix can calculate the condition number and store the upper bound
        if hasattr(matrix_circuit, "condition_bounds") and matrix_circuit.condition_bounds() is not\
                None:
            kappa = matrix_circuit.condition_bounds()[1]
        else:
            kappa = 1
        # Update the number of qubits required to represent the eigenvalues
        nl = max(nb + 1, int(np.log2(kappa)) + 1)

        # check if the matrix can calculate bounds for the eigenvalues
        if hasattr(matrix_circuit,
                   "eigs_bounds") and matrix_circuit.eigs_bounds() is not None:
            lambda_min, lambda_max = matrix_circuit.eigs_bounds()
            # Constant so that the minimum eigenvalue is represented exactly, since it contributes
            # the most to the solution of the system
            delta = self._get_delta(nl, lambda_min, lambda_max)
            # Update evolution time
            matrix_circuit.evolution_time = 2 * np.pi * delta / lambda_min
            # Update the scaling of the solution
            self.scaling = lambda_min
        else:
            delta = 1 / (2**nl)
            print("The solution will be calculated up to a scaling factor.")

        if self._exact_reciprocal:
            reciprocal_circuit = ExactReciprocal(nl, delta)
            # Update number of ancilla qubits
            na = matrix_circuit.num_ancillas
        else:
            # Calculate breakpoints for the reciprocal approximation
            num_values = 2**nl
            constant = delta
            a = int(round(num_values**(2 / 3)))  # pylint: disable=invalid-name

            # Calculate the degree of the polynomial and the number of intervals
            r = 2 * constant / a + np.sqrt(np.abs(1 - (2 * constant / a)**2))
            degree = min(
                nb,
                int(
                    np.log(1 +
                           (16.23 * np.sqrt(np.log(r)**2 +
                                            (np.pi / 2)**2) * kappa *
                            (2 * kappa - self._epsilon_r)) / self._epsilon_r)))
            num_intervals = int(
                np.ceil(np.log((num_values - 1) / a) / np.log(5)))

            # Calculate breakpoints and polynomials
            breakpoints = []
            for i in range(0, num_intervals):
                # Add the breakpoint to the list
                breakpoints.append(a * (5**i))

                # Define the right breakpoint of the interval
                if i == num_intervals - 1:
                    breakpoints.append(num_values - 1)

            reciprocal_circuit = PiecewiseChebyshev(
                lambda x: np.arcsin(constant / x), degree, breakpoints, nl)
            na = max(matrix_circuit.num_ancillas,
                     reciprocal_circuit.num_ancillas)

        # Initialise the quantum registers
        qb = QuantumRegister(nb)  # right hand side and solution
        ql = QuantumRegister(nl)  # eigenvalue evaluation qubits
        if na > 0:
            qa = AncillaRegister(na)  # ancilla qubits
        qf = QuantumRegister(nf)  # flag qubits

        if na > 0:
            qc = QuantumCircuit(qb, ql, qa, qf)
        else:
            qc = QuantumCircuit(qb, ql, qf)

        # State preparation
        qc.append(vector_circuit, qb[:])
        # QPE
        phase_estimation = PhaseEstimation(nl, matrix_circuit)
        if na > 0:
            qc.append(phase_estimation,
                      ql[:] + qb[:] + qa[:matrix_circuit.num_ancillas])
        else:
            qc.append(phase_estimation, ql[:] + qb[:])
        # Conditioned rotation
        if self._exact_reciprocal:
            qc.append(reciprocal_circuit, ql[::-1] + [qf[0]])
        else:
            qc.append(reciprocal_circuit.to_instruction(),
                      ql[:] + [qf[0]] + qa[:reciprocal_circuit.num_ancillas])
        # QPE inverse
        if na > 0:
            qc.append(phase_estimation.inverse(),
                      ql[:] + qb[:] + qa[:matrix_circuit.num_ancillas])
        else:
            qc.append(phase_estimation.inverse(), ql[:] + qb[:])
        return qc
    def test_piecewise_chebyshev_mutability(self):
        """Test the mutability of the piecewise Chebyshev approximation."""
        def pw_poly(x, f_x):
            if breakpoints[0] <= x < breakpoints[-1]:
                return f_x(x)
            return np.arcsin(1)

        def f_x_1(x):
            return x / 2

        pw_approximation = PiecewiseChebyshev(f_x_1)

        with self.subTest(msg="missing number of state qubits"):
            with self.assertRaises(AttributeError):  # no state qubits set
                print(pw_approximation.draw())

        with self.subTest(
                msg="default setup, just setting number of state qubits"):
            pw_approximation.num_state_qubits = 2
            pw_approximation.f_x = f_x_1
            # set to the default breakpoints for pw_poly
            breakpoints = [0, 4]
            pw_approximation.breakpoints = breakpoints
            self.assertFunctionIsCorrect(pw_approximation,
                                         lambda x: pw_poly(x, f_x_1))

        def f_x_2(x):
            return x / 4

        with self.subTest(msg="setting non-default values"):
            breakpoints = [0, 2]
            degree = 2
            pw_approximation.breakpoints = breakpoints
            pw_approximation.degree = degree
            pw_approximation.f_x = f_x_2
            self.assertFunctionIsCorrect(pw_approximation,
                                         lambda x: pw_poly(x, f_x_2))

        def f_x_3(x):
            return x**2

        with self.subTest(msg="changing all values"):
            pw_approximation.num_state_qubits = 4
            breakpoints = [1, 3, 6]
            degree = 3
            pw_approximation.breakpoints = breakpoints
            pw_approximation.degree = degree
            pw_approximation.f_x = f_x_3
            self.assertFunctionIsCorrect(pw_approximation,
                                         lambda x: pw_poly(x, f_x_3))