From d7b99393510082963f0ad4ef01a634de00a68566 Mon Sep 17 00:00:00 2001 From: Ed Younis Date: Mon, 2 Oct 2023 17:55:55 -0400 Subject: [PATCH 01/23] Implemented update_error_mul --- bqskit/compiler/passdata.py | 4 ++++ tests/compiler/test_data.py | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/bqskit/compiler/passdata.py b/bqskit/compiler/passdata.py index a615b1174..e5905b6df 100644 --- a/bqskit/compiler/passdata.py +++ b/bqskit/compiler/passdata.py @@ -262,3 +262,7 @@ def become(self, other: PassData, deepcopy: bool = False) -> None: self._placement = copy.copy(other._placement) self._data = copy.copy(other._data) self._seed = copy.copy(other._seed) + + def update_error_mul(self, error: float) -> None: + """Update the error multiplicatively.""" + self.error = (1 - ((1 - self.error) * (1 - error))) diff --git a/tests/compiler/test_data.py b/tests/compiler/test_data.py index a525c2449..075a95c82 100644 --- a/tests/compiler/test_data.py +++ b/tests/compiler/test_data.py @@ -1,6 +1,7 @@ from __future__ import annotations from bqskit.compiler.passdata import PassData +from bqskit.ir.circuit import Circuit from bqskit.ir.lang.qasm2.qasm2 import OPENQASM2Language @@ -14,3 +15,14 @@ def test_measures_doesnt_error() -> None: """ circuit = OPENQASM2Language().decode(input) _ = PassData(circuit) + + +def test_update_error_mul() -> None: + data = PassData(Circuit(1)) + assert data.error == 0.0 + data.update_error_mul(0.5) + assert data.error == 0.5 + data.update_error_mul(0.5) + assert data.error == 0.75 + data.update_error_mul(0.5) + assert data.error == 0.875 From 6885a78913cdfa5b809f5bdf7ddc640fe8725b22 Mon Sep 17 00:00:00 2001 From: Ed Younis Date: Mon, 2 Oct 2023 17:56:24 -0400 Subject: [PATCH 02/23] Fixed tagged gate with dict data --- bqskit/ir/gates/composed/tagged.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bqskit/ir/gates/composed/tagged.py b/bqskit/ir/gates/composed/tagged.py index d01b93ab6..3455b35b8 100644 --- a/bqskit/ir/gates/composed/tagged.py +++ b/bqskit/ir/gates/composed/tagged.py @@ -102,4 +102,7 @@ def __eq__(self, other: object) -> bool: ) def __hash__(self) -> int: + if isinstance(self.tag, dict): + return hash((self.gate, tuple(self.tag.items()))) + return hash((self.gate, self.tag)) From ff8180ad068ba63089d4ead1ea9a45e1fedb7635 Mon Sep 17 00:00:00 2001 From: Ed Younis Date: Mon, 2 Oct 2023 17:57:25 -0400 Subject: [PATCH 03/23] ForEach always updates error now --- bqskit/passes/control/foreach.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bqskit/passes/control/foreach.py b/bqskit/passes/control/foreach.py index 8c3c9da02..02b8ddf49 100644 --- a/bqskit/passes/control/foreach.py +++ b/bqskit/passes/control/foreach.py @@ -235,8 +235,7 @@ async def run(self, circuit: Circuit, data: PassData) -> None: block_data['replaced'] = True # Calculate Error - if self.calculate_error_bound: - error_sum += block_data.error + error_sum += block_data.error else: block_data['replaced'] = False @@ -247,8 +246,8 @@ async def run(self, circuit: Circuit, data: PassData) -> None: data[self.key].append(completed_block_datas) # Record error + data.update_error_mul(error_sum) if self.calculate_error_bound: - data.error = (1 - ((1 - data.error) * (1 - error_sum))) _logger.debug(f'New circuit error is {data.error}.') From c0495709abea703cbf62c40f5a337476e0e0e685 Mon Sep 17 00:00:00 2001 From: Ed Younis Date: Mon, 2 Oct 2023 18:00:11 -0400 Subject: [PATCH 04/23] Circuit append methods now return cycle of new op --- bqskit/ir/circuit.py | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/bqskit/ir/circuit.py b/bqskit/ir/circuit.py index 2b4607a49..c924e2de4 100644 --- a/bqskit/ir/circuit.py +++ b/bqskit/ir/circuit.py @@ -1035,13 +1035,16 @@ def point( raise ValueError('No such operation exists in the circuit.') - def append(self, op: Operation) -> None: + def append(self, op: Operation) -> int: """ - Append `op` to the end of the circuit. + Append `op` to the end of the circuit and return its cycle index. Args: op (Operation): The operation to append. + Returns: + int: The cycle index of the appended operation. + Raises: ValueError: If `op` cannot be placed on the circuit due to either an invalid location or gate radix mismatch. @@ -1093,12 +1096,14 @@ def append(self, op: Operation) -> None: self._gate_info[op.gate] = 0 self._gate_info[op.gate] += 1 + return cycle_index + def append_gate( self, gate: Gate, location: CircuitLocationLike, params: RealVector = [], - ) -> None: + ) -> int: """ Append the gate object to the circuit on the qudits in location. @@ -1110,6 +1115,9 @@ def append_gate( params (RealVector): The gate's parameters. (Default: all zeros) + Returns: + int: The cycle index of the appended gate. + Examples: >>> from bqskit.ir.gates import HGate >>> circ = Circuit(1) @@ -1119,7 +1127,7 @@ def append_gate( See Also: :func:`append` """ - self.append(Operation(gate, location, params)) + return self.append(Operation(gate, location, params)) def append_circuit( self, @@ -1127,7 +1135,7 @@ def append_circuit( location: CircuitLocationLike, as_circuit_gate: bool = False, move: bool = False, - ) -> None: + ) -> int: """ Append `circuit` at the qudit location specified. @@ -1143,6 +1151,9 @@ def append_circuit( move (bool): Move circuit into circuit gate rather than copy. (Default: False) + Returns: + int: The starting cycle index of the appended circuit. + Raises: ValueError: If `circuit` is not the same size as `location`. @@ -1165,12 +1176,19 @@ def append_circuit( if as_circuit_gate: op = Operation(CircuitGate(circuit, move), location, circuit.params) - self.append(op) - return + return self.append(op) + cycle_index: int | None = None for op in circuit: mapped_location = [location[q] for q in op.location] - self.append(Operation(op.gate, mapped_location, op.params)) + ci = self.append(Operation(op.gate, mapped_location, op.params)) + if cycle_index is None: + cycle_index = ci + + if cycle_index is None: + raise RuntimeError('Cannot append empty circuit.') + + return cycle_index def extend(self, ops: Iterable[Operation]) -> None: """ From 64734aca13ca635d3df7dd73941609e36ab8c603 Mon Sep 17 00:00:00 2001 From: Ed Younis Date: Mon, 2 Oct 2023 18:01:11 -0400 Subject: [PATCH 05/23] PAM now stores select data per block --- bqskit/passes/mapping/pam.py | 52 ++++++++++++++++++++++++++-- bqskit/passes/mapping/routing/pam.py | 6 +++- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/bqskit/passes/mapping/pam.py b/bqskit/passes/mapping/pam.py index 8af5d42b9..5b9117ace 100644 --- a/bqskit/passes/mapping/pam.py +++ b/bqskit/passes/mapping/pam.py @@ -4,8 +4,11 @@ import itertools as it import logging from typing import Dict +from typing import Literal +from typing import overload from typing import Sequence from typing import Tuple +from typing import TypedDict import numpy as np @@ -14,6 +17,7 @@ from bqskit.ir.point import CircuitPoint from bqskit.passes.mapping.sabre import GeneralizedSabreAlgorithm from bqskit.qis.graph import CouplingGraph +from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix _logger = logging.getLogger(__name__) @@ -22,6 +26,15 @@ PAMBlockTAPermData = Dict[CouplingGraph, PAMBlockPermData] +class PAMBlockResultData(TypedDict): + pre_perm: tuple[int, ...] + post_perm: tuple[int, ...] + original_utry: UnitaryMatrix + + +PAMBlockResultDict = Dict[CircuitPoint, PAMBlockResultData] + + class PermutationAwareMappingAlgorithm(GeneralizedSabreAlgorithm): """Route the circuit with permutation awareness.""" @@ -72,6 +85,28 @@ def __init__( extended_set_weight, ) + @overload # type: ignore + def forward_pass( + self, + circuit: Circuit, + pi: list[int], + cg: CouplingGraph, + perm_data: dict[CircuitPoint, PAMBlockTAPermData], + modify_circuit: Literal[False] = False, + ) -> None: + ... + + @overload + def forward_pass( + self, + circuit: Circuit, + pi: list[int], + cg: CouplingGraph, + perm_data: dict[CircuitPoint, PAMBlockTAPermData], + modify_circuit: Literal[True], + ) -> PAMBlockResultDict: + ... + def forward_pass( # type: ignore self, circuit: Circuit, @@ -79,7 +114,7 @@ def forward_pass( # type: ignore cg: CouplingGraph, perm_data: dict[CircuitPoint, PAMBlockTAPermData], modify_circuit: bool = False, - ) -> None: + ) -> PAMBlockResultDict | None: """ Apply a forward pass of the PAM algorithm to `pi`. @@ -109,6 +144,7 @@ def forward_pass( # type: ignore if modify_circuit: mapped_circuit = Circuit(circuit.num_qudits, circuit.radixes) + out_data: PAMBlockResultDict = {} # Main Loop while len(F) > 0: @@ -154,11 +190,17 @@ def forward_pass( # type: ignore if modify_circuit: physical_location = [pi[q] for q in op.location] - mapped_circuit.append_circuit( + cycle = mapped_circuit.append_circuit( circ, physical_location, True, ) + new_point = CircuitPoint(cycle, physical_location[0]) + out_data[new_point] = { + 'pre_perm': self._global_to_local_perm(p1), + 'post_perm': self._global_to_local_perm(p2), + 'original_utry': op.get_unitary(), + } self._apply_perm(p2, pi) @@ -213,6 +255,12 @@ def forward_pass( # type: ignore if modify_circuit: circuit.become(mapped_circuit) + return out_data + + def _global_to_local_perm(self, gperm: Sequence[int]) -> tuple[int, ...]: + """Return the local permutation from a global permutation.""" + global_to_local_map = {q: i for i, q in enumerate(sorted(gperm))} + return tuple(global_to_local_map[i] for i in gperm) def _get_best_perm( self, diff --git a/bqskit/passes/mapping/routing/pam.py b/bqskit/passes/mapping/routing/pam.py index 2bdb8a614..2f6e24ccd 100644 --- a/bqskit/passes/mapping/routing/pam.py +++ b/bqskit/passes/mapping/routing/pam.py @@ -16,6 +16,9 @@ class PAMRoutingPass(PermutationAwareMappingAlgorithm, BasePass): + + out_data_key = '_pam_routing_block_out_data' + async def run(self, circuit: Circuit, data: PassData) -> None: """Perform the pass's operation, see :class:`BasePass` for more.""" model = self.get_model(circuit, data) @@ -35,8 +38,9 @@ async def run(self, circuit: Circuit, data: PassData) -> None: else: pi = [i for i in range(circuit.num_qudits)] - self.forward_pass(circuit, pi, subgraph, perm_data, modify_circuit=True) + out_data = self.forward_pass(circuit, pi, subgraph, perm_data, True) if 'final_mapping' in data: self._apply_perm(data['final_mapping'], pi) data['final_mapping'] = pi _logger.info(f'Finished routing with layout: {str(pi)}') + data[self.out_data_key] = out_data From 09a6766adaca17a1b6e94036fa7a9432f14111ee Mon Sep 17 00:00:00 2001 From: Ed Younis Date: Mon, 2 Oct 2023 18:03:20 -0400 Subject: [PATCH 06/23] Implemented PAMVerificationSequence and helpers --- bqskit/passes/__init__.py | 27 +++++++ bqskit/passes/control/__init__.py | 2 + bqskit/passes/control/foreach.py | 12 ++++ bqskit/passes/mapping/__init__.py | 12 ++++ bqskit/passes/mapping/setmodel.py | 34 +++++++++ bqskit/passes/mapping/verify.py | 114 ++++++++++++++++++++++++++++++ 6 files changed, 201 insertions(+) create mode 100644 bqskit/passes/mapping/verify.py diff --git a/bqskit/passes/__init__.py b/bqskit/passes/__init__.py index eb5c81425..eb8e41607 100644 --- a/bqskit/passes/__init__.py +++ b/bqskit/passes/__init__.py @@ -128,6 +128,18 @@ PAMLayoutPass PAMRoutingPass EmbedAllPermutationsPass + ExtractModelConnectivityPass + RestoreModelConnevtivityPass + + +.. rubric:: PAM Verification Passes + +These passes either perform upper-bound error analysis of the PAM process. + + TagPAMBlockDataPass + CalculatePAMErrorsPass + UnTagPAMBlockDataPass + PAMVerificationSequence .. rubric:: Utility Passes @@ -148,6 +160,7 @@ LogErrorPass FillSingleQuditGatesPass StructureAnalysisPass + ClearAllBlockData .. rubric:: IO Passes @@ -193,6 +206,7 @@ from bqskit.passes.alias import PassAlias from bqskit.passes.control.dothendecide import DoThenDecide from bqskit.passes.control.dowhileloop import DoWhileLoopPass +from bqskit.passes.control.foreach import ClearAllBlockData from bqskit.passes.control.foreach import ForEachBlockPass from bqskit.passes.control.ifthenelse import IfThenElsePass from bqskit.passes.control.paralleldo import ParallelDo @@ -223,8 +237,14 @@ from bqskit.passes.mapping.placement.trivial import TrivialPlacementPass from bqskit.passes.mapping.routing.pam import PAMRoutingPass from bqskit.passes.mapping.routing.sabre import GeneralizedSabreRoutingPass +from bqskit.passes.mapping.setmodel import ExtractModelConnectivityPass +from bqskit.passes.mapping.setmodel import RestoreModelConnevtivityPass from bqskit.passes.mapping.setmodel import SetModelPass from bqskit.passes.mapping.topology import SubtopologySelectionPass +from bqskit.passes.mapping.verify import CalculatePAMErrorsPass +from bqskit.passes.mapping.verify import PAMVerificationSequence +from bqskit.passes.mapping.verify import TagPAMBlockDataPass +from bqskit.passes.mapping.verify import UnTagPAMBlockDataPass from bqskit.passes.measure import ExtractMeasurements from bqskit.passes.measure import RestoreMeasurements from bqskit.passes.noop import NOOPPass @@ -285,6 +305,7 @@ __all__ = [ 'DoWhileLoopPass', + 'ClearAllBlockData', 'ForEachBlockPass', 'IfThenElsePass', 'PassPredicate', @@ -376,4 +397,10 @@ 'AllConstantSingleQuditGates', 'GeneralSQDecomposition', 'StructureAnalysisPass', + 'ExtractModelConnectivityPass', + 'RestoreModelConnevtivityPass', + 'TagPAMBlockDataPass', + 'CalculatePAMErrorsPass', + 'UnTagPAMBlockDataPass', + 'PAMVerificationSequence', ] diff --git a/bqskit/passes/control/__init__.py b/bqskit/passes/control/__init__.py index 302a71867..990f7ed6a 100644 --- a/bqskit/passes/control/__init__.py +++ b/bqskit/passes/control/__init__.py @@ -3,6 +3,7 @@ from bqskit.passes.control.dothendecide import DoThenDecide from bqskit.passes.control.dowhileloop import DoWhileLoopPass +from bqskit.passes.control.foreach import ClearAllBlockData from bqskit.passes.control.foreach import ForEachBlockPass from bqskit.passes.control.ifthenelse import IfThenElsePass from bqskit.passes.control.paralleldo import ParallelDo @@ -24,6 +25,7 @@ __all__ = [ 'DoWhileLoopPass', 'ForEachBlockPass', + 'ClearAllBlockData', 'IfThenElsePass', 'PassPredicate', 'ChangePredicate', diff --git a/bqskit/passes/control/foreach.py b/bqskit/passes/control/foreach.py index 02b8ddf49..be9bc2e0b 100644 --- a/bqskit/passes/control/foreach.py +++ b/bqskit/passes/control/foreach.py @@ -505,3 +505,15 @@ def gen_replace_filter(method: str, model: MachineModel) -> ReplaceFilterFn: ReplaceFilterFn = Callable[[Circuit, Operation], bool] + + +class ClearAllBlockData(BasePass): + """Clear all block data and passed down data from the pass data.""" + + async def run(self, circuit: Circuit, data: PassData) -> None: + """Perform the pass's operation, see :class:`BasePass` for more.""" + for key in list(data.keys()): + if key.startswith(ForEachBlockPass.key): + del data[key] + elif key.startswith(ForEachBlockPass.pass_down_key_prefix): + del data[key] diff --git a/bqskit/passes/mapping/__init__.py b/bqskit/passes/mapping/__init__.py index 7dd744579..5ffc9dd2f 100644 --- a/bqskit/passes/mapping/__init__.py +++ b/bqskit/passes/mapping/__init__.py @@ -9,8 +9,14 @@ from bqskit.passes.mapping.placement.trivial import TrivialPlacementPass from bqskit.passes.mapping.routing.pam import PAMRoutingPass from bqskit.passes.mapping.routing.sabre import GeneralizedSabreRoutingPass +from bqskit.passes.mapping.setmodel import ExtractModelConnectivityPass +from bqskit.passes.mapping.setmodel import RestoreModelConnevtivityPass from bqskit.passes.mapping.setmodel import SetModelPass from bqskit.passes.mapping.topology import SubtopologySelectionPass +from bqskit.passes.mapping.verify import CalculatePAMErrorsPass +from bqskit.passes.mapping.verify import PAMVerificationSequence +from bqskit.passes.mapping.verify import TagPAMBlockDataPass +from bqskit.passes.mapping.verify import UnTagPAMBlockDataPass __all__ = [ 'GeneralizedSabreLayoutPass', @@ -23,4 +29,10 @@ 'PAMRoutingPass', 'EmbedAllPermutationsPass', 'SubtopologySelectionPass', + 'ExtractModelConnectivityPass', + 'RestoreModelConnevtivityPass', + 'TagPAMBlockDataPass', + 'CalculatePAMErrorsPass', + 'UnTagPAMBlockDataPass', + 'PAMVerificationSequence', ] diff --git a/bqskit/passes/mapping/setmodel.py b/bqskit/passes/mapping/setmodel.py index 95294df47..2e26a1e0e 100644 --- a/bqskit/passes/mapping/setmodel.py +++ b/bqskit/passes/mapping/setmodel.py @@ -7,6 +7,7 @@ from bqskit.compiler.machine import MachineModel from bqskit.compiler.passdata import PassData from bqskit.ir.circuit import Circuit +from bqskit.qis.graph import CouplingGraph _logger = logging.getLogger(__name__) @@ -34,3 +35,36 @@ async def run(self, circuit: Circuit, data: PassData) -> None: data.model = self.model # Update Model data.placement = list(range(circuit.num_qudits)) # Reset placement + + +class ExtractModelConnectivityPass(BasePass): + """ + Extracts and saves the current target machine model's connectivity. + + The model will remain unchanged except that it will be fully connected until + the RestoreModelConnevtivityPass is executed. + """ + + key = '_ExtractModelConnectivityPass_connectivity' + + async def run(self, circuit: Circuit, data: PassData) -> None: + """Perform the pass's operation, see :class:`BasePass` for more.""" + data[self.key] = data.model.coupling_graph + data.model.coupling_graph = CouplingGraph.all_to_all( + data.model.num_qudits, + ) + + +class RestoreModelConnevtivityPass(BasePass): + """Restores the connectivity of the target machine model.""" + + async def run(self, circuit: Circuit, data: PassData) -> None: + """Perform the pass's operation, see :class:`BasePass` for more.""" + if ExtractModelConnectivityPass.key not in data: + raise RuntimeError( + 'Cannot restore connectivity without first ' + 'extracting it using ExtractModelConnectivityPass.', + ) + + data.model.coupling_graph = data[ExtractModelConnectivityPass.key] + del data[ExtractModelConnectivityPass.key] diff --git a/bqskit/passes/mapping/verify.py b/bqskit/passes/mapping/verify.py new file mode 100644 index 000000000..e4411ad06 --- /dev/null +++ b/bqskit/passes/mapping/verify.py @@ -0,0 +1,114 @@ +"""This module implements the PAMVerificationSequence and helper passes.""" +from bqskit.compiler.basepass import BasePass +from bqskit.compiler.passdata import PassData +from bqskit.ir.circuit import Circuit +from bqskit.ir.gates.constant.unitary import ConstantUnitaryGate +from bqskit.passes.alias import PassAlias +from bqskit.passes.control.foreach import ForEachBlockPass +from bqskit.passes.mapping.pam import PAMBlockResultDict +from bqskit.passes.mapping.routing.pam import PAMRoutingPass +from bqskit.passes.partitioning.quick import QuickPartitioner +from bqskit.passes.util.unfold import UnfoldPass +from bqskit.qis.permutation import PermutationMatrix +from bqskit.utils.typing import is_integer +from bqskit.ir.gates import TaggedGate + + +class TagPAMBlockDataPass(BasePass): + """Tag the blocks with the PAM block data.""" + + async def run(self, circuit: Circuit, data: PassData) -> None: + """Perform the pass's operation, see :class:`BasePass` for more.""" + if PAMRoutingPass.out_data_key not in data: + raise RuntimeError('PAMRoutingPass must be run to verify results.') + + block_datas: PAMBlockResultDict = data[PAMRoutingPass.out_data_key] + for block_point, block_data in block_datas.items(): + op = circuit[block_point] + tagged_gate = TaggedGate(op.gate, block_data) + circuit.replace_gate( + block_point, + tagged_gate, + op.location, + op.params + ) + + del data[PAMRoutingPass.out_data_key] + + +class CalculatePAMErrorsPass(BasePass): + """Calculates error of a panel consisting of blocks tagged with PAM data.""" + + async def run(self, circuit: Circuit, data: PassData) -> None: + """Perform the pass's operation, see :class:`BasePass` for more.""" + # calculate approximate (current) panel unitary + current_unitary = circuit.get_unitary() + + # calculate exact panel unitary + exact_circuit = Circuit(circuit.num_qudits, circuit.radixes) + for op in circuit: + if not isinstance(op.gate, TaggedGate): + raise RuntimeError('Expected tagged gate.') + + pi = op.gate.tag['pre_perm'] + pf = op.gate.tag['post_perm'] + in_utry = op.gate.tag['original_utry'] + PI = PermutationMatrix.from_qubit_location(in_utry.num_qudits, pi) + PF = PermutationMatrix.from_qubit_location(in_utry.num_qudits, pf) + exact_circuit.append_gate( + ConstantUnitaryGate(PF @ in_utry @ PI.T), + op.location, + ) + + exact_unitary = exact_circuit.get_unitary() + + # calculate error + data.update_error_mul(current_unitary.get_distance_from(exact_unitary)) + + +class UnTagPAMBlockDataPass(BasePass): + """Untag the blocks with the PAM block data.""" + + async def run(self, circuit: Circuit, data: PassData) -> None: + """Perform the pass's operation, see :class:`BasePass` for more.""" + for cycle, op in circuit.operations_with_cycles(): + if not isinstance(op.gate, TaggedGate): + raise RuntimeError('Expected tagged gate.') + + circuit.replace_gate( + (cycle, op.location[0]), + op.gate.gate, + op.location, + op.params + ) + + +class PAMVerificationSequence(PassAlias): + """Calculates the error of a PAM sequence.""" + + def __init__(self, error_sim_size: int = 8) -> None: + """ + Construct a PAMVerificationPass. + + Args: + error_sim_size (int): The block size to use during error + calculations. + """ + if not is_integer(error_sim_size): + raise TypeError(f'Expected integer, got {type(error_sim_size)}.') + + if error_sim_size < 2: + raise ValueError('Expected positive integer greater than 1.') + + self.error_sim_size = error_sim_size + + def get_passes(self) -> list[BasePass]: + """Return the aliased passes, see :class:`PassAlias` for more info.""" + return [ + TagPAMBlockDataPass(), + QuickPartitioner(self.error_sim_size), + ForEachBlockPass(CalculatePAMErrorsPass()), + UnfoldPass(), + UnTagPAMBlockDataPass(), + ] + From a0cb9642152ede45b6f1aeaa8de7d492291ed623 Mon Sep 17 00:00:00 2001 From: Ed Younis Date: Mon, 2 Oct 2023 18:04:04 -0400 Subject: [PATCH 07/23] Added PAM workflow factory with verification --- bqskit/compiler/compile.py | 219 ++++++++++++++++++++++++------------- 1 file changed, 144 insertions(+), 75 deletions(-) diff --git a/bqskit/compiler/compile.py b/bqskit/compiler/compile.py index 95e70dc24..85e96f360 100644 --- a/bqskit/compiler/compile.py +++ b/bqskit/compiler/compile.py @@ -51,8 +51,11 @@ from bqskit.passes.mapping.placement.greedy import GreedyPlacementPass from bqskit.passes.mapping.routing.pam import PAMRoutingPass from bqskit.passes.mapping.routing.sabre import GeneralizedSabreRoutingPass +from bqskit.passes.mapping.setmodel import ExtractModelConnectivityPass +from bqskit.passes.mapping.setmodel import RestoreModelConnevtivityPass from bqskit.passes.mapping.setmodel import SetModelPass from bqskit.passes.mapping.topology import SubtopologySelectionPass +from bqskit.passes.mapping.verify import PAMVerificationSequence from bqskit.passes.measure import ExtractMeasurements from bqskit.passes.measure import RestoreMeasurements from bqskit.passes.noop import NOOPPass @@ -1128,6 +1131,141 @@ def build_sabre_mapping_workflow() -> Workflow: ) +def build_seqpam_mapping_optimization_workflow( + optimization_level: int = 4, + synthesis_epsilon: float = 1e-8, + num_layout_passes: int = 3, + block_size: int = 3, + error_sim_size: int | None = None, +) -> Workflow: + """ + Build a Sequential-Permutation-Aware Mapping and Optimizing Workflow. + + Note: + - This workflow assumes that SetModelPass will be run earlier in the + full workflow and doesn't add it in here. + + - This will apply the placement found during the workflow. The + resulting circuit will be physically mapped. + + Args: + optimization_level (int): The optimization level. See :func:`compile` + for more information. + + synthesis_epsilon (float): The maximum distance between target + and circuit unitary allowed to declare successful synthesis. + Set to 0 for exact synthesis. (Default: 1e-8) + + num_layout_passes (int): The number of layout forward and backward + passes to run. See :class:`PamLayoutPass` for more information. + (Default: 3) + + block_size (int): The size of the blocks to partition into. + Warning, the number of permutation evaluated increases + factorially and the difficulty of each permutation increases + exponentially with this. (Default: 3) + + error_sim_size (int | None): The size of the blocks to simulate + errors on. If None, then no error analysis is performed. + (Default: None) + + Raises: + ValueError: If block_size < 2. + + ValueError: If error_sim_size < block_size. + """ + if not is_integer(block_size): + raise TypeError( + f'Expected block_size to be int, got {type(block_size)}.', + ) + + if block_size < 2: + raise ValueError(f'Expected block_size > 1, got {block_size}.') + + if error_sim_size is not None and not is_integer(block_size): + raise TypeError( + f'Expected int for error_sim_size, got {type(error_sim_size)}.', + ) + + if error_sim_size is not None and error_sim_size < block_size: + raise ValueError( + f'Expected error_sim_size >= block_size, got {error_sim_size}.', + ) + + qsearch = QSearchSynthesisPass( + success_threshold=synthesis_epsilon, + instantiate_options=get_instantiate_options(optimization_level), + ) + + leap = LEAPSynthesisPass( + success_threshold=synthesis_epsilon, + min_prefix_size=9, + instantiate_options=get_instantiate_options(optimization_level), + ) + + if error_sim_size is not None: + post_pam_seq: BasePass = PAMVerificationSequence(error_sim_size) + else: + post_pam_seq = NOOPPass() + + return Workflow( + IfThenElsePass( + NotPredicate(WidthPredicate(2)), + [ + ExtractModelConnectivityPass(), + QuickPartitioner(block_size), + ForEachBlockPass( + IfThenElsePass( + WidthPredicate(4), + EmbedAllPermutationsPass( + inner_synthesis=qsearch, + input_perm=True, + output_perm=False, + vary_topology=False, + ), + EmbedAllPermutationsPass( + inner_synthesis=leap, + input_perm=True, + output_perm=False, + vary_topology=False, + ), + ), + ), + PAMRoutingPass(), + post_pam_seq, + UnfoldPass(), + RestoreModelConnevtivityPass(), + + SubtopologySelectionPass(block_size), + QuickPartitioner(block_size), + ForEachBlockPass( + IfThenElsePass( + WidthPredicate(4), + EmbedAllPermutationsPass( + inner_synthesis=qsearch, + input_perm=False, + output_perm=True, + vary_topology=True, + ), + EmbedAllPermutationsPass( + inner_synthesis=leap, + input_perm=False, + output_perm=True, + vary_topology=True, + ), + ), + ), + ApplyPlacement(), + PAMLayoutPass(num_layout_passes), + PAMRoutingPass(0.1), + post_pam_seq, + UnfoldPass(), + ], + ), + name='SeqPAM Mapping', + ) + + def build_gate_deletion_optimization_workflow( optimization_level: int = 1, synthesis_epsilon: float = 1e-8, @@ -1370,89 +1508,20 @@ def _opt4_workflow( error_sim_size: int = 8, ) -> list[BasePass]: """Build optimization Level 4 workflow for circuit compilation.""" - if error_threshold is not None: - _logger.warning( - 'Automated error upper bound calculated is not yet' - ' ready for opt level 4.', - ) - if max_synthesis_size > 3: _logger.warning( 'It is currently recommended to set max_synthesis_size to 3' ' for optimization level 4. This may change in the future.', ) - qsearch = QSearchSynthesisPass( - success_threshold=synthesis_epsilon, - instantiate_options=get_instantiate_options(4), - ) - leap = LEAPSynthesisPass( - success_threshold=synthesis_epsilon, - min_prefix_size=9, - instantiate_options=get_instantiate_options(4), - ) - return [ - SetModelPass( - MachineModel( - model.num_qudits, - None, - model.gate_set, - model.radixes, - ), - ), - Workflow( - IfThenElsePass( - NotPredicate(WidthPredicate(2)), - [ - QuickPartitioner(3), - ForEachBlockPass( - IfThenElsePass( - WidthPredicate(4), - EmbedAllPermutationsPass( - inner_synthesis=qsearch, - input_perm=True, - output_perm=False, - vary_topology=False, - ), - EmbedAllPermutationsPass( - inner_synthesis=leap, - input_perm=True, - output_perm=False, - vary_topology=False, - ), - ), - ), - PAMRoutingPass(), - UnfoldPass(), + SetModelPass(model), - SetModelPass(model), - SubtopologySelectionPass(3), - QuickPartitioner(3), - ForEachBlockPass( - IfThenElsePass( - WidthPredicate(4), - EmbedAllPermutationsPass( - inner_synthesis=qsearch, - input_perm=False, - output_perm=True, - vary_topology=True, - ), - EmbedAllPermutationsPass( - inner_synthesis=leap, - input_perm=False, - output_perm=True, - vary_topology=True, - ), - ), - ), - ApplyPlacement(), - PAMLayoutPass(3), - PAMRoutingPass(0.1), - UnfoldPass(), - ], - ), - name='SeqPAM Mapping', + build_seqpam_mapping_optimization_workflow( + 4, + synthesis_epsilon, + block_size=max_synthesis_size, + error_sim_size=None if error_threshold is None else error_sim_size, ), build_multi_qudit_retarget_workflow( From 2f50ec8998ae7dd79a13e9c9179846c39f052723 Mon Sep 17 00:00:00 2001 From: Ed Younis Date: Mon, 2 Oct 2023 18:11:23 -0400 Subject: [PATCH 08/23] Added PAM Verification Test --- tests/compiler/compile/_data/adder9.qasm | 165 ++++++++++++++++++++++ tests/compiler/compile/conftest.py | 18 +++ tests/compiler/compile/test_pam_verify.py | 26 ++++ 3 files changed, 209 insertions(+) create mode 100644 tests/compiler/compile/_data/adder9.qasm create mode 100644 tests/compiler/compile/test_pam_verify.py diff --git a/tests/compiler/compile/_data/adder9.qasm b/tests/compiler/compile/_data/adder9.qasm new file mode 100644 index 000000000..3e107df8e --- /dev/null +++ b/tests/compiler/compile/_data/adder9.qasm @@ -0,0 +1,165 @@ +OPENQASM 2.0; +include "qelib1.inc"; +qreg node[9]; +u2(0,pi) node[2]; +u3(pi,0,pi) node[3]; +u3(pi,0,pi) node[4]; +u2(0,pi) node[6]; +u3(pi,0,pi) node[7]; +u3(pi,0,pi) node[8]; +cx node[3],node[2]; +cx node[4],node[8]; +cx node[7],node[6]; +u1(7*pi/4) node[2]; +u1(7*pi/4) node[6]; +cx node[1],node[2]; +cx node[5],node[6]; +u1(pi/4) node[2]; +u1(pi/4) node[6]; +cx node[3],node[2]; +cx node[7],node[6]; +u1(7*pi/4) node[2]; +u1(pi/4) node[3]; +u1(7*pi/4) node[6]; +u1(pi/4) node[7]; +cx node[1],node[2]; +cx node[5],node[6]; +u1(pi/4) node[2]; +u1(pi/4) node[6]; +cx node[1],node[2]; +cx node[5],node[6]; +cx node[2],node[3]; +cx node[6],node[7]; +cx node[1],node[2]; +cx node[5],node[6]; +u1(pi/4) node[1]; +cx node[2],node[3]; +u1(pi/4) node[5]; +cx node[6],node[7]; +cx node[0],node[1]; +u1(7*pi/4) node[3]; +u1(7*pi/4) node[7]; +cx node[1],node[0]; +cx node[3],node[2]; +cx node[7],node[6]; +cx node[0],node[1]; +u1(7*pi/4) node[2]; +u1(7*pi/4) node[6]; +cx node[1],node[2]; +u1(pi/4) node[2]; +cx node[3],node[2]; +u1(7*pi/4) node[2]; +u1(pi/4) node[3]; +cx node[1],node[2]; +u2(0,5*pi/4) node[2]; +cx node[2],node[6]; +u1(pi/4) node[6]; +cx node[7],node[6]; +u1(7*pi/4) node[6]; +u1(pi/4) node[7]; +cx node[2],node[6]; +cx node[2],node[3]; +u2(0,5*pi/4) node[6]; +cx node[3],node[2]; +cx node[2],node[3]; +cx node[1],node[2]; +cx node[3],node[7]; +u1(pi/4) node[1]; +u1(7*pi/4) node[2]; +u1(pi/4) node[3]; +u1(7*pi/4) node[7]; +cx node[1],node[2]; +cx node[3],node[7]; +cx node[8],node[7]; +cx node[7],node[6]; +cx node[8],node[7]; +cx node[7],node[6]; +u2(0,pi) node[6]; +cx node[7],node[6]; +u1(7*pi/4) node[6]; +cx node[6],node[7]; +cx node[7],node[6]; +cx node[6],node[7]; +cx node[3],node[7]; +u1(pi/4) node[7]; +cx node[6],node[7]; +u1(pi/4) node[6]; +u1(7*pi/4) node[7]; +cx node[3],node[7]; +cx node[2],node[3]; +u1(pi/4) node[7]; +cx node[3],node[2]; +cx node[2],node[3]; +cx node[2],node[6]; +u1(pi/4) node[2]; +u1(7*pi/4) node[6]; +cx node[2],node[6]; +cx node[5],node[6]; +cx node[6],node[7]; +cx node[5],node[6]; +u1(7*pi/4) node[7]; +cx node[6],node[7]; +cx node[5],node[6]; +cx node[6],node[7]; +u1(pi/4) node[7]; +cx node[6],node[7]; +u1(pi/4) node[6]; +u1(7*pi/4) node[7]; +cx node[5],node[6]; +cx node[6],node[5]; +cx node[5],node[6]; +cx node[6],node[7]; +cx node[6],node[5]; +u2(0,5*pi/4) node[7]; +u1(7*pi/4) node[5]; +u1(pi/4) node[6]; +cx node[5],node[6]; +cx node[6],node[5]; +cx node[5],node[6]; +cx node[2],node[6]; +u2(0,pi) node[2]; +cx node[3],node[2]; +u1(7*pi/4) node[2]; +cx node[1],node[2]; +u1(pi/4) node[2]; +cx node[3],node[2]; +u1(7*pi/4) node[2]; +u1(pi/4) node[3]; +cx node[1],node[2]; +u1(pi/4) node[2]; +cx node[1],node[2]; +cx node[2],node[3]; +cx node[1],node[2]; +u1(pi/4) node[1]; +cx node[2],node[3]; +cx node[1],node[2]; +u1(7*pi/4) node[3]; +cx node[2],node[3]; +cx node[1],node[2]; +cx node[0],node[1]; +cx node[2],node[3]; +cx node[1],node[0]; +cx node[0],node[1]; +cx node[1],node[2]; +cx node[2],node[3]; +cx node[1],node[2]; +cx node[2],node[3]; +cx node[3],node[2]; +u1(7*pi/4) node[2]; +cx node[1],node[2]; +u1(pi/4) node[2]; +cx node[3],node[2]; +u1(7*pi/4) node[2]; +u1(pi/4) node[3]; +cx node[1],node[2]; +u2(0,5*pi/4) node[2]; +cx node[2],node[3]; +cx node[3],node[2]; +cx node[2],node[3]; +cx node[1],node[2]; +u1(pi/4) node[1]; +u1(7*pi/4) node[2]; +cx node[0],node[1]; +cx node[1],node[0]; +cx node[0],node[1]; +cx node[1],node[2]; diff --git a/tests/compiler/compile/conftest.py b/tests/compiler/compile/conftest.py index b2e4cf863..bba69cf4e 100644 --- a/tests/compiler/compile/conftest.py +++ b/tests/compiler/compile/conftest.py @@ -1,5 +1,6 @@ from __future__ import annotations +import os from typing import Any import pytest @@ -9,3 +10,20 @@ def optimization_level(request: Any) -> int: """All valid optimization_levels for the `compile` function.""" return request.param + + +if os.path.isdir(os.path.join(os.path.dirname(__file__), '_data')): + params = os.listdir(os.path.join(os.path.dirname(__file__), '_data')) +else: + params = [] + + +@pytest.fixture( + params=params, + ids=lambda qasm_file: os.path.splitext(os.path.basename(qasm_file))[0], +) +def medium_qasm_file(request: Any) -> str: + """Provide location of a medium qasm file.""" + cur_dir = os.path.dirname(__file__) + path = os.path.join(cur_dir, '_data') + return os.path.join(path, request.param) diff --git a/tests/compiler/compile/test_pam_verify.py b/tests/compiler/compile/test_pam_verify.py new file mode 100644 index 000000000..c930f0246 --- /dev/null +++ b/tests/compiler/compile/test_pam_verify.py @@ -0,0 +1,26 @@ +from bqskit.ir.circuit import Circuit +from bqskit.compiler.machine import MachineModel +from bqskit.compiler.compiler import Compiler +from bqskit.compiler.compile import build_seqpam_mapping_optimization_workflow +from bqskit.passes import SetModelPass +from bqskit.qis.permutation import PermutationMatrix + + +def test_pam_verify(compiler: Compiler, medium_qasm_file: str) -> None: + circuit = Circuit.from_file(medium_qasm_file) + out_circuit, data = compiler.compile( + circuit, + [ + SetModelPass(MachineModel(circuit.num_qudits)), + build_seqpam_mapping_optimization_workflow(1, error_sim_size=8), + ], + request_data=True, + ) + upper_bound_error = data.error + pi = data['initial_mapping'] + pf = data['final_mapping'] + out_utry = out_circuit.get_unitary() + PI = PermutationMatrix.from_qubit_location(out_circuit.num_qudits, pi) + PF = PermutationMatrix.from_qubit_location(out_circuit.num_qudits, pf) + exact_error = out_utry.get_distance_from(PF.T @ circuit.get_unitary() @ PI) + assert upper_bound_error >= exact_error From 3615da4e46e5aa3e63116861ae9db3947e0f39eb Mon Sep 17 00:00:00 2001 From: Ed Younis Date: Mon, 2 Oct 2023 18:11:38 -0400 Subject: [PATCH 09/23] Added OTS compilation test --- tests/compiler/compile/test_compile.py | 27 ++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/compiler/compile/test_compile.py diff --git a/tests/compiler/compile/test_compile.py b/tests/compiler/compile/test_compile.py new file mode 100644 index 000000000..8b982725a --- /dev/null +++ b/tests/compiler/compile/test_compile.py @@ -0,0 +1,27 @@ +from bqskit.compiler.compile import compile +from bqskit.ir.circuit import Circuit +from bqskit.compiler.machine import MachineModel +from bqskit.compiler.compiler import Compiler +from bqskit.compiler.compile import build_seqpam_mapping_optimization_workflow +from bqskit.passes import SetModelPass +from bqskit.qis.permutation import PermutationMatrix + + +def test_medium_circuit_compile( + compiler: Compiler, + optimization_level: int, + medium_qasm_file: str, +) -> None: + circuit = Circuit.from_file(medium_qasm_file) + out_circuit, pi, pf = compile( + circuit, + optimization_level=optimization_level, + with_mapping=True, + compiler=compiler, + ) + in_utry = circuit.get_unitary() + out_utry = out_circuit.get_unitary() + PI = PermutationMatrix.from_qubit_location(out_circuit.num_qudits, pi) + PF = PermutationMatrix.from_qubit_location(out_circuit.num_qudits, pf) + error = out_utry.get_distance_from(PF.T @ in_utry @ PI, 1) + assert error <= 1e-8 From de93b98f5b130bb1de24ffc5c5dcff85aea129e4 Mon Sep 17 00:00:00 2001 From: Ed Younis Date: Mon, 2 Oct 2023 18:12:56 -0400 Subject: [PATCH 10/23] pre-commit --- bqskit/passes/mapping/verify.py | 23 ++++++++++++----------- tests/compiler/compile/test_compile.py | 7 +++---- tests/compiler/compile/test_pam_verify.py | 8 +++++--- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/bqskit/passes/mapping/verify.py b/bqskit/passes/mapping/verify.py index e4411ad06..ef5671169 100644 --- a/bqskit/passes/mapping/verify.py +++ b/bqskit/passes/mapping/verify.py @@ -1,7 +1,10 @@ """This module implements the PAMVerificationSequence and helper passes.""" +from __future__ import annotations + from bqskit.compiler.basepass import BasePass from bqskit.compiler.passdata import PassData from bqskit.ir.circuit import Circuit +from bqskit.ir.gates import TaggedGate from bqskit.ir.gates.constant.unitary import ConstantUnitaryGate from bqskit.passes.alias import PassAlias from bqskit.passes.control.foreach import ForEachBlockPass @@ -11,7 +14,6 @@ from bqskit.passes.util.unfold import UnfoldPass from bqskit.qis.permutation import PermutationMatrix from bqskit.utils.typing import is_integer -from bqskit.ir.gates import TaggedGate class TagPAMBlockDataPass(BasePass): @@ -21,7 +23,7 @@ async def run(self, circuit: Circuit, data: PassData) -> None: """Perform the pass's operation, see :class:`BasePass` for more.""" if PAMRoutingPass.out_data_key not in data: raise RuntimeError('PAMRoutingPass must be run to verify results.') - + block_datas: PAMBlockResultDict = data[PAMRoutingPass.out_data_key] for block_point, block_data in block_datas.items(): op = circuit[block_point] @@ -30,9 +32,9 @@ async def run(self, circuit: Circuit, data: PassData) -> None: block_point, tagged_gate, op.location, - op.params + op.params, ) - + del data[PAMRoutingPass.out_data_key] @@ -49,7 +51,7 @@ async def run(self, circuit: Circuit, data: PassData) -> None: for op in circuit: if not isinstance(op.gate, TaggedGate): raise RuntimeError('Expected tagged gate.') - + pi = op.gate.tag['pre_perm'] pf = op.gate.tag['post_perm'] in_utry = op.gate.tag['original_utry'] @@ -74,12 +76,12 @@ async def run(self, circuit: Circuit, data: PassData) -> None: for cycle, op in circuit.operations_with_cycles(): if not isinstance(op.gate, TaggedGate): raise RuntimeError('Expected tagged gate.') - + circuit.replace_gate( (cycle, op.location[0]), op.gate.gate, op.location, - op.params + op.params, ) @@ -96,12 +98,12 @@ def __init__(self, error_sim_size: int = 8) -> None: """ if not is_integer(error_sim_size): raise TypeError(f'Expected integer, got {type(error_sim_size)}.') - + if error_sim_size < 2: raise ValueError('Expected positive integer greater than 1.') - + self.error_sim_size = error_sim_size - + def get_passes(self) -> list[BasePass]: """Return the aliased passes, see :class:`PassAlias` for more info.""" return [ @@ -111,4 +113,3 @@ def get_passes(self) -> list[BasePass]: UnfoldPass(), UnTagPAMBlockDataPass(), ] - diff --git a/tests/compiler/compile/test_compile.py b/tests/compiler/compile/test_compile.py index 8b982725a..857a092a0 100644 --- a/tests/compiler/compile/test_compile.py +++ b/tests/compiler/compile/test_compile.py @@ -1,9 +1,8 @@ +from __future__ import annotations + from bqskit.compiler.compile import compile -from bqskit.ir.circuit import Circuit -from bqskit.compiler.machine import MachineModel from bqskit.compiler.compiler import Compiler -from bqskit.compiler.compile import build_seqpam_mapping_optimization_workflow -from bqskit.passes import SetModelPass +from bqskit.ir.circuit import Circuit from bqskit.qis.permutation import PermutationMatrix diff --git a/tests/compiler/compile/test_pam_verify.py b/tests/compiler/compile/test_pam_verify.py index c930f0246..5f19e2a8f 100644 --- a/tests/compiler/compile/test_pam_verify.py +++ b/tests/compiler/compile/test_pam_verify.py @@ -1,7 +1,9 @@ -from bqskit.ir.circuit import Circuit -from bqskit.compiler.machine import MachineModel -from bqskit.compiler.compiler import Compiler +from __future__ import annotations + from bqskit.compiler.compile import build_seqpam_mapping_optimization_workflow +from bqskit.compiler.compiler import Compiler +from bqskit.compiler.machine import MachineModel +from bqskit.ir.circuit import Circuit from bqskit.passes import SetModelPass from bqskit.qis.permutation import PermutationMatrix From ad12356579bf2606250ddbd167a31919b12d78ac Mon Sep 17 00:00:00 2001 From: Justin Kalloor Date: Thu, 5 Oct 2023 15:11:50 -0700 Subject: [PATCH 11/23] Small fix to make PAM work for different gate sets --- bqskit/passes/mapping/embed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bqskit/passes/mapping/embed.py b/bqskit/passes/mapping/embed.py index 5bea43fa1..b00921d71 100644 --- a/bqskit/passes/mapping/embed.py +++ b/bqskit/passes/mapping/embed.py @@ -135,7 +135,7 @@ async def run(self, circuit: Circuit, data: PassData) -> None: datas = [] for graph in graphs: - model = MachineModel(circuit.num_qudits, graph) + model = MachineModel(circuit.num_qudits, graph, data.gate_set, data.model.radixes) target_data = copy.deepcopy(data) target_data.model = model datas.append(target_data) From ae1dffa959a90d9c2a24e35077c6edda441c870d Mon Sep 17 00:00:00 2001 From: Ed Younis Date: Sun, 15 Oct 2023 17:53:54 -0400 Subject: [PATCH 12/23] More clearly defined the roles of some data vars --- bqskit/compiler/passdata.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/bqskit/compiler/passdata.py b/bqskit/compiler/passdata.py index e5905b6df..160d4f44e 100644 --- a/bqskit/compiler/passdata.py +++ b/bqskit/compiler/passdata.py @@ -146,7 +146,12 @@ def placement(self, _val: Sequence[int]) -> None: @property def initial_mapping(self) -> list[int]: - """Return the initial mapping of logical to physical qudits.""" + """ + Return the initial mapping of logical to physical qudits. + + This always maps how the logical qudits from the original circuit start + on the physical qudits of the current circuit. + """ return self._initial_mapping @initial_mapping.setter @@ -166,7 +171,12 @@ def initial_mapping(self, _val: Sequence[int]) -> None: @property def final_mapping(self) -> list[int]: - """Return the final mapping of logical to physical qudits.""" + """ + Return the final mapping of logical to physical qudits. + + This always maps how the logical qudits from the original circuit end on + the physical qudits of the current circuit. + """ return self._final_mapping @final_mapping.setter From d7ae7cbc85f8fc31fd3c79c6bc40e1bad9e19a69 Mon Sep 17 00:00:00 2001 From: Ed Younis Date: Sun, 15 Oct 2023 18:13:22 -0400 Subject: [PATCH 13/23] Update use of data vars --- bqskit/passes/mapping/apply.py | 12 ++++++------ bqskit/passes/mapping/layout/pam.py | 7 ++++--- bqskit/passes/mapping/layout/sabre.py | 10 +++++----- bqskit/passes/mapping/pam.py | 7 ------- bqskit/passes/mapping/routing/pam.py | 21 ++++++--------------- bqskit/passes/mapping/routing/sabre.py | 13 ++++--------- bqskit/passes/mapping/sabre.py | 8 ++++++++ 7 files changed, 33 insertions(+), 45 deletions(-) diff --git a/bqskit/passes/mapping/apply.py b/bqskit/passes/mapping/apply.py index c3ea462dd..4f64c4b6a 100644 --- a/bqskit/passes/mapping/apply.py +++ b/bqskit/passes/mapping/apply.py @@ -17,13 +17,13 @@ async def run(self, circuit: Circuit, data: PassData) -> None: """Perform the pass's operation, see :class:`BasePass` for more.""" model = data.model placement = data.placement + + # Place circuit on the model according to the placement physical_circuit = Circuit(model.num_qudits, model.radixes) physical_circuit.append_circuit(circuit, placement) circuit.become(physical_circuit) - if 'final_mapping' in data: - pi = data['final_mapping'] - data['final_mapping'] = [placement[p] for p in pi] - if 'initial_mapping' in data: - pi = data['initial_mapping'] - data['initial_mapping'] = [placement[p] for p in pi] + + # Update the relevant data variables + data.initial_mapping = [placement[p] for p in data.initial_mapping] + data.final_mapping = [placement[p] for p in data.final_mapping] data.placement = list(i for i in range(model.num_qudits)) diff --git a/bqskit/passes/mapping/layout/pam.py b/bqskit/passes/mapping/layout/pam.py index 86e0c4708..479d04bd6 100644 --- a/bqskit/passes/mapping/layout/pam.py +++ b/bqskit/passes/mapping/layout/pam.py @@ -82,10 +82,11 @@ async def run(self, circuit: Circuit, data: PassData) -> None: if not subgraph.is_fully_connected(): raise RuntimeError('Cannot route circuit on disconnected qudits.') - pi = data.initial_mapping + pi = [i for i in range(circuit.num_qudits)] + for _ in range(self.total_passes): self.forward_pass(circuit, pi, subgraph, perm_data) self.backward_pass(circuit, pi, subgraph) - data.initial_mapping = pi - _logger.info(f'Found layout: {str(pi)}') + self._apply_perm(pi, data.placement) + _logger.info(f'Found layout: {pi}, new placement: {data.placement}') diff --git a/bqskit/passes/mapping/layout/sabre.py b/bqskit/passes/mapping/layout/sabre.py index b37150833..7a22a205a 100644 --- a/bqskit/passes/mapping/layout/sabre.py +++ b/bqskit/passes/mapping/layout/sabre.py @@ -69,15 +69,15 @@ def __init__( async def run(self, circuit: Circuit, data: PassData) -> None: """Perform the pass's operation, see :class:`BasePass` for more.""" - subgraph = self.get_connectivity(circuit, data) + subgraph = data.connectivity if not subgraph.is_fully_connected(): raise RuntimeError('Cannot layout circuit on disconnected qudits.') - pi = data.initial_mapping + pi = [i for i in range(circuit.num_qudits)] + for _ in range(self.total_passes): self.forward_pass(circuit, pi, subgraph) self.backward_pass(circuit, pi, subgraph) - # select qubits - data.initial_mapping = pi - _logger.info(f'Found layout: {str(pi)}') + self._apply_perm(pi, data.placement) + _logger.info(f'Found layout: {pi}, new placement: {data.placement}') diff --git a/bqskit/passes/mapping/pam.py b/bqskit/passes/mapping/pam.py index 5b9117ace..e2cda3802 100644 --- a/bqskit/passes/mapping/pam.py +++ b/bqskit/passes/mapping/pam.py @@ -403,10 +403,3 @@ def _score_perm( pi[:] = pi_bkp[:] return front + extend - - def _apply_perm(self, perm: Sequence[int], pi: list[int]) -> None: - """Apply the `perm` permutation to the current mapping `pi`.""" - _logger.debug('applying permutation %s' % str(perm)) - pi_c = {q: pi[perm[i]] for i, q in enumerate(sorted(perm))} - for q in perm: - pi[q] = pi_c[q] diff --git a/bqskit/passes/mapping/routing/pam.py b/bqskit/passes/mapping/routing/pam.py index 2f6e24ccd..8e8781fb7 100644 --- a/bqskit/passes/mapping/routing/pam.py +++ b/bqskit/passes/mapping/routing/pam.py @@ -1,7 +1,6 @@ """This module implements the PAMRoutingPass.""" from __future__ import annotations -import copy import logging from bqskit.compiler.basepass import BasePass @@ -21,26 +20,18 @@ class PAMRoutingPass(PermutationAwareMappingAlgorithm, BasePass): async def run(self, circuit: Circuit, data: PassData) -> None: """Perform the pass's operation, see :class:`BasePass` for more.""" - model = self.get_model(circuit, data) - placement = self.get_placement(circuit, data) - subgraph = model.coupling_graph.get_subgraph(placement) + subgraph = data.connectivity + if not subgraph.is_fully_connected(): + raise RuntimeError('Cannot route circuit on disconnected qudits.') perm_data: dict[CircuitPoint, PAMBlockTAPermData] = {} block_datas = data[ForEachBlockPass.key][-1] for block_data in block_datas: perm_data[block_data['point']] = block_data['permutation_data'] - if not subgraph.is_fully_connected(): - raise RuntimeError('Cannot route circuit on disconnected qudits.') - - if 'initial_mapping' in data: - pi = copy.deepcopy(data['initial_mapping']) - else: - pi = [i for i in range(circuit.num_qudits)] - + pi = [i for i in range(circuit.num_qudits)] out_data = self.forward_pass(circuit, pi, subgraph, perm_data, True) - if 'final_mapping' in data: - self._apply_perm(data['final_mapping'], pi) - data['final_mapping'] = pi + data.final_mapping = [pi[x] for x in data.initial_mapping] + _logger.info(f'Finished routing with layout: {str(pi)}') data[self.out_data_key] = out_data diff --git a/bqskit/passes/mapping/routing/sabre.py b/bqskit/passes/mapping/routing/sabre.py index 8b899ec32..2fd67fe41 100644 --- a/bqskit/passes/mapping/routing/sabre.py +++ b/bqskit/passes/mapping/routing/sabre.py @@ -1,7 +1,6 @@ """This module implements the GeneralizedSabreRoutingPass class.""" from __future__ import annotations -import copy import logging from bqskit.compiler.basepass import BasePass @@ -22,16 +21,12 @@ class GeneralizedSabreRoutingPass(BasePass, GeneralizedSabreAlgorithm): async def run(self, circuit: Circuit, data: PassData) -> None: """Perform the pass's operation, see :class:`BasePass` for more.""" - subgraph = self.get_connectivity(circuit, data) + subgraph = data.connectivity if not subgraph.is_fully_connected(): raise RuntimeError('Cannot route circuit on disconnected qudits.') - if 'initial_mapping' in data: - pi = copy.deepcopy(data['initial_mapping']) - else: - pi = [i for i in range(circuit.num_qudits)] - + pi = [i for i in range(circuit.num_qudits)] self.forward_pass(circuit, pi, subgraph, modify_circuit=True) - # TODO: if final_mapping is already in data, apply it first - data['final_mapping'] = pi + data.final_mapping = [pi[x] for x in data.initial_mapping] + _logger.info(f'Finished routing with layout: {str(pi)}') diff --git a/bqskit/passes/mapping/sabre.py b/bqskit/passes/mapping/sabre.py index d8977b93f..df9f0feda 100644 --- a/bqskit/passes/mapping/sabre.py +++ b/bqskit/passes/mapping/sabre.py @@ -9,6 +9,7 @@ import numpy as np from bqskit.ir.circuit import Circuit +from bqskit.ir.gates.circuitgate import CircuitGate from bqskit.ir.gates.constant.swap import SwapGate from bqskit.ir.operation import Operation from bqskit.ir.point import CircuitPoint @@ -510,3 +511,10 @@ def _uphill_swaps( if pi[center_qudit] == p1 or pi[center_qudit] == p2: continue yield (p1, p2) + + def _apply_perm(self, perm: Sequence[int], pi: list[int]) -> None: + """Apply the `perm` permutation to the current mapping `pi`.""" + _logger.debug('applying permutation %s' % str(perm)) + pi_c = {q: pi[perm[i]] for i, q in enumerate(sorted(perm))} + for q in perm: + pi[q] = pi_c[q] From d020a20afeabbd432a98e23872fa34a22249585f Mon Sep 17 00:00:00 2001 From: Ed Younis Date: Sun, 15 Oct 2023 18:13:58 -0400 Subject: [PATCH 14/23] All single-qudit gate blocks always executable --- bqskit/passes/mapping/sabre.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bqskit/passes/mapping/sabre.py b/bqskit/passes/mapping/sabre.py index df9f0feda..1f9ca67d1 100644 --- a/bqskit/passes/mapping/sabre.py +++ b/bqskit/passes/mapping/sabre.py @@ -330,9 +330,13 @@ def backward_pass( def _can_exe(self, op: Operation, pi: list[int], cg: CouplingGraph) -> bool: """Return true if `op` is executable given the current mapping `pi`.""" - # TODO: check if circuitgate of only 1-qubit gates + if isinstance(op.gate, CircuitGate): + if all(g.num_qudits == 1 for g in op.gate._circuit.gate_set): + return True + if op.num_qudits == 1: return True + physical_qudits = [pi[i] for i in op.location] return cg.get_subgraph(physical_qudits).is_fully_connected() From 26a502591e6cf4228cf555106f609447d73cb6f3 Mon Sep 17 00:00:00 2001 From: Ed Younis Date: Sun, 15 Oct 2023 18:14:09 -0400 Subject: [PATCH 15/23] Updated pam workflow --- bqskit/compiler/compile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bqskit/compiler/compile.py b/bqskit/compiler/compile.py index 85e96f360..2999ab555 100644 --- a/bqskit/compiler/compile.py +++ b/bqskit/compiler/compile.py @@ -1259,6 +1259,7 @@ def build_seqpam_mapping_optimization_workflow( PAMLayoutPass(num_layout_passes), PAMRoutingPass(0.1), post_pam_seq, + ApplyPlacement(), UnfoldPass(), ], ), From 4e0f8e0b45430c413926d69593a810a6642568ea Mon Sep 17 00:00:00 2001 From: Ed Younis Date: Sun, 15 Oct 2023 18:14:58 -0400 Subject: [PATCH 16/23] Fixed issues with pam-verify --- bqskit/passes/mapping/verify.py | 42 ++++++++++++++++----------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/bqskit/passes/mapping/verify.py b/bqskit/passes/mapping/verify.py index ef5671169..545635e14 100644 --- a/bqskit/passes/mapping/verify.py +++ b/bqskit/passes/mapping/verify.py @@ -49,18 +49,20 @@ async def run(self, circuit: Circuit, data: PassData) -> None: # calculate exact panel unitary exact_circuit = Circuit(circuit.num_qudits, circuit.radixes) for op in circuit: - if not isinstance(op.gate, TaggedGate): - raise RuntimeError('Expected tagged gate.') - - pi = op.gate.tag['pre_perm'] - pf = op.gate.tag['post_perm'] - in_utry = op.gate.tag['original_utry'] - PI = PermutationMatrix.from_qubit_location(in_utry.num_qudits, pi) - PF = PermutationMatrix.from_qubit_location(in_utry.num_qudits, pf) - exact_circuit.append_gate( - ConstantUnitaryGate(PF @ in_utry @ PI.T), - op.location, - ) + if isinstance(op.gate, TaggedGate): + pi = op.gate.tag['pre_perm'] + pf = op.gate.tag['post_perm'] + in_utry = op.gate.tag['original_utry'] + n = in_utry.num_qudits + PI = PermutationMatrix.from_qubit_location(n, pi) + PF = PermutationMatrix.from_qubit_location(n, pf) + exact_circuit.append_gate( + ConstantUnitaryGate(PF.T @ in_utry @ PI), + op.location, + ) + + else: + exact_circuit.append_gate(op.gate, op.location, op.params) exact_unitary = exact_circuit.get_unitary() @@ -74,15 +76,13 @@ class UnTagPAMBlockDataPass(BasePass): async def run(self, circuit: Circuit, data: PassData) -> None: """Perform the pass's operation, see :class:`BasePass` for more.""" for cycle, op in circuit.operations_with_cycles(): - if not isinstance(op.gate, TaggedGate): - raise RuntimeError('Expected tagged gate.') - - circuit.replace_gate( - (cycle, op.location[0]), - op.gate.gate, - op.location, - op.params, - ) + if isinstance(op.gate, TaggedGate): + circuit.replace_gate( + (cycle, op.location[0]), + op.gate.gate, + op.location, + op.params, + ) class PAMVerificationSequence(PassAlias): From 2d501a5cab1a129f0e96043fcede6cc07e33aad0 Mon Sep 17 00:00:00 2001 From: Ed Younis Date: Sun, 15 Oct 2023 18:15:24 -0400 Subject: [PATCH 17/23] pre-commit --- bqskit/passes/mapping/embed.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bqskit/passes/mapping/embed.py b/bqskit/passes/mapping/embed.py index b00921d71..bd848f05d 100644 --- a/bqskit/passes/mapping/embed.py +++ b/bqskit/passes/mapping/embed.py @@ -135,7 +135,10 @@ async def run(self, circuit: Circuit, data: PassData) -> None: datas = [] for graph in graphs: - model = MachineModel(circuit.num_qudits, graph, data.gate_set, data.model.radixes) + model = MachineModel( + circuit.num_qudits, graph, + data.gate_set, data.model.radixes, + ) target_data = copy.deepcopy(data) target_data.model = model datas.append(target_data) From 10b2c188bf7616026b21354ef11c193647fd3186 Mon Sep 17 00:00:00 2001 From: Ed Younis Date: Mon, 16 Oct 2023 10:46:59 -0400 Subject: [PATCH 18/23] Fixed mapping issue --- bqskit/passes/mapping/routing/pam.py | 2 +- bqskit/passes/mapping/routing/sabre.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bqskit/passes/mapping/routing/pam.py b/bqskit/passes/mapping/routing/pam.py index 8e8781fb7..b5048aebd 100644 --- a/bqskit/passes/mapping/routing/pam.py +++ b/bqskit/passes/mapping/routing/pam.py @@ -31,7 +31,7 @@ async def run(self, circuit: Circuit, data: PassData) -> None: pi = [i for i in range(circuit.num_qudits)] out_data = self.forward_pass(circuit, pi, subgraph, perm_data, True) - data.final_mapping = [pi[x] for x in data.initial_mapping] + data.final_mapping = [pi[x] for x in data.final_mapping] _logger.info(f'Finished routing with layout: {str(pi)}') data[self.out_data_key] = out_data diff --git a/bqskit/passes/mapping/routing/sabre.py b/bqskit/passes/mapping/routing/sabre.py index 2fd67fe41..200866871 100644 --- a/bqskit/passes/mapping/routing/sabre.py +++ b/bqskit/passes/mapping/routing/sabre.py @@ -27,6 +27,6 @@ async def run(self, circuit: Circuit, data: PassData) -> None: pi = [i for i in range(circuit.num_qudits)] self.forward_pass(circuit, pi, subgraph, modify_circuit=True) - data.final_mapping = [pi[x] for x in data.initial_mapping] + data.final_mapping = [pi[x] for x in data.final_mapping] _logger.info(f'Finished routing with layout: {str(pi)}') From a803bdeb89e14240c94b819d0095150adeb9bd29 Mon Sep 17 00:00:00 2001 From: Ed Younis Date: Mon, 16 Oct 2023 10:47:05 -0400 Subject: [PATCH 19/23] More testing --- tests/compiler/compile/test_compile.py | 34 ++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/compiler/compile/test_compile.py b/tests/compiler/compile/test_compile.py index 857a092a0..c14e52aa3 100644 --- a/tests/compiler/compile/test_compile.py +++ b/tests/compiler/compile/test_compile.py @@ -1,22 +1,55 @@ from __future__ import annotations +from typing import Callable + +import pytest + from bqskit.compiler.compile import compile from bqskit.compiler.compiler import Compiler +from bqskit.compiler.machine import MachineModel from bqskit.ir.circuit import Circuit +from bqskit.qis.graph import CouplingGraph from bqskit.qis.permutation import PermutationMatrix +def default_model_gen(circuit: Circuit) -> MachineModel: + """Generate a default model for the given circuit.""" + return MachineModel(circuit.num_qudits) + + +def linear_model_gen(circuit: Circuit) -> MachineModel: + """Generate a linear model for the given circuit.""" + return MachineModel( + circuit.num_qudits, + CouplingGraph.linear(circuit.num_qudits), + ) + + +@pytest.mark.parametrize( + 'gen_model', + [ + default_model_gen, + linear_model_gen, + ], + ids=[ + 'default', + 'linear', + ], +) def test_medium_circuit_compile( compiler: Compiler, optimization_level: int, medium_qasm_file: str, + gen_model: Callable[[Circuit], MachineModel], ) -> None: circuit = Circuit.from_file(medium_qasm_file) + model = gen_model(circuit) out_circuit, pi, pf = compile( circuit, optimization_level=optimization_level, with_mapping=True, compiler=compiler, + model=model, ) in_utry = circuit.get_unitary() out_utry = out_circuit.get_unitary() @@ -24,3 +57,4 @@ def test_medium_circuit_compile( PF = PermutationMatrix.from_qubit_location(out_circuit.num_qudits, pf) error = out_utry.get_distance_from(PF.T @ in_utry @ PI, 1) assert error <= 1e-8 + assert model.is_compatible(out_circuit) From a5a224df299fea42112b55e5ed585f6e7b49c3d3 Mon Sep 17 00:00:00 2001 From: Ed Younis Date: Mon, 16 Oct 2023 12:39:59 -0400 Subject: [PATCH 20/23] Update tests --- tests/ir/circuit/test_op_gate_circ_methods.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/ir/circuit/test_op_gate_circ_methods.py b/tests/ir/circuit/test_op_gate_circ_methods.py index 06f240874..13caca3a8 100644 --- a/tests/ir/circuit/test_op_gate_circ_methods.py +++ b/tests/ir/circuit/test_op_gate_circ_methods.py @@ -339,6 +339,8 @@ class TestAppendCircuit: @given(circuits()) def test_reconstruct(self, circuit: Circuit) -> None: + if circuit.num_operations == 0: + return new_circuit = Circuit(circuit.num_qudits, circuit.radixes) new_circuit.append_circuit(circuit, list(range(circuit.num_qudits))) check_no_idle_cycles(new_circuit) @@ -346,6 +348,8 @@ def test_reconstruct(self, circuit: Circuit) -> None: @given(circuits()) def test_reconstruct_larger(self, circuit: Circuit) -> None: + if circuit.num_operations == 0: + return new_circ = Circuit(circuit.num_qudits + 1, circuit.radixes + (2,)) new_circ.append_circuit(circuit, list(range(circuit.num_qudits))) check_no_idle_cycles(new_circ) From b94ce8f71c34cc2f54b27af71cef2e2eb3dcb632 Mon Sep 17 00:00:00 2001 From: Ed Younis Date: Tue, 17 Oct 2023 06:08:53 -0400 Subject: [PATCH 21/23] Removed error case in append_circuit --- bqskit/ir/circuit.py | 9 ++++----- tests/ir/circuit/test_op_gate_circ_methods.py | 4 ---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/bqskit/ir/circuit.py b/bqskit/ir/circuit.py index c924e2de4..d58b299a7 100644 --- a/bqskit/ir/circuit.py +++ b/bqskit/ir/circuit.py @@ -1152,7 +1152,8 @@ def append_circuit( (Default: False) Returns: - int: The starting cycle index of the appended circuit. + int: The starting cycle index of the appended circuit. If the + appended circuit is empty, then this will be -1. Raises: ValueError: If `circuit` is not the same size as `location`. @@ -1178,16 +1179,14 @@ def append_circuit( op = Operation(CircuitGate(circuit, move), location, circuit.params) return self.append(op) - cycle_index: int | None = None + cycle_index = -1 + for op in circuit: mapped_location = [location[q] for q in op.location] ci = self.append(Operation(op.gate, mapped_location, op.params)) if cycle_index is None: cycle_index = ci - if cycle_index is None: - raise RuntimeError('Cannot append empty circuit.') - return cycle_index def extend(self, ops: Iterable[Operation]) -> None: diff --git a/tests/ir/circuit/test_op_gate_circ_methods.py b/tests/ir/circuit/test_op_gate_circ_methods.py index 13caca3a8..06f240874 100644 --- a/tests/ir/circuit/test_op_gate_circ_methods.py +++ b/tests/ir/circuit/test_op_gate_circ_methods.py @@ -339,8 +339,6 @@ class TestAppendCircuit: @given(circuits()) def test_reconstruct(self, circuit: Circuit) -> None: - if circuit.num_operations == 0: - return new_circuit = Circuit(circuit.num_qudits, circuit.radixes) new_circuit.append_circuit(circuit, list(range(circuit.num_qudits))) check_no_idle_cycles(new_circuit) @@ -348,8 +346,6 @@ def test_reconstruct(self, circuit: Circuit) -> None: @given(circuits()) def test_reconstruct_larger(self, circuit: Circuit) -> None: - if circuit.num_operations == 0: - return new_circ = Circuit(circuit.num_qudits + 1, circuit.radixes + (2,)) new_circ.append_circuit(circuit, list(range(circuit.num_qudits))) check_no_idle_cycles(new_circ) From a2664432d4547e79208e357a8f093300284e8e84 Mon Sep 17 00:00:00 2001 From: Ed Younis Date: Tue, 17 Oct 2023 07:47:55 -0400 Subject: [PATCH 22/23] Updated sabre test with apply placement --- tests/passes/mapping/test_sabre.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/passes/mapping/test_sabre.py b/tests/passes/mapping/test_sabre.py index 9c91c0047..e64c945f4 100644 --- a/tests/passes/mapping/test_sabre.py +++ b/tests/passes/mapping/test_sabre.py @@ -9,10 +9,10 @@ from bqskit.passes import GeneralizedSabreRoutingPass from bqskit.passes import GreedyPlacementPass from bqskit.passes import SetModelPass +from bqskit.passes.mapping.apply import ApplyPlacement from bqskit.qis import PermutationMatrix from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix - def test_simple(compiler: Compiler) -> None: cg = (0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (0, 7) model = MachineModel(8, cg) @@ -38,6 +38,7 @@ def test_simple(compiler: Compiler) -> None: GreedyPlacementPass(), GeneralizedSabreLayoutPass(), GeneralizedSabreRoutingPass(), + ApplyPlacement(), ] cc, data = compiler.compile(circuit, workflow, True) @@ -45,5 +46,9 @@ def test_simple(compiler: Compiler) -> None: pf = data['final_mapping'] PI = PermutationMatrix.from_qubit_location(5, pi) PF = PermutationMatrix.from_qubit_location(5, pf) + inactive = [i for i in range(cc.num_qudits) if i not in cc.active_qudits] + inactive.sort(reverse=True) + for i in inactive: + cc.pop_qudit(i) assert cc.get_unitary().get_distance_from(PF.T @ in_utry @ PI) < 1e-7 assert all(e in cg for e in cc.coupling_graph) From 89fac09b33f119da05b782261b469b7cc0b31375 Mon Sep 17 00:00:00 2001 From: Ed Younis Date: Tue, 17 Oct 2023 08:13:12 -0400 Subject: [PATCH 23/23] pre-commit --- tests/passes/mapping/test_sabre.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/passes/mapping/test_sabre.py b/tests/passes/mapping/test_sabre.py index e64c945f4..d2374da11 100644 --- a/tests/passes/mapping/test_sabre.py +++ b/tests/passes/mapping/test_sabre.py @@ -13,6 +13,7 @@ from bqskit.qis import PermutationMatrix from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix + def test_simple(compiler: Compiler) -> None: cg = (0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (0, 7) model = MachineModel(8, cg)