1
+ """
2
+ DD strategies, sequences, and coloring assignments.
3
+ """
4
+
1
5
from dataclasses import dataclass
2
6
from typing import List , Dict , Optional , Union
7
+
8
+ import numpy as np
9
+ import rustworkx as rx
10
+
3
11
from qiskit .circuit .library import IGate , XGate , YGate , U1Gate , RZGate
4
12
from qiskit import QuantumCircuit
5
- import numpy as np
6
13
7
14
8
15
# Default gateset for DD pulses
@@ -211,34 +218,127 @@ def from_dict(cls, data: Dict[str, any]) -> "DDStrategy":
211
218
212
219
213
220
class ColorAssignment :
214
- """Assignment of device qubits to colors."""
221
+ """Assignment of device qubits to colors based on connectivity graph ."""
215
222
216
- def __init__ (self , assignments : Dict [int , List [int ]]):
223
+ def __init__ (
224
+ self , graph : Optional [rx .PyGraph ] = None , backend : Optional ["Backend" ] = None
225
+ ):
217
226
"""
218
227
Initialize color assignment.
219
228
220
229
Args:
221
- assignments: Dictionary mapping colors to lists of qubit indices
230
+ graph: Connectivity graph where nodes are qubits and edges are connections.
231
+ If None and backend is provided, will extract from backend.
232
+ backend: Backend to extract connectivity from if graph not provided.
233
+
234
+ Raises:
235
+ ValueError: If neither graph nor backend is provided.
222
236
"""
223
- self .assignments = assignments
224
- self ._validate ()
237
+ if graph is None and backend is None :
238
+ raise ValueError ("Must provide either graph or backend" )
239
+
240
+ if graph is None :
241
+ # Extract from backend
242
+ if hasattr (backend , "coupling_map" ) and backend .coupling_map :
243
+ self .graph = backend .coupling_map .graph .to_undirected ()
244
+ else :
245
+ # Fallback: create complete graph
246
+ n_qubits = backend .num_qubits if hasattr (backend , "num_qubits" ) else 1
247
+ self .graph = rx .PyGraph ()
248
+ self .graph .add_nodes_from (range (n_qubits ))
249
+ else :
250
+ self .graph = graph
251
+
252
+ # Perform graph coloring
253
+ self ._color_map = rx .graph_greedy_color (self .graph )
254
+
255
+ # Create assignments dictionary (color -> list of qubits)
256
+ self .assignments = {}
257
+ for qubit , color in self ._color_map .items ():
258
+ if color not in self .assignments :
259
+ self .assignments [color ] = []
260
+ self .assignments [color ].append (qubit )
261
+
225
262
# Create reverse mapping for efficiency
226
- self ._qubit_to_color = {}
263
+ self ._qubit_to_color = self ._color_map .copy ()
264
+
265
+ @classmethod
266
+ def from_circuit (cls , circuit : QuantumCircuit ) -> "ColorAssignment" :
267
+ """
268
+ Create color assignment from circuit connectivity.
269
+
270
+ Args:
271
+ circuit: Quantum circuit to extract connectivity from.
272
+
273
+ Returns:
274
+ ColorAssignment based on circuit structure.
275
+ """
276
+ # Build connectivity graph from circuit
277
+ graph = rx .PyGraph ()
278
+ qubits = set ()
279
+ edges = set ()
280
+
281
+ for instruction in circuit .data :
282
+ if instruction .operation .name in ["cx" , "ecr" , "cz" ]: # Two-qubit gates
283
+ qubits_involved = [q ._index for q in instruction .qubits ]
284
+ if len (qubits_involved ) == 2 :
285
+ q1 , q2 = qubits_involved
286
+ qubits .add (q1 )
287
+ qubits .add (q2 )
288
+ edges .add ((min (q1 , q2 ), max (q1 , q2 )))
289
+ else :
290
+ # Track all qubits
291
+ for q in instruction .qubits :
292
+ qubits .add (q ._index )
293
+
294
+ # Add all qubits as nodes
295
+ graph .add_nodes_from (sorted (qubits ))
296
+
297
+ # Add edges
298
+ for q1 , q2 in edges :
299
+ graph .add_edge (q1 , q2 , None )
300
+
301
+ return cls (graph = graph )
302
+
303
+ @classmethod
304
+ def from_manual_assignment (
305
+ cls , assignments : Dict [int , List [int ]]
306
+ ) -> "ColorAssignment" :
307
+ """
308
+ Create from manual color assignments.
309
+
310
+ Args:
311
+ assignments: Dictionary mapping colors to lists of qubit indices.
312
+
313
+ Returns:
314
+ ColorAssignment with specified assignments.
315
+ """
316
+ # Create a graph where qubits with different colors are connected
317
+ graph = rx .PyGraph ()
318
+ all_qubits = set ()
319
+ for qubits in assignments .values ():
320
+ all_qubits .update (qubits )
321
+
322
+ graph .add_nodes_from (sorted (all_qubits ))
323
+
324
+ # Connect qubits with different colors
325
+ colors = list (assignments .keys ())
326
+ for i in range (len (colors )):
327
+ for j in range (i + 1 , len (colors )):
328
+ for q1 in assignments [colors [i ]]:
329
+ for q2 in assignments [colors [j ]]:
330
+ graph .add_edge (q1 , q2 , None )
331
+
332
+ # Create instance and override the computed coloring
333
+ instance = cls (graph = graph )
334
+ instance .assignments = assignments
335
+ instance ._qubit_to_color = {}
227
336
for color , qubits in assignments .items ():
228
337
for qubit in qubits :
229
- self ._qubit_to_color [qubit ] = color
338
+ instance ._qubit_to_color [qubit ] = color
339
+ instance ._color_map = instance ._qubit_to_color .copy ()
230
340
231
- def _validate (self ):
232
- """Validate color assignments."""
233
- # Check for overlapping qubit assignments
234
- assigned = set ()
235
- for color , qubits in self .assignments .items ():
236
- if not isinstance (qubits , list ):
237
- raise TypeError (f"Qubits for color { color } must be a list" )
238
- overlap = assigned .intersection (qubits )
239
- if overlap :
240
- raise ValueError (f"Qubits { overlap } assigned to multiple colors" )
241
- assigned .update (qubits )
341
+ return instance
242
342
243
343
def get_color (self , qubit : int ) -> Optional [int ]:
244
344
"""Get color assigned to a qubit."""
@@ -252,3 +352,24 @@ def get_qubits(self, color: int) -> List[int]:
252
352
def n_colors (self ) -> int :
253
353
"""Number of colors used in assignment."""
254
354
return len (self .assignments )
355
+
356
+ def to_dict (self ) -> Dict [int , int ]:
357
+ """
358
+ Convert to qubit->color mapping dictionary.
359
+
360
+ Returns:
361
+ Dictionary mapping qubit indices to color values.
362
+ """
363
+ return self ._color_map .copy ()
364
+
365
+ def validate_coloring (self ) -> bool :
366
+ """
367
+ Validate that the coloring is proper (no adjacent nodes have same color).
368
+
369
+ Returns:
370
+ True if coloring is valid, False otherwise.
371
+ """
372
+ for edge in self .graph .edge_list ():
373
+ if self ._color_map [edge [0 ]] == self ._color_map [edge [1 ]]:
374
+ return False
375
+ return True
0 commit comments