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 gc_decomp(U): """Group Commutator Decomposition.""" def diagonalize(U): _, V = np.linalg.eig(U) return ops.Operator(V) # Because of moderate numerical instability, it can happen # that the trace is just a tad over 2.000000. If this happens, # we tolerate it and set the trace to exactly 2.000000. tr = np.trace(U) if tr > 2.0: tr = 2.0 # We know how to compute theta from u_to_bloch(). theta = 2.0 * np.arccos(np.real(tr / 2)) # The angle phi comes from eq 10 in 'The Solovay-Kitaev Algorithm' by # Dawson, Nielsen. phi = 2.0 * np.arcsin(np.sqrt(np.sqrt((0.5 - 0.5 * np.cos(theta / 2))))) axis, _ = u_to_bloch(U) V = ops.RotationX(phi) if axis[2] < 0: W = ops.RotationY(2 * np.pi - phi) else: W = ops.RotationY(phi) V1 = diagonalize(U) V2 = diagonalize(V @ W @ V.adjoint() @ W.adjoint()) S = V1 @ V2.adjoint() V_tilde = S @ V @ S.adjoint() W_tilde = S @ W @ S.adjoint() return V_tilde, W_tilde
def gc_decomp(U): """Group commutator decomposition.""" def diagonalize(U): _, V = np.linalg.eig(U) return ops.Operator(V) # Get axis and theta for the operator. axis, theta = u_to_bloch(U) # The angle phi comes from eq 10 in 'The Solovay-Kitaev Algorithm' by # Dawson, Nielsen. It is fully derived in the book section on the # theorem and algorithm. phi = 2.0 * np.arcsin(np.sqrt( np.sqrt((0.5 - 0.5 * np.cos(theta / 2))))) V = ops.RotationX(phi) if axis[2] > 0: W = ops.RotationY(2 * np.pi - phi) else: W = ops.RotationY(phi) Ud = diagonalize(U) VWVdWdd = diagonalize(V @ W @ V.adjoint() @ W.adjoint()) S = Ud @ VWVdWdd.adjoint() V_hat = S @ V @ S.adjoint() W_hat = S @ W @ S.adjoint() return V_hat, W_hat
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 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_global_phase(self): """Exercise 4.14 in Nielson, Chuang, HTH == phase*rotX(pi/4).""" h = ops.Hadamard() op = h(ops.Tgate()(h)) # If equal up to a global phase, all values should be equal. phase = op / ops.RotationX(math.pi / 4) self.assertTrue( math.isclose(phase[0, 0].real, phase[0, 1].real, abs_tol=1e-6)) self.assertTrue( math.isclose(phase[0, 0].imag, phase[0, 1].imag, abs_tol=1e-6)) self.assertTrue( math.isclose(phase[0, 0].real, phase[1, 0].real, abs_tol=1e-6)) self.assertTrue( math.isclose(phase[0, 0].imag, phase[1, 0].imag, abs_tol=1e-6)) self.assertTrue( math.isclose(phase[0, 0].real, phase[1, 1].real, abs_tol=1e-6)) self.assertTrue( math.isclose(phase[0, 0].imag, phase[1, 1].imag, abs_tol=1e-6))
def test_control_equalities(self): """Exercise 4.31 Nielson, Chung.""" i, x, y, z = ops.Pauli() x1 = x * i x2 = i * x y1 = y * i y2 = i * y z1 = z * i z2 = i * z c = ops.Cnot(0, 1) theta = 25.0 * math.pi / 180.0 rx2 = i * ops.RotationX(theta) rz1 = ops.RotationZ(theta) * i self.assertTrue(c(x1(c)).is_close(x1(x2))) self.assertTrue((c @ x1 @ c).is_close(x1 @ x2)) self.assertTrue((c @ y1 @ c).is_close(y1 @ x2)) self.assertTrue((c @ z1 @ c).is_close(z1)) self.assertTrue((c @ x2 @ c).is_close(x2)) self.assertTrue((c @ y2 @ c).is_close(z1 @ y2)) self.assertTrue((c @ z2 @ c).is_close(z1 @ z2)) self.assertTrue((rz1 @ c).is_close(c @ rz1)) self.assertTrue((rx2 @ c).is_close(c @ rx2))
def rx(self, idx: int, theta: float): self.apply1(ops.RotationX(theta), idx, 'rx', val=theta)
def test_double_rot(self): """Make sure rotations add up.""" rx = ops.RotationX(45.0 / 180.0 * math.pi) self.assertTrue( (rx @ rx).is_close(ops.RotationX(90.0 / 180.0 * math.pi)))
def rx(self, idx, theta): self.apply1(ops.RotationX(theta), idx, 'rx', val=theta)