def test_row_identity(self):
        C41 = dnx.chimera_graph(4, 1)
        coord = dnx.chimera_coordinates(4, 1, 4)

        labels = dnx.canonical_chimera_labeling(C41)
        labels = {v: coord.chimera_to_linear(labels[v]) for v in labels}

        G = nx.relabel_nodes(C41, labels, copy=True)

        self.assertTrue(nx.is_isomorphic(G, C41))
    def test_3x3_identity(self):
        C33 = dnx.chimera_graph(3, 3)
        coord = dnx.chimera_coordinates(3, 3, 4)

        labels = dnx.canonical_chimera_labeling(C33)
        labels = {v: coord.chimera_to_linear(labels[v]) for v in labels}

        G = nx.relabel_nodes(C33, labels, copy=True)

        self.assertTrue(nx.is_isomorphic(G, C33))
예제 #3
0
 def __init__(self, guest: Graph, host: ChimeraGraph):
     if host.faulty_nodes or host.faulty_edges:
         raise NotImplementedError(
             "Chimera graphs with faults are not supported by these algorithms")
     self.guest = guest
     self.host = host
     m, l = host.params
     dnx_coords = dnx.chimera_coordinates(m, t=l)
     self.linear_to_chimera = dnx_coords.linear_to_chimera
     self.chimera_to_linear = dnx_coords.chimera_to_linear
     self.forward_embed = None
    def test_tile_identity(self):
        C1 = dnx.chimera_graph(1)
        coord = dnx.chimera_coordinates(1, 1, 4)

        labels = dnx.canonical_chimera_labeling(C1)
        labels = {v: coord.chimera_to_linear(labels[v]) for v in labels}

        G = nx.relabel_nodes(C1, labels, copy=True)

        self.assertTrue(nx.is_isomorphic(G, C1))
        self.assertEqual(set(G), set(C1))
    def test_bqm_tile_identity(self):
        J = {e: -1 for e in dnx.chimera_graph(1).edges}
        C1bqm = dimod.BinaryQuadraticModel.from_ising({}, J)
        coord = dnx.chimera_coordinates(1, 1, 4)

        labels = dnx.canonical_chimera_labeling(C1bqm)
        labels = {v: coord.chimera_to_linear(labels[v]) for v in labels}

        bqm = C1bqm.relabel_variables(labels, inplace=False)

        self.assertEqual(bqm, C1bqm)
    def test_reversed(self):
        C33 = nx.OrderedGraph()
        C33.add_nodes_from(reversed(range(3 * 3 * 4)))
        C33.add_edges_from(dnx.chimera_graph(3, 3, 4).edges)
        coord = dnx.chimera_coordinates(3, 3, 4)

        labels = dnx.canonical_chimera_labeling(C33)
        labels = {v: coord.chimera_to_linear(labels[v]) for v in labels}

        G = nx.relabel_nodes(C33, labels, copy=True)

        self.assertTrue(nx.is_isomorphic(G, C33))
예제 #7
0
def double_triangle_clique(chimera_graph: ChimeraGraph) -> Dict[int, List[int]]:
    """
    Performs a double-sided triangle embedding in a similar fashion as described by the PSSA paper.
    'Graph Minors from Simulated Annealing for Annealing Machines with Sparse Connectivity'

    Reference:
        Y. Sugie, Y. Yoshida, N. Mertig, T. Takemoto, H. Teramoto, A. Nakamura, I. Takigawa, S.-i.
        Minato, M.Yamaoka, and T. Komatsuzaki, Minor-embedding heuristics for large-scale
        annealing processors with sparse hardware graphs of up to 102,400 nodes,
        2020. arXiv:2004.03819 [quant-ph]:

    Args:
        chimera_graph: Chimera host to transform.

    Returns: an embedding.
        !NOTE: The vertex sets are represented as lists but the ordering matters.
        The nodes should be ordered according to the chain formed on the hardware hardware.
    """
    m, l = chimera_graph.params
    to_linear = dnx.chimera_coordinates(m, t=l).chimera_to_linear

    # Embed the upper triangular
    top_embed = [[] for _ in range(m * l)]
    for i in range(m * l):
        cell, unit = i // l, i % l
        # Add the nodes above diagonal cell
        for j in range(cell):
            top_embed[i].append(to_linear((j, cell, 0, unit)))
        # Add the two nodes in the diagonal cell
        top_embed[i].extend((to_linear(
            (cell, cell, 0, unit)), to_linear((cell, cell, 1, unit))))
        # Add the nodes to right of diagonal cell
        for j in range(cell + 1, m):
            top_embed[i].append(to_linear((cell, j, 1, unit)))

    # Embed the lower triangular
    bot_embed = [[] for _ in range((m - 1) * l)]
    for i in range((m - 1) * l):
        cell, unit = i // l, i % l
        # Add the nodes to left of diagonal cell
        for j in range(cell):
            bot_embed[i].append(to_linear((cell + 1, j, 1, unit)))
        # Add the two nodes in the diagonal cell
        bot_embed[i].extend((to_linear(
            (cell + 1, cell, 1, unit)), to_linear((cell + 1, cell, 0, unit))))
        # Add the nodes below diagonal cell
        for j in range(cell + 1, m - 1):
            bot_embed[i].append(to_linear((j + 1, cell, 0, unit)))

    combined = top_embed + bot_embed

    return {i: combined[i] for i in range(len(combined))}
예제 #8
0
파일: chimera.py 프로젝트: CharJon/GeCO
def _initialize_weights_chimera(chimera_graph, size, draw_inter_weight,
                                draw_intra_weight, draw_other_weight):
    c_coor = dwave.chimera_coordinates(size)
    for _from, _to in chimera_graph.edges:
        _from_nice = c_coor.linear_to_chimera(_from)
        _to_nice = c_coor.linear_to_chimera(_to)
        if in_same_chimera_tile(_from_nice, _to_nice):
            # edge from one side to the other (internal edge)
            if not on_same_side(_from_nice, _to_nice):
                chimera_graph.add_edge(_from, _to, weight=draw_intra_weight())
            else:  # odd couplers
                chimera_graph.add_edge(_from, _to, weight=draw_other_weight())
        else:
            chimera_graph.add_edge(_from, _to, weight=draw_inter_weight())
예제 #9
0
    def __init__(self, *args, **kwargs):
        super(TestBusclique, self).__init__(*args, **kwargs)

        self.c16 = dnx.chimera_graph(16)
        self.p16 = dnx.pegasus_graph(16)
        self.c16c = dnx.chimera_graph(16, coordinates=True, data=False)
        self.c428 = dnx.chimera_graph(4, n=2, t=8)
        self.c248 = dnx.chimera_graph(2, n=4, t=8)
        self.c42A = dnx.chimera_graph(4, n=2, t=9)
        c4c_0 = subgraph_node_yield(dnx.chimera_graph(4, coordinates=True),
                                    .95)
        p4c_0 = subgraph_node_yield(dnx.pegasus_graph(4, coordinates=True),
                                    .95)
        c4c = [
            c4c_0,
            subgraph_edge_yield(c4c_0, .95),
            subgraph_edge_yield_few_bad(c4c_0, .95, 6)
        ]
        p4c = [
            p4c_0,
            subgraph_edge_yield(p4c_0, .95),
            subgraph_edge_yield_few_bad(p4c_0, .95, 6)
        ]

        p4coords = dnx.pegasus_coordinates(4)
        c4coords = dnx.chimera_coordinates(4, 4, 4)

        c4 = [
            relabel(c, c4coords.iter_chimera_to_linear,
                    c4coords.iter_chimera_to_linear_pairs, 4) for c in c4c
        ]

        p4 = [
            relabel(p, p4coords.iter_pegasus_to_linear,
                    p4coords.iter_pegasus_to_linear_pairs, 4) for p in p4c
        ]

        p4n = [
            relabel(p,
                    p4coords.iter_pegasus_to_nice,
                    p4coords.iter_pegasus_to_nice_pairs,
                    4,
                    nice_coordinates=True) for p in p4c
        ]

        self.c4, self.c4_nd, self.c4_d = list(zip(c4, c4c))
        self.p4, self.p4_nd, self.p4_d = list(zip(p4, p4c, p4n))
예제 #10
0
def bipartite_with_faults(chimera_graph: ChimeraGraph):
    """
    Create a bipartite template embedding which allows for faulty nodes and edges.

    Args:
        chimera_graph: Chimera host to transform.

    Returns: Tuple (left, right) for embedding in each respective partition.
    """

    def append_nonempty(super, sub):
        if sub:
            super.append(sub)

    m, l = chimera_graph.params
    faulty = chimera_graph.faulty_nodes
    to_linear = dnx.chimera_coordinates(m, t=l).chimera_to_linear

    h_embed = []
    for i in range(m * l):
        chain = []
        cell, unit = i // l, i % l
        for j in range(m):
            ln = to_linear((cell, j, 1, unit))
            if ln in faulty:
                append_nonempty(h_embed, chain)
                chain = []
            else:
                chain.append(ln)
        append_nonempty(h_embed, chain)

    v_embed = []
    for i in range(m * l):
        chain = []
        cell, unit = i // l, i % l
        for j in range(m):
            ln = to_linear((j, cell, 0, unit))
            if ln in faulty:
                append_nonempty(v_embed, chain)
                chain = []
            else:
                chain.append(ln)
        append_nonempty(v_embed, chain)

    return h_embed, v_embed
예제 #11
0
    def test_coordinate_generator(self):
        G = dnx.chimera_graph(4, 2, coordinates=True, data=True)
        H = dnx.chimera_graph(4, 2, coordinates=False, data=True)
        self.assertTrue(nx.is_isomorphic(G, H))

        Gnodes = set(G.nodes)
        Glabels = set(n['linear_index'] for n in G.nodes.values())

        Hnodes = set(H.nodes)
        Hlabels = set(n['chimera_index'] for n in H.nodes.values())

        self.assertEqual(Gnodes, Hlabels)
        self.assertEqual(Hnodes, Glabels)

        coords = dnx.chimera_coordinates(2, 1)
        f = nx.relabel_nodes(g, coords.chimera_to_linear, copy=True)
        self.assertEqual(set(f.edges), set(h.edges))

        e = nx.relabel_nodes(h, coords.linear_to_chimera, copy=True)
        self.assertEqual(set(e.edges), set(g.edges))
    def test_construction_string_labels(self):
        C22 = dnx.chimera_graph(2, 2, 3)
        coord = dnx.chimera_coordinates(2, 2, 3)

        alpha = 'abcdefghijklmnopqrstuvwxyz'

        bqm = dimod.BinaryQuadraticModel.empty(dimod.BINARY)

        for u, v in reversed(list(C22.edges)):
            bqm.add_interaction(alpha[u], alpha[v], 1)

        assert len(bqm.quadratic) == len(C22.edges)
        assert len(bqm) == len(C22)

        labels = dnx.canonical_chimera_labeling(bqm)
        labels = {v: alpha[coord.chimera_to_linear(labels[v])] for v in labels}

        bqm2 = bqm.relabel_variables(labels, inplace=False)

        self.assertEqual(bqm, bqm2)
예제 #13
0
def overlap_clique(chimera_graph: ChimeraGraph):
    """
    Returns a clique overlap template embedding as described in 'Template-based minor embedding
    for adiabatic quantum optimization'.

    Reference:
        T. Serra, T. Huang, A. Raghunathan, and D. Bergman, Template-based minor embedding for
        adiabatic quantum optimization, 2019. arXiv:1910.02179 [cs.DS].

    Args:
        chimera_graph: Chimera host to transform.

    Returns: an embedding.
    """
    m, l = chimera_graph.params
    to_linear = dnx.chimera_coordinates(m, t=l).chimera_to_linear

    # Embed the clique major
    top_embed = [[] for _ in range(m * l)]
    for i in range(m * l):
        cell, unit = i // l, i % l
        # Add the nodes above diagonal cell
        for j in range(cell):
            top_embed[i].append(to_linear((j, cell, 0, unit)))
        # Add the two nodes in the diagonal cell
        top_embed[i].extend((to_linear(
            (cell, cell, 0, unit)), to_linear((cell, cell, 1, unit))))
        # Add the entire row
        for j in range(0, m):
            top_embed[i].append(to_linear((cell, j, 1, unit)))

    # Embed the clique minor
    bot_embed = [[] for _ in range((m - 1) * l)]
    for i in range((m - 1) * l):
        cell, unit = i // l, i % l
        for j in range(cell, m - 1):
            bot_embed[i].append(to_linear((j + 1, cell, 0, unit)))

    combined = top_embed + bot_embed

    return {i: combined[i] for i in range(len(combined))}
예제 #14
0
    def test_graph_relabeling(self):
        def graph_equal(g, h):
            self.assertEqual(set(g), set(h))
            self.assertEqual(set(map(tuple, map(sorted, g.edges))),
                             set(map(tuple, map(sorted, g.edges))))
            for v, d in g.nodes(data=True):
                self.assertEqual(h.nodes[v], d)

        coords = dnx.chimera_coordinates(3)
        for data in True, False:
            c3l = dnx.chimera_graph(3, data=data)
            c3c = dnx.chimera_graph(3, data=data, coordinates=True)

            graph_equal(c3l, coords.graph_to_linear(c3c))
            graph_equal(c3l, coords.graph_to_linear(c3l))
            graph_equal(c3c, coords.graph_to_chimera(c3l))
            graph_equal(c3c, coords.graph_to_chimera(c3c))

        h = dnx.chimera_graph(2)
        del h.graph['labels']
        with self.assertRaises(ValueError):
            coords.graph_to_linear(h)
        with self.assertRaises(ValueError):
            coords.graph_to_chimera(h)
예제 #15
0
    def test_nonsquare_coordinate_generator(self):
        #issue 149 found an issue with non-square generators -- let's be extra careful here
        for (m, n) in [(2, 4), (4, 2)]:
            G = dnx.chimera_graph(m, n, coordinates=True, data=True)
            H = dnx.chimera_graph(m, n, coordinates=False, data=True)
            self.assertTrue(nx.is_isomorphic(G, H))

            Gnodes = set(G.nodes)
            Glabels = set(q['linear_index'] for q in G.nodes.values())

            Hnodes = set(H.nodes)
            Hlabels = set(q['chimera_index'] for q in H.nodes.values())

            self.assertEqual(Gnodes, Hlabels)
            self.assertEqual(Hnodes, Glabels)

            coords = dnx.chimera_coordinates(m, n)
            F = nx.relabel_nodes(G, coords.chimera_to_linear, copy=True)
            self.assertEqual(set(map(frozenset, F.edges)),
                             set(map(frozenset, H.edges)))

            E = nx.relabel_nodes(H, coords.linear_to_chimera, copy=True)
            self.assertEqual(set(map(frozenset, E.edges)),
                             set(map(frozenset, G.edges)))
예제 #16
0
def _lookup_intersection_coordinates(G):
    """For a dwave_networkx graph G, this returns a dictionary mapping the 
    lattice points to sets of vertices of G. 
    
    For Chimera, Pegasus and Zephyr, each lattice point corresponds to the 2
    qubits intersecting at that point.
    """
    graph_data = G.graph
    family = graph_data.get("family")
    data_key = None
    intersection_points = defaultdict(set)
    if family == "chimera":
        shore = graph_data.get("tile")
        collect_intersection_points = _chimera_all_intersection_points
        if graph_data["labels"] == "coordinate":

            def get_coords(v):
                return v
        elif graph_data["data"]:
            data_key = "chimera_index"
        else:
            coords = dnx.chimera_coordinates(graph_data['rows'],
                                             n=graph_data['columns'],
                                             t=shore)
            get_coords = coords.linear_to_chimera
    elif family == "pegasus":
        shore = [
            graph_data['vertical_offsets'], graph_data['horizontal_offsets']
        ]
        collect_intersection_points = _pegasus_all_intersection_points
        if graph_data["labels"] == "coordinate":

            def get_coords(v):
                return v
        elif graph_data["data"]:
            data_key = "pegasus_index"
        else:
            coords = dnx.pegasus_coordinates(graph_data['rows'])
            if graph_data['labels'] == 'int':
                get_coords = coords.linear_to_pegasus
            elif graph_data['labels'] == 'nice':
                get_coords = coords.nice_to_pegasus
    elif family == "zephyr":
        shore = graph_data.get("tile")
        collect_intersection_points = _zephyr_all_intersection_points
        if graph_data["labels"] == "coordinate":

            def get_coords(v):
                return v
        elif graph_data["data"]:
            data_key = "zephyr_index"
        else:
            coords = dnx.zephyr_coordinates(graph_data['rows'], t=shore)
            get_coords = coords.linear_to_zephyr

    if data_key is None:
        for v in G:
            collect_intersection_points(intersection_points, shore, v,
                                        *get_coords(v))
    else:
        for v, d in G.nodes(data=True):
            collect_intersection_points(intersection_points, shore, v,
                                        *d[data_key])

    return intersection_points
예제 #17
0
def quadripartite_with_faults(chimera_graph: ChimeraGraph):
    """
    Create a quadripartite template embedding which allows for faulty nodes and edges.

    Args:
        chimera_graph: Chimera host to transform.

    Returns: Tuple (U1, U2, U3, U4) for embedding in each respective partition.
    """

    def append_nonempty(super, sub):
        if sub:
            super.append(sub)

    m, l = chimera_graph.params
    faulty = chimera_graph.faulty_nodes
    to_linear = dnx.chimera_coordinates(m, t=l).chimera_to_linear

    U1, U4 = [], []
    for i in range(m * l):
        chain1, chain4 = [], []
        cell, unit = i // l, i % l
        for j in range(m):
            ln = to_linear((cell, j, 1, unit))
            if ln in faulty:
                if i < m * l / 2:
                    append_nonempty(U1, chain1)
                    chain1 = []
                else:
                    append_nonempty(U4, chain4)
                    chain4 = []
            else:
                if i < m * l / 2:
                    chain1.append(ln)
                else:
                    chain4.append(ln)
        append_nonempty(U1, chain1)
        append_nonempty(U4, chain4)

    U2, U3 = [], []
    for i in range(m * l):
        chain2, chain3 = [], []
        cell, unit = i // l, i % l
        for j in range(m):
            ln = to_linear((j, cell, 0, unit))
            if ln in faulty:
                if j < m / 2:
                    append_nonempty(U2, chain2)
                    chain2 = []
                else:
                    append_nonempty(U3, chain3)
                    chain3 = []
            else:
                if j < m / 2:
                    chain2.append(ln)
                else:
                    chain3.append(ln)
        append_nonempty(U2, chain2)
        append_nonempty(U3, chain3)

    return U1, U2, U3, U4
예제 #18
0
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