fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(12, 6)) # Draw the QUBO as a networkx graph pos = nx.spring_layout(G) nx.draw_networkx(G, pos=pos, font_size=10, node_size=100, node_color='cyan', ax=axes[0]) # Embed the graph on Chimera dwave_sampler_chimera = DWaveSampler(solver={'topology__type': 'chimera'}) chimera_edges = dwave_sampler_chimera.edgelist chimera_graph = dnx.chimera_graph(16, edge_list=chimera_edges) clique_embedding_chimera = find_clique_embedding(N, chimera_graph) # Draw the graph embedded on Chimera dnx.draw_chimera_embedding(chimera_graph, clique_embedding_chimera, embedded_graph=G, unused_color=None, ax=axes[1]) # Embed the graph on Pegasus dwave_sampler_pegasus = DWaveSampler(solver={'topology__type': 'pegasus'}) pegasus_edges = dwave_sampler_pegasus.edgelist pegasus_graph = dnx.pegasus_graph(16, edge_list=pegasus_edges) clique_embedding_pegasus = find_clique_embedding(N, pegasus_graph)
def reconstruct(nodes): return dnx.chimera_graph(16, node_list=nodes)
def Chimera(n, l=4): return dnx.chimera_graph(n, n, l, coordinates=True)
def generate_graphs(): # Store the results in csv files dir_path = os.path.dirname(os.path.abspath(__file__)) results_path = os.path.join(dir_path, "results/embedding/") if not (os.path.exists(results_path)): print('Results directory ' + results_path + ' does not exist. We will create it.') os.makedirs(results_path) file_name = instance + '_graphs' file_name = os.path.join(results_path, file_name) # time horizons and time limit in seconds if TEST: random.seed(seed) time_limit = 120 # Number of times the heuristic is run n_heur = 100 else: time_limit = 3600 # Number of times the heuristic is run n_heur = 1000 columns = [ 'id', 'n_nodes', 'n_edges', 'nodes', 'edges', 'alpha', 'target', 'density_target' ] results = pd.DataFrame(columns=columns) # Graph corresponding to D-Wave 2000Q qpu = DWaveSampler() qpu_edges = qpu.edgelist qpu_nodes = qpu.nodelist X = dnx.chimera_graph(16, node_list=qpu_nodes, edge_list=qpu_edges) nx.write_edgelist(X, os.path.join(results_path, "X.edgelist")) for k in range(K): for n in range(n0, N): print(n) temp = dict() # Target graph statistics temp['target'] = qpu.solver.id temp['density_target'] = nx.density(X) # Graph generation if instance == "cycle": # Cycle graphs Input = nx.cycle_graph(n) temp['id'] = "cycle_" + str(n) alpha = np.floor(n / 2) elif instance == "devil": # Devil graphs Input, alpha = devil_graphs(n) temp['id'] = "devil_rank_" + str(n) else: # Random graphs Input = nx.erdos_renyi_graph(n, prob) temp['id'] = "random_" + str(n) + "_" + str(prob) + "_" + str( k) alpha = 0 # Input graph parameters temp['alpha'] = alpha temp['n_nodes'] = Input.number_of_nodes() temp['n_edges'] = Input.number_of_edges() temp['nodes'] = Input.nodes() temp['edges'] = Input.edges() # Problem reformulations # Proposed and Laserre reformulation reforms = ['p', 'l'] for ref in reforms: if ref == 'p': Q, offset = proposed(Input, M=1, draw=False) elif ref == 'l': Q, offset = laserre(Input, draw=False) # Graphs generation bqm = dimod.BinaryQuadraticModel.from_qubo(Q, offset=offset) edges = list( itertools.chain(bqm.quadratic, ((v, v) for v in bqm.linear))) G = nx.Graph() G.add_edges_from(edges) # Graph statistics temp['nodes_' + ref] = G.nodes() temp['edges_' + ref] = G.edges() temp['n_nodes_' + ref] = G.number_of_nodes() temp['n_edges_' + ref] = G.number_of_edges() temp['density_' + ref] = nx.density(G) results = results.append(temp, ignore_index=True) results.to_csv(file_name + ".csv") sol_total = pd.DataFrame.from_dict(results) sol_total.to_excel(os.path.join(results_path, file_name + ".xlsx"))
def test__matching_qubo(self): # _matching_qubo creates a qubo that gives a matching for the given graph. # let's check that the solutions are all matchings G = nx.complete_graph(5) MAG = .75 # magnitude arg for _matching_qubo edge_mapping = {edge: idx for idx, edge in enumerate(G.edges())} edge_mapping.update({(e1, e0): idx for (e0, e1), idx in edge_mapping.items()}) inv_edge_mapping = {idx: edge for edge, idx in edge_mapping.items()} Q = _matching_qubo(G, edge_mapping, magnitude=MAG) # now for each combination of ege, we check that if the combination # is a matching, it has qubo_energy 0, otherwise greater than 0. Which # is the desired behaviour infeasible_gap = float('inf') for edge_vars in powerset(set(edge_mapping.values())): # get the matching from the variables potential_matching = {inv_edge_mapping[v] for v in edge_vars} # get the sample from the edge_vars sample = {v: 0 for v in edge_mapping.values()} for v in edge_vars: sample[v] = 1 if dnx.is_matching(potential_matching): self.assertEqual(qubo_energy(sample, Q), 0.) else: en = qubo_energy(sample, Q) if en < infeasible_gap: infeasible_gap = en self.assertGreaterEqual(en, MAG) self.assertEqual(MAG, infeasible_gap) # # Another graph, Chimera tile this time # G = dnx.chimera_graph(1, 1, 4) MAG = .67 edge_mapping = {edge: idx for idx, edge in enumerate(G.edges())} edge_mapping.update({(e1, e0): idx for (e0, e1), idx in edge_mapping.items()}) inv_edge_mapping = {idx: edge for edge, idx in edge_mapping.items()} Q = _matching_qubo(G, edge_mapping, magnitude=MAG) # now for each combination of ege, we check that if the combination # is a matching, it has qubo_energy 0, otherwise greater than 0. Which # is the desired behaviour infeasible_gap = float('inf') for edge_vars in powerset(set(edge_mapping.values())): # get the matching from the variables potential_matching = {inv_edge_mapping[v] for v in edge_vars} # get the sample from the edge_vars sample = {v: 0 for v in edge_mapping.values()} for v in edge_vars: sample[v] = 1 if dnx.is_matching(potential_matching): self.assertEqual(qubo_energy(sample, Q), 0.) else: en = qubo_energy(sample, Q) if en < infeasible_gap: infeasible_gap = en self.assertGreaterEqual(en, MAG) self.assertEqual(MAG, infeasible_gap)
def test__maximal_matching_qubo(self): G = nx.complete_graph(5) B = 1 # magnitude arg for _maximal_matching_qubo edge_mapping = {edge: idx for idx, edge in enumerate(G.edges())} edge_mapping.update({(e1, e0): idx for (e0, e1), idx in edge_mapping.items()}) inv_edge_mapping = {idx: edge for edge, idx in edge_mapping.items()} Q = _maximal_matching_qubo(G, edge_mapping, magnitude=B) # now for each combination of edges, we check that if the combination # is a maximal matching, it has energy magnitude * |edges| ground_energy = -1. * B * len(G.edges()) infeasible_gap = float('inf') for edge_vars in powerset(set(edge_mapping.values())): # get the matching from the variables potential_matching = {inv_edge_mapping[v] for v in edge_vars} # get the sample from the edge_vars sample = {v: 0 for v in edge_mapping.values()} for v in edge_vars: sample[v] = 1 if dnx.is_maximal_matching(G, potential_matching): self.assertEqual(qubo_energy(sample, Q), ground_energy) elif not dnx.is_matching(potential_matching): # for now we don't care about these, they should be covered by the _matching_qubo # part of the QUBO function pass else: en = qubo_energy(sample, Q) gap = en - ground_energy if gap < infeasible_gap: infeasible_gap = gap self.assertLessEqual(B, infeasible_gap) # # Another graph, Chimera tile this time # G = dnx.chimera_graph(1, 2, 2) B = 1 # magnitude arg for _maximal_matching_qubo edge_mapping = {edge: idx for idx, edge in enumerate(G.edges())} edge_mapping.update({(e1, e0): idx for (e0, e1), idx in edge_mapping.items()}) inv_edge_mapping = {idx: edge for edge, idx in edge_mapping.items()} Q = _maximal_matching_qubo(G, edge_mapping, magnitude=B) # now for each combination of edges, we check that if the combination # is a maximal matching, it has energy magnitude * |edges| ground_energy = -1. * B * len(G.edges()) infeasible_gap = float('inf') for edge_vars in powerset(set(edge_mapping.values())): # get the matching from the variables potential_matching = {inv_edge_mapping[v] for v in edge_vars} # get the sample from the edge_vars sample = {v: 0 for v in edge_mapping.values()} for v in edge_vars: sample[v] = 1 if dnx.is_maximal_matching(G, potential_matching): # print potential_matching, qubo_energy(Q, sample) self.assertLess(abs(qubo_energy(sample, Q) - ground_energy), 10**-8) elif not dnx.is_matching(potential_matching): # for now we don't care about these, they should be covered by the _matching_qubo # part of the QUBO function pass else: en = qubo_energy(sample, Q) gap = en - ground_energy if gap < infeasible_gap: infeasible_gap = gap self.assertLessEqual(B - infeasible_gap, 10**-8)
def check_if_proper(): nP = 2 instances = [[[1, 2, 3, 4], [0]], [[2, 3, 4, 5], [0]], [[2, 4, 6, 8], [0]], [[1, 2, 3, 6], [0]], [[2, 3, 4, 7], [2]], [[10, 11, 12, 15], [2]], [[10, 20, 30, 40], [0]]] for i in instances: jobs = i[0] diff = i[1] table = [] print print("#########################") print("Instance ", i) print( "c1 | c2 | Optimum | Range | Gap |Relation" ) for c1_mult in range(1, 11): c1 = max(jobs) * c1_mult + 1 Q = create.createQUBO(jobs, nP, diff, c1_mult, 1) best, second, worst = solve.solution_distribution( Q, len(jobs), nP, diff, c1, 1) print(c1, " | 1 | ", best, " | ", worst - best, " | ", second - best, " | ", float(second - best) / float(worst - best)) embedding = { 0: [1760, 1632, 1764], 1: [1628, 1636, 1634], 2: [1638, 1630, 1627], 3: [1752, 1624, 1759, 1767], 4: [1763, 1635, 1765], 5: [1761, 1633], 6: [1754, 1626, 1758, 1766], 7: [1631, 1639, 1625], 8: [1637, 1629] } base_sampler = neal.SimulatedAnnealingSampler() G = dnx.chimera_graph(16, 16, 4) nodelist = G.nodes() edgelist = G.edges() source_edgelist = list(Q) sampler = dimod.StructureComposite(base_sampler, nodelist, edgelist) new_sampler = FixedEmbeddingComposite(sampler, embedding) solution = {'energy': [], "valid": [], 'max_time': []} for k in range(100): s = solve.solveQUBO(Q, jobs, nP, new_sampler) solution['energy'].append(s['energy']) solution['valid'].append(s['valid']) solution['max_time'].append(s['max_time']) table.append(solution) print("Solutions ") print( "c1 | c2 | Energies | Valid | Max_Time " ) for c1_mult in range(1, 11): print( max(jobs) * c1_mult + 1, " | 1 | ", table[c1_mult - 1]['energy'], " | ", table[c1_mult - 1]['valid'], " | ", table[c1_mult - 1]['max_time'])
def test__shore_size_tiles(self): for t in range(1, 8): G = dnx.chimera_graph(1, 1, t) self.assertEqual(_chimera_shore_size(G.adj, len(G.edges)), t)
from concurrent.futures import Future from unittest import mock from uuid import uuid4 import numpy as np import dimod import dwave_networkx as dnx from dwave.cloud.exceptions import SolverOfflineError, SolverNotFoundError from dwave.system.samplers import DWaveSampler from dwave.system.warnings import EnergyScaleWarning, TooFewSamplesWarning C16 = dnx.chimera_graph(16) # remove one node from C16 to simulate a not-fully-yielded system C16.remove_node(42) edges = set(tuple(edge) for edge in C16.edges) edges.update([(v, u) for u, v in edges]) # solver has bi-directional class MockSolver(): nodes = set(range(2048)) edges = edges properties = {'readout_thermalization_range': [0.0, 10000.0], 'annealing_time_range': [1.0, 2000.0], 'default_readout_thermalization': 0.0, 'parameters': {'num_spin_reversal_transforms': '',
def make_origin_embeddings(qpu_sampler=None, lattice_type=None): """Creates optimal embeddings for a lattice. The embeddings created are compatible with the topology and shape of a specified ``qpu_sampler``. Args: qpu_sampler (:class:`dimod.Sampler`, optional): Quantum sampler such as a D-Wave system. If not specified, the :class:`~dwave.system.samplers.DWaveSampler` sampler class is used to select a QPU solver with a topology compatible with the specified ``lattice_type`` (e.g. an Advantage system for a 'pegasus' lattice type). lattice_type (str, optional, default=qpu_sampler.properties['topology']['type']): Options are: * "cubic" Embeddings compatible with the schemes arXiv:2009.12479 and arXiv:2003.00133 are created for a ``qpu_sampler`` of topology type either 'pegasus' or 'chimera'. * "pegasus" Embeddings are chain length one (minimal and native). If ``qpu_sampler`` topology type is 'pegasus', maximum scale subgraphs are embedded using the ``nice_coordinates`` vector labeling scheme for variables. * "chimera" Embeddings are chain length one (minimal and native). If ``qpu_sampler`` topology type is 'chimera', maximum scale chimera subgraphs are embedded using the chimera vector labeling scheme for variables. Returns: A list of embeddings. Each embedding is a dictionary, mapping geometric problem keys to sets of qubits (chains) compatible with the ``qpu_sampler``. Examples: This example creates a list of three cubic lattice embeddings compatible with the default online system. These three embeddings are related by rotation of the lattice: for a Pegasus P16 system the embeddings are for lattices of size (15,15,12), (12,15,15) and (15,12,15) respectively. >>> from dwave.system.samplers import DWaveSampler # doctest: +SKIP >>> sampler = DWaveSampler() # doctest: +SKIP >>> embeddings = make_origin_embeddings(qpu_sampler=sampler, ... lattice_type='cubic') # doctest: +SKIP """ if qpu_sampler is None: if lattice_type == 'pegasus' or lattice_type == 'chimera': qpu_sampler = DWaveSampler(solver={'topology__type': lattice_type}) else: qpu_sampler = DWaveSampler() qpu_type = qpu_sampler.properties['topology']['type'] if lattice_type is None: lattice_type = qpu_type qpu_shape = qpu_sampler.properties['topology']['shape'] target = nx.Graph() target.add_edges_from(qpu_sampler.edgelist) if qpu_type == lattice_type: # Fully yielded fully utilized native topology problem. # This method is also easily adapted to work for any chain-length 1 # embedding origin_embedding = {q: [q] for q in qpu_sampler.properties['qubits']} if lattice_type == 'pegasus': # Trimming to nice_coordinate supported embeddings is not a unique, # options, it has some advantages and some disadvantages: proposed_source = dnx.pegasus_graph(qpu_shape[0], nice_coordinates=True) proposed_source = nx.relabel_nodes( proposed_source, { q: dnx.pegasus_coordinates(qpu_shape[0]).nice_to_linear(q) for q in proposed_source.nodes() }) lin_to_vec = dnx.pegasus_coordinates(qpu_shape[0]).linear_to_nice elif lattice_type == 'chimera': proposed_source = dnx.chimera_graph(qpu_shape[0], qpu_shape[1], qpu_shape[2]) lin_to_vec = dnx.chimera_coordinates( qpu_shape[0]).linear_to_chimera else: raise ValueError( f'Unsupported native processor topology {qpu_type}. ' 'Support for Zephyr and other topologies is straightforward to ' 'add subject to standard dwave_networkx library tool availability.' ) elif lattice_type == 'cubic': if qpu_type == 'pegasus': vec_to_lin = dnx.pegasus_coordinates( qpu_shape[0]).pegasus_to_linear L = qpu_shape[0] - 1 dimensions = [L, L, 12] # See arXiv:2003.00133 origin_embedding = { (x, y, z): [ vec_to_lin((0, x, z + 4, y)), vec_to_lin((1, y + 1, 7 - z, x)) ] for x in range(L) for y in range(L) for z in range(8) if target.has_edge(vec_to_lin(( 0, x, z + 4, y)), vec_to_lin((1, y + 1, 7 - z, x))) } origin_embedding.update({ (x, y, z): [ vec_to_lin((0, x + 1, z - 8, y)), vec_to_lin((1, y, 19 - z, x)) ] for x in range(L) for y in range(L) for z in range(8, 12) if target.has_edge(vec_to_lin(( 0, x + 1, z - 8, y)), vec_to_lin((1, y, 19 - z, x))) }) elif qpu_type == 'chimera': vec_to_lin = dnx.chimera_coordinates( qpu_shape[0], qpu_shape[1], qpu_shape[2]).chimera_to_linear L = qpu_shape[0] // 2 dimensions = [L, L, 8] # See arxiv:2009.12479, one choice amongst many origin_embedding = { (x, y, z): [ vec_to_lin(coord) for coord in [(2 * x + 1, 2 * y, 0, z), (2 * x, 2 * y, 0, z), (2 * x, 2 * y, 1, z), (2 * x, 2 * y + 1, 1, z)] ] for x in range(L) for y in range(L) for z in range(4) if target.has_edge(vec_to_lin(( 2 * x + 1, 2 * y, 0, z)), vec_to_lin((2 * x, 2 * y, 0, z))) and target.has_edge(vec_to_lin(( 2 * x, 2 * y, 0, z)), vec_to_lin((2 * x, 2 * y, 1, z))) and target.has_edge(vec_to_lin(( 2 * x, 2 * y, 1, z)), vec_to_lin((2 * x, 2 * y + 1, 1, z))) } origin_embedding.update({ (x, y, 4 + z): [ vec_to_lin(coord) for coord in [(2 * x + 1, 2 * y, 1, z), (2 * x + 1, 2 * y + 1, 1, z), (2 * x + 1, 2 * y + 1, 0, z), (2 * x, 2 * y + 1, 0, z)] ] for x in range(L) for y in range(L) for z in range(4) if target.has_edge(vec_to_lin(( 2 * x + 1, 2 * y, 1, z)), vec_to_lin((2 * x + 1, 2 * y + 1, 1, z))) and target.has_edge(vec_to_lin(( 2 * x + 1, 2 * y + 1, 1, z)), vec_to_lin((2 * x + 1, 2 * y + 1, 0, z))) and target. has_edge(vec_to_lin((2 * x + 1, 2 * y + 1, 0, z)), vec_to_lin((2 * x, 2 * y + 1, 0, z))) }) else: raise ValueError(f'Unsupported qpu_sampler topology {qpu_type} ' 'for cubic lattice solver') proposed_source = _make_cubic_lattice(dimensions) else: raise ValueError('Unsupported combination of lattice_type ' 'and qpu_sampler topology') origin_embedding = _yield_limited_origin_embedding(origin_embedding, proposed_source, target) if qpu_type == lattice_type: # Convert keys to standard vector scheme: origin_embedding = { lin_to_vec(node): origin_embedding[node] for node in origin_embedding } # We can propose additional embeddings. Or we can use symmetries of the # target graph (automorphisms), to create additional embedding options. # This is important in the cubic case, because the subregion shape and # embedding features are asymmetric in the x, y and z directions. # Various symmetries can be exploited in all lattices. origin_embeddings = [origin_embedding] if lattice_type == 'cubic': # A rotation is sufficient for demonstration purposes: origin_embeddings.append({(key[2], key[0], key[1]): value for key, value in origin_embedding.items()}) origin_embeddings.append({(key[1], key[2], key[0]): value for key, value in origin_embedding.items()}) elif lattice_type == 'pegasus': # A horizontal to vertical flip is sufficient for demonstration purposes: # Flip north-east to south-west axis (see draw_pegasus): L = qpu_shape[0] origin_embeddings.append({(key[0], L - 2 - key[2], L - 2 - key[1], 1 - key[3], 3 - key[4]): value for key, value in origin_embedding.items()}) else: # A horizontal to vertical flip is sufficient for demonstration purposes: origin_embeddings.append({(key[1], key[0], 1 - key[2], key[3]): value for key, value in origin_embedding.items()}) return origin_embeddings
def test_dimod_response_vs_list(self): # should be able to handle either a dimod response or a list of dicts G = dnx.chimera_graph(1, 1, 3) coloring = dnx.min_vertex_coloring(G, ExactSolver()) coloring = dnx.min_vertex_coloring(G, SimulatedAnnealingSampler())
def __init__(self, sampler, sub_m, sub_n, t=4): self.parameters = sampler.parameters.copy() self.properties = properties = {'child_properties': sampler.properties} tile = dnx.chimera_graph(sub_m, sub_n, t) self.nodelist = sorted(tile.nodes) self.edgelist = sorted(sorted(edge) for edge in tile.edges) # dimod.Structured abstract base class automatically populates adjacency and structure as # mixins based on nodelist and edgelist if not isinstance(sampler, dimod.Structured): # we could also just tile onto the unstructured sampler but in that case we would need # to know how many tiles to use raise ValueError("given child sampler should be structured") self.children = [sampler] nodes_per_cell = t * 2 edges_per_cell = t * t m = n = int( ceil(sqrt(ceil(len(sampler.structure.nodelist) / nodes_per_cell)))) # assume square lattice shape system = dnx.chimera_graph(m, n, t, node_list=sampler.structure.nodelist, edge_list=sampler.structure.edgelist) c2i = { chimera_index: linear_index for (linear_index, chimera_index) in system.nodes(data='chimera_index') } sub_c2i = { chimera_index: linear_index for (linear_index, chimera_index) in tile.nodes(data='chimera_index') } # Count the connections between these qubits def _between(qubits1, qubits2): edges = [ edge for edge in system.edges if edge[0] in qubits1 and edge[1] in qubits2 ] return len(edges) # Get the list of qubits in a cell def _cell_qubits(i, j): return [ c2i[(i, j, u, k)] for u in range(2) for k in range(t) if (i, j, u, k) in c2i ] # get a mask of complete cells cells = [[False for _ in range(n)] for _ in range(m)] for i in range(m): for j in range(n): qubits = _cell_qubits(i, j) cells[i][j] = len(qubits) == nodes_per_cell and _between( qubits, qubits) == edges_per_cell # List of 'embeddings' self.embeddings = properties['embeddings'] = embeddings = [] # For each possible chimera cell check if the next few cells are complete for i in range(m + 1 - sub_m): for j in range(n + 1 - sub_n): # Check if the sub cells are matched match = all(cells[i + sub_i][j + sub_j] for sub_i in range(sub_m) for sub_j in range(sub_n)) # Check if there are connections between the cells. for sub_i in range(sub_m): for sub_j in range(sub_n): if sub_m > 1 and sub_i < sub_m - 1: match &= _between( _cell_qubits(i + sub_i, j + sub_j), _cell_qubits(i + sub_i + 1, j + sub_j)) == t if sub_n > 1 and sub_j < sub_n - 1: match &= _between( _cell_qubits(i + sub_i, j + sub_j), _cell_qubits(i + sub_i, j + sub_j + 1)) == t if match: # Pull those cells out into an embedding. embedding = {} for sub_i in range(sub_m): for sub_j in range(sub_n): cells[i + sub_i][j + sub_j] = False # Mark cell as matched for u in range(2): for k in range(t): embedding[sub_c2i[sub_i, sub_j, u, k]] = { c2i[(i + sub_i, j + sub_j, u, k)] } embeddings.append(embedding) if len(embeddings) == 0: raise ValueError( "no tile embeddings found; is the sampler Chimera structured?")
def reconstructor(m, n, t): return lambda nodes: dnx.chimera_graph( m, n=n, t=t, node_list=nodes)
def test_chimera_layout_nodata(self): G = dnx.chimera_graph(2, 2, 4, data=False) pos = dnx.chimera_layout(G)
def test_draw_chimera_yield(self): G = dnx.chimera_graph(2, 2, 4, data=False) G.remove_edges_from([(0, 6), (10, 13), (26, 31)]) G.remove_nodes_from([18, 23]) dnx.draw_chimera_yield(G)
def test_chimera_layout_edgelist_singletile(self): G = dnx.chimera_graph(1, 1, 16, data=False) pos = dnx.chimera_layout(G.edges())
def test_chimera_layout_basic(self): G = dnx.chimera_graph(1, 1, 4) pos = dnx.chimera_layout(G)
def test__shore_size_columns(self): # 2, 1, 1 is the same as 1, 1, 2 for m in range(2, 11): for t in range(9, 1, -1): G = dnx.chimera_graph(m, 1, t) self.assertEqual(_chimera_shore_size(G.adj, len(G.edges)), t)
def test_chimera_layout_typical(self): G = dnx.chimera_graph(2, 2, 4) pos = dnx.chimera_layout(G)
def test_maximal_matching_combined_qubo(self): """combine the qubo's generated by _maximal_matching_qubo and _matching_qubo and make sure they have the correct infeasible gap""" G = nx.complete_graph(5) delta = max(G.degree(node) for node in G) # maximum degree A = 1 # magnitude arg for _matching_qubo B = .75 * A / (delta - 2.) # magnitude arg for _maximal_matching_qubo edge_mapping = {edge: idx for idx, edge in enumerate(G.edges())} edge_mapping.update({(e1, e0): idx for (e0, e1), idx in edge_mapping.items()}) inv_edge_mapping = {idx: edge for edge, idx in edge_mapping.items()} Qm = _matching_qubo(G, edge_mapping, magnitude=A) Qmm = _maximal_matching_qubo(G, edge_mapping, magnitude=B) Q = defaultdict(float) for edge, bias in Qm.items(): Q[edge] += bias for edge, bias in Qmm.items(): Q[edge] += bias Q = dict(Q) # now for each combination of edges, we check that if the combination # is a maximal matching, and if so that is has ground energy, else # there is an infeasible gap ground_energy = -1. * B * len(G.edges()) # from maximal matching infeasible_gap = float('inf') for edge_vars in powerset(set(edge_mapping.values())): # get the matching from the variables potential_matching = {inv_edge_mapping[v] for v in edge_vars} # get the sample from the edge_vars sample = {v: 0 for v in edge_mapping.values()} for v in edge_vars: sample[v] = 1 if dnx.is_maximal_matching(G, potential_matching): # print potential_matching, qubo_energy(Q, sample) self.assertLess(abs(qubo_energy(sample, Q) - ground_energy), 10**-8) else: en = qubo_energy(sample, Q) gap = en - ground_energy if gap < infeasible_gap: infeasible_gap = gap self.assertLessEqual(B - infeasible_gap, 10**-8) # # Another graph, Chimera tile this time # G = dnx.chimera_graph(1, 1, 4) delta = max(G.degree(node) for node in G) # maximum degree A = 1 # magnitude arg for _matching_qubo B = .95 * A / (delta - 2.) # magnitude arg for _maximal_matching_qubo edge_mapping = {edge: idx for idx, edge in enumerate(G.edges())} edge_mapping.update({(e1, e0): idx for (e0, e1), idx in edge_mapping.items()}) inv_edge_mapping = {idx: edge for edge, idx in edge_mapping.items()} Qm = _matching_qubo(G, edge_mapping, magnitude=A) Qmm = _maximal_matching_qubo(G, edge_mapping, magnitude=B) Q = defaultdict(float) for edge, bias in Qm.items(): Q[edge] += bias for edge, bias in Qmm.items(): Q[edge] += bias Q = dict(Q) # now for each combination of edges, we check that if the combination # is a maximal matching, and if so that is has ground energy, else # there is an infeasible gap ground_energy = -1. * B * len(G.edges()) # from maximal matching infeasible_gap = float('inf') for edge_vars in powerset(set(edge_mapping.values())): # get the matching from the variables potential_matching = {inv_edge_mapping[v] for v in edge_vars} # get the sample from the edge_vars sample = {v: 0 for v in edge_mapping.values()} for v in edge_vars: sample[v] = 1 if dnx.is_maximal_matching(G, potential_matching): # print potential_matching, qubo_energy(Q, sample) self.assertLess(abs(qubo_energy(sample, Q) - ground_energy), 10**-8) else: en = qubo_energy(sample, Q) gap = en - ground_energy if gap < infeasible_gap: infeasible_gap = gap self.assertLessEqual(B - infeasible_gap, 10**-8)
def test_chimera_layout_center(self): G = dnx.chimera_graph(2, 2, 4) pos = dnx.chimera_layout(G, center=(5, 5)) with self.assertRaises(ValueError): pos = dnx.chimera_layout(G, center=(5, 5, 5))
# distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ============================================================================= import unittest import dimod import dimod.testing as dtest import dwave_networkx from dimod import ExactSolver from dwave.system import ReverseBatchStatesComposite, ReverseAdvanceComposite C4 = dwave_networkx.chimera_graph(4, 4, 4) class MockReverseSampler(dimod.Sampler, dimod.Structured): nodelist = None edgelist = None properties = None parameters = None def __init__(self, broken_nodes=None): if broken_nodes is None: self.nodelist = sorted(C4.nodes) self.edgelist = sorted(sorted(edge) for edge in C4.edges) else: self.nodelist = sorted(v for v in C4.nodes if v not in broken_nodes)
def test_chimera_layout_lowdim(self): G = dnx.chimera_graph(2, 2, 4) with self.assertRaises(ValueError): pos = dnx.chimera_layout(G, dim=1)
H_0 = -(IX + XI) λ, v = np.linalg.eigh(H_0) print("Eigenvalues:", λ) print("Eigenstate for lowest eigenvalue", v[:, 0]) # Annealing J = {(0, 1): 1.0, (1, 2): -1.0} h = {0: 0, 1: 0, 2: 0} model = dimod.BinaryQuadraticModel(h, J, 0.0, dimod.SPIN) sampler = dimod.SimulatedAnnealingSampler() response = sampler.sample(model, num_reads=10) print("Energy of samples:") print([solution.energy for solution in response.data()]) # Chimera graph for connectivity structure connectivity_structure = dnx.chimera_graph(2, 2) dnx.draw_chimera(connectivity_structure) plt.show() G = nx.complete_graph(9) plt.axis("off") nx.draw_networkx(G, with_labels=False) embedded_graph = minorminer.find_embedding(G.edges(), connectivity_structure.edges()) dnx.draw_chimera_embedding(connectivity_structure, embedded_graph) plt.show() max_chain_length = 0 for _, chain in embedded_graph.items(): if len(chain) > max_chain_length: max_chain_length = len(chain) print(max_chain_length)
def test_chimera_layout_weird_nodata(self): G = dnx.chimera_graph(2, 2, 4) del G.graph["family"] with self.assertRaises(ValueError): pos = dnx.chimera_layout(G, dim=1)
# %% # initialize # https://docs.ocean.dwavesys.com/en/latest/examples/topology_samplers.html import neal import dimod import dwave_networkx as dnx import networkx as nx import dwave.embedding from dwave.system import DWaveSampler, EmbeddingComposite import matplotlib.pyplot as plt # %% # Creating a Chimera Sapmler C16 = dnx.chimera_graph(16) # print(C16) classical_sampler = neal.SimulatedAnnealingSampler() sampler = dimod.StructureComposite(classical_sampler, C16.nodes, C16.edges) h = {v: 0.0 for v in C16.nodes} J = {(u, v): 1 for u, v in C16.edges} sampleset = sampler.sample_ising(h, J) embedding_sampler = EmbeddingComposite(sampler) qpu_sampler = DWaveSampler(solver={ 'qpu': True, 'num_active_qubits__within': [2000, 2048] }) QPUGraph = nx.Graph(qpu_sampler.edgelist) all(v in C16.nodes for v in QPUGraph.nodes) all(edge in C16.edges for edge in QPUGraph.edges)
def test_chimera_layout_coords(self): G = dnx.chimera_graph(2, 2, 4, coordinates=True) pos = dnx.chimera_layout(G)
def __init__(self, sampler, sub_m, sub_n, t=4): self.parameters = sampler.parameters.copy() self.properties = properties = {'child_properties': sampler.properties} tile = dnx.chimera_graph(sub_m, sub_n, t) self.nodelist = sorted(tile.nodes) self.edgelist = sorted(sorted(edge) for edge in tile.edges) # dimod.Structured abstract base class automatically populates adjacency # and structure as mixins based on nodelist and edgelist if not isinstance(sampler, dimod.Structured): # we could also just tile onto the unstructured sampler but in that # case we would need to know how many tiles to use raise ValueError("given child sampler should be structured") self.children = [sampler] # Chimera values (unless pegasus specified) num_sublattices = 1 nodes_per_cell = t * 2 edges_per_cell = t * t if not ('topology' in sampler.properties and 'type' in sampler.properties['topology'] and 'shape' in sampler.properties['topology']): raise ValueError('To use this composite it is necessary for the' 'structured sampler to have an explicit topology' '(sampler.properties[\'topology\']). Necessary' 'fields are \'type\' and \'shape\'. ') if sampler.properties['topology']['type'] == 'chimera': if len(sampler.properties['topology']['shape']) != 3: raise ValueError('topology shape is not of length 3 ' '(not compatible with chimera)') if sampler.properties['topology']['shape'][2] != t: raise ValueError('Tiling methodology requires that solver' 'and subproblem have identical shore size') m = sampler.properties['topology']['shape'][0] n = sampler.properties['topology']['shape'][1] else: if len(sampler.properties['topology']['shape']) != 1: raise ValueError('topology shape is not of length 1 ' '(not compatible with pegasus)') # Full yield in odd-couplers also required. # Generalizes chimera subgraph requirement and leads to some # simplification of expressions, but at with a cost in cell-yield edges_per_cell += t # Square solvers only by pegasus lattice definition PN yields # 3 by N-1 by N-1 cells: num_sublattices = 3 m = n = sampler.properties['topology']['shape'][0] - 1 if t != 4: raise ValueError( 't=4 for all pegasus processors, value is not typically' 'stored in solver properties and is difficult to infer.' 'Therefore only the value t=4 is supported.') if num_sublattices == 1: # Chimera defaults. Appended coordinates (treat as first and only sublattice) system = dnx.chimera_graph(m, n, t, node_list=sampler.structure.nodelist, edge_list=sampler.structure.edgelist) c2i = {(0, *chimera_index): linear_index for (linear_index, chimera_index) in system.nodes(data='chimera_index')} else: system = dnx.pegasus_graph(m, node_list=sampler.structure.nodelist, edge_list=sampler.structure.edgelist) # Vector specification in terms of nice coordinates: c2i = { dnx.pegasus_coordinates(m + 1).linear_to_nice(linear_index): linear_index for linear_index in system.nodes() } sub_c2i = { chimera_index: linear_index for (linear_index, chimera_index) in tile.nodes(data='chimera_index') } # Count the connections between these qubits def _between(qubits1, qubits2): edges = [ edge for edge in system.edges if edge[0] in qubits1 and edge[1] in qubits2 ] return len(edges) # Get the list of qubits in a cell def _cell_qubits(s, i, j): return [ c2i[(s, i, j, u, k)] for u in range(2) for k in range(t) if (s, i, j, u, k) in c2i ] # get a mask of complete cells cells = [[[False for _ in range(n)] for _ in range(m)] for _ in range(num_sublattices)] for s in range(num_sublattices): for i in range(m): for j in range(n): qubits = _cell_qubits(s, i, j) cells[s][i][j] = (len(qubits) == nodes_per_cell and _between(qubits, qubits) == edges_per_cell) # List of 'embeddings' self.embeddings = properties['embeddings'] = embeddings = [] # For each possible chimera cell check if the next few cells are complete for s in range(num_sublattices): for i in range(m + 1 - sub_m): for j in range(n + 1 - sub_n): # Check if the sub cells are matched match = all(cells[s][i + sub_i][j + sub_j] for sub_i in range(sub_m) for sub_j in range(sub_n)) # Check if there are connections between the cells. # Both Pegasus and Chimera have t vertical and t horizontal between cells: for sub_i in range(sub_m): for sub_j in range(sub_n): if sub_m > 1 and sub_i < sub_m - 1: match &= _between( _cell_qubits(s, i + sub_i, j + sub_j), _cell_qubits(s, i + sub_i + 1, j + sub_j)) == t if sub_n > 1 and sub_j < sub_n - 1: match &= _between( _cell_qubits(s, i + sub_i, j + sub_j), _cell_qubits(s, i + sub_i, j + sub_j + 1)) == t if match: # Pull those cells out into an embedding. embedding = {} for sub_i in range(sub_m): for sub_j in range(sub_n): cells[s][i + sub_i][ j + sub_j] = False # Mark cell as matched for u in range(2): for k in range(t): embedding[sub_c2i[sub_i, sub_j, u, k]] = { c2i[(s, i + sub_i, j + sub_j, u, k)] } self.embeddings.append(embedding) if len(self.embeddings) == 0: raise ValueError("no tile embeddings found; " "is the sampler Pegasus or Chimera structured?")
def __init__(self, broken_nodes=None, broken_edges=None, topology_type='chimera', topology_shape=None, **config): if topology_type == 'zephyr': if topology_shape is None: topology_shape = [2, 4] elif len(topology_shape) != 2: raise ValueError('topology_shape must be a 2-value ' 'list for Zephyr') # Z2 for small manageable (but non-trivial) default. # Z15 full scale. solver_graph = dnx.zephyr_graph(topology_shape[0], topology_shape[1]) elif topology_type == 'pegasus': if topology_shape is None: topology_shape = [3] elif len(topology_shape) != 1: raise ValueError('topology_shape must be a single-value ' 'list for Pegasus') # P3 fabric_only for small manageable (but non-trivial) default. # P16 full scale. solver_graph = dnx.pegasus_graph(topology_shape[0], fabric_only=True) elif topology_type == 'chimera': if topology_shape is None: topology_shape = [4, 4, 4] elif len(topology_shape) != 3: raise ValueError('topology_shape must be 3-value list ' 'for Chimera') # solver_graph for small manageable (but non-trivial) default. # C16 full scale. solver_graph = dnx.chimera_graph(topology_shape[0], topology_shape[1], topology_shape[2]) else: raise ValueError("Only 'chimera', 'pegasus' and 'zephyr' " "topologies are supported") if broken_nodes is None and broken_edges is None: self.nodelist = sorted(solver_graph.nodes) self.edgelist = sorted( tuple(sorted(edge)) for edge in solver_graph.edges) else: if broken_nodes is None: broken_nodes = [] self.nodelist = sorted( set(solver_graph.nodes).difference(broken_nodes)) if broken_edges == None: broken_edges = [] self.edgelist = sorted( tuple(sorted((u, v))) for u, v in solver_graph.edges if u not in broken_nodes and v not in broken_nodes and ( u, v) not in broken_edges and (v, u) not in broken_edges) # mark the sample kwargs self.parameters = parameters = {} parameters['num_reads'] = ['num_reads_range'] parameters['flux_biases'] = ['j_range'] parameters['label'] = [] # add the interesting properties manually self.properties = properties = {} properties['j_range'] = [-1.0, 1.0] properties['h_range'] = [-2.0, 2.0] properties['num_reads_range'] = [1, 10000] properties['num_qubits'] = len(solver_graph) properties['category'] = 'qpu' properties['quota_conversion_rate'] = 1 properties['topology'] = { 'type': topology_type, 'shape': topology_shape } properties['chip_id'] = 'MockDWaveSampler' properties['annealing_time_range'] = [1.0, 2000.0] properties['num_qubits'] = len(self.nodelist) properties['extended_j_range'] = [-2.0, 1.0] properties["supported_problem_types"] = ['ising', 'qubo'] # add some occasionally useful properties properties["default_annealing_time"] = 20.0 properties["default_programming_thermalization"] = 1000.0 properties["default_readout_thermalization"] = 0.0 properties["h_gain_schedule_range"] = [-4.0, 4.0] properties["max_anneal_schedule_points"] = 12 properties["max_h_gain_schedule_points"] = 20 properties["per_qubit_coupling_range"] = [-18.0, 15.0] properties["problem_run_duration_range"] = [0.0, 1000000.0] properties["programming_thermalization_range"] = [0.0, 10000.0] properties["readout_thermalization_range"] = [0.0, 10000.0] properties["qubits"] = self.nodelist.copy() properties["couplers"] = self.edgelist.copy()