def run_experiment(): """Run single, defined experiment for secret 11.""" psi = state.zeros(4) u = make_u() psi = ops.Hadamard(2)(psi) psi = u(psi) psi = ops.Hadamard(2)(psi) # Because of the xor patterns (Yanofski 6.64) # measurement will only find those qubit strings where # the scalar product of z (lower bits) and secret string: # <z, c> = 0 # # This should measure |00> and |11> with equal probability. # If true, than we can derive the secret string as being 11 # because f(00) = f(11) and because f(00) = f(00 ^ c) -> c = 11 # print('Measure likely states (want: pairs of 00 or 11):') for bits in helper.bitprod(4): if psi.prob(*bits) > 0.01: if (bits[0] == 0 and bits[1] == 1) or (bits[0] == 1 and bits[1] == 0): raise AssertionError('Invalid Results') print('|{}{} {}{}> = 0 : {:.2f} dot % 2: {:.2f}'.format( bits[0], bits[1], bits[2], bits[3], psi.prob(*bits), dot2(bits)))
def run_experiment(a1: np.complexfloating, a2: np.complexfloating, target: float) -> None: """Construct swap test circuit and measure.""" # The circuit is quite simple: # # |0> --- H --- o --- H --- Measure # | # a1 --------- x --------- # | # a2 ----------x --------- psi = state.bitstring(0) * state.qubit(a1) * state.qubit(a2) psi = ops.Hadamard()(psi, 0) psi = ops.ControlledU(0, 1, ops.Swap(1, 2))(psi) psi = ops.Hadamard()(psi, 0) # Measure once. p0, _ = ops.Measure(psi, 0) if abs(p0 - target) > 0.05: raise AssertionError( 'Probability {:.2f} off more than 5% from target {:.2f}'.format( p0, target)) print('Similarity of a1: {:.2f}, a2: {:.2f} ==> %: {:.2f}'.format( a1, a2, 100.0 * p0))
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 test_double_hadamard(self): """Check that Hadamard is fully reversible.""" psi = state.zeros(2) psi2 = ops.Hadamard(2)(ops.Hadamard(2)(psi)) self.assertEqual(psi2.nbits, 2) self.assertTrue(psi.is_close(psi2)) combo = ops.Hadamard(2) @ ops.Hadamard(2) psi3 = combo(psi) self.assertEqual(psi3.nbits, 2) self.assertTrue(psi.is_close(psi3)) self.assertTrue(psi.density().is_pure())
def main(argv): if len(argv) > 1: raise app.UsageError('Too many command-line arguments.') num_experiments = 10 depth = 8 recursion = 4 print('SK algorithm - depth: {}, recursion: {}, experiments: {}'. format(depth, recursion, num_experiments)) base = [to_su2(ops.Hadamard()), to_su2(ops.Tgate())] gates = create_unitaries(base, depth) sum_dist = 0.0 for i in range(num_experiments): U = (ops.RotationX(2.0 * np.pi * random.random()) @ ops.RotationY(2.0 * np.pi * random.random()) @ ops.RotationZ(2.0 * np.pi * random.random())) U_approx = sk_algo(U, gates, recursion) dist = trace_dist(U, U_approx) sum_dist += dist phi1 = U(state.zero) phi2 = U_approx(state.zero) print('[{:2d}]: Trace Dist: {:.4f} State: {:6.4f}%'. format(i, dist, 100.0 * (1.0 - np.real(np.dot(phi1, phi2.conj()))))) print('Gates: {}, Mean Trace Dist:: {:.4f}'. format(len(gates), sum_dist / num_experiments))
def random_gates(min_length, max_length, num_experiments): """Just create random sequences, find the best.""" base = [to_su2(ops.Hadamard()), to_su2(ops.Tgate())] U = (ops.RotationX(2.0 * np.pi * random.random()) @ ops.RotationY(2.0 * np.pi * random.random()) @ ops.RotationZ(2.0 * np.pi * random.random())) min_dist = 1000 for _ in range(num_experiments): seq_length = min_length + random.randint(0, max_length) U_approx = ops.Identity() for _ in range(seq_length): g = random.randint(0, 1) U_approx = U_approx @ base[g] dist = trace_dist(U, U_approx) min_dist = min(dist, min_dist) phi1 = U(state.zero) phi2 = U_approx(state.zero) print('Trace Dist: {:.4f} State: {:6.4f}%'. format(min_dist, 100.0 * (1.0 - np.real(np.dot(phi1, phi2.conj())))))
def test_had_cnot_had(self): """Exercise 4.20 in Nielson, Chuang, H2.Cnot(0,1).H2==Cnot(1,0).""" h2 = ops.Hadamard(2) cnot = ops.Cnot(0, 1) op = h2(cnot(h2)) self.assertTrue(op.is_close(ops.Cnot(1, 0)))
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 operator_order(): """Evaluate order of operations and corresponding matmuls.""" hi = ops.Hadamard() * ops.Identity() cx = ops.Cnot(0, 1) # Make sure that the order of evaluation is correct. For example, # this simple circuit: # # |0> --- H --- o --- # | # |0> ----------X --- # # p0 p1 p2 # # Can be evaluated step wise, applying each gate to psi: psi_0 = state.zeros(2) psi_1 = hi(psi_0) psi_2 = cx(psi_1) # Or via a combined operator. Yet, the order ot the ops # has to be reversed from above picture: combined_op = (cx @ hi) combined_psi = state.zeros(2) combined_psi_2 = combined_op(combined_psi) if not psi_2.is_close(combined_psi_2): raise AssertionError('Invalid order of operators from matmul') # This can also be expressed via the function call construct: combined_f = hi(cx) combined_f_psi = state.zeros(2) combined_f_psi_2 = combined_f(combined_f_psi) if not psi_2.is_close(combined_f_psi_2): raise AssertionError('Invalid order of operators from call construct')
def with_matmul(): psi = state.zeros(n) ident = ops.Identity(n) h = ops.Hadamard(n) psi = (ident @ h)(psi) return psi
def test_padding(self): ident = ops.Identity(3) h = ops.Hadamard() op = ident(h, 0) op_manual = h * ops.Identity(2) self.assertTrue(op.is_close(op_manual)) op = ident(h, 1) op_manual = ops.Identity() * h * ops.Identity() self.assertTrue(op.is_close(op_manual)) op = ident(h, 2) op_manual = ops.Identity(2) * h self.assertTrue(op.is_close(op_manual)) ident = ops.Identity(4) cx = ops.Cnot(0, 1) op = ident(cx, 0) op_manual = cx * ops.Identity(2) self.assertTrue(op.is_close(op_manual)) op = ident(cx, 1) op_manual = ops.Identity(1) * cx * ops.Identity(1) self.assertTrue(op.is_close(op_manual)) op = ident(cx, 2) op_manual = ops.Identity(2) * cx self.assertTrue(op.is_close(op_manual))
def main(argv): if len(argv) > 1: raise app.UsageError('Too many command-line arguments.') # Step 1: Alice and Bob share an entangled pair, and separate. psi = bell.bell_state(0, 0) # Step 2: Alice wants to teleport a qubit |x> to Bob, # which is in the state: # |x> = a|0> + b|1> (with a^2 + b^2 == 1) a = 0.6 b = math.sqrt(1.0 - a * a) x = state.qubit(a, b) print('Quantum Teleportation') print('Start with EPR Pair a={:.2f}, b={:.2f}'.format(a, b)) # Produce combined state. alice = x * psi # Alice lets the 1st qubit interact with the 2nd qubit, which is her # part of the entangle state with Bob. alice = ops.Cnot(0, 1)(alice) # Now she applies a Hadamard to qubit 0. Bob still owns qubit 2. alice = ops.Hadamard()(alice, idx=0) # Alices measures and communicates the result (|00>, |01>, ...) to Bob. alice_measures(alice, a, b, 0, 0) alice_measures(alice, a, b, 0, 1) alice_measures(alice, a, b, 1, 0) alice_measures(alice, a, b, 1, 1)
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 by_op(): psi = state.zeros(n) ident = ops.Identity(n) h = ops.Hadamard(n) psi = ident(psi) psi = h(psi) return psi
def bell_state(a, b) -> state.State: """Make one of the four bell states with a, b from {0,1}.""" if a not in [0, 1] or b not in [0, 1]: raise ValueError('Bell state arguments are bits and must be 0 or 1.') psi = state.bitstring(a, b) psi = ops.Hadamard()(psi) return ops.Cnot()(psi)
def test_measure(self): psi = state.zeros(2) psi = ops.Hadamard()(psi) psi = ops.Cnot(0, 1)(psi) p0, psi2 = ops.Measure(psi, 0) self.assertTrue(math.isclose(p0, 0.5, abs_tol=1e-5)) # Measure again - now state should have collapsed. p0, _ = ops.Measure(psi2, 0) self.assertTrue(math.isclose(p0, 1.0, abs_tol=1e-6))
def test_acceleration(self): psi = state.bitstring(1, 0, 1, 0) qc = circuit.qc() qc.bitstring(1, 0, 1, 0) for i in range(4): qc.x(i) psi.apply(ops.PauliX(), i) qc.y(i) psi.apply(ops.PauliY(), i) qc.z(i) psi.apply(ops.PauliZ(), i) qc.h(i) psi.apply(ops.Hadamard(), i) if i: qc.cu1(0, i, 1.1) psi.apply_controlled(ops.U1(1.1), 0, i) if not psi.is_close(qc.psi): raise AssertionError('Numerical Problems') psi = state.bitstring(1, 0, 1, 0, 1) qc = circuit.qc() qc.bitstring(1, 0, 1, 0, 1) for n in range(5): qc.h(n) psi.apply(ops.Hadamard(), n) for i in range(0, 5): qc.cu1(n - (i + 1), n, math.pi / float(2**(i + 1))) psi.apply_controlled(ops.U1(math.pi / float(2**(i + 1))), n - (i + 1), n) for i in range(0, 5): qc.cu1(n - (i + 1), n, -math.pi / float(2**(i + 1))) psi.apply_controlled(ops.U1(-math.pi / float(2**(i + 1))), n - (i + 1), n) qc.h(n) psi.apply(ops.Hadamard(), n) if not psi.is_close(qc.psi): raise AssertionError('Numerical Problems')
def run_experiment(nbits): """Run single, defined experiment for secret 11.""" psi = state.zeros(nbits * 2) c = make_c(nbits) u = make_u(nbits, c) psi = ops.Hadamard(nbits)(psi) psi = u(psi) psi = ops.Hadamard(nbits)(psi) # Because of the xor patterns (Yanofski 6.64) # measurement will only find those qubit strings where # the scalar product of z (lower bits) and secret string: # <z, c> = 0 # print('Measure likely states:') for bits in helper.bitprod(nbits * 2): if psi.prob(*bits) > 0.0 and dot2(bits, nbits) < 1.0: print('|{}> = 0 : {:.2f} dot % 2: {:.2f}'.format( bits, psi.prob(*bits), dot2(bits, nbits)))
def time_series(limit): """Simple time series for gate application.""" def bench(): psi = state.zeros(nbits) for i in range(nbits): psi = apply_single_gate(h, i, psi) h = ops.Hadamard() for i in range(10, limit): nbits = i print('qubit: {}, muls: {}, mem: {}k, time: {:.3f} secs'.format( nbits, 2**nbits * 4, 2**nbits * 16 / 1024, timeit.timeit(bench, number=1)))
def ghz_state(nbits) -> state.State: """Make a maximally entangled nbits state (GHZ State).""" # Simple construction via: # # |0> --- H --- o --------- # |0> ----------X --- o --- # |0> ----------------X --- ... # psi = state.zeros(nbits) psi = ops.Hadamard()(psi) for offset in range(nbits - 1): psi = ops.Cnot(0, 1)(psi, offset) return psi
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 hipster_single(): """Single-qubit Hipster Technique.""" # This is a nice trick, outlined in this paper on "Hipster": # https://arxiv.org/pdf/1601.07195.pdf # # The observation is that to apply a single-qubit gate to a # gubit with index i, take the binary representation of inidices and # apply the transformation matrix to the elements according # to the power of 2 index. Generally: # "Performing a single-qubit gate on qubit k of n-qubit quantum # register applies G to pairs of amplitudes whose indices differ in # k-th bits of their binary index". # # For example, for a 2-qubit system, to apply a gate to qubit 0: # apply G to # q11, q12 psi[0], psi[1] # q21, q22 psi[2], psi[3] # # To apply to qubit 1: # q11, q12 psi[0], psi[2] # q21, q22 psi[1], psi[3] # # 'Outer loop' jumps by 2**(nbits+1) # 'Inner loop' jumps by 2**k # # To maintain the qubit / index ordering of this infrastructure, # the qubit index in the paper is reversed to the qubit index here. # (Hence the (nbits - qubit - 1) above) # # Make sure that for sample gates and all states the transformations # are identical. # for gate in (ops.PauliX(), ops.PauliZ(), ops.Hadamard(), ops.RotationX(0.5)): nbits = 5 for bits in helper.bitprod(nbits): psi = state.bitstring(*bits) qubit = random.randint(0, nbits - 1) # Full matrix (O(n*n). op = ops.Identity(qubit) * gate * ops.Identity(nbits - qubit - 1) psi1 = op(psi) # Single Qubit (O(n)) psi = apply_single_gate(gate, qubit, psi) if not psi.is_close(psi1): raise AssertionError('Invalid Single Gate Application.')
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 bob_measures(psi: state.State, expect0: int, expect1: int): """Bob measures both bits (in computational basis).""" # Change Hadamard basis back to computational basis. psi = ops.Cnot(0, 1)(psi) psi = ops.Hadamard()(psi) p0, _ = ops.Measure(psi, 0, tostate=expect1) p1, _ = ops.Measure(psi, 1, tostate=expect0) if (not math.isclose(p0, 1.0, abs_tol=1e-6) or not math.isclose(p1, 1.0, abs_tol=1e-6)): raise AssertionError(f'Invalid Result p0 {p0} p1 {p1}') print(f'Expected/matched: |{expect0}{expect1}>.')
def test_dft(self): """Build 'manually' a 3 qubit gate, Nielsen/Chuang Box 5.1.""" h = ops.Hadamard() op = ops.Identity(3) op = op(h, 0) op = op(ops.ControlledU(1, 0, ops.Rk(2)), 0) # S-gate op = op(ops.ControlledU(2, 0, ops.Rk(3)), 0) # T-gate op = op(h, 1) op = op(ops.ControlledU(1, 0, ops.Rk(2)), 1) # S-gate op = op(h, 2) op = op(ops.Swap(0, 2), 0) op3 = ops.Qft(3) self.assertTrue(op3.is_close(op))
def test_equalities(self): """Exercise 4.13 in Nielson, Chuang.""" _, x, y, z = ops.Pauli() h = ops.Hadamard() op = h(x(h)) self.assertTrue(op.is_close(ops.PauliZ())) op = h(y(h)) self.assertTrue(op.is_close(-1.0 * ops.PauliY())) op = h(z(h)) self.assertTrue(op.is_close(ops.PauliX())) op = x(z) self.assertTrue(op.is_close(1.0j * ops.PauliY()))
def test_bloch(self): psi = state.zeros(1) x, y, z = helper.density_to_cartesian(psi.density()) self.assertEqual(x, 0.0) self.assertEqual(y, 0.0) self.assertEqual(z, 1.0) psi = ops.PauliX()(psi) x, y, z = helper.density_to_cartesian(psi.density()) self.assertEqual(x, 0.0) self.assertEqual(y, 0.0) self.assertEqual(z, -1.0) psi = ops.Hadamard()(psi) x, y, z = helper.density_to_cartesian(psi.density()) self.assertTrue(math.isclose(x, -1.0, abs_tol=1e-6)) self.assertTrue(math.isclose(y, 0.0, abs_tol=1e-6)) self.assertTrue(math.isclose(z, 0.0, abs_tol=1e-6))
def basis_kick2(): """Another way to look at this H-Cnot-H phase kick.""" # This produces the vector [0, 1, 0, 0] psi = state.bitstring(0, 1) # Applying Hadamard: [0.5, -0.5, 0.5, -0.5] h2 = ops.Hadamard(2) psi = h2(psi) # Acting Cnot on this vector: [0.5, -0.5, -0.5, 0.5] psi = ops.Cnot()(psi) # Final Hadamard: [0, 0, 0, 1] psi = h2(psi) # which is |11> p11 = state.bitstring(1, 1) if not psi.is_close(p11): raise AssertionError('Something is wrong with the phase kick')