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))
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)
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)
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))
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