Source code for kosmos.partitioning.partition

from dataclasses import dataclass, field

from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister

from kosmos.partitioning.circuit_converter import to_gate_list
from kosmos.topology.node import QuantumNode


[docs] @dataclass class Partition: """Circuit partition with QPU assignment. Attributes: id (str): Identifier for the partition. circuit (QuantumCircuit): Quantum circuit assigned to this partition. logical_qubit_mapping (dict[int, int]): Mapping from logical to physical qubit indices. original_gate_indices (list[int]): Indices of gates from the original circuit that are in this partition. start_time (int | None): Scheduled start time for partition execution, if allocated. node (QuantumNode | None): The quantum node this partition is allocated to, if single-node partition. qubit_to_node_mapping (dict[int, QuantumNode]): Mapping from physical qubits to nodes for multi-node partitions. """ id: str circuit: QuantumCircuit logical_qubit_mapping: dict[int, int] original_gate_indices: list[int] = None start_time: int | None = None node: QuantumNode | None = None qubit_to_node_mapping: dict[int, QuantumNode] = field(default_factory=dict) def __post_init__(self) -> None: """Set default value for original_gate_indices.""" if self.original_gate_indices is None: self.original_gate_indices = [] @property def min_gate_index(self) -> int: """Get earliest gate index from original circuit. Returns: int: Minimum gate index, or infinity if no gates. """ return min(self.original_gate_indices) if self.original_gate_indices else float("inf") @property def physical_qubits_used(self) -> set[int]: """Get set of physical qubits used by this partition. Returns: set[int]: Qubit indices from the original circuit. """ return set(self.logical_qubit_mapping.values()) @property def is_multi_node(self) -> bool: """Check if partition is allocated acress multiple nodes. Returns: bool: True if partitions spans mutliple nodes. """ return bool(self.qubit_to_node_mapping) @property def allocated_nodes(self) -> set[QuantumNode]: """Get all nodes this partition is allocated to. Returns: set[QuantumNode]: Set of nodes hosting qubits from this partition. """ if self.is_multi_node: return set(self.qubit_to_node_mapping.values()) if self.node is not None: return {self.node} return set()
[docs] def get_node_for_qubit(self, physical_qubit: int) -> QuantumNode | None: """Get the node hosting a specific physical qubit. Args: physical_qubit (int): Physical qubit index. Returns: QuantumNode | None: Node hosting the qubit, or None if not allocated. """ if self.is_multi_node: return self.qubit_to_node_mapping.get(physical_qubit) return self.node
@property def end_time(self) -> int | None: """Calculate end time based on start time and circuit depth. Returns: int | None: Time the partition finishes, if a start time is given. """ if self.start_time is None: return None return self.start_time + self.circuit.depth()
[docs] def create_subcircuit_from_gates( gate_list: list[dict], ) -> tuple[QuantumCircuit, dict[int, int]]: """Create a subcircuit from a list of gates. Args: gate_list (list[dict]): List of gate dictionaries. Returns: tuple[QuantumCircuit, dict[int, int]]: Subcircuit and logical to physical qubit mapping. """ qubits_used = set() for gate in gate_list: qubits_used.update(gate["qubits"]) logical_to_physical = dict(enumerate(sorted(qubits_used))) physical_to_logical = {phys: log for log, phys in logical_to_physical.items()} num_qubits = len(qubits_used) q = QuantumRegister(num_qubits, "q") c = ClassicalRegister(num_qubits, "c") subcircuit = QuantumCircuit(q, c) for gate in gate_list: logical_qubits = [physical_to_logical[q] for q in gate["qubits"]] gate_method = getattr(subcircuit, gate["name"]) params = gate.get("params", []) gate_method(*params, *logical_qubits) return subcircuit, logical_to_physical
[docs] def partitions_from_assignments( circuit: QuantumCircuit, assignments: dict[int, int], ) -> list[Partition]: """Convert partitioning assignments to Partition objects. Args: circuit (QuantumCircuit): Original circuit. assignments (dict[int, int]): Gate index to partition ID mapping. Returns: list[Partition]: List of Partition objects with subcircuits. """ gates = to_gate_list(circuit) qubit_to_partition: dict[int, int] = {} for gate_idx, partition_id in assignments.items(): gate = gates[gate_idx] for qubit in gate["qubits"]: if qubit not in qubit_to_partition: qubit_to_partition[qubit] = partition_id complete_assignments = dict(assignments) for gate_idx, gate in enumerate(gates): if gate_idx not in complete_assignments: gate_qubits = gate["qubits"] if len(gate_qubits) == 1: qubit = gate_qubits[0] complete_assignments[gate_idx] = qubit_to_partition.get(qubit, 0) else: control_qubit = gate_qubits[0] complete_assignments[gate_idx] = qubit_to_partition.get(control_qubit, 0) partition_gates: dict[int, list[tuple[int, dict]]] = {} for gate_idx, partition_id in complete_assignments.items(): if partition_id not in partition_gates: partition_gates[partition_id] = [] partition_gates[partition_id].append((gate_idx, gates[gate_idx])) partitions = [] for partition_id in sorted(partition_gates.keys()): gate_indices_and_gates = partition_gates[partition_id] gate_indices_and_gates.sort(key=lambda x: x[0]) gate_indices = [g[0] for g in gate_indices_and_gates] gate_list = [g[1] for g in gate_indices_and_gates] subcircuit, logical_to_physical = create_subcircuit_from_gates(gate_list) partitions.append( Partition( id=f"partition_{partition_id}", circuit=subcircuit, logical_qubit_mapping=logical_to_physical, original_gate_indices=gate_indices, ) ) return partitions