def operator_per_state(): """First foray into more effective math for 1-qubit operators.""" # Make product state q0 = state.qubit(alpha=0.5) q1 = state.qubit(alpha=0.9) psi = q0 * q1 # Compute via combined operator. op = ops.PauliX() * ops.Identity(1) psi1 = op(psi) # Combine via 1-qubit on q0 * q1 psi2 = ops.PauliX()(q0) * q1 if not psi1.is_close(psi2): raise AssertionError( 'Wrong tensor math and application of 1-qubit gate.') # Same thing, but apply to qubit 1. op = ops.Identity() * ops.PauliX() psi1 = op(psi) psi2 = q0 * ops.PauliX()(q1) if not psi1.is_close(psi2): raise AssertionError( 'Wrong tensor math and application of 1-qubit gate.')
def incr_mod_9(qc, aux): """Increment-by-1 modulo 9 circuit.""" # We achieve this with help of an ancilla: # # -X------------ o X 0 # -o--X--------- 0 | 0 # -o--o--X------ 0 | 0 # -o--o--o--X--- o X 0 # | | | # needs an extra ancillary: # | | | # ... X--o--X -> |0> # for i in range(4): ctl = [] for j in range(4 - 1, i, -1): ctl.append(j) qc.multi_control(ctl, i, aux, ops.PauliX(), 'multi-X') qc.multi_control([0, [1], [2], 3], aux[4], aux, ops.PauliX(), 'multi-X') qc.cx(aux[4], 0) qc.cx(aux[4], 3) qc.multi_control([[0], [1], [2], [3]], aux[4], aux, ops.PauliX(), 'multi-X')
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 run_single_qubit_mult(): """Run experiments with single qubits.""" # Construct Hamiltonian. hamil = (random.random() * ops.PauliX() + random.random() * ops.PauliY() + random.random() * ops.PauliZ()) # Compute known minimum eigenvalue. eigvals = np.linalg.eigvalsh(hamil) # Brute force over the Bloch sphere. min_val = 1000.0 for i in range(0, 180, 10): for j in range(0, 180, 10): theta = np.pi * i / 180.0 phi = np.pi * j / 180.0 # Build the ansatz with two rotation gates. ansatz = single_qubit_ansatz(theta, phi) # Compute <psi ! H ! psi>. Find smallest one, which will be # the best approximation to the minimum eigenvalue from above. # In this version, we just multiply out the result. psi = np.dot(ansatz.psi.adjoint(), hamil(ansatz.psi)) if psi < min_val: min_val = psi # Result from brute force approach: print('Minimum: {:.4f}, Estimated: {:.4f}, Delta: {:.4f}'.format( eigvals[0], np.real(min_val), np.real(min_val - eigvals[0])))
def test_multi1(self): c = circuit.qc('multi', eager=False) comp = c.reg(6) aux = c.reg(6) ctl = [0, 1, 2, 3, 4] c.multi_control(ctl, 5, aux, ops.PauliX(), f'multi-x({ctl}, 5)') self.assertEqual(41, c.ir.ngates)
def test_multi0(self): c = circuit.qc('multi', eager=True) comp = c.reg(4, (1, 0, 0, 1)) aux = c.reg(4) ctl = [0, [1], [2]] c.multi_control(ctl, 3, aux, ops.PauliX(), f'multi-x({ctl}, 5)') self.assertGreater(c.psi.prob(1, 0, 0, 0, 0, 0, 0, 0), 0.99)
def test_xyx(self): """Exercise 4.7 in Nielson, Chuang, XYX == -Y.""" x = ops.PauliX() y = ops.PauliY() op = x(y(x)) self.assertTrue(op.is_close(-1.0 * ops.PauliY()))
def test_cnot0(self): """Check implementation of ControlledU via Cnot0.""" # Check operator itself. x = ops.PauliX() * ops.Identity() self.assertTrue(ops.Cnot0(0, 1). is_close(x @ ops.Cnot(0, 1) @ x)) # Compute simplest case with Cnot0. psi = state.bitstring(1, 0) psi2 = ops.Cnot0(0, 1)(psi) self.assertTrue(psi.is_close(psi2)) # Compute via explicit constrution. psi2 = (x @ ops.Cnot(0, 1) @ x)(psi) self.assertTrue(psi.is_close(psi2)) # Different offsets. psi2 = ops.Cnot0(0, 1)(state.bitstring(0, 1)) self.assertTrue(psi2.is_close(state.bitstring(0, 0))) psi2 = ops.Cnot0(0, 3)(state.bitstring(0, 0, 0, 0, 1)) self.assertTrue(psi2.is_close(state.bitstring(0, 0, 0, 1, 1))) psi2 = ops.Cnot0(4, 0)(state.bitstring(1, 0, 0, 0, 0)) self.assertTrue(psi2.is_close(state.bitstring(0, 0, 0, 0, 0)))
def alice_manipulates(psi: state.State, bit0: int, bit1: int) -> state.State: """Alice encodes 2 classical bits in her 1 qubit.""" ret = ops.Identity(2)(psi) if bit0: ret = ops.PauliX()(ret) if bit1: ret = ops.PauliZ()(ret) return ret
def alice_manipulates(psi, bit0, bit1): """Alice encodes 2 classical bits in her 1 qubit.""" ret = ops.Identity(2)(psi) if bit0: ret = ops.PauliZ()(ret) if bit1: ret = ops.PauliX()(ret) return ret
def test_bell_and_pauli(self): b00 = bell.bell_state(0, 0) bell_xz = ops.PauliX()(b00) bell_xz = ops.PauliZ()(bell_xz) bell_iy = (1j * ops.PauliY())(b00) self.assertTrue(np.allclose(bell_xz, bell_iy))
def single_qubit(): """Compute Pauli representation of single qubit.""" for i in range(10): # First we construct a circuit with just one, very random qubit. # qc = circuit.qc('random qubit') qc.qubit(random.random()) qc.rx(0, math.pi * random.random()) qc.ry(0, math.pi * random.random()) qc.rz(0, math.pi * random.random()) # Every qubit (rho) can be put in the Pauli Representation, # which is this Sum over i from 0 to 3 inclusive, representing # the four Pauli matrices (including the Identity): # # 3 # rho = 1/2 * Sum(c_i * Pauli_i) # i=0 # # To compute the various factors c_i, we multiply the Pauli # matrices with the density matrix and take the trace. This # trace is the computed factor: # rho = qc.psi.density() i = np.trace(ops.Identity() @ rho) x = np.trace(ops.PauliX() @ rho) y = np.trace(ops.PauliY() @ rho) z = np.trace(ops.PauliZ() @ rho) # Let's verify the result and construct a density matrix # from the Pauli matrices using the computed factors: # new_rho = 0.5 * (i * ops.Identity() + x * ops.PauliX() + y * ops.PauliY() + z * ops.PauliZ()) if not np.allclose(rho, new_rho): raise AssertionError('Invalid Pauli Representation') print(f'qubit({qc.psi[0]:11.2f}, {qc.psi[1]:11.2f}) = ', end='') print(f'{i:11.2f} I + {x:11.2f} X + {y:11.2f} Y + {z:11.2f} Z')
def alice_measures(alice: state.State, expect0: np.complexfloating, expect1: np.complexfloating, qubit0: np.complexfloating, qubit1: np.complexfloating): """Force measurement and get teleported qubit.""" # Alices measure her state and get a collapsed |qubit0 qubit1>. # She let's Bob know which one of the 4 combinations she obtained. # We force measurement here, collapsing to a state with the # first two qubits collapsed. Bob's qubit is still unmeasured. _, alice0 = ops.Measure(alice, 0, tostate=qubit0) _, alice1 = ops.Measure(alice0, 1, tostate=qubit1) # Depending on what was measured and communicated, Bob has to # one of these things to his qubit2: if qubit0 == 0 and qubit1 == 0: pass if qubit0 == 0 and qubit1 == 1: alice1 = ops.PauliX()(alice1, idx=2) if qubit0 == 1 and qubit1 == 0: alice1 = ops.PauliZ()(alice1, idx=2) if qubit0 == 1 and qubit1 == 1: alice1 = ops.PauliX()(ops.PauliZ()(alice1, idx=2), idx=2) # Now Bob measures his qubit (2) (without collapse, so we can # 'measure' it twice. This is not necessary, but good to double check # the maths). p0, _ = ops.Measure(alice1, 2, tostate=0, collapse=False) p1, _ = ops.Measure(alice1, 2, tostate=1, collapse=False) # Alice should now have 'teleported' the qubit in state 'x'. # We sqrt() the probability, we want to show (original) amplitudes. bob_a = math.sqrt(p0.real) bob_b = math.sqrt(p1.real) print('Teleported (|{:d}{:d}>) a={:.2f}, b={:.2f}'.format( qubit0, qubit1, bob_a, bob_b)) if (not math.isclose(expect0, bob_a, abs_tol=1e-6) or not math.isclose(expect1, bob_b, abs_tol=1e-6)): raise AssertionError('Invalid result.')
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 test_multi_n(self): c = circuit.qc('multi', eager=True) c.reg(4, (1, 0, 0, 1)) aux = c.reg(4) ctl = [] c.multi_control(ctl, 3, aux, ops.PauliX(), 'single') self.assertGreater(c.psi.prob(1, 0, 0, 0, 0, 0, 0, 0), 0.99) ctl = [0] c.multi_control(ctl, 3, aux, ops.PauliX(), 'single') self.assertGreater(c.psi.prob(1, 0, 0, 1, 0, 0, 0, 0), 0.99) ctl = [1] c.multi_control(ctl, 3, aux, ops.PauliX(), 'single') self.assertGreater(c.psi.prob(1, 0, 0, 1, 0, 0, 0, 0), 0.99) ctl = [[1]] c.multi_control(ctl, 3, aux, ops.PauliX(), 'single') self.assertGreater(c.psi.prob(1, 0, 0, 0, 0, 0, 0, 0), 0.99) ctl = [0, [1], [2]] c.multi_control(ctl, 3, aux, ops.PauliX(), 'single') self.assertGreater(c.psi.prob(1, 0, 0, 1, 0, 0, 0, 0), 0.99)
def incr(qc, idx, nbits, aux, controller=[]): """Increment-by-1 circuit.""" # See "Efficient Quantum Circuit Implementation of # Quantum Walks" by Douglas, Wang. # # -X-- # -o--X-- # -o--o--X-- # -o--o--o--X-- # ... for i in range(0, nbits): ctl=controller.copy() for j in range(nbits-1, i, -1): ctl.append(j+idx) qc.multi_control(ctl, i+idx, aux, ops.PauliX(), "multi-1-X")
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 decr(qc, idx, nbits, aux, controller=[]): """Decrement-by-1 circuit.""" # See "Efficient Quantum Circuit Implementation of # Quantum Walks" by Douglas, Wang. # # Similar to incr, except controlled-by-0's are being used. # # -X-- # -0--X-- # -0--0--X-- # -0--0--0--X-- # ... for i in range(0, nbits): ctl=controller.copy() for j in range(nbits-1, i, -1): ctl.append([j+idx]) qc.multi_control(ctl, i+idx, aux, ops.PauliX(), "multi-0-X")
def single_gate_complexity(): """Compare times for full matmul vs single-gate.""" nbits = 12 qubit = random.randint(0, nbits - 1) gate = ops.PauliX() def with_matmul(): psi = state.zeros(nbits) op = ops.Identity(qubit) * gate * ops.Identity(nbits - qubit - 1) psi = op(psi) def apply_single(): psi = state.zeros(nbits) psi = apply_single_gate(gate, qubit, psi) print('Time with full matmul: {:.3f} secs'.format( timeit.timeit(with_matmul, number=1))) print('Time with single gate: {:.3f} secs'.format( timeit.timeit(apply_single, number=1)))
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_single_qubit_measure(): """Run measurement experiments with single qubits.""" # Construct Hamiltonian. a = random.random() b = random.random() c = random.random() hamil = (a * ops.PauliX() + b * ops.PauliY() + c * ops.PauliZ()) # Compute known minimum eigenvalue. eigvals = np.linalg.eigvalsh(hamil) min_val = 1000.0 for i in range(0, 360, 5): for j in range(0, 180, 5): theta = np.pi * i / 360.0 phi = np.pi * j / 180.0 # X Basis qc = single_qubit_ansatz(theta, phi) qc.h(0) val_a = a * qc.pauli_expectation(0) # Y Basis qc = single_qubit_ansatz(theta, phi) qc.sdag(0) qc.h(0) val_b = b * qc.pauli_expectation(0) # Z Basis qc = single_qubit_ansatz(theta, phi) val_c = c * qc.pauli_expectation(0) expectation = val_a + val_b + val_c if expectation < min_val: min_val = expectation print('Minimum eigenvalue: {:.3f}, Delta: {:.3f}'.format( eigvals[0], min_val - eigvals[0]))
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 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 test_v_gate(self): """V^2 == X.""" s = ops.Vgate() @ ops.Vgate() self.assertTrue(s.is_close(ops.PauliX()))
def test_v_gate(self): """Test that V^2 == X.""" t = ops.Vgate() self.assertTrue(t(t).is_close(ops.PauliX()))
def test_unitary(self): self.assertTrue(ops.PauliX().is_unitary()) self.assertTrue(ops.PauliY().is_unitary()) self.assertTrue(ops.PauliZ().is_unitary()) self.assertTrue(ops.Identity().is_unitary())
def decr(qc, idx, nbits, aux, controller=[]): for i in range(0, nbits): ctl = controller.copy() for j in range(nbits - 1, i, -1): ctl.append([j + idx]) qc.multi_control(ctl, i + idx, aux, ops.PauliX(), "multi-0-X")
def cx(self, idx0: int, idx1: int): self.applyc(ops.PauliX(), idx0, idx1, 'cx')
def x(self, idx: int): self.apply1(ops.PauliX(), idx, 'x')