def hipster_multi(): """Multi-qubit, optimized application.""" nbits = 7 for bits in helper.bitprod(nbits): psi = state.bitstring(*bits) for target in range(1, nbits): # Full matrix (O(n*n). op = (ops.Identity(target - 1) * ops.Cnot(target - 1, target) * ops.Identity(nbits - target - 1)) psi1 = op(psi) # Single Qubit (O(n)) psi = apply_controlled_gate(ops.PauliX(), target - 1, target, psi) if not psi.is_close(psi1): raise AssertionError('Invalid Single Gate Application.') psi = state.bitstring(1, 1, 0, 0, 1) pn = ops.Cnot(1, 4)(psi, 1) if not pn.is_close(apply_controlled_gate(ops.PauliX(), 1, 4, psi)): raise AssertionError('Invalid Cnot') pn = ops.Cnot(4, 1)(psi, 1) if not pn.is_close(apply_controlled_gate(ops.PauliX(), 4, 1, psi)): raise AssertionError('Invalid Cnot') pn = ops.ControlledU(0, 1, ops.ControlledU(1, 4, ops.PauliX()))(psi) psi = state.qubit(alpha=0.6) * state.ones(2) pn = ops.Cnot(0, 2)(psi) if not pn.is_close(apply_controlled_gate(ops.PauliX(), 0, 2, psi)): raise AssertionError('Invalid Cnot')
def test_density_to_cartesian(self): """Test density to cartesian conversion.""" q0 = state.zeros(1) rho = q0.density() x, y, z = helper.density_to_cartesian(rho) self.assertEqual(x, 0.0) self.assertEqual(y, 0.0) self.assertEqual(z, 1.0) q1 = state.ones(1) rho = q1.density() x, y, z = helper.density_to_cartesian(rho) self.assertEqual(x, 0.0) self.assertEqual(y, 0.0) self.assertEqual(z, -1.0) qh = ops.Hadamard()(q0) rho = qh.density() x, y, z = helper.density_to_cartesian(rho) self.assertTrue(math.isclose(np.real(x), 1.0, abs_tol=1e-6)) self.assertTrue(math.isclose(np.real(y), 0.0)) self.assertTrue(math.isclose(np.real(z), 0.0, abs_tol=1e-6)) qr = ops.RotationZ(90.0 * math.pi / 180.0)(qh) rho = qr.density() x, y, z = helper.density_to_cartesian(rho) self.assertTrue(math.isclose(np.real(x), 0.0, abs_tol=1e-6)) self.assertTrue(math.isclose(np.real(y), -1.0, abs_tol=1e-6)) self.assertTrue(math.isclose(np.real(z), 0.0, abs_tol=1e-6))
def basis_kick1(): """Simple H-Cnot-H phase kick.""" psi = state.zeros(3) * state.ones(1) psi = ops.Hadamard(4)(psi) psi = ops.Cnot(2, 3)(psi, 2) psi = ops.Hadamard(4)(psi) if psi.prob(0, 0, 1, 1) < 0.9: raise AssertionError('Something is wrong with the phase kick')
def run_experiment(nbits: int) -> None: """Run full experiment for a given number of bits.""" c = make_c(nbits - 1) u = make_u(nbits, c) psi = state.zeros(nbits - 1) * state.ones(1) psi = ops.Hadamard(nbits)(psi) psi = u(psi) psi = ops.Hadamard(nbits)(psi) check_result(nbits, c, psi)
def run_oracle_experiment(nbits) -> None: """Run full experiment for a given number of bits.""" c = make_c(nbits - 1) f = make_oracle_f(c) u = ops.OracleUf(nbits, f) psi = state.zeros(nbits - 1) * state.ones(1) psi = ops.Hadamard(nbits)(psi) psi = u(psi) psi = ops.Hadamard(nbits)(psi) check_result(nbits, c, psi)
def run_experiment(nbits, flavor): """Run full experiment for a given flavor of f().""" f = make_f(nbits - 1, flavor) u = ops.OracleUf(nbits, f) psi = (ops.Hadamard(nbits - 1)(state.zeros(nbits - 1)) * ops.Hadamard()(state.ones(1))) psi = u(psi) psi = (ops.Hadamard(nbits - 1) * ops.Identity(1))(psi) # Measure all of |0>. If all close to 1.0, f() is constant. for idx in range(nbits - 1): p0, _ = ops.Measure(psi, idx, tostate=0, collapse=False) if not math.isclose(p0, 1.0, abs_tol=1e-5): return exp_balanced return exp_constant
def test_bell(self): """Check successful entanglement by computing the schmidt_number.""" b00 = bell.bell_state(0, 0) self.assertGreater(b00.schmidt_number([1]), 1.0) self.assertTrue( b00.is_close((state.zeros(2) + state.ones(2)) / math.sqrt(2))) # Note the order is reversed from pictorials. op_exp = (ops.Cnot(0, 1) @ (ops.Hadamard() * ops.Identity())) b00_exp = op_exp(state.zeros(2)) self.assertTrue(b00.is_close(b00_exp)) b01 = bell.bell_state(0, 1) self.assertGreater(b01.schmidt_number([1]), 1.0) b10 = bell.bell_state(1, 0) self.assertGreater(b10.schmidt_number([1]), 1.0) b11 = bell.bell_state(1, 1) self.assertGreater(b11.schmidt_number([1]), 1.0)
def test_simple_state(self): psi = state.zero self.assertEqual(psi[0], 1) self.assertEqual(psi[1], 0) psi = state.one self.assertEqual(psi[0], 0) self.assertEqual(psi[1], 1) psi = state.zeros(8) self.assertEqual(psi[0], 1) for i in range(1, 2**8 - 1): self.assertEqual(psi[i], 0) psi = state.ones(8) for i in range(0, 2**8 - 2): self.assertEqual(psi[i], 0) self.assertEqual(psi[2**8 - 1], 1) psi = state.rand(8) self.assertEqual(psi.nbits, 8)
def ControlledU(idx0, idx1, u): """Control qubit at idx1 via controlling qubit at idx0.""" if idx0 == idx1: raise ValueError('Control and controlled qubit must not be equal.') p0 = Projector(state.zeros(1)) p1 = Projector(state.ones(1)) ifill = Identity(int(math.fabs(idx1 - idx0)) - 1) # space between qubits ufill = Identity()**u.nbits # 'width' of U in terms of Identity matrices if idx1 > idx0: if idx1 - idx0 > 1: op = p0 * ifill * ufill + p1 * ifill * u else: op = p0 * ufill + p1 * u else: if idx0 - idx1 > 1: op = ufill * ifill * p0 + u * ifill * p1 else: op = ufill * p0 + u * p1 return op
def basis_changes(): """Explore basis changes via Hadamard.""" # Generate [1, 0] psi = state.zeros(1) # Hadamard will result in 1/sqrt(2) [1, 1] (|+>) psi = ops.Hadamard()(psi) # Generate [0, 1] psi = state.ones(1) # Hadamard on |1> will result in 1/sqrt(2) [1, -1] (|->) psi = ops.Hadamard()(psi) # Simple PauliX will result in 1/sqrt(2) [-1, 1] psi = ops.PauliX()(psi) # But back to computational, will result in -|1>. # Global phases can be ignored. psi = ops.Hadamard()(psi) if not np.allclose(psi[1], -1.0): raise AssertionError('Invalid basis change.')
def basis_changes(): """Explore basis changes via Hadamard.""" # Generate [1, 0] psi = state.zeros(1) # Hadamard will result in 1/sqrt(2) [1, 1] (|+>) psi = ops.Hadamard()(psi) # Generate [0, 1] psi = state.ones(1) # Hadamard on |1> will result in 1/sqrt(2) [1, -1] (|->) psi = ops.Hadamard()(psi) # Simple PauliX will result in 1/sqrt(2) [-1, 1] psi = ops.PauliX()(psi) # But back to computational, will result in -|1>, but phases # can be ignored. psi = ops.Hadamard()(psi) if psi[1] > -0.95: raise AssertionError('Invalid Basis Changes')
def run_experiment(nbits, solutions) -> None: """Run full experiment for a given flavor of f().""" # Note that op_zero multiplies the diagonal elements of the operator by -1, # except for element [0][0]. This can be interpreted as "rotating around # the |00..)>" state. More pragmatically, multiplying this op_zero with # a Hadamard from the left and right gives a matrix of this form: # # 2/N-1 2/N 2/N ... 2/N # 2/N 2N-1 2/N ... 2/N # 2/N 2/N 2/N-1 ... 2/N # 2/N 2/N 2/N ... 2/N-1 # # Multiplying this matrix with a state vector computes exactly: # 2u - c_x # for every vector element c_x, with u being the mean over the # state vector. This is the defintion of inversion about the mean. # zero_projector = np.zeros((2**nbits, 2**nbits)) zero_projector[0, 0] = 1 op_zero = ops.Operator(zero_projector) # Make f and Uf. Note: # We reserve space for an ancilla 'y', which is unused in # Grover's algorithm. This allows reuse of the Deutsch Uf builder. # # We use the Oracle construction for convenience. It is rather # slow (full matrix) for larger qubit counts. Once can construct # a 'regular' function for the grover search algorithms, but this # function is different for each bitstring and that quickly gets # confusing. # f = make_f(nbits, solutions) uf = ops.OracleUf(nbits+1, f) # Build state with 1 ancilla of |1>. # psi = state.zeros(nbits) * state.ones(1) for i in range(nbits + 1): psi.apply1(ops.Hadamard(), i) # Build Grover operator, note Id() for the ancilla. # The Grover operator is the combination of: # - phase inversion via the u unitary # - inversion about the mean (see matrix above) # hn = ops.Hadamard(nbits) reflection = op_zero * 2.0 - ops.Identity(nbits) inversion = hn(reflection(hn)) * ops.Identity() grover = inversion(uf) # Number of Grover iterations # # There are at least 2 ways to compute the required number of iterations. # # 1) Most text books, eg., page 157 of Kaye, Laflamme and Mosca, find # that the highest probability of finding the right results occurs # after pi/4 sqrt(n) rotations. # # 2) For grover specifically, with 1 solution the iteration count is: # int(math.pi / 4 * math.sqrt(n)) # # 3) For grover with multiple solutions: # int(math.pi / 4 * math.sqrt(n / solutions)) # # 4) For amplitude amplification, it's the probability of good # solutions, which is trivial with the Grover equal # superposition here: # int(math.sqrt(n / solutions)) # iterations = int(math.pi / 4 * math.sqrt(2**nbits / solutions)) for _ in range(iterations): psi = grover(psi) # Measurement - pick element with higher probability. # # Note: We constructed the Oracle with n+1 qubits, to allow # for the 'xor-ancillary'. To check the result, we need to # ignore this ancilla. # maxbits, maxprob = psi.maxprob() result = f(maxbits[:-1]) print('Got f({}) = {}, want: 1, #: {:2d}, p: {:6.4f}' .format(maxbits[:-1], result, solutions, maxprob)) if result != 1: raise AssertionError('something went wrong, measured invalid state')
def ones(self, n: int) -> None: self.psi = self.psi * state.ones(n) self.global_reg = self.global_reg + n
def run_experiment(nbits) -> None: """Run full experiment for a given flavor of f().""" hn = ops.Hadamard(nbits) h = ops.Hadamard() n = 2**nbits # Note that op_zero multiplies the diagonal elements of the operator by -1, # except for element [0][0]. This can be interpreted as "rotating around # the |00..)>" state. More pragmatically, multiplying this op_zero with # a Hadamard from the left and right gives a matrix of this form: # # 2/N-1 2/N 2/N ... 2/N # 2/N 2N-1 2/N ... 2/N # 2/N 2/N 2/N-1 ... 2/N # 2/N 2/N 2/N ... 2/N-1 # # Multiplying this matrix with a state vector computes exactly: # 2u - c_x # for every vector element c_x, with u being the mean over the # state vector. This is the defintion of inversion about the mean. # zero_projector = np.zeros((n, n)) zero_projector[0, 0] = 1 op_zero = ops.Operator(zero_projector) # Make f and Uf. Note: We reserve space for an ancilla 'y', which is unused # in Grover's algorithm. This allows reuse of the Deutsch Uf builder. # # We use the Oracle construction for convenience. It is rather slow (full # matrix) for larger qubit counts. Once can construct a 'regular' function # for the grover search algorithms, but this function is different for # each bitstring and that quickly gets quite confusing. # # Good examples for 2-qubit and 3-qubit functions can be found here: # https://qiskit.org/textbook/ch-algorithms/grover.html#3qubits # f = make_f(nbits) u = ops.OracleUf(nbits + 1, f) # Build state with 1 ancilla of |1>. # psi = state.zeros(nbits) * state.ones(1) for i in range(nbits + 1): psi.apply(h, i) # Build Grover operator, note Id() for the ancilla. # The Grover operator is the combination of: # - phase inversion via the u unitary # - inversion about the mean (see matrix above) # reflection = op_zero * 2.0 - ops.Identity(nbits) inversion = hn(reflection(hn)) * ops.Identity() grover = inversion(u) # Number of Grover iterations # # There are at least 2 ways to compute the required number of iterations. # # 1) Most text books, eg., page 157 of Kaye, Laflamme and Mosca, find # that the highest probability of finding the right results occurs # after pi/4 sqrt(n) rotations. # # 2) I found this computation in qcircuits: # angle_to_rotate = np.arccos(np.sqrt(1 / n)) # rotation_angle = 2 * np.arcsin(np.sqrt(1 / n)) # iterations = int(round(angle_to_rotate / rotation_angle)) # # Both produce identical results. # iterations = int(math.pi / 4 * math.sqrt(n)) for _ in range(iterations): psi = grover(psi) # Measurement - pick element with higher probability. # # Note: We constructed the Oracle with n+1 qubits, to allow # for the 'xor-ancillary'. To check the result, we need to # ignore this ancilla. # maxbits, maxprobs = psi.maxprob() result = f(maxbits[:-1]) print(f'Got f({maxbits[:-1]}) = {result}, want: 1') if result != 1: raise AssertionError('something went wrong, measured invalid state')
def run_experiment(nbits_phase, nbits_grover, solutions) -> None: """Run full experiment for a given number of solutions.""" # Building the Grover Operator, see grover.py n = 2**nbits_grover zero_projector = np.zeros((n, n)) zero_projector[0, 0] = 1 op_zero = ops.Operator(zero_projector) f = make_f(nbits_grover, solutions) u = ops.OracleUf(nbits_grover + 1, f) # The state for the counting algorithm. # We reserve nbits for the phase estimation. # We also reserve nbits for the Oracle. # These numbers could be adjusted to achieve better # accuracy. Yet, this keeps the code a little bit simpler, # while trading off a few off-by-1 estimation errors. # # We also add the |1> for the Oracle. # psi = (state.zeros(nbits_phase) * state.zeros(nbits_grover) * state.ones(1)) # Apply Hadamard to all the qubits. for i in range(nbits_phase + nbits_grover + 1): psi.apply(ops.Hadamard(), i) # Construct the Grover Operator. reflection = op_zero * 2.0 - ops.Identity(nbits_grover) hn = ops.Hadamard(nbits_grover) inversion = hn(reflection(hn)) * ops.Identity() grover = inversion(u) # Now that we have the Grover Operator, we have to perform # phase estimation. This loop is a copy from phase_estimation.py # with more comments there. # for idx, inv in enumerate(range(nbits_phase - 1, -1, -1)): u2 = grover for _ in range(idx): u2 = u2(u2) psi = ops.ControlledU(inv, nbits_phase, u2)(psi, inv) # Reverse QFT gives us the phase as a fraction of 2*Pi psi = ops.Qft(nbits_phase).adjoint()(psi) # Get the state with highest probability and compute the phase # as a binary fraction. Note that the probability increases # as M, the number of solutions, gets closer and closer to N, # the total mnumber of states. maxbits, maxprob = psi.maxprob() phi_estimate = sum(maxbits[i] * 2**(-i - 1) for i in range(nbits_phase)) # We know that after phase estimation, this holds: # # sin(phi/2) = sqrt(M/N) # M = N * sin(phi/2)^2 # # Hence we can compute M. We keep the result to 2 digit to visualize # the errors. Note that the phi_estimate is a fraction of 2*PI, hence # the 1/2 in above formula cancels out against the 2 and we compute: M = round(n * math.sin(phi_estimate * math.pi)**2, 2) print('Estimate: {:.4f} prob: {:5.2f}% --> M: {:5.2f}, want: {:2d}'.format( phi_estimate, maxprob * 100.0, M, solutions))
def ones(self, n): self.psi = self.psi * state.ones(n)