def test_controlled_z(self): """Exercise 4.18 in Nielson, Chuang, CZ(0, 1) == CZ(1, 0).""" z0 = ops.ControlledU(0, 1, ops.PauliZ()) z1 = ops.ControlledU(1, 0, ops.PauliZ()) self.assertTrue(z0.is_close(z1)) z0 = ops.ControlledU(0, 7, ops.PauliZ()) z1 = ops.ControlledU(7, 0, ops.PauliZ()) self.assertTrue(z0.is_close(z1))
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 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 graph_to_hamiltonian(n: int, nodes: List[int]) -> ops.Operator: """Compute Hamiltonian matrix from graph.""" H = np.zeros((2**n, 2**n)) for node in nodes: idx1 = node[0] idx2 = node[1] if idx1 > idx2: idx1, idx2 = idx2, idx1 op = 1.0 for _ in range(idx1): op = op * ops.Identity() op = op * (node[2] * ops.PauliZ()) for _ in range(idx1 + 1, idx2): op = op * ops.Identity() op = op * (node[2] * ops.PauliZ()) for _ in range(idx2 + 1, n): op = op * ops.Identity() H = H + op return ops.Operator(H)
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_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_rk(self): rk0 = ops.Rk(0) self.assertTrue(rk0.is_close(ops.Identity())) rk1 = ops.Rk(1) self.assertTrue(rk1.is_close(ops.PauliZ())) rk2 = ops.Rk(2) self.assertTrue(rk2.is_close(ops.Sgate())) rk3 = ops.Rk(3) self.assertTrue(rk3.is_close(ops.Tgate())) for idx in range(8): psi = state.zeros(2) psi = (ops.Rk(idx)**2 @ ops.Rk(-idx)**2)(psi) self.assertTrue(psi.is_close(state.zeros(2)))
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 test_s_gate(self): """S^2 == Z.""" x = ops.Sgate() @ ops.Sgate() self.assertTrue(x.is_close(ops.PauliZ()))
def cz(self, idx0, idx1): self.apply_controlled(ops.PauliZ(), idx0, idx1, 'cz')
def z(self, idx: int): self.apply1(ops.PauliZ(), idx, 'z')
def cz(self, idx0: int, idx1: int): self.applyc(ops.PauliZ(), idx0, idx1, 'cz')
def two_qubit(): """Compute Pauli representation for two-qubit system.""" for iteration in range(10): # First we construct a circuit with two, very random qubits. # qc = circuit.qc('random qubit') qc.qubit(random.random()) qc.qubit(random.random()) # Potentially entangle them. qc.h(0) qc.cx(0, 1) # Additionally rotate around randomly. for i in range(2): qc.rx(i, math.pi * random.random()) qc.ry(i, math.pi * random.random()) qc.rz(i, math.pi * random.random()) # Compute density matrix. rho = qc.psi.density() # Every rho can be put in the 2-qubit Pauli representation, # which is this Sum over i, j from 0 to 3 inclusive, representing # the four Pauli matrices (including the Identity): # # 3 # rho = 1/4 * Sum(c_ij * Pauli_i kron Pauli_j) # i,j=0 # # To compute the various factors c_ij, we multiply the Pauli # tensor products with the density matrix and take the trace. This # trace is the computed factor: # paulis = [ops.Identity(), ops.PauliX(), ops.PauliY(), ops.PauliZ()] c = np.zeros((4, 4), dtype=np.complex64) for i in range(4): for j in range(4): tprod = paulis[i] * paulis[j] c[i][j] = np.trace(rho @ tprod) # To test whether the two qubits are entangled, the diagonal factors # (without c[0][0]) are added up. If the sum is < 1.0, the qubit # states are still seperable. # diag = np.abs(c[1][1]) + np.abs(c[2][2]) + np.abs(c[3][3]) print(f'{iteration}: diag: {diag:5.2f} ', end='') if diag > 1.0: print('--> Entangled') else: print('Seperable') # Let's verify the result and construct a density matrix # from the Pauli matrices using the computed factors: # new_rho = np.zeros((4, 4), dtype=np.complex64) for i in range(4): for j in range(4): tprod = paulis[i] * paulis[j] new_rho = new_rho + c[i][j] * tprod if not np.allclose(rho, new_rho / 4, atol=1e-5): raise AssertionError('Invalid Pauli Representation')
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())