def find_gap(graph, use_Z2_symmetry=True): # Compute the number of ground states and first excited states cost = HamiltonianMaxCut(graph, cost_function=False, use_Z2_symmetry=use_Z2_symmetry) # Generate a dummy graph with one fewer nodes # Cut cost and driver hamiltonian in half to account for Z2 symmetry if use_Z2_symmetry: driver = HamiltonianDriver(graph=Graph(nx.complete_graph(graph.n - 1))) driver.hamiltonian row = list(range(2 ** (graph.n - 1))) column = list(range(2 ** (graph.n - 1))) column.reverse() driver._hamiltonian = driver._hamiltonian + (-1) ** graph.n * sparse.csr_matrix( (np.ones(2 ** (graph.n - 1)), (row, column))) else: driver = HamiltonianDriver(graph=Graph(nx.complete_graph(graph.n))) n_ground = len(ground_states(cost.hamiltonian)) times = np.arange(0, 1, .01) def schedule(t): cost.energies = (t,) driver.energies = (t - 1,) min_gap = np.inf for i in range(len(times)): schedule(times[i]) eigvals = SchrodingerEquation(hamiltonians=[cost, driver]).eig(k=n_ground + 1, return_eigenvectors=False) eigvals = np.flip(eigvals) if eigvals[-1] - eigvals[0] < min_gap: min_gap = eigvals[-1] - eigvals[0] return min_gap, n_ground
def node_removed_torus(y, x, return_mis=False): graph = nx.grid_2d_graph(x, y, periodic=True) # Remove 3 of four corners graph.remove_node((0, 0)) graph.add_edge((1, 0), (y - 1, 0), weight=1) graph.add_edge((1, 0), (1, x - 1), weight=1) graph.add_edge((0, 1), (0, x - 1), weight=1) graph.add_edge((0, 1), (y - 1, 1), weight=1) nodes = graph.nodes new_nodes = list(range(len(nodes))) mapping = dict(zip(nodes, new_nodes)) nx.relabel_nodes(graph, mapping, copy=False) if return_mis: return Graph(graph), x * y / 2 - 1 else: return Graph(graph)
def generate_bipartite_graph(n, m, p, visualize=False): graph = nx.algorithms.bipartite.generators.random_graph(n, m, p) print(graph.edges) if visualize: nx.draw(graph, pos=nx.bipartite_layout(graph, list(graph.nodes)[:n])) plt.show() return Graph(graph)
def node_defect_torus(y, x, return_mis=False): graph = nx.grid_2d_graph(x, y, periodic=True) # Remove 3 of four corners for node in graph.nodes: graph.nodes[node]['weight'] = 1 graph.nodes[(0, 0)]['weight'] = 2 #print(graph.nodes) # graph.remove_node((0, 0)) nodes = graph.nodes new_nodes = list(range(len(nodes))) mapping = dict(zip(nodes, new_nodes)) nx.relabel_nodes(graph, mapping, copy=False) if return_mis: return Graph(graph), x * y / 2 - 1 else: return Graph(graph)
def generate_regular_graph(d, n, visualize=False): graph = nx.random_regular_graph(d, n) print(graph.edges) if visualize: nx.draw(graph) plt.show() return Graph(graph)
def effective_markov_chain(graph=None, n=3, initial_state=None, omega_g=1, omega_r=1, gamma=1, tf=1): def schedule(t, tf): return [4 * (tf - t) / tf * omega_r ** 2 / gamma, 4 * t / tf * omega_g ** 2 / gamma] # Generate a graph if graph is None: graph, mis = line(n) graph = Graph(graph) # Generate the transition matrix # For each IS, look at spin flips generated by the laser # Over-allocate space g_to_r = transition_matrix(graph, (1, 0)) r_to_g = transition_matrix(graph, (0, 1)) if initial_state is None: initial_state = np.zeros((g_to_r.shape[0], 1), dtype=np.float64) initial_state[-1, -1] = 1 t0 = 0 num = tf * 5 state = initial_state times = np.linspace(t0, tf, num=num) dt = times[1] - times[0] cost = np.zeros(len(times)) for i in range(len(times)): state_transitions = np.sum(dt * r_to_g * schedule(times[i], tf)[0] + dt * g_to_r * schedule(times[i], tf)[1], axis=0) state = np.multiply((1 - state_transitions.T), state) + ( dt * r_to_g * schedule(times[i], tf)[0] + dt * g_to_r * schedule(times[i], tf)[1]) @ state cost[i] = state[0] # Normalize s # print((np.sum(np.abs(s)))) # s = s/(np.sum(np.abs(s))) plt.plot(times, cost) plt.show() print(state)
def ARvstime_EIT(tf=10, graph=None, mis=None, show_graph=False, n=3): schedule = lambda t, tf: [[t / tf, (tf - t) / tf, 1], [1]] if graph is None: graph, mis = line_graph(n=n) graph = Graph(graph) if show_graph: nx.draw(graph) plt.show() rabi1 = 3 rabi2 = 3 # Generate the driving laser1 = hamiltonian.HamiltonianDriver(transition=(0, 1), energy=rabi1, code=rydberg, IS_subspace=True, graph=graph) laser2 = hamiltonian.HamiltonianDriver(transition=(1, 2), energy=rabi2, code=rydberg, IS_subspace=True, graph=graph) rydberg_hamiltonian_cost = hamiltonian.HamiltonianMIS(graph, code=rydberg, detuning=1, energy=0, IS_subspace=True) # Initialize spontaneous emission spontaneous_emission_rate = 1 spontaneous_emission = lindblad_operators.SpontaneousEmission( transition=(1, 2), rate=spontaneous_emission_rate, code=rydberg, IS_subspace=True, graph=graph) # Initialize master equation master_equation = LindbladMasterEquation( hamiltonians=[laser2, laser1], jump_operators=[spontaneous_emission]) # Begin with all qubits in the ground codes psi = np.zeros((rydberg_hamiltonian_cost.hamiltonian.shape[0], 1), dtype=np.complex128) psi[-1, -1] = 1 psi = tools.outer_product(psi, psi) # Generate annealing schedule results = master_equation.run_ode_solver( psi, 0, tf, num_from_time(tf), schedule=lambda t: schedule(t, tf)) cost_function = [ rydberg_hamiltonian_cost.cost_function(results[i], is_ket=False) / mis for i in range(results.shape[0]) ] print(cost_function[-1]) plt.scatter(np.linspace(0, tf, num_from_time(tf)), cost_function, c='teal', label='approximation ratio') plt.legend() plt.xlabel(r'Approximation ratio') plt.ylabel(r'Time $t$') plt.show()
def sk_integer(n, verbose=False): graph = nx.complete_graph(n) weights = [-1, 1] for (i, j) in itertools.combinations(range(n), 2): graph[i][j]['weight'] = weights[np.random.randint(2)] if verbose: print(graph.edges.data()) return Graph(graph)
def low_energy_subspace_at_fixed_time(graph, s, use_Z2_symmetry=True, n_ground=None, k=None, p=2): # Compute the number of ground states and first excited states if p == 2: cost = HamiltonianMaxCut(graph, cost_function=False, use_Z2_symmetry=use_Z2_symmetry) if p != 2: ham = graph if use_Z2_symmetry: graph = sk_integer(int(np.log2(graph.shape[0]))+1) else: graph = sk_integer(int(np.log2(graph.shape[0]))) cost = HamiltonianMaxCut(graph, cost_function=False, use_Z2_symmetry=use_Z2_symmetry) # Replace the cost Hamiltonian cost._diagonal_hamiltonian = ham if use_Z2_symmetry: cost._hamiltonian = sparse.csr_matrix((cost._diagonal_hamiltonian.flatten(), (np.arange(2 ** (graph.n-1)), np.arange(2 ** (graph.n-1)))),shape=(2 ** (graph.n-1), 2 ** (graph.n-1))) else: cost._hamiltonian = sparse.csr_matrix((cost._diagonal_hamiltonian.flatten(), (np.arange(2 ** (graph.n)), np.arange(2 ** (graph.n)))),shape=(2 ** (graph.n), 2 ** (graph.n))) # Generate a dummy graph with one fewer nodes # Cut cost and driver hamiltonian in half to account for Z2 symmetry if use_Z2_symmetry: driver = HamiltonianDriver(graph=Graph(nx.complete_graph(graph.n - 1))) driver.hamiltonian row = list(range(2 ** (graph.n - 1))) column = list(range(2 ** (graph.n - 1))) column.reverse() driver._hamiltonian = driver._hamiltonian + (-1) ** graph.n * sparse.csr_matrix( (np.ones(2 ** (graph.n - 1)), (row, column))) else: driver = HamiltonianDriver(graph=Graph(nx.complete_graph(graph.n))) if k is None: if n_ground is None: n_ground = len(ground_states(cost.hamiltonian)) k=n_ground+1 def schedule(t): cost.energies = (np.sqrt(np.math.factorial(p)/(2 * graph.n**(p-1))) * t,) driver.energies = (1 - t,) schedule(s) eigvals = SchrodingerEquation(hamiltonians=[cost, driver]).eig(k=k, return_eigenvectors=False) eigvals = np.flip(eigvals) return s, eigvals
def gap_over_time(graph, verbose=False, use_Z2_symmetry=True): # Compute the number of ground states and first excited states cost = HamiltonianMaxCut(graph, cost_function=False, use_Z2_symmetry=use_Z2_symmetry) print(np.min(cost.hamiltonian) / graph.n ** (3 / 2)) # Generate a dummy graph with one fewer nodes # Cut cost and driver hamiltonian in half to account for Z2 symmetry if use_Z2_symmetry: driver = HamiltonianDriver(graph=Graph(nx.complete_graph(graph.n - 1))) driver.hamiltonian row = list(range(2 ** (graph.n - 1))) column = list(range(2 ** (graph.n - 1))) column.reverse() driver._hamiltonian = driver._hamiltonian + (-1) ** graph.n * sparse.csr_matrix( (np.ones(2 ** (graph.n - 1)), (row, column))) else: driver = HamiltonianDriver(graph=Graph(nx.complete_graph(graph.n))) if verbose: print(ground_states(cost.hamiltonian)) n_ground = len(ground_states(cost.hamiltonian)) # print(driver.hamiltonian.todense()) # print(np.linalg.eigh(driver.hamiltonian.todense())) if verbose: print('degeneracy ', n_ground) times = np.arange(0, 1, .01) def schedule(t): cost.energies = (1 / np.sqrt(graph.n) * t,) driver.energies = (1 - t,) all_eigvals = np.zeros((len(times), n_ground + 1)) for i in range(len(times)): if verbose: print(times[i]) schedule(times[i]) eigvals = SchrodingerEquation(hamiltonians=[cost, driver]).eig(k=n_ground + 1, return_eigenvectors=False) eigvals = np.flip(eigvals) all_eigvals[i] = eigvals for i in range(n_ground + 1): plt.scatter(times, all_eigvals[:, i], color='blue', s=2) if verbose: print(all_eigvals) plt.xlabel(r'normalized time $s$') plt.ylabel(r'energy') plt.show()
def small_3regular_graph(visualize=False): edges = [(6, 9), (6, 4), (6, 0), (9, 3), (9, 0), (1, 3), (1, 5), (1, 7), (3, 8), (4, 7), (4, 2), (7, 0), (2, 8), (2, 5), (8, 5)] graph = nx.Graph() graph.add_nodes_from(list(range(10))) graph.add_edges_from(edges) if visualize: nx.draw(graph, pos=nx.bipartite_layout(graph, list(graph.nodes)[:5])) plt.show() return Graph(graph)
def small_bipartite_graph(visualize=False): edges = [(0, 5), (0, 6), (0, 7), (0, 8), (0, 9), (1, 5), (1, 6), (1, 7), (1, 8), (1, 9), (2, 5), (2, 6), (2, 7), (2, 8), (2, 9), (3, 5), (3, 7), (3, 8), (3, 9), (4, 5), (4, 6), (4, 7), (4, 9)] graph = nx.Graph() graph.add_nodes_from(list(range(10))) graph.add_edges_from(edges) if visualize: nx.draw(graph, pos=nx.bipartite_layout(graph, list(graph.nodes)[:5])) plt.show() return Graph(graph)
def medium_3regular_graph(visualize=False): edges = [(4, 7), (4, 10), (4, 15), (7, 15), (7, 2), (10, 11), (10, 12), (11, 9), (11, 5), (2, 5), (2, 3), (5, 14), (3, 14), (3, 6), (14, 15), (12, 0), (12, 8), (9, 1), (9, 13), (6, 8), (6, 13), (1, 13), (1, 0), (0, 8)] graph = nx.Graph() graph.add_nodes_from(list(range(10))) graph.add_edges_from(edges) if visualize: nx.draw(graph, pos=nx.bipartite_layout(graph, list(graph.nodes)[:5])) plt.show() return Graph(graph)
def medium_bipartite_graph(visualize=False): edges = [(0, 7), (0, 8), (0, 9), (0, 12), (0, 13), (1, 7), (1, 8), (1, 10), (1, 11), (1, 12), (1, 13), (2, 7), (2, 8), (2, 9), (2, 10), (2, 12), (2, 13), (3, 7), (3, 8), (3, 9), (3, 10), (4, 8), (4, 11), (4, 12), (4, 13), (5, 8), (5, 10), (5, 11), (6, 7), (6, 8), (6, 10), (6, 11), (6, 13)] graph = nx.Graph() graph.add_nodes_from(list(range(10))) graph.add_edges_from(edges) if visualize: nx.draw(graph, pos=nx.bipartite_layout(graph, list(graph.nodes)[:5])) plt.show() return Graph(graph)
def jump_rate_scaling(): graph = Graph(nx.star_graph(n=4)) # line_graph(n=3) energy = 1 phis = np.arange(.001, .01, .001) rates = np.zeros(len(phis)) rydberg_hamiltonian_cost = hamiltonian.HamiltonianMIS(graph, IS_subspace=True, code=qubit) print(rydberg_hamiltonian_cost.hamiltonian) for d in range(len(phis)): print(d) laser = EffectiveOperatorHamiltonian(omega_g=np.cos(phis[d]), omega_r=np.sin(phis[d]), energies=(energy, ), graph=graph) dissipation = EffectiveOperatorDissipation(omega_g=np.cos(phis[d]), omega_r=np.sin(phis[d]), rates=(energy, ), graph=graph) # Find the dressed states simulation = SchrodingerEquation(hamiltonians=[laser]) eigval, eigvec = simulation.eig() eigval = [ rydberg_hamiltonian_cost.approximation_ratio( State(eigvec[i].T, is_ket=True, graph=graph, IS_subspace=True)) for i in range(eigval.shape[0]) ] print(eigval) optimal_eigval_index = np.argmax(eigval) optimal_eigval = eigvec[optimal_eigval_index] rate = 0 for i in range(graph.n): # Compute the decay rate of the MIS into other states for j in range(eigvec.shape[0]): if j != optimal_eigval_index: rate += np.abs(eigvec[j] @ dissipation.jump_operators[i] @ optimal_eigval.T)**2 rates[d] = rate fit = np.polyfit(np.log(phis), np.log(rates), deg=1) plt.scatter(np.log(phis), np.log(rates), color='teal') plt.plot(np.log(phis), fit[0] * np.log(phis) + fit[1], color='k', label=r'$y=$' + str(np.round(fit[0], decimals=2)) + r'$x+$' + str(np.round(fit[1], decimals=2))) plt.legend() plt.xlabel(r'$\log(\phi)$') plt.ylabel(r'$\log(\rm{rate})$') plt.show()
def delta_vs_T(): graph, mis = degree_fails_graph(return_mis=True) graph = Graph(graph) graph.mis_size = mis times = np.concatenate([np.arange(7, 11, 1), np.arange(20, 110, 10)]) detunings = np.arange(3, 27, 3) print(times, detunings) cost_function = [] for i in range(len(times)): cost_function_detuning = [] for j in range(len(detunings)): simulation = adiabatic_simulation(graph, noise_model='continuous', delta=detunings[j]) results, info = simulation.run( times[i], schedule=lambda t, tf: simulation.rydberg_MIS_schedule( t, tf, coefficients=[np.sqrt(detunings[j]), 1]), method='RK23') cost = simulation.cost_hamiltonian.cost_function( results[-1]) / simulation.graph.mis_size print(times[i], detunings[j], cost) cost_function_detuning.append(cost) cost_function.append(cost_function_detuning) plt.imshow(cost_function, vmin=0, vmax=1, interpolation=None, extent=[0, max(detunings), max(times), 0], origin='upper') plt.xticks(detunings) plt.yticks(times) plt.colorbar() plt.xlabel(r'Detuning $\delta$') plt.ylabel(r'Annealing time $T$') plt.show()
def make_ring_graph_multiring(n): assert n % 2 == 0 and n % 3 == 0 graph_dict = {x: {(x + i) % n: {'weight': 1} for i in [-1, 1]} for x in range(n)} for i in range(int(n)): if i % 3 == 0: graph_dict[i][(i + int(n / 2) + 1) % n] = {'weight': 1} graph_dict[(i + int(n / 2) + 1) % n][i] = {'weight': 1} graph_dict[i][(i + int(n / 2) - 1) % n] = {'weight': 1} graph_dict[(i + int(n / 2) - 1) % n][i] = {'weight': 1} else: graph_dict[i][(i + int(n / 2)) % n] = {'weight': 1} graph_dict[(i + int(n / 2)) % n][i] = {'weight': 1} graph = nx.to_networkx_graph(graph_dict) return Graph(graph)
def large_bipartite_graph(visualize=False): edges = [(0, 11), (0, 13), (0, 14), (0, 15), (0, 17), (0, 18), (0, 19), (1, 10), (1, 14), (1, 15), (1, 16), (1, 18), (2, 10), (2, 11), (2, 13), (2, 14), (2, 19), (3, 12), (3, 16), (3, 18), (3, 19), (4, 11), (4, 13), (4, 14), (4, 15), (4, 16), (4, 18), (5, 10), (5, 12), (5, 14), (5, 15), (5, 17), (5, 18), (5, 19), (6, 10), (6, 12), (6, 13), (6, 14), (6, 15), (6, 16), (6, 17), (6, 18), (6, 19), (7, 10), (7, 11), (7, 12), (7, 13), (7, 14), (7, 16), (7, 17), (7, 18), (7, 19), (8, 10), (8, 12), (8, 13), (8, 14), (8, 16), (8, 17), (8, 18), (8, 19), (9, 11), (9, 12), (9, 13), (9, 14), (9, 16), (9, 17), (9, 19)] graph = nx.Graph() graph.add_nodes_from(list(range(10))) graph.add_edges_from(edges) if visualize: nx.draw(graph, pos=nx.bipartite_layout(graph, list(graph.nodes)[:5])) plt.show() return Graph(graph)
def generate_SDP_graph(d, epsilon, visualize=False, le=False): graph = nx.Graph() graph.add_nodes_from(np.arange(2**d)) for i in range(2**d): for j in range(2**d): binary_i = 2 * (tools.int_to_nary(i, size=d) - 1 / 2) / np.sqrt(d) binary_j = 2 * (tools.int_to_nary(j, size=d) - 1 / 2) / np.sqrt(d) if le: if (1 - np.dot(binary_i, binary_j)) / 2 > 1 - epsilon + 1e-5: graph.add_edge(i, j, weight=1) else: if np.isclose((1 - np.dot(binary_i, binary_j)) / 2, 1 - epsilon): graph.add_edge(i, j, weight=1) if visualize: nx.draw(graph, with_labels=True) plt.show() return Graph(graph)
def omega_vs_T(graph=None, mis=None, show_graph=False, n=3): schedule = lambda t, tf: [[t / tf, (tf - t) / tf, 1], [1]] if graph is None: graph, mis = line_graph(n=n) graph = Graph(graph) if show_graph: nx.draw(graph) plt.show() times = np.arange(20, 100, 10) omegas = np.arange(5, 8, .25) print(times, omegas) cost_function = [] for i in range(len(times)): cost_function_detuning = [] for j in range(len(omegas)): simulation = adiabatic_simulation(graph, noise_model='continuous', delta=omegas[j]) results = master_equation.run_ode_solver( psi, 0, times[i], num_from_time(times[i]), schedule=lambda t: schedule(t, times[i])) cost = rydberg_hamiltonian_cost.cost_function(results[-1], is_ket=False) / mis print(times[i], omegas[j], cost) cost_function_detuning.append(cost) cost_function.append(cost_function_detuning) plt.imshow(cost_function, vmin=0, vmax=1, interpolation=None, extent=[0, max(omegas), max(times), 0], origin='upper') plt.xticks(np.linspace(0, max(omegas), 10)) plt.yticks(np.linspace(0, max(times), 10)) plt.colorbar() plt.xlabel(r'Rabi frequency $\Omega$') plt.ylabel(r'Annealing time $T$') plt.show()
def time_performance(): graph, mis = line_graph(3, return_mis=True) # graph, mis = degree_fails_graph(return_mis=True) graph = Graph(graph) ratios_d = [1] ratios_r = [1] for d in ratios_d: for r in ratios_r: print(d, r) def rydberg_EIT_schedule(t, tf, coefficients=None): if coefficients is None: coefficients = [1, 1] for i in range(len(simulation_eit.hamiltonian)): if i == 0: # We don't want to update normal detunings simulation_eit.hamiltonian[i].energies = [ t / tf * coefficients[0] ] if i == 1: simulation_eit.hamiltonian[i].energies = [ (tf - t) / tf * coefficients[1] ] return True simulation_eit = eit_simulation(graph, noise_model='continuous', gamma=1, delta=d, Omega_g=r, Omega_r=r) simulation_eit.performance_vs_total_time( [5, 10, 15, 20], metric='cost_function', schedule=lambda t, tf: rydberg_EIT_schedule( t, tf, coefficients=[r, r]), plot=True, verbose=True, method='RK23')
def find_gap_fixed_n(n, use_degenerate=True, use_Z2_symmetry=True, verbose=False, p=2): # Compute the number of ground states and first excited states n_ground = np.inf if not use_degenerate: if use_Z2_symmetry: if p == 2: while n_ground != 1: graph = sk_integer(n) cost = HamiltonianMaxCut(graph, cost_function=False, use_Z2_symmetry=use_Z2_symmetry) n_ground = len(ground_states(cost.hamiltonian)) if verbose and n_ground != 1: print('Initially failed to find graph, n_ground =', n_ground) else: graph = sk_hamiltonian(n, p=p, use_degenerate=use_degenerate, use_Z2_symmetry=use_Z2_symmetry) else: if p == 2: while n_ground != 2: graph = sk_integer(n) cost = HamiltonianMaxCut(graph, cost_function=False, use_Z2_symmetry=use_Z2_symmetry) n_ground = len(ground_states(cost.hamiltonian)) if verbose and n_ground != 2: print('Initially failed to find graph, n_ground =', n_ground) else: graph = sk_hamiltonian(n, p=p, use_degenerate=use_degenerate, use_Z2_symmetry=use_Z2_symmetry) else: if p == 2: graph = sk_integer(n) cost = HamiltonianMaxCut(graph, cost_function=False, use_Z2_symmetry=use_Z2_symmetry) n_ground = len(ground_states(cost.hamiltonian)) else: graph = sk_hamiltonian(n, p=p, use_degenerate=use_degenerate, use_Z2_symmetry=use_Z2_symmetry) if p != 2: ham = graph graph = sk_integer(n) cost = HamiltonianMaxCut(graph, cost_function=False, use_Z2_symmetry=use_Z2_symmetry) # Replace the cost Hamiltonian cost._diagonal_hamiltonian = ham n_ground = len(ground_states(ham)) if use_Z2_symmetry: cost._hamiltonian = sparse.csr_matrix((cost._diagonal_hamiltonian.flatten(), (np.arange(2 ** (graph.n - 1)), np.arange( 2 ** (graph.n - 1)))), shape=(2 ** (graph.n - 1), 2 ** (graph.n - 1))) else: cost._hamiltonian = sparse.csr_matrix((cost._diagonal_hamiltonian.flatten(), (np.arange(2 ** (graph.n)), np.arange(2 ** (graph.n)))), shape=(2 ** (graph.n), 2 ** (graph.n))) # Generate a dummy graph with one fewer nodes # Cut cost and driver hamiltonian in half to account for Z2 symmetry if use_Z2_symmetry: driver = HamiltonianDriver(graph=Graph(nx.complete_graph(graph.n - 1))) driver.hamiltonian row = list(range(2 ** (graph.n - 1))) column = list(range(2 ** (graph.n - 1))) column.reverse() driver._hamiltonian = driver._hamiltonian + (-1) ** graph.n * sparse.csr_matrix( (np.ones(2 ** (graph.n - 1)), (row, column))) else: driver = HamiltonianDriver(graph=Graph(nx.complete_graph(graph.n))) def schedule(t): cost.energies = (np.sqrt(np.math.factorial(p)/(2 * graph.n**(p-1))) * t,) driver.energies = (1-t,) def gap(t): t = t[0] if verbose: print('time', t) schedule(t) eigvals = SchrodingerEquation(hamiltonians=[cost, driver]).eig(k=n_ground + 1, return_eigenvectors=False) eigvals = np.flip(eigvals) if verbose: print('gap', eigvals[-1]-eigvals[0]) return eigvals[-1]-eigvals[0] # Now minimize the gap min_gap = scipy.optimize.minimize(gap, .85, bounds=[(0, 1)]) return min_gap.fun
def __init__(self, omega_g, omega_r, rates=(1, ), graph: Graph = None, IS_subspace=True, code=qubit): self.omega_g = omega_g self.omega_r = omega_r self.IS_subspace = IS_subspace self.transition = (0, 1) self.graph = graph # Construct jump operators if self.IS_subspace: # Generate sparse mixing Hamiltonian assert graph is not None assert isinstance(graph, Graph) if code is not qubit: IS, nary_to_index, num_IS = graph.independent_sets_code( self.code) else: # We have already solved for this information IS, nary_to_index, num_IS = graph.independent_sets, graph.binary_to_index, graph.num_independent_sets self._jump_operators_rg = [] self._jump_operators_gg = [] # For each atom, consider the states spontaneous emission can generate transitions between # Over-allocate space for j in range(graph.n): rows_rg = np.zeros(num_IS, dtype=int) columns_rg = np.zeros(num_IS, dtype=int) entries_rg = np.zeros(num_IS, dtype=int) rows_gg = np.zeros(num_IS, dtype=int) columns_gg = np.zeros(num_IS, dtype=int) entries_gg = np.zeros(num_IS, dtype=int) num_terms_gg = 0 num_terms_rg = 0 for i in IS: if IS[i][2][j] == self.transition[0]: # Flip spin at this location # Get binary representation temp = IS[i][2].copy() temp[j] = self.transition[1] flipped_temp = tools.nary_to_int(temp, base=code.d) if flipped_temp in nary_to_index: # This is a valid spin flip rows_rg[num_terms_rg] = nary_to_index[flipped_temp] columns_rg[num_terms_rg] = i entries_rg[num_terms_rg] = 1 num_terms_rg += 1 elif IS[i][2][j] == self.transition[1]: rows_gg[num_terms_gg] = i columns_gg[num_terms_gg] = i entries_gg[num_terms_gg] = 1 num_terms_gg += 1 # Cut off the excess in the arrays columns_rg = columns_rg[:num_terms_rg] rows_rg = rows_rg[:num_terms_rg] entries_rg = entries_rg[:num_terms_rg] columns_gg = columns_gg[:num_terms_gg] rows_gg = rows_gg[:num_terms_gg] entries_gg = entries_gg[:num_terms_gg] # Now, append the jump operator jump_operator_rg = sparse.csc_matrix( (entries_rg, (rows_rg, columns_rg)), shape=(num_IS, num_IS)) jump_operator_gg = sparse.csc_matrix( (entries_gg, (rows_gg, columns_gg)), shape=(num_IS, num_IS)) self._jump_operators_rg.append(jump_operator_rg) self._jump_operators_gg.append(jump_operator_gg) self._jump_operators_rg = np.asarray(self._jump_operators_rg) self._jump_operators_gg = np.asarray(self._jump_operators_gg) else: #self._jump_operators_rg = [] #self._jump_operators_gg = [] op_rg = np.array([[[0, 0], [1, 0]]]) op_gg = np.array([[[0, 0], [0, 1]]]) """for i in range(self.graph.n): jump_operator_rg = tools.tensor_product( [np.identity(2 ** i), op_rg, np.identity(2 ** (self.graph.n - i-1))]) self._jump_operators_rg.append(jump_operator_rg) jump_operator_gg = tools.tensor_product( [np.identity(2 ** i), op_gg, np.identity(2 ** (self.graph.n - i-1))]) self._jump_operators_gg.append(jump_operator_gg)""" self._jump_operators_rg = op_rg self._jump_operators_gg = op_gg super().__init__(None, rates=rates, graph=graph, IS_subspace=IS_subspace, code=code)
import networkx as nx import numpy as np import matplotlib.pyplot as plt from qsim.tools.tools import * from qsim.evolution.hamiltonian import HamiltonianMaxCut from qsim.graph_algorithms.graph import Graph from sklearn.metrics import pairwise_distances from scipy.sparse import csr_matrix while True: d = 5 n = 24 graph = nx.random_regular_graph(d, n) hamz = HamiltonianMaxCut(Graph(graph)) maxcut_size = np.max(hamz._diagonal_hamiltonian).astype(int) mincut_size = np.min(hamz._diagonal_hamiltonian).astype(int) #plt.hist(np.array(csr_matrix.diagonal(hamz.hamiltonian)), bins=maxcut_size-mincut_size, range=(mincut_size, maxcut_size)) #plt.show() independence_polynomial = [] for i in range(maxcut_size - 7, maxcut_size): independence_polynomial.append( len(np.argwhere(hamz._diagonal_hamiltonian == i).T[0])) ratios = [] for i in range(len(independence_polynomial) - 1): try: ratios.append(independence_polynomial[i] / independence_polynomial[i + 1]) except ZeroDivisionError: ratios.append(0) print(independence_polynomial, ratios) if True: #ratios[-1] > 3:
def effective_operator_comparison(graph=None, mis=None, tf=10, show_graph=False, n=3, gamma=500): # Generate annealing schedule def schedule1(t, tf): return [[t / tf, (tf - t) / tf, 1], [1]] if graph is None: graph, mis = line_graph(n=n) graph = Graph(graph) if show_graph: nx.draw(graph) plt.show() # Generate the driving and Rydberg Hamiltonians rabi1 = 1 laser1 = hamiltonian.HamiltonianDriver(transition=(0, 1), energies=[rabi1], code=rydberg, IS_subspace=True, graph=graph) rabi2 = 1 laser2 = hamiltonian.HamiltonianDriver(transition=(1, 2), energies=[rabi2], code=rydberg, IS_subspace=True, graph=graph) rydberg_hamiltonian_cost = hamiltonian.HamiltonianRydberg(graph, code=rydberg, detuning=1, energy=0, IS_subspace=True) # Initialize spontaneous emission spontaneous_emission_rate = gamma spontaneous_emission = lindblad_operators.SpontaneousEmission( transition=(1, 2), rate=spontaneous_emission_rate, code=rydberg, IS_subspace=True, graph=graph) strong_spontaneous_emission_rate = (1, 1) strong_spontaneous_emission = lindblad_operators.StrongSpontaneousEmission( transition=(0, 2), rates=strong_spontaneous_emission_rate, code=rydberg, IS_subspace=True, graph=graph) def schedule2(t, tf): return [[], [(2 * t / tf / np.sqrt(spontaneous_emission_rate), 2 * (tf - t) / tf / np.sqrt(spontaneous_emission_rate))]] # Initialize master equation master_equation = LindbladMasterEquation( hamiltonians=[laser2, laser1], jump_operators=[spontaneous_emission]) # Begin with all qubits in the ground codes psi = np.zeros((rydberg_hamiltonian_cost.hamiltonian.shape[0], 1), dtype=np.complex128) psi[-1, -1] = 1 psi = tools.outer_product(psi, psi) # Integrate the master equation results1 = master_equation.run_ode_solver( psi, 0, tf, num_from_time(tf), schedule=lambda t: schedule1(t, tf)) cost_function = [ rydberg_hamiltonian_cost.cost_function(results1[i], is_ket=False) / mis for i in range(results1.shape[0]) ] # Initialize master equation master_equation = LindbladMasterEquation( hamiltonians=[], jump_operators=[strong_spontaneous_emission]) # Integrate the master equation results2 = master_equation.run_ode_solver( psi, 0, tf, num_from_time(tf), schedule=lambda t: schedule2(t, tf)) cost_function_strong = [ rydberg_hamiltonian_cost.cost_function(results2[i], is_ket=False) / mis for i in range(results2.shape[0]) ] print(results2[-1]) times = np.linspace(0, tf, num_from_time(tf)) # Compute the fidelity of the results fidelities = [ tools.fidelity(results1[i], results2[i]) for i in range(results1.shape[0]) ] plt.plot(times, fidelities, color='r', label='Fidelity') plt.plot(times, cost_function, color='teal', label='Rydberg EIT approximation ratio') plt.plot(times, cost_function_strong, color='y', linestyle=':', label='Effective operator approximation ratio') plt.hlines(1, 0, max(times), linestyles=':', colors='k') plt.legend(loc='lower right') plt.ylim(0, 1.03) plt.xlabel(r'Annealing time $t$') plt.ylabel(r'Approximation ratio') plt.show()
def __init__(self, omega_g, omega_r, rates=(1, ), graph: Graph = None, IS_subspace=True, code=qubit): self.omega_g = omega_g self.omega_r = omega_r self.IS_subspace = IS_subspace self.transition = (0, 1) self.graph = graph # Construct jump operators if self.IS_subspace: # Generate sparse mixing Hamiltonian assert graph is not None assert isinstance(graph, Graph) if code is not qubit: IS, num_IS = graph.independent_sets_qudit(self.code) else: # We have already solved for this information IS, num_IS = graph.independent_sets, graph.num_independent_sets self._jump_operators_rg = [] self._jump_operators_gg = [] # For each atom, consider the states spontaneous emission can generate transitions between # Over-allocate space for j in range(graph.n): rows_rg = np.zeros(num_IS, dtype=int) columns_rg = np.zeros(num_IS, dtype=int) entries_rg = np.zeros(num_IS, dtype=int) rows_gg = np.zeros(num_IS, dtype=int) columns_gg = np.zeros(num_IS, dtype=int) entries_gg = np.zeros(num_IS, dtype=int) num_terms_gg = 0 num_terms_rg = 0 for i in range(IS.shape[0]): if IS[i, j] == self.transition[0]: # Flip spin at this location # Get binary representation temp = IS[i].copy() temp[j] = self.transition[1] where_matched = (np.argwhere( np.sum(np.abs(IS - temp), axis=1) == 0).flatten()) if len(where_matched) > 0: # This is a valid spin flip rows_rg[num_terms_rg] = where_matched[0] columns_rg[num_terms_rg] = i entries_rg[num_terms_rg] = 1 num_terms_rg += 1 elif IS[i, j] == self.transition[1]: rows_gg[num_terms_gg] = i columns_gg[num_terms_gg] = i entries_gg[num_terms_gg] = 1 num_terms_gg += 1 # Cut off the excess in the arrays columns_rg = columns_rg[:num_terms_rg] rows_rg = rows_rg[:num_terms_rg] entries_rg = entries_rg[:num_terms_rg] columns_gg = columns_gg[:num_terms_gg] rows_gg = rows_gg[:num_terms_gg] entries_gg = entries_gg[:num_terms_gg] # Now, append the jump operator jump_operator_rg = sparse.csc_matrix( (entries_rg, (rows_rg, columns_rg)), shape=(num_IS, num_IS)) jump_operator_gg = sparse.csc_matrix( (entries_gg, (rows_gg, columns_gg)), shape=(num_IS, num_IS)) self._jump_operators_rg.append(jump_operator_rg) self._jump_operators_gg.append(jump_operator_gg) self._jump_operators_rg = np.asarray(self._jump_operators_rg) self._jump_operators_gg = np.asarray(self._jump_operators_gg) else: # self._jump_operators_rg = [] # self._jump_operators_gg = [] op_rg = np.array([[[0, 0], [1, 0]]]) op_gg = np.array([[[0, 0], [0, 1]]]) self._jump_operators_rg = op_rg self._jump_operators_gg = op_gg super().__init__(None, rates=rates, graph=graph, IS_subspace=IS_subspace, code=code)
def __init__(self, omega_g, omega_r, energies=(1, ), graph: Graph = None, IS_subspace=True, code=qubit): # Just need to define self.hamiltonian assert IS_subspace self.energies = energies self.IS_subspace = IS_subspace self.graph = graph self.omega_r = omega_r self.omega_g = omega_g self.code = code assert self.code is qubit if self.IS_subspace: # Generate sparse mixing Hamiltonian assert graph is not None assert isinstance(graph, Graph) if code is not qubit: IS, num_IS = graph.independent_sets_qudit(self.code) else: # We have already solved for this information IS, num_IS = graph.independent_sets, graph.num_independent_sets self.transition = (0, 1) self._hamiltonian_rr = np.zeros((num_IS, num_IS)) self._hamiltonian_gg = np.zeros((num_IS, num_IS)) self._hamiltonian_cross_terms = np.zeros((num_IS, num_IS)) for k in range(IS.shape[0]): self._hamiltonian_rr[k, k] = np.sum( IS[k][1] == self.transition[0]) self._hamiltonian_gg[k, k] = np.sum( IS[k][1] == self.transition[1]) self._csc_hamiltonian_rr = sparse.csc_matrix(self._hamiltonian_rr) self._csc_hamiltonian_gg = sparse.csc_matrix(self._hamiltonian_gg) # For each IS, look at spin flips generated by the laser # Over-allocate space rows = np.zeros(graph.n * num_IS, dtype=int) columns = np.zeros(graph.n * num_IS, dtype=int) entries = np.zeros(graph.n * num_IS, dtype=float) num_terms = 0 for i in range(graph.num_independent_sets): for j in range(graph.n): if IS[i, j] == self.transition[1]: # Flip spin at this location # Get binary representation temp = IS[i].copy() temp[j] = self.transition[0] where_matched = (np.argwhere( np.sum(np.abs(IS - temp), axis=1) == 0).flatten()) if len(where_matched) > 0: # This is a valid spin flip rows[num_terms] = where_matched[0] columns[num_terms] = i entries[num_terms] = 1 num_terms += 1 # Cut off the excess in the arrays columns = columns[:2 * num_terms] rows = rows[:2 * num_terms] entries = entries[:2 * num_terms] # Populate the second half of the entries according to self.pauli columns[num_terms:2 * num_terms] = rows[:num_terms] rows[num_terms:2 * num_terms] = columns[:num_terms] entries[num_terms:2 * num_terms] = entries[:num_terms] # Now, construct the Hamiltonian self._csc_hamiltonian_cross_terms = sparse.csc_matrix( (entries, (rows, columns)), shape=(num_IS, num_IS)) self._hamiltonian_cross_terms = self._csc_hamiltonian_cross_terms else: # We are not in the IS subspace pass
def test_logical_codes(self): # Construct a known graph G = nx.random_regular_graph(1, 2) for e in G.edges: G[e[0]][e[1]]['weight'] = 1 nx.draw_networkx(G) # Uncomment to visualize graph # plt.draw_graph(G) G = Graph(G) print('No logical encoding:') hc_qubit = hamiltonian.HamiltonianMIS(G) hamiltonians = [hc_qubit, hamiltonian.HamiltonianDriver()] sim = qaoa.SimulateQAOA(G, cost_hamiltonian=hc_qubit, hamiltonian=hamiltonians, noise_model=None, noise=noises) # Set the default variational operators results = sim.find_parameters_brute(n=10) self.assertTrue(np.isclose(results['approximation_ratio'], 1)) print('Two qubit code:') hc_two_qubit_code = hamiltonian.HamiltonianMIS(G, code=two_qubit_code) hamiltonians = [ hc_two_qubit_code, hamiltonian.HamiltonianDriver(code=two_qubit_code) ] sim_code = qaoa.SimulateQAOA(G, code=two_qubit_code, cost_hamiltonian=hc_two_qubit_code, hamiltonian=hamiltonians) # Find optimal parameters via brute force search sim_code.find_parameters_brute(n=10) self.assertTrue(np.isclose(results['approximation_ratio'], 1)) print('Two qubit code with penalty:') # Set the default variational operators with a penalty Hamiltonian hc_qubit = hamiltonian.HamiltonianMIS(G, code=two_qubit_code) hamiltonians = [ hc_qubit, hamiltonian.HamiltonianBookatzPenalty(code=two_qubit_code), hamiltonian.HamiltonianDriver(code=two_qubit_code) ] sim_penalty = qaoa.SimulateQAOA(G, cost_hamiltonian=hc_qubit, hamiltonian=hamiltonians, code=two_qubit_code) # You should get the same thing results = sim_penalty.find_parameters_brute(n=10) self.assertTrue(np.isclose(results['approximation_ratio'], 1)) print('Jordan-Farhi-Shor code:') hc_jordan_farhi_shor = hamiltonian.HamiltonianMIS( G, code=jordan_farhi_shor) hamiltonians = [ hc_jordan_farhi_shor, hamiltonian.HamiltonianDriver(code=jordan_farhi_shor) ] sim_code = qaoa.SimulateQAOA(G, code=jordan_farhi_shor, cost_hamiltonian=hc_jordan_farhi_shor, hamiltonian=hamiltonians) sim_code.find_parameters_brute(n=10) self.assertTrue(np.isclose(results['approximation_ratio'], 1))