Example #1
0
 def test_binary_decimal_to_float(self):
     """Tests conversion of a string binary decimal to a float."""
     for n in range(10):
         binary_decimal = "1" + "0" * n
         self.assertEqual(
             QSVE.binary_decimal_to_float(binary_decimal, big_endian=True),
             0.5)
         self.assertEqual(
             QSVE.binary_decimal_to_float(binary_decimal, big_endian=False),
             2**(-n - 1))
Example #2
0
 def test_binary_decimal_to_float_conversion(self):
     """Tests converting binary decimals (e.g., 0.10 = 0.5 or 0.01 = 0.25) to floats, and vice versa."""
     for num in np.linspace(0, 0.99, 25):
         self.assertAlmostEqual(
             QSVE.binary_decimal_to_float(QSVE.to_binary_decimal(num,
                                                                 nbits=30),
                                          big_endian=True), num)
Example #3
0
    def test_singular_values_from_theta_values_for_two_by_two_identity_matrix(
            self):
        """Tests the correct theta values (in the range [0, 1] are measured for the identity matrix.

        These theta values are 0.25 and 0.75, or 0.01 and 0.11 as binary decimals, respectively.

        The identity matrix A = [[1, 0], [0, 1]] has singular value 1 and Froebenius norm sqrt(2). It follows that

                                    sigma / ||A||_F = 1 / sqrt(2)

        Since                       cos(pi * theta) = sigma / ||A||_F,

        we must have                cos(pi * theta) = 1 / sqrt(2),

        which means that theta = - 0.25 or theta = 0.25. After mapping from the interval [-1/2, 1/2] to the interval
        [0, 1] via

                                    theta ----> theta           (if 0 <= theta <= 1 / 2)
                                    theta ----> theta + 1       (if -1 / 2 <= theta < 0)

        (which is what we measure in QSVE), the possible outcomes are thus 0.25 and 0.75. These correspond to binary
        decimals 0.01 and 0.10, respectively.

        This test does QSVE on the identity matrix using 2, 3, 4, 5, and 6 precision qubits for QPE.
        """
        # Define the identity matrix
        matrix = np.identity(2)

        # Create the QSVE instance
        qsve = QSVE(matrix)

        for nprecision_bits in [2, 3, 4, 5, 6]:
            # Get the circuit to perform QSVE with terminal measurements on the QPE register
            circuit = qsve.create_circuit(nprecision_bits=nprecision_bits,
                                          terminal_measurements=True)

            # Run the quantum circuit for QSVE
            sim = BasicAer.get_backend("qasm_simulator")
            job = execute(circuit, sim, shots=10000)

            # Get the output bit strings from QSVE
            res = job.result()
            counts = res.get_counts()
            thetas_binary = np.array(list(counts.keys()))

            # Make sure there are only two measured values
            self.assertEqual(len(thetas_binary), 2)

            # Convert the measured angles to floating point values
            thetas = [
                qsve.binary_decimal_to_float(binary_decimal, big_endian=False)
                for binary_decimal in thetas_binary
            ]
            thetas = [qsve.convert_measured(theta) for theta in thetas]

            # Make sure the theta values are correct
            self.assertEqual(len(thetas), 2)
            self.assertIn(0.25, thetas)
            self.assertIn(-0.25, thetas)
Example #4
0
    def test_singular_values_two_by_two_three_pi_over_eight_singular_vector2(
            self):
        """Tests computing the singular values of the matrix

                    A = [[cos(3 * pi / 8), 0],
                         [0, sin(3 * pi / 8)]]

        with an input singular vector. Checks that only one singular value is present in the measurement outcome.
        """
        # Define the matrix
        matrix = np.array([[np.cos(3 * np.pi / 8), 0],
                           [0, np.sin(3 * np.pi / 8)]])

        # Do the classical SVD. (Note: We could just access the singular values from the diagonal matrix elements.)
        _, sigmas, _ = np.linalg.svd(matrix)

        # Get the quantum circuit for QSVE
        for nprecision_bits in range(3, 7):
            qsve = QSVE(matrix)
            circuit = qsve.create_circuit(nprecision_bits=nprecision_bits,
                                          init_state_row_and_col=[0, 1, 0, 0],
                                          terminal_measurements=True)

            # Run the quantum circuit for QSVE
            sim = BasicAer.get_backend("qasm_simulator")
            job = execute(circuit, sim, shots=10000)

            # Get the output bit strings from QSVE
            res = job.result()
            counts = res.get_counts()
            thetas_binary = np.array(list(counts.keys()))

            # Convert from the binary strings to theta values
            computed = [
                qsve.convert_measured(qsve.binary_decimal_to_float(bits))
                for bits in thetas_binary
            ]

            # Convert from theta values to singular values
            qsigmas = [
                np.cos(np.pi * theta) for theta in computed if theta > 0
            ]

            # Sort the sigma values for comparison
            sigmas = list(sorted(sigmas))
            qsigmas = list(sorted(qsigmas))

            # Make sure the quantum solution is close to the classical solution
            self.assertTrue(np.allclose(sigmas[1], qsigmas))
Example #5
0
class LinearSystemSolverQSVE:
    """Quantum algorithm for solving linear systems of equations based on quantum singular value estimation (QSVE)."""
    def __init__(self, Amatrix, bvector, precision=3, cval=0.5):
        """Initializes a LinearSystemSolver.

        Args:
            Amatrix : numpy.ndarray
                Matrix in the linear system Ax = b.

            bvector : numpy.ndarray
                Vector in the linear system Ax = b.

            precision : int
                Number of bits of precision to use in the QSVE subroutine.
        """
        self._matrix = deepcopy(Amatrix)
        self._vector = deepcopy(bvector)
        self._precision = precision
        self._cval = cval
        self._qsve = QSVE(Amatrix,
                          singular_vector=bvector,
                          nprecision_bits=precision)

    @property
    def matrix(self):
        return self._matrix

    @property
    def vector(self):
        return self._vector

    def classical_solution(self, normalized=True):
        """Returns the solution of the linear system found classically.

        Args:
            normalized : bool (default value = True)
                If True, the classical solution is normalized (by the L2 norm), else it is un-normalized.
        """
        xclassical = np.linalg.solve(self._matrix, self._vector)
        if normalized:
            return xclassical / np.linalg.norm(xclassical, ord=2)
        return xclassical

    def _hhl_rotation(self,
                      circuit,
                      eval_register,
                      ancilla_qubit,
                      constant=0.5):
        """Adds the gates for the HHL rotation to perform the transformation


            sum_j beta_j |lambda_j>  ---->  sum_j beta_j / lambda_j |lambda_j>

        Args:

        """
        # The number of controls is the number of qubits in the eval_register
        ncontrols = len(eval_register)

        for ii in range(2**ncontrols):
            # Get the bitstring for this index
            bitstring = np.binary_repr(ii, ncontrols)

            # Do the initial sequence of NOT gates to get controls/anti-controls correct
            for (ind, bit) in enumerate(bitstring):
                if bit == "0":
                    circuit.x(eval_register[ind])

            # Determine the theta value in this amplitude
            theta = self._qsve.binary_decimal_to_float(bitstring)

            # Compute the eigenvalue in this register
            eigenvalue = self._qsve.matrix_norm() * np.cos(np.pi * theta)

            # TODO: Is this correct to do?
            if np.isclose(eigenvalue, 0.0):
                continue

            # Determine the angle of rotation for the Y-rotation
            angle = 2 * np.arccos(constant / eigenvalue)

            # Do the controlled Y-rotation
            mcry(circuit,
                 angle,
                 eval_register,
                 ancilla_qubit,
                 None,
                 mode="noancilla")

            # Do the final sequence of NOT gates to get controls/anti-controls correct
            for (ind, bit) in enumerate(bitstring):
                if bit == "0":
                    circuit.x(eval_register[ind])

    def create_circuit(self, return_registers=False):
        """Creates the circuit that solves the linear system Ax = b."""
        # Get the QSVE circuit
        circuit, qpe_register, row_register, col_register = self._qsve.create_circuit(
            return_registers=True)

        # Make a copy to take the inverse of later
        qsve_circuit = deepcopy(circuit)

        # Add the ancilla register (of one qubit) for the HHL rotation
        ancilla = QuantumRegister(1, name="anc")
        circuit.add_register(ancilla)

        # Do the HHL rotation
        self._hhl_rotation(circuit, qpe_register, ancilla[0], self._cval)

        # Add the inverse QSVE circuit (without the initial data loading subroutines)
        circuit += self._qsve.create_circuit(initial_loads=False).inverse()

        if return_registers:
            return circuit, qpe_register, row_register, col_register, ancilla
        return circuit

    def _run(self, simulator, shots):
        """Runs the quantum circuit and returns the measurement counts."""
        pass

    def quantum_solution(self):
        pass

    def compute_expectation(self, observable):
        pass