Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ on:
jobs:
build-sdist:
name: 🐍 Packaging
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-sdist.yml@d6314c45667c131055a0389afc110e8dedc6da3f # v1.17.11
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-sdist.yml@c54be38945ae206affae9925ecd9c346f54c71b7 # v1.17.12

build-wheel:
name: 🐍 Packaging
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-wheel-build.yml@d6314c45667c131055a0389afc110e8dedc6da3f # v1.17.11
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-wheel-build.yml@c54be38945ae206affae9925ecd9c346f54c71b7 # v1.17.12

deploy:
if: github.event_name == 'release' && github.event.action == 'published'
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ concurrency:
jobs:
change-detection:
name: 🔍 Change
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-change-detection.yml@d6314c45667c131055a0389afc110e8dedc6da3f # v1.17.11
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-change-detection.yml@c54be38945ae206affae9925ecd9c346f54c71b7 # v1.17.12

python-tests:
name: 🐍 Test
Expand All @@ -24,15 +24,15 @@ jobs:
fail-fast: false
matrix:
runs-on: [ubuntu-24.04, macos-15, windows-2025]
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@d6314c45667c131055a0389afc110e8dedc6da3f # v1.17.11
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@c54be38945ae206affae9925ecd9c346f54c71b7 # v1.17.12
with:
runs-on: ${{ matrix.runs-on }}

python-coverage:
name: 🐍 Coverage
needs: [change-detection, python-tests]
if: fromJSON(needs.change-detection.outputs.run-python-tests)
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-coverage.yml@d6314c45667c131055a0389afc110e8dedc6da3f # v1.17.11
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-coverage.yml@c54be38945ae206affae9925ecd9c346f54c71b7 # v1.17.12
permissions:
contents: read
id-token: write
Expand All @@ -41,7 +41,7 @@ jobs:
name: 🐍 Lint
needs: change-detection
if: fromJSON(needs.change-detection.outputs.run-python-tests)
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-linter.yml@d6314c45667c131055a0389afc110e8dedc6da3f # v1.17.11
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-linter.yml@c54be38945ae206affae9925ecd9c346f54c71b7 # v1.17.12
with:
enable-ty: true
enable-mypy: false
Expand All @@ -50,13 +50,13 @@ jobs:
name: 🚀 CD
needs: change-detection
if: fromJSON(needs.change-detection.outputs.run-cd)
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-sdist.yml@d6314c45667c131055a0389afc110e8dedc6da3f # v1.17.11
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-sdist.yml@c54be38945ae206affae9925ecd9c346f54c71b7 # v1.17.12

build-wheel:
name: 🚀 CD
needs: change-detection
if: fromJSON(needs.change-detection.outputs.run-cd)
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-wheel-build.yml@d6314c45667c131055a0389afc110e8dedc6da3f # v1.17.11
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-wheel-build.yml@c54be38945ae206affae9925ecd9c346f54c71b7 # v1.17.12

# this job does nothing and is only used for branch protection
required-checks-pass:
Expand Down
8 changes: 4 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ repos:

## Check the pyproject.toml file
- repo: https://github.com/henryiii/validate-pyproject-schema-store
rev: 2026.02.15
rev: 2026.02.22
hooks:
- id: validate-pyproject
priority: 0
Expand All @@ -56,7 +56,7 @@ repos:

## Check for spelling
- repo: https://github.com/adhtruong/mirrors-typos
rev: v1.43.4
rev: v1.43.5
hooks:
- id: typos
priority: 0
Expand All @@ -78,7 +78,7 @@ repos:

## Ensure uv lock file is up-to-date
- repo: https://github.com/astral-sh/uv-pre-commit
rev: 0.10.3
rev: 0.10.4
hooks:
- id: uv-lock
priority: 0
Expand Down Expand Up @@ -114,7 +114,7 @@ repos:

## Python linting using ruff
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.1
rev: v0.15.2
hooks:
- id: ruff-format
priority: 1
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ docs = [
dev = [
{include-group = "test"},
"nox>=2025.11.12",
"ty==0.0.17",
"ty==0.0.18",
]

[project.urls]
Expand Down
5 changes: 3 additions & 2 deletions src/mqt/predictor/rl/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,9 @@ def get_state_sample(max_qubits: int, path_training_circuits: Path, rng: Generat

try:
qc = QuantumCircuit.from_qasm_file(file_list[random_index]) # ty: ignore[invalid-argument-type]
except Exception:
raise RuntimeError("Could not read QuantumCircuit from: " + str(file_list[random_index])) from None
except Exception as e:
msg = f"Could not read QuantumCircuit from: {file_list[random_index]}"
raise RuntimeError(msg) from e

return qc, str(file_list[random_index])

Expand Down
4 changes: 2 additions & 2 deletions src/mqt/predictor/rl/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,8 @@ def final_layout_pytket_to_qiskit(pytket_circuit: Circuit, qiskit_circuit: Quant


def final_layout_bqskit_to_qiskit(
bqskit_initial_layout: list[int],
bqskit_final_layout: list[int],
bqskit_initial_layout: tuple[int, ...],
bqskit_final_layout: tuple[int, ...],
compiled_qc: QuantumCircuit,
initial_qc: QuantumCircuit,
) -> TranspileLayout:
Expand Down
22 changes: 13 additions & 9 deletions src/mqt/predictor/rl/predictorenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,17 +320,19 @@ def apply_action(self, action_index: int) -> QuantumCircuit | None:

def _apply_qiskit_action(self, action: Action, action_index: int) -> QuantumCircuit:
if action.name == "QiskitO3" and isinstance(action, DeviceDependentAction):
assert callable(action.transpile_pass)
passes_ = action.transpile_pass(
factory = cast("Callable[[list[str], CouplingMap | None], list[Task]]", action.transpile_pass)
passes = factory(
self.device.operation_names,
CouplingMap(self.device.build_coupling_map()) if self.layout else None,
)
passes = cast("list[Task]", passes_)
assert action.do_while is not None
pm = PassManager([DoWhileController(passes, do_while=action.do_while)])
else:
passes_ = action.transpile_pass(self.device) if callable(action.transpile_pass) else action.transpile_pass
passes = cast("list[Task]", passes_)
if callable(action.transpile_pass):
factory = cast("Callable[[Target], list[Task]]", action.transpile_pass)
passes = factory(self.device)
else:
passes = cast("list[Task]", action.transpile_pass)
pm = PassManager(passes)

altered_qc = pm.run(self.state)
Expand Down Expand Up @@ -372,8 +374,11 @@ def _handle_qiskit_layout_postprocessing(

def _apply_tket_action(self, action: Action, action_index: int) -> QuantumCircuit:
tket_qc = qiskit_to_tk(self.state, preserve_param_uuid=True)
passes = action.transpile_pass(self.device) if callable(action.transpile_pass) else action.transpile_pass
assert isinstance(passes, list)
if callable(action.transpile_pass):
factory = cast("Callable[[Target], list[Task]]", action.transpile_pass)
passes = factory(self.device)
else:
passes = cast("list[Task]", action.transpile_pass)
for pass_ in passes:
assert isinstance(pass_, TketBasePass | PreProcessTKETRoutingAfterQiskitLayout)
pass_.apply(tket_qc)
Expand Down Expand Up @@ -402,7 +407,6 @@ def _apply_bqskit_action(self, action: Action, action_index: int) -> QuantumCirc
ValueError: If the action index is not in the action set or if the action origin is not supported.
"""
bqskit_qc = qiskit_to_bqskit(self.state)
assert callable(action.transpile_pass)
if action_index in self.actions_opt_indices:
transpile = cast("Callable[[Circuit], Circuit]", action.transpile_pass)
bqskit_compiled_qc = transpile(bqskit_qc)
Expand All @@ -411,7 +415,7 @@ def _apply_bqskit_action(self, action: Action, action_index: int) -> QuantumCirc
bqskit_compiled_qc = factory(self.device)(bqskit_qc)
elif action_index in self.actions_mapping_indices:
factory = cast(
"Callable[[Target], Callable[[Circuit], tuple[Circuit, list[int], list[int]]]]",
"Callable[[Target], Callable[[Circuit], tuple[Circuit, tuple[int, ...], tuple[int, ...]]]]",
action.transpile_pass,
)
bqskit_compiled_qc, initial, final = factory(self.device)(bqskit_qc)
Expand Down
11 changes: 7 additions & 4 deletions tests/compilation/test_helper_rl.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@
from mqt.predictor.rl.parsing import postprocess_vf2postlayout

if TYPE_CHECKING:
from collections.abc import Callable

from qiskit.passmanager.base_tasks import Task
from qiskit.transpiler import Target


def test_create_feature_dict() -> None:
Expand Down Expand Up @@ -58,8 +61,8 @@ def test_vf2_layout_and_postlayout() -> None:
passes: list[Task] | None = None
for layout_action in get_actions_by_pass_type()[PassType.LAYOUT]:
if layout_action.name == "VF2Layout":
assert callable(layout_action.transpile_pass)
passes = cast("list[Task]", layout_action.transpile_pass(dev))
factory = cast("Callable[[Target], list[Task]]", layout_action.transpile_pass)
passes = factory(dev)
break
assert passes is not None
pm = PassManager(passes)
Expand All @@ -76,8 +79,8 @@ def test_vf2_layout_and_postlayout() -> None:
post_layout_passes: list[Task] | None = None
for layout_action in get_actions_by_pass_type()[PassType.FINAL_OPT]:
if layout_action.name == "VF2PostLayout":
assert callable(layout_action.transpile_pass)
post_layout_passes = cast("list[Task]", layout_action.transpile_pass(dev_success))
factory = cast("Callable[[Target], list[Task]]", layout_action.transpile_pass)
post_layout_passes = factory(dev_success)
break
assert post_layout_passes is not None

Expand Down
47 changes: 26 additions & 21 deletions tests/compilation/test_integration_further_SDKs.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from bqskit.ext import bqskit_to_qiskit, qiskit_to_bqskit
from bqskit.ir.circuit import Circuit
from mqt.bench.targets import get_available_device_names, get_device
from pytket._tket.passes import BasePass as TketBasePass # noqa: PLC2701
from pytket.circuit import Qubit
from pytket.extensions.qiskit import qiskit_to_tk, tk_to_qiskit
from qiskit import QuantumCircuit
Expand All @@ -27,16 +26,21 @@

from mqt.predictor.rl.actions import CompilationOrigin, PassType, get_actions_by_pass_type
from mqt.predictor.rl.parsing import (
PreProcessTKETRoutingAfterQiskitLayout,
final_layout_bqskit_to_qiskit,
final_layout_pytket_to_qiskit,
)

if TYPE_CHECKING:
from collections.abc import Callable

from pytket._tket.passes import BasePass as TketBasePass
from qiskit.passmanager.base_tasks import Task
from qiskit.transpiler import Target

from mqt.predictor.rl.actions import Action
from mqt.predictor.rl.parsing import (
PreProcessTKETRoutingAfterQiskitLayout,
)


@pytest.fixture
Expand All @@ -58,8 +62,8 @@ def test_bqskit_o2_action(available_actions_dict: dict[PassType, list[Action]])
qc.cx(0, 1)

bqskit_qc = qiskit_to_bqskit(qc)
assert callable(action_bqskit_o2.transpile_pass)
bqskit_qc_optimized = action_bqskit_o2.transpile_pass(bqskit_qc)
factory = cast("Callable[[Circuit], Circuit]", action_bqskit_o2.transpile_pass)
bqskit_qc_optimized = factory(bqskit_qc)
assert isinstance(bqskit_qc_optimized, Circuit)
optimized_qc = bqskit_to_qiskit(bqskit_qc_optimized)

Expand All @@ -83,9 +87,8 @@ def test_bqskit_synthesis_action(device: Target, available_actions_dict: dict[Pa
check_nat_gates(qc)
assert not check_nat_gates.property_set["all_gates_in_basis"]

assert callable(action_bqskit_synthesis_action.transpile_pass)
lambda_ = action_bqskit_synthesis_action.transpile_pass(device)
assert callable(lambda_)
factory = cast("Callable[[Target], Callable[[Circuit], Circuit]]", action_bqskit_synthesis_action.transpile_pass)
lambda_ = factory(device)
bqskit_qc = qiskit_to_bqskit(qc)
if "rigetti" in device.description or "ionq" in device.description or "iqm" in device.description:
with pytest.raises(ValueError, match=re.escape("not supported in BQSKIT")):
Expand Down Expand Up @@ -122,10 +125,11 @@ def test_bqskit_mapping_action_swaps_necessary(available_actions_dict: dict[Pass

device = get_device("ibm_falcon_27")
bqskit_qc = qiskit_to_bqskit(qc)
assert callable(bqskit_mapping_action.transpile_pass)
lambda_ = bqskit_mapping_action.transpile_pass(device)
assert callable(lambda_)
bqskit_qc_mapped, input_mapping, output_mapping = lambda_(bqskit_qc)
factory = cast(
"Callable[[Target], Callable[[Circuit], tuple[Circuit, tuple[int, ...], tuple[int, ...]]]]",
bqskit_mapping_action.transpile_pass,
)
bqskit_qc_mapped, input_mapping, output_mapping = factory(device)(bqskit_qc)
mapped_qc = bqskit_to_qiskit(bqskit_qc_mapped)
layout = final_layout_bqskit_to_qiskit(input_mapping, output_mapping, mapped_qc, qc)

Expand Down Expand Up @@ -186,10 +190,11 @@ def test_bqskit_mapping_action_no_swaps_necessary(available_actions_dict: dict[P
device = get_device("quantinuum_h2_56")

bqskit_qc = qiskit_to_bqskit(qc_no_swap_needed)
assert callable(bqskit_mapping_action.transpile_pass)
lambda_ = bqskit_mapping_action.transpile_pass(device)
assert callable(lambda_)
bqskit_qc_mapped, input_mapping, output_mapping = lambda_(bqskit_qc)
factory = cast(
"Callable[[Target], Callable[[Circuit], tuple[Circuit, tuple[int, ...], tuple[int, ...]]]]",
bqskit_mapping_action.transpile_pass,
)
bqskit_qc_mapped, input_mapping, output_mapping = factory(device)(bqskit_qc)
mapped_qc = bqskit_to_qiskit(bqskit_qc_mapped)
layout = final_layout_bqskit_to_qiskit(input_mapping, output_mapping, mapped_qc, qc_no_swap_needed)
assert layout is not None
Expand All @@ -211,8 +216,8 @@ def test_tket_routing(available_actions_dict: dict[PassType, list[Action]]) -> N
device = get_device("quantinuum_h2_56")

layout_action = available_actions_dict[PassType.LAYOUT][0]
assert callable(layout_action.transpile_pass)
passes_ = cast("list[Task]", layout_action.transpile_pass(device))
factory = cast("Callable[[Target], list[Task]]", layout_action.transpile_pass)
passes_ = factory(device)
pm = PassManager(passes_)
layouted_qc = pm.run(qc)
initial_layout = pm.property_set["layout"]
Expand All @@ -225,11 +230,11 @@ def test_tket_routing(available_actions_dict: dict[PassType, list[Action]]) -> N
assert routing_action is not None

tket_qc = qiskit_to_tk(layouted_qc, preserve_param_uuid=True)
assert callable(routing_action.transpile_pass)
passes = routing_action.transpile_pass(device)
assert isinstance(passes, list)
factory = cast(
"Callable[[Target], list[TketBasePass | PreProcessTKETRoutingAfterQiskitLayout]]", routing_action.transpile_pass
)
passes = factory(device)
for pass_ in passes:
assert isinstance(pass_, TketBasePass | PreProcessTKETRoutingAfterQiskitLayout)
pass_.apply(tket_qc)

qbs = tket_qc.qubits
Expand Down
Loading
Loading