From 1858efe78a0a8b6e5e03156dc32962e1436b9441 Mon Sep 17 00:00:00 2001 From: Jack Raymond <10591246+jackraymond@users.noreply.github.com> Date: Thu, 21 May 2026 12:05:39 -0700 Subject: [PATCH 1/5] Add 12 line helper function --- dwave/experimental/multicolor_anneal/utils.py | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/dwave/experimental/multicolor_anneal/utils.py b/dwave/experimental/multicolor_anneal/utils.py index 5d73b2c..77979f2 100644 --- a/dwave/experimental/multicolor_anneal/utils.py +++ b/dwave/experimental/multicolor_anneal/utils.py @@ -19,7 +19,9 @@ __all__ = ["qubit_to_Advantage2_annealing_line", "make_tds_graph"] -def qubit_to_Advantage2_annealing_line(n: int | tuple, shape: tuple) -> int: +def qubit_to_Advantage2_annealing_line( + n: int | tuple, shape: tuple, num_lines: int = 6 +) -> int: """Return the annealing line associated to an Advantage2 qubit Advantage2 processors can allow for multicolor annealing based in @@ -34,35 +36,40 @@ def qubit_to_Advantage2_annealing_line(n: int | tuple, shape: tuple) -> int: n: qubit label, as an integer, or a Zephyr coordinate as a 5-tuple shape: Advantage2 processor shape, accessible as a solver property properties['topology']['shape'] + num_lines: number of annealing lines, may be 6 or 12. Returns: Integer annealing line assignment for Advantage2 processors - using 6-annealing line control. + using 6 or 12-annealing line control. Examples: Retrieve MCA annealing lines' properties for a default solver, and - if a 6 color scheme is used confirm the programmatic mapping is + if a 6 (or 12) color scheme is used confirm the programmatic mapping is in agreement with the multicolor annealing properties on all qubits and lines >>> from dwave.system import DWaveSampler >>> import dwave.experimental.multicolor_anneal as mca - >>> qpu = DWaveSampler() # doctest: +SKIP >>> annealing_lines = mca.get_properties(qpu) # doctest: +SKIP - >>> if len(annealing_lines) == 6: # doctest: +SKIP - >>> assert(all(mca.qubit_to_Advantage2_annealing_line(n)==al_idx for al_idx, al in enumerate(annealing_lines) for n in al['qubits'])) # doctest: +SKIP + >>> shape = shape=qpu.properties['topology']['shape'] + >>> num_lines = len(annealing_lines) # doctest: +SKIP + >>> assert(all(mca.qubit_to_Advantage2_annealing_line(n, shape, num_lines)==al_idx for al_idx, al in enumerate(annealing_lines) for n in al['qubits'])) # doctest: +SKIP To explicitly select a solver that supports advanced annealing features, such as multi-color annealing, see :attr:`~dwave.experimental.fast_reverse_anneal.api.SOLVER_FILTER`. """ if isinstance(n, tuple): - u, w, k, j, z = n + u, _, _, j, z = n else: - u, w, k, j, z = zephyr_coordinates(*shape).linear_to_zephyr(n) - - return 3 * u + (1 - 2 * z - j) % 3 + u, _, _, j, z = zephyr_coordinates(*shape).linear_to_zephyr(n) + if num_lines == 6: + return 3 * u + (1 - 2 * z - j) % 3 + elif num_lines == 12: + return 6 * u + (z % 3 + 3 * j) + else: + raise ValueError("num_lines must be 6 or 12") def make_tds_graph( From 325a6cb8edbbe4c0110810a8c58c8e34e1ec556a Mon Sep 17 00:00:00 2001 From: Jack Raymond <10591246+jackraymond@users.noreply.github.com> Date: Thu, 21 May 2026 12:13:51 -0700 Subject: [PATCH 2/5] Update example, tested both with and without client on 12 and 6-line systems --- examples/mca_embedding.py | 97 ++++++++++++++++++++++++++++----------- 1 file changed, 69 insertions(+), 28 deletions(-) diff --git a/examples/mca_embedding.py b/examples/mca_embedding.py index c7b8392..38a2aa7 100644 --- a/examples/mca_embedding.py +++ b/examples/mca_embedding.py @@ -40,9 +40,11 @@ def main( use_client: bool = False, solver: dict | str | None = None, - detector_line: int = 0, - source_line: int = 3, + detector_lines: None | tuple[int, ...] = None, + source_lines: None | tuple[int, ...] = None, biclique_target_lines: set | None = None, + profile: str | None = None, + num_lines: int | None = None, ): """Examples of multicolor annealing. @@ -69,33 +71,52 @@ def main( using a defect-free Advantage2 processor graph at a Zephyr[6] scale, using the standard 6-anneal line scheme. solver: Name of the solver, or dictionary of characteristics. - detector_line: The integer index of the detector line. - source_line: The integer index of the source line. + profile: The name of the D-Wave client profile to use. + detector_lines: The integer indices of the detector lines. + By default 0 for a 6 annealing line scheme, and (0, 6) + for a 12 annealing line scheme. + source_lines: The integer indices of the source lines. + By default 3 for a 6 annealing line scheme, and (3, 9) + for a 12 annealing line scheme. biclique_target_lines: a pair of lines used for embedding a a biclique. The remaining lines are used as detectors. Subject to device yield limitations, a biclique of size up to K_{8,8} is possible. {2,3} by default. Raises: ValueError: If the number of lines is less than 3, or - if ``detector_line`` or ``source_line`` is not in + if ``detector_lines`` or ``source_lines`` is not in range [0, ``num_lines``). """ # when available, use feature-based search to default the solver. if use_client: - qpu = DWaveSampler(solver=solver) + qpu = DWaveSampler(solver=solver, profile=profile) annealing_lines = get_properties(qpu) line_assignments = { n: al_idx for al_idx, al in enumerate(annealing_lines) for n in al["qubits"] } - num_lines = len(annealing_lines) + if num_lines is None: + num_lines = len(annealing_lines) + elif num_lines != len(annealing_lines): + raise ValueError( + f"num_lines {num_lines} does not match the number of annealing lines {len(annealing_lines)} for the selected solver." + ) else: shape = [6, 4] qpu = MockDWaveSampler(topology_type="zephyr", topology_shape=[6, 4]) + if num_lines is None: + num_lines = 6 line_assignments = { - n: qubit_to_Advantage2_annealing_line(n, shape) for n in qpu.nodelist + n: qubit_to_Advantage2_annealing_line(n, shape, num_lines) + for n in qpu.nodelist } - num_lines = 6 + if num_lines == 6: + detector_lines = (0,) + source_lines = (3,) + else: + detector_lines = (0, 4) + source_lines = (6, 10) + if biclique_target_lines is None: biclique_target_lines = {0, 3} # Plot the colored graph: @@ -106,9 +127,9 @@ def main( def T_line_assignments(n: int): line = line_assignments[n] - if line == detector_line: + if line in detector_lines: return "detector" - elif line == source_line: + elif line in source_lines: return "source" else: return "target" @@ -117,18 +138,18 @@ def T_line_assignments(n: int): node_color = [colors[line_assignments[n]] for n in T.nodes()] plt.figure("Annealing lines") draw_zephyr(T, node_color=node_color, node_size=5, edge_color="grey") - print( "Embed many single-target variable problems, with a source and detector" - " associated to every qubit." + " associated to every qubit. A timeout of 60 seconds is set, but" + " typically less time is required." ) target_graph = nx.Graph() target_graph.add_node(0) S, Snode_to_tds = make_tds_graph(target_graph) - subgraph_kwargs = dict(node_labels=(Snode_to_tds, Tnode_to_tds), as_embedding=True) + subgraph_kwargs = dict(node_labels=(Snode_to_tds, Tnode_to_tds), as_embedding=True, timeout=60) embs = find_multiple_embeddings( - S, T, max_num_emb=None, embedder_kwargs=subgraph_kwargs, one_to_iterable=True + S, T, max_num_emb=None, embedder_kwargs=subgraph_kwargs, one_to_iterable=True, timeout=60 ) used_nodes = {v[0] for emb in embs for v in emb.values()} node_color = [ @@ -140,6 +161,8 @@ def T_line_assignments(n: int): print( "Embed a loop of length 64, with a source and detector " "associated to every qubit Embedding for a ring of length L." + " A timeout of 60 seconds is used, but typically less time is" + " required." ) L = 64 target_graph = nx.from_edgelist((i, (i + 1) % L) for i in range(L)) @@ -148,8 +171,8 @@ def T_line_assignments(n: int): plt.figure("A loop decorated with sources and detectors") colors_S = { "target": "k", - "source": colors[source_line], - "detector": colors[detector_line], + "source": colors[source_lines[0]], + "detector": colors[detector_lines[0]], } def loop_pos(n, n_range): @@ -162,7 +185,6 @@ def loop_pos(n, n_range): mult * np.cos(2 * np.pi * n[1] / n_range), mult * np.sin(2 * np.pi * n[1] / n_range), ) - else: return (np.cos(2 * np.pi * n / n_range), np.sin(2 * np.pi * n / n_range)) @@ -171,16 +193,21 @@ def loop_pos(n, n_range): nx.draw_networkx( S, node_color=node_color, pos=pos, with_labels=False, node_size=640 / L ) - - subgraph_kwargs = dict(node_labels=(Snode_to_tds, Tnode_to_tds), as_embedding=True) + subgraph_kwargs = dict( + node_labels=(Snode_to_tds, Tnode_to_tds), as_embedding=True, timeout=60 + ) emb = find_subgraph(S, T, **subgraph_kwargs) used_nodes = {v[0] for v in emb.values()} node_color = [ colors[line_assignments[n]] if n in used_nodes else "grey" for n in T.nodes() ] - plt.figure("The embedded loop with detectors and sources") - draw_parallel_embeddings(T, embeddings=[emb], S=S, node_color=node_color) - + if emb: + plt.figure("The embedded loop with detectors and sources") + draw_parallel_embeddings(T, embeddings=[emb], S=S, node_color=node_color) + else: + raise RuntimeError( + "Embedding failed for 64, perhaps try something smaller or increasing timeout." + ) print( "Identify nodes that are each attached to at least one source and one" " detector. These might be shared amongst target nodes of a complex" @@ -189,8 +216,8 @@ def loop_pos(n, n_range): def has_source_and_detector(T, n): return any( - line_assignments[nn] == detector_line for nn in T.neighbors(n) - ) and any(line_assignments[nn] == source_line for nn in T.neighbors(n)) + line_assignments[nn] in detector_lines for nn in T.neighbors(n) + ) and any(line_assignments[nn] in source_lines for nn in T.neighbors(n)) Tsub = T.subgraph( {n for n in T.nodes() if has_source_and_detector(T, n)} @@ -201,8 +228,8 @@ def has_source_and_detector(T, n): node_color = [ ( colors[line_assignments[n]] - if line_assignments[n] == detector_line - or line_assignments[n] == source_line + if line_assignments[n] in detector_lines + or line_assignments[n] in source_lines else "grey" ) for n in T.nodes() @@ -256,12 +283,26 @@ def has_source_and_detector(T, n): parser.add_argument( "--solver_name", type=str, - help="Option to specify QPU solver when use_client is True, by default an experimental system supporting fast reverse anneal", + help="Option to specify QPU solver when use_client is True, by default an experimental system supporting experimental features.", default=SOLVER_FILTER, ) + parser.add_argument( + "--profile", + type=str, + help="Option to specify QPU profile when use_client is True, by default None.", + default=None, + ) + parser.add_argument( + "--num_lines", + type=int, + help="Option to specify the number of annealing lines when use_client is True, by default None.", + default=None, + ) args = parser.parse_args() main( use_client=args.use_client, solver=args.solver_name, + profile=args.profile, + num_lines=args.num_lines, ) From 7f16a7f94acc71f3b42b88c8a567eeaee59e91c5 Mon Sep 17 00:00:00 2001 From: Jack Raymond <10591246+jackraymond@users.noreply.github.com> Date: Thu, 21 May 2026 13:53:37 -0700 Subject: [PATCH 3/5] Removed profile argument, export DWAVE_PROFILE=vpn; python mca_embedding.py .. is an improvement --- examples/mca_embedding.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/examples/mca_embedding.py b/examples/mca_embedding.py index 38a2aa7..a8bfd6c 100644 --- a/examples/mca_embedding.py +++ b/examples/mca_embedding.py @@ -43,7 +43,6 @@ def main( detector_lines: None | tuple[int, ...] = None, source_lines: None | tuple[int, ...] = None, biclique_target_lines: set | None = None, - profile: str | None = None, num_lines: int | None = None, ): """Examples of multicolor annealing. @@ -71,7 +70,6 @@ def main( using a defect-free Advantage2 processor graph at a Zephyr[6] scale, using the standard 6-anneal line scheme. solver: Name of the solver, or dictionary of characteristics. - profile: The name of the D-Wave client profile to use. detector_lines: The integer indices of the detector lines. By default 0 for a 6 annealing line scheme, and (0, 6) for a 12 annealing line scheme. @@ -90,7 +88,7 @@ def main( # when available, use feature-based search to default the solver. if use_client: - qpu = DWaveSampler(solver=solver, profile=profile) + qpu = DWaveSampler(solver=solver) annealing_lines = get_properties(qpu) line_assignments = { n: al_idx for al_idx, al in enumerate(annealing_lines) for n in al["qubits"] @@ -286,12 +284,6 @@ def has_source_and_detector(T, n): help="Option to specify QPU solver when use_client is True, by default an experimental system supporting experimental features.", default=SOLVER_FILTER, ) - parser.add_argument( - "--profile", - type=str, - help="Option to specify QPU profile when use_client is True, by default None.", - default=None, - ) parser.add_argument( "--num_lines", type=int, @@ -303,6 +295,5 @@ def has_source_and_detector(T, n): main( use_client=args.use_client, solver=args.solver_name, - profile=args.profile, num_lines=args.num_lines, ) From d6f209807e0f6ccebe3f14a3b4d13975f52518b5 Mon Sep 17 00:00:00 2001 From: Jack Raymond <10591246+jackraymond@users.noreply.github.com> Date: Tue, 26 May 2026 16:58:33 -0700 Subject: [PATCH 4/5] Apply suggestions from code review Co-authored-by: Radomir Stevanovic --- dwave/experimental/multicolor_anneal/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dwave/experimental/multicolor_anneal/utils.py b/dwave/experimental/multicolor_anneal/utils.py index 77979f2..ed88f24 100644 --- a/dwave/experimental/multicolor_anneal/utils.py +++ b/dwave/experimental/multicolor_anneal/utils.py @@ -52,7 +52,7 @@ def qubit_to_Advantage2_annealing_line( >>> import dwave.experimental.multicolor_anneal as mca >>> qpu = DWaveSampler() # doctest: +SKIP >>> annealing_lines = mca.get_properties(qpu) # doctest: +SKIP - >>> shape = shape=qpu.properties['topology']['shape'] + >>> shape = qpu.properties['topology']['shape'] # doctest: +SKIP >>> num_lines = len(annealing_lines) # doctest: +SKIP >>> assert(all(mca.qubit_to_Advantage2_annealing_line(n, shape, num_lines)==al_idx for al_idx, al in enumerate(annealing_lines) for n in al['qubits'])) # doctest: +SKIP From f4b2550961be8781e0ea5a04c1f2b1bba58c5433 Mon Sep 17 00:00:00 2001 From: Jack Raymond <10591246+jackraymond@users.noreply.github.com> Date: Tue, 26 May 2026 17:06:08 -0700 Subject: [PATCH 5/5] Add release note, make L=64 a documented function argument --- examples/mca_embedding.py | 21 ++++++++++++------- ...-12-line-mca-support-b700e512b19eb693.yaml | 10 +++++++++ 2 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/add-12-line-mca-support-b700e512b19eb693.yaml diff --git a/examples/mca_embedding.py b/examples/mca_embedding.py index a8bfd6c..f6e7674 100644 --- a/examples/mca_embedding.py +++ b/examples/mca_embedding.py @@ -44,6 +44,7 @@ def main( source_lines: None | tuple[int, ...] = None, biclique_target_lines: set | None = None, num_lines: int | None = None, + L: int = 64, ): """Examples of multicolor annealing. @@ -51,7 +52,7 @@ def main( qubit problem in many places on the processor, such that every qubit is connected to a source and detector qubit on the specified lines. - The second example embeds a loop of 64 target qubits with each target coupled + The second example embeds a loop of target qubits with each target coupled to a source and detector qubit. The third example shows embedding of a chimera like graph where every target node has a coupling available to a source and target line. Sources @@ -144,10 +145,17 @@ def T_line_assignments(n: int): target_graph = nx.Graph() target_graph.add_node(0) S, Snode_to_tds = make_tds_graph(target_graph) - subgraph_kwargs = dict(node_labels=(Snode_to_tds, Tnode_to_tds), as_embedding=True, timeout=60) + subgraph_kwargs = dict( + node_labels=(Snode_to_tds, Tnode_to_tds), as_embedding=True, timeout=60 + ) embs = find_multiple_embeddings( - S, T, max_num_emb=None, embedder_kwargs=subgraph_kwargs, one_to_iterable=True, timeout=60 + S, + T, + max_num_emb=None, + embedder_kwargs=subgraph_kwargs, + one_to_iterable=True, + timeout=60, ) used_nodes = {v[0] for emb in embs for v in emb.values()} node_color = [ @@ -157,12 +165,11 @@ def T_line_assignments(n: int): draw_parallel_embeddings(T, embeddings=embs, S=S, node_color=node_color) print( - "Embed a loop of length 64, with a source and detector " - "associated to every qubit Embedding for a ring of length L." + f"Embed a loop of length L={L}, with a source and detector " + "associated to every qubit." " A timeout of 60 seconds is used, but typically less time is" " required." ) - L = 64 target_graph = nx.from_edgelist((i, (i + 1) % L) for i in range(L)) S, Snode_to_tds = make_tds_graph(target_graph) @@ -204,7 +211,7 @@ def loop_pos(n, n_range): draw_parallel_embeddings(T, embeddings=[emb], S=S, node_color=node_color) else: raise RuntimeError( - "Embedding failed for 64, perhaps try something smaller or increasing timeout." + f"Embedding failed for loops of length L={L}, perhaps try something smaller or increase the timeout." ) print( "Identify nodes that are each attached to at least one source and one" diff --git a/releasenotes/notes/add-12-line-mca-support-b700e512b19eb693.yaml b/releasenotes/notes/add-12-line-mca-support-b700e512b19eb693.yaml new file mode 100644 index 0000000..9139ca0 --- /dev/null +++ b/releasenotes/notes/add-12-line-mca-support-b700e512b19eb693.yaml @@ -0,0 +1,10 @@ +--- +prelude: > + Advantage2 systems support 12 and 6 line multicolor annealing schemes, now + so does the code base. +features: + - | + New function to map qubits to line assignments without calling the client. +fixes: + - | + Updated examples to support 12 line systems.