-
-
Notifications
You must be signed in to change notification settings - Fork 23
✨ Expand Qiskit RL actions #794
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v3/674-normalized-features
Are you sure you want to change the base?
Changes from all commits
890ee09
4cb43a1
27ad066
62ce9c8
f8aaad9
bcf2df6
8a50c24
6fd59d5
72d115c
b780667
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,7 +11,9 @@ | |
| from __future__ import annotations | ||
|
|
||
| import logging | ||
| from typing import TYPE_CHECKING, cast | ||
| from functools import cache | ||
| from importlib import import_module | ||
| from typing import TYPE_CHECKING, Any, cast | ||
|
|
||
| from qiskit.circuit import StandardEquivalenceLibrary | ||
| from qiskit.circuit.library import ( | ||
|
|
@@ -37,24 +39,33 @@ | |
| from qiskit.transpiler import CouplingMap, PassManager, TranspileLayout | ||
| from qiskit.transpiler.passes import ( | ||
| ApplyLayout, | ||
| BasicSwap, | ||
| BasisTranslator, | ||
| Collect2qBlocks, | ||
| CollectCliffords, | ||
| CommutativeCancellation, | ||
| CommutativeInverseCancellation, | ||
| ConsolidateBlocks, | ||
| Decompose, | ||
| DenseLayout, | ||
| Depth, | ||
| ElidePermutations, | ||
| EnlargeWithAncilla, | ||
| FixedPoint, | ||
| FullAncillaAllocation, | ||
| GatesInBasis, | ||
| InverseCancellation, | ||
| LookaheadSwap, | ||
| MinimumPoint, | ||
| Optimize1qGatesDecomposition, | ||
| Optimize1qGatesSimpleCommutation, | ||
| OptimizeCliffords, | ||
| RemoveDiagonalGatesBeforeMeasure, | ||
| RemoveIdentityEquivalent, | ||
| SabreLayout, | ||
| SabreSwap, | ||
| Size, | ||
| TrivialLayout, | ||
| UnitarySynthesis, | ||
| VF2Layout, | ||
| VF2PostLayout, | ||
|
|
@@ -81,6 +92,39 @@ | |
|
|
||
| logger = logging.getLogger("mqt-predictor") | ||
|
|
||
| _AI_ROUTING_ACTION_NAMES = frozenset({"AIRouting", "AIRouting_opt"}) | ||
|
|
||
|
|
||
| @cache | ||
| def _load_airouting() -> type[Any]: | ||
| """Load IBM's optional AI routing pass.""" | ||
| try: | ||
| module = import_module("qiskit_ibm_transpiler.ai.routing") | ||
| except ImportError as exc: | ||
| msg = "AIRouting requires a qiskit-ibm-transpiler installation compatible with this environment." | ||
| raise RuntimeError(msg) from exc | ||
| return cast("type[Any]", vars(module)["AIRouting"]) | ||
|
|
||
|
|
||
| def _airouting_pass(*, coupling_map: CouplingMap, layout_mode: str) -> Task: | ||
| """Construct IBM's local AI routing pass.""" | ||
| return _load_airouting()( | ||
| coupling_map=coupling_map, | ||
| optimization_level=3, | ||
| layout_mode=layout_mode, | ||
| local_mode=True, | ||
| ) | ||
|
|
||
|
|
||
| @cache | ||
| def _is_ai_routing_available() -> bool: | ||
| """Return whether IBM's AI routing pass can be imported.""" | ||
| try: | ||
| _load_airouting() | ||
| except RuntimeError: | ||
| return False | ||
| return True | ||
|
|
||
|
|
||
| def qiskit_optimization_actions() -> list[Action]: | ||
| """Returns the Qiskit optimization actions.""" | ||
|
|
@@ -149,7 +193,11 @@ def qiskit_optimization_actions() -> list[Action]: | |
| "OptimizeCliffords", | ||
| CompilationOrigin.QISKIT, | ||
| PassType.OPT, | ||
| [OptimizeCliffords()], | ||
| [ | ||
| CollectCliffords(), | ||
| OptimizeCliffords(), | ||
| Decompose(gates_to_decompose="clifford", apply_synthesis=True), | ||
| ], | ||
| preserves_layout=True, | ||
| preserves_routing=False, | ||
| preserves_synthesis=False, | ||
|
|
@@ -163,6 +211,32 @@ def qiskit_optimization_actions() -> list[Action]: | |
| preserves_routing=True, | ||
| preserves_synthesis=False, | ||
| ), | ||
| DeviceIndependentAction( | ||
| "RemoveIdentityEquivalent", | ||
| CompilationOrigin.QISKIT, | ||
| PassType.OPT, | ||
| [RemoveIdentityEquivalent()], | ||
| preserves_layout=True, | ||
| preserves_routing=True, | ||
| preserves_synthesis=True, | ||
| ), | ||
| DeferredDeviceAction( | ||
| "Optimize1qGatesSimpleCommutation", | ||
| CompilationOrigin.QISKIT, | ||
| PassType.OPT, | ||
| transpile_pass=lambda device: cast( | ||
| "list[Task]", | ||
| [ | ||
| Optimize1qGatesSimpleCommutation( | ||
| basis=device.operation_names, | ||
| run_to_completion=True, | ||
| ) | ||
| ], | ||
| ), | ||
| preserves_layout=True, | ||
| preserves_routing=True, | ||
| preserves_synthesis=True, | ||
| ), | ||
| ] | ||
|
|
||
|
|
||
|
|
@@ -249,9 +323,93 @@ def qiskit_layout_actions() -> list[Action]: | |
| ], | ||
| ), | ||
| ), | ||
| DeferredDeviceAction( | ||
| "TrivialLayout", | ||
| CompilationOrigin.QISKIT, | ||
| PassType.LAYOUT, | ||
| transpile_pass=lambda device: cast( | ||
| "list[Task]", | ||
| [ | ||
| TrivialLayout(coupling_map=CouplingMap(device.build_coupling_map())), | ||
| FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), | ||
| EnlargeWithAncilla(), | ||
| ApplyLayout(), | ||
| ], | ||
| ), | ||
| ), | ||
| DeferredDeviceAction( | ||
| "ElidePermutations", | ||
| CompilationOrigin.QISKIT, | ||
| PassType.LAYOUT, | ||
| transpile_pass=lambda device: cast( | ||
| "list[Task]", | ||
| [ | ||
| ElidePermutations(), | ||
| TrivialLayout(coupling_map=CouplingMap(device.build_coupling_map())), | ||
| FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), | ||
| EnlargeWithAncilla(), | ||
| ApplyLayout(), | ||
| ], | ||
| ), | ||
| ), | ||
| ] | ||
|
|
||
|
|
||
| def qiskit_routing_actions() -> list[Action]: | ||
| """Return the Qiskit routing actions.""" | ||
| return [ | ||
| DeferredDeviceAction( | ||
| "SabreSwap", | ||
| CompilationOrigin.QISKIT, | ||
| PassType.ROUTING, | ||
| transpile_pass=lambda device: cast( | ||
| "list[Task]", [SabreSwap(coupling_map=CouplingMap(device.build_coupling_map()), heuristic="decay")] | ||
| ), | ||
| ), | ||
| DeferredDeviceAction( | ||
| "BasicSwap", | ||
| CompilationOrigin.QISKIT, | ||
| PassType.ROUTING, | ||
| transpile_pass=lambda device: cast( | ||
| "list[Task]", [BasicSwap(coupling_map=CouplingMap(device.build_coupling_map()))] | ||
| ), | ||
| ), | ||
| DeferredDeviceAction( | ||
| "LookaheadSwap", | ||
| CompilationOrigin.QISKIT, | ||
| PassType.ROUTING, | ||
| transpile_pass=lambda device: cast( | ||
| "list[Task]", | ||
| [ | ||
| LookaheadSwap( | ||
| coupling_map=CouplingMap(device.build_coupling_map()), | ||
| search_depth=1, | ||
| search_width=1, | ||
| ) | ||
| ], | ||
| ), | ||
| ), | ||
|
Comment on lines
+358
to
+391
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Everything that is not SABRE here is a bit pointless to add because these outer routing methods aren't really developed or improved anymore and there is hardly ever any reason to choose them.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh i see. my naive thought was "cool more passes -> more room for experimantation for the agent", but you are right they seem hardly useful compared to SABRE
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd actually argue that more passes can be quite counter-productive if the passes being added do not really add value but just enlarge the search space. |
||
| ] | ||
|
|
||
|
|
||
| def qiskit_ai_routing_action() -> Action: | ||
| """Return IBM's AI routing action.""" | ||
| return DeferredDeviceAction( | ||
| "AIRouting", | ||
| CompilationOrigin.QISKIT, | ||
| PassType.ROUTING, | ||
| transpile_pass=lambda device: cast( | ||
| "list[Task]", | ||
| [ | ||
| _airouting_pass( | ||
| coupling_map=device.build_coupling_map(), | ||
| layout_mode="improve", | ||
| ) | ||
| ], | ||
| ), | ||
| ) | ||
|
|
||
|
|
||
| def qiskit_mapping_action() -> Action: | ||
| """Returns the Qiskit mapping action.""" | ||
| return DeferredDeviceAction( | ||
|
|
@@ -264,6 +422,24 @@ def qiskit_mapping_action() -> Action: | |
| ) | ||
|
|
||
|
|
||
| def qiskit_ai_mapping_action() -> Action: | ||
| """Return the combined AI layout and routing action.""" | ||
| return DeferredDeviceAction( | ||
| "AIRouting_opt", | ||
| CompilationOrigin.QISKIT, | ||
| PassType.MAPPING, | ||
| transpile_pass=lambda device: cast( | ||
| "list[Task]", | ||
| [ | ||
| _airouting_pass( | ||
| coupling_map=device.build_coupling_map(), | ||
| layout_mode="optimize", | ||
| ), | ||
| ], | ||
| ), | ||
| ) | ||
|
|
||
|
|
||
| def qiskit_synthesis_action() -> Action: | ||
| """Returns the Qiskit synthesis action.""" | ||
| return DeferredDeviceAction( | ||
|
|
@@ -365,16 +541,22 @@ def run_qiskit_action( | |
| if action.pass_type in {PassType.LAYOUT, PassType.MAPPING, PassType.FINAL_OPT}: | ||
| altered_qc, layout = _postprocess_layout_action(action, pm.property_set, altered_qc, layout, input_qubit_count) | ||
| elif action.pass_type == PassType.ROUTING and layout and pm.property_set["final_layout"] is not None: | ||
| layout.final_layout = pm.property_set["final_layout"] | ||
| routing_layout = pm.property_set["final_layout"] | ||
| layout.final_layout = ( | ||
| layout.final_layout.compose(routing_layout, circuit.qubits) | ||
| if layout.final_layout is not None | ||
| else routing_layout | ||
| ) | ||
|
|
||
| if altered_qc.count_ops().get("unitary"): | ||
| # Custom "unitary" gates can not be processed further by other passes | ||
| altered_qc = altered_qc.decompose(gates_to_decompose="unitary") | ||
|
|
||
| return altered_qc, layout | ||
|
|
||
|
|
||
| def is_qiskit_action_available(action: Action, device: Target) -> bool: | ||
| """Return whether a Qiskit action is available for the current device.""" | ||
| if action.name in _AI_ROUTING_ACTION_NAMES and not _is_ai_routing_available(): | ||
| return False | ||
| # Only allow VF2PostLayout if "ibm" is in the device name # TODO: Why? | ||
| return action.name != "VF2PostLayout" or "ibm" in device.description | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a bit of a strange addition. The Elision pass is a regular pass, not a Layout per-se. I see no reason why it should be coupled to a trivial layout.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the thing I was trying to workaround here is that elidepermuations alone do produce a
virtual_permutation_layout, which would be lost after the pass if not directly followed by a layout. The cleaner solution would probably be to track that along in our layout bookkeeping, but i have to investigate first what that would do in a cross-compiler scenario (i.e., when interleaving with TKET for example).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. Then I'd rather defer the addition of this to a later PR. I do not believe the current elide+trivial-layout pass provides any meaningful value.