def test_calc_env_matrix(self): u1 = unitary_group.rvs(8) u2 = u1.conj().T ct = CircuitTensor(u1, []) env = ct.calc_env_matrix([0, 1, 2]) self.assertTrue(np.allclose(env, u2))
def __init__(self, gate_size, num_qubits, locations, native): self.gate_size = gate_size self.num_qubits = num_qubits self.locations = [ tuple([int(x) for x in location]) for location in locations ] self.native = native self.target = CircuitTensor(np.identity(2**self.num_qubits), self.get_qfactor()).utry self.data = {}
def test_calc_env_matrix_invalid(self): u1 = unitary_group.rvs(8) u2 = u1.conj().T ct = CircuitTensor(u1, []) self.assertRaises(ValueError, ct.calc_env_matrix, [0, 1, 2, 3]) self.assertRaises(TypeError, ct.calc_env_matrix, "a")
def get_distance(circuit, target): """ Returns the distance between the circuit and the unitary target. Args: circuit (list[Gate]): The circuit. target (np.ndarray): The unitary target. Returns: (float): The distance between the circuit and unitary target. """ ct = CircuitTensor(target, circuit) num_qubits = utils.get_num_qubits(target) return 1 - (np.abs(np.trace(ct.utry)) / (2**num_qubits))
def test_apply_right(self): u1 = unitary_group.rvs(8) u2 = unitary_group.rvs(4) g = Gate(u2, (0, 1)) ct = CircuitTensor(u1, []) ct.apply_right(g) prod = np.kron(u2, np.identity(2)) @ u1.conj().T prod_test = ct.utry self.assertTrue(np.allclose(prod, prod_test)) ct.apply_right(g) prod = np.kron(u2, np.identity(2)) @ prod prod_test = ct.utry self.assertTrue(np.allclose(prod, prod_test))
class CircuitDataPoint(): def __init__(self, gate_size, num_qubits, locations, native): self.gate_size = gate_size self.num_qubits = num_qubits self.locations = [ tuple([int(x) for x in location]) for location in locations ] self.native = native self.target = CircuitTensor(np.identity(2**self.num_qubits), self.get_qfactor()).utry self.data = {} @staticmethod def generate_circuit(gate_size, num_qubits, length): native = gate_size <= 1 gate_size = 2 if native else gate_size locations = list(it.combinations(range(num_qubits), 2)) locations = np.array(locations) idxs = np.random.choice(len(locations), length, replace=True) locations = locations[idxs] return CircuitDataPoint(gate_size, num_qubits, locations, native) def get_qfactor(self): circuit = [] if self.native: for pair in self.locations: circuit.append(CnotGate(pair[0], pair[1])) circuit.append(Gate(unitary_group.rvs(2), (pair[0], ))) circuit.append(Gate(unitary_group.rvs(2), (pair[1], ))) return circuit for location in self.locations: circuit.append(Gate(unitary_group.rvs(2**self.gate_size), location)) return circuit def get_qsearch(self): if not self.native: return None steps = [] u30 = qsearch.gates.U3Gate() u31 = qsearch.gates.U3Gate() for pair in self.locations: min_idx = min(pair) max_idx = max(pair) cnot = qsearch.gates.NonadjacentCNOTGate(max_idx - min_idx + 1, pair[0] - min_idx, pair[1] - min_idx) if max_idx - min_idx == 1: u_layer = qsearch.gates.KroneckerGate(u30, u31) else: mid_layer = qsearch.gates.IdentityGate(max_idx - min_idx - 1) u_layer = qsearch.gates.KroneckerGate(u30, mid_layer, u31) p_layer = qsearch.gates.ProductGate(cnot, u_layer) up = qsearch.gates.IdentityGate(min_idx) down = qsearch.gates.IdentityGate(self.num_qubits - max_idx - 1) steps.append(qsearch.gates.KroneckerGate(up, p_layer, down)) return qsearch.gates.ProductGate(*steps) def get_qfast(self): return FixedModel(self.target, self.gate_size, [], LBFGSOptimizer(), success_threshold=1e-8, structure=self.locations) def count_qfactor_tries(self): dist = 1 tries = 0 self.data["qfactor_retry_times"] = [] while dist > 1e-8: start = timer() tries += 1 res = optimize(self.get_qfactor(), self.target, min_iters=0) dist = qfactor.get_distance(res, self.target) end = timer() self.data["qfactor_retry_times"].append(end - start) self.data["qfactor_retries"] = tries def count_qfast_tries(self): dist = 1 tries = 0 self.data["qfast_retry_times"] = [] while dist > 1e-8: start = timer() tries += 1 model = self.get_qfast() model.optimize(fine=True) dist = model.distance() end = timer() self.data["qfast_retry_times"].append(end - start) self.data["qfast_retries"] = tries def count_qsearch_tries(self): solver = qsearch.solvers.LeastSquares_Jac_SolverNative() options = qsearch.options.Options() options.target = self.target tries = 0 dist = 1 circ = self.get_qsearch() self.data["qsearch_retry_times"] = [] while dist > 1e-8: start = timer() tries += 1 U, xopts = solver.solve_for_unitary(circ, options) dist = 1 - (np.abs(np.trace(self.target.conj().T @ U)) / U.shape[0]) end = timer() self.data["qsearch_retry_times"].append(end - start) self.data["qsearch_retries"] = tries
def optimize(circuit, target, diff_tol_a=1e-12, diff_tol_r=1e-6, dist_tol=1e-10, max_iters=100000, min_iters=1000, slowdown_factor=0.0): """ Optimize distance between circuit and target unitary. Args: circuit (list[Gate]): The circuit to optimize. target (np.ndarray): The target unitary matrix. diff_tol_a (float): Terminate when the difference in distance between iterations is less than this threshold. diff_tol_r (float): Terminate when the relative difference in distance between iterations is iless than this threshold: |c1 - c2| <= diff_tol_a + diff_tol_r * abs( c1 ) dist_tol (float): Terminate when the distance is less than this threshold. max_iters (int): Maximum number of iterations. min_iters (int): Minimum number of iterations. slowdown_factor (float): A positive number less than 1. The larger this factor, the slower the optimization. Returns: (list[Gate]): The optimized circuit. """ if not isinstance(circuit, list): raise TypeError("The circuit argument is not a list of gates.") if not all([isinstance(g, Gate) for g in circuit]): raise TypeError("The circuit argument is not a list of gates.") if not utils.is_unitary(target): raise TypeError("The target matrix is not unitary.") if not isinstance(diff_tol_a, float) or diff_tol_a > 0.5: raise TypeError("Invalid absolute difference threshold.") if not isinstance(diff_tol_r, float) or diff_tol_r > 0.5: raise TypeError("Invalid relative difference threshold.") if not isinstance(dist_tol, float) or dist_tol > 0.5: raise TypeError("Invalid distance threshold.") if not isinstance(max_iters, int) or max_iters < 0: raise TypeError("Invalid maximum number of iterations.") if not isinstance(min_iters, int) or min_iters < 0: raise TypeError("Invalid minimum number of iterations.") if slowdown_factor < 0 or slowdown_factor >= 1: raise TypeError("Slowdown factor is a positive number less than 1.") ct = CircuitTensor(target, circuit) c1 = 0 c2 = 1 it = 0 while True: # Termination conditions if it > min_iters: if np.abs(c1 - c2) <= diff_tol_a + diff_tol_r * np.abs(c1): diff = np.abs(c1 - c2) logger.info(f"Terminated: |c1 - c2| = {diff}" " <= diff_tol_a + diff_tol_r * |c1|.") break if it > max_iters: logger.info("Terminated: iteration limit reached.") break it += 1 # from right to left for k in range(len(circuit)): rk = len(circuit) - 1 - k # Remove current gate from right of circuit tensor ct.apply_right(circuit[rk], inverse=True) # Update current gate if not circuit[rk].fixed: env = ct.calc_env_matrix(circuit[rk].location) circuit[rk].update(env, slowdown_factor) # Add updated gate to left of circuit tensor ct.apply_left(circuit[rk]) # from left to right for k in range(len(circuit)): # Remove current gate from left of circuit tensor ct.apply_left(circuit[k], inverse=True) # Update current gate if not circuit[k].fixed: env = ct.calc_env_matrix(circuit[k].location) circuit[k].update(env, slowdown_factor) # Add updated gate to right of circuit tensor ct.apply_right(circuit[k]) c2 = c1 c1 = np.abs(np.trace(ct.utry)) c1 = 1 - (c1 / (2**ct.num_qubits)) if c1 <= dist_tol: logger.info(f"Terminated: c1 = {c1} <= dist_tol.") return circuit if it % 100 == 0: logger.info(f"iteration: {it}, cost: {c1}") if it % 40 == 0: ct.reinitialize() return circuit
def test_apply_right_invalid(self): u1 = unitary_group.rvs(8) ct = CircuitTensor(u1, []) self.assertRaises(Exception, ct.apply_right, "a")
def test_gate_constructor_valid(self): gate = Gate(self.TOFFOLI, (0, 1, 2)) ct = CircuitTensor(self.TOFFOLI, [gate]) self.assertTrue(np.array_equal(gate.utry, ct.gate_list[0].utry)) self.assertTrue(len(ct.gate_list) == 1) self.assertTrue(np.allclose(ct.utry, np.identity(8)))
def test_toffoli_tensor ( self ): toffoli = np.array( [ [ 1, 0, 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 1, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 0, 0, 1, 0 ] ] ) p12 = calc_permutation_matrix( 3, (1, 2) ) p02 = calc_permutation_matrix( 3, (0, 2) ) cnot = np.array( [ [ 1, 0, 0, 0 ], [ 0, 1, 0, 0 ], [ 0, 0, 0, 1 ], [ 0, 0, 1, 0 ] ] ) H = (np.sqrt(2)/2) * np.array( [ [ 1, 1 ], [ 1, -1 ] ] ) T = np.array( [ [ 1, 0 ], [ 0, np.exp( 1j * np.pi/4 ) ] ] ) I = np.identity( 2 ) u1 = np.kron( I, T.conj().T ) @ cnot @ np.kron( I, H ) u2 = np.kron( I, T ) @ cnot u3 = np.kron( I, T.conj().T ) @ cnot u4 = np.kron( I, H @ T ) @ cnot u5 = cnot @ np.kron( T, T.conj().T ) @ cnot @ np.kron( I, T ) circuit = [ Gate( u1, (1, 2) ), Gate( u2, (0, 2) ), Gate( u3, (1, 2) ), Gate( u4, (0, 2) ), Gate( u5, (0, 1) ) ] c1 = p12 @ np.kron( u1, I ) @ p12.T c2 = p02 @ np.kron( u2, I ) @ p02.T c3 = p12 @ np.kron( u3, I ) @ p12.T c4 = p02 @ np.kron( u4, I ) @ p02.T c5 = np.kron( u5, I ) self.assertTrue( np.allclose( toffoli, c5 @ c4 @ c3 @ c2 @ c1 ) ) ct = CircuitTensor( toffoli, [] ) self.assertTrue( np.allclose( ct.utry, toffoli.conj().T ) ) ct.apply_right( circuit[0] ) self.assertTrue( np.allclose( ct.utry, c1 @ toffoli.conj().T ) ) ct.apply_right( circuit[1] ) self.assertTrue( np.allclose( ct.utry, c2 @ c1 @ toffoli.conj().T ) ) ct.apply_right( circuit[2] ) self.assertTrue( np.allclose( ct.utry, c3 @ c2 @ c1 @ toffoli.conj().T ) ) ct.apply_right( circuit[3] ) self.assertTrue( np.allclose( ct.utry, c4 @ c3 @ c2 @ c1 @ toffoli.conj().T ) ) ct.apply_right( circuit[4] ) self.assertTrue( np.allclose( ct.utry, c5 @ c4 @ c3 @ c2 @ c1 @ toffoli.conj().T ) ) self.assertTrue( np.allclose( ct.utry, np.identity( 8 ) ) ) ct = CircuitTensor( toffoli, circuit ) self.assertTrue( np.allclose( ct.utry, np.identity( 8 ) ) )