Add GNN-Based Predictor with DAG Preprocessing#430
Add GNN-Based Predictor with DAG Preprocessing#430antotu wants to merge 190 commits intomunich-quantum-toolkit:mainfrom
Conversation
…into gnn-branch
…into gnn-branch
…into gnn-branch
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @src/mqt/predictor/_version.py:
- Around line 21-27: Replace the hardcoded "TYPE_CHECKING = False" assignment
with a proper import from typing (use "from typing import TYPE_CHECKING") so the
conditional block becomes reachable during static type checking; keep the
existing TYPE_CHECKING conditional that defines VERSION_TUPLE and COMMIT_ID for
type checkers and the else branch that assigns object to VERSION_TUPLE and
COMMIT_ID.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (1)
src/mqt/predictor/_version.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-25T13:28:19.850Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: src/mqt/predictor/rl/predictorenv.py:271-271
Timestamp: 2025-12-25T13:28:19.850Z
Learning: In the munich-quantum-toolkit/predictor repository, Ruff has a broad rule set including FLake8-SLF (slf) in extend-select. For private member access (e.g., self.state._layout = self.layout) in src/mqt/predictor/rl/predictorenv.py, include a per-file noqa directive: # noqa: SLF001. This is appropriate even if Ruff reports RUF100 as unused, to acknowledge intentional private attribute access and to avoid false positives in this specific code path. Apply this directive only to the files where private-member access is intentionally used and where SLF001 is the correct rationale.
Applied to files:
src/mqt/predictor/_version.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
- GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
- GitHub Check: 🐍 Test (macos-14) / 🐍 macos-14
🔇 Additional comments (1)
src/mqt/predictor/_version.py (1)
1-10: Remove this generated file from version control.This file is explicitly marked as generated by setuptools-scm and should not be tracked in version control (lines 9-10). Tracking generated files causes merge conflicts and synchronization issues. The file will be automatically generated during package installation from your git metadata.
Add
src/mqt/predictor/_version.pyto.gitignoreand remove it from the repository.⛔ Skipped due to learnings
Learnt from: denialhaag Repo: munich-quantum-toolkit/core PR: 1383 File: python/mqt/core/ir/operations.pyi:9-16 Timestamp: 2025-12-15T01:59:17.023Z Learning: In the munich-quantum-toolkit/core repository, stub files (.pyi) are auto-generated by nanobind's stubgen tool and should not be manually modified for style preferences, as changes would be overwritten during regeneration.
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In @src/mqt/predictor/ml/predictor.py:
- Around line 544-546: Remove the per-batch debug print statements in the
training loop of src/mqt/predictor/ml/predictor.py (look inside the
train/train_epoch function or the loop that constructs the model with
output_dim=num_outputs, dropout_p=dropout, bidirectional=bidirectional); delete
any print(...) calls that run every batch or change them to conditional logging
(e.g., logger.debug behind a verbose flag) or aggregate and log once per epoch
instead.
- Around line 527-536: Remove the leftover debug print statements in the
cross-validation loop: delete the prints that output "Predictor", "Train
subset:", the loop that prints each batch from train_loader, and the prints that
show batch_device.y and "preds:"/targets shapes; if you need runtime visibility
replace them with proper logging at debug level (e.g., logger.debug) tied to the
training function that builds train_loader/val_loader so normal stdout is not
polluted. Ensure references to DataLoader, train_loader, val_loader,
batch_device, preds and targets remain unchanged except for removing the print
calls.
In @tests/compilation/test_predictor_rl.py:
- Around line 112-148: The two tests
test_qcompile_with_newly_trained_models_quantinuum and
test_qcompile_with_newly_trained_models duplicate setup/assertions; refactor by
parameterizing the device name (e.g., via pytest.mark.parametrize with values
"ibm_falcon_127" and "quantinuum_h2_56") or extract a small helper that accepts
device_name and runs the common sequence (get_device, get_benchmark,
Predictor(...), model_path check + pytest.raises, Predictor.train_model,
rl_compile, GatesInBasis checks and final asserts). Keep unique expectations
(the FileNotFoundError match) computed from the device/figure_of_merit so
rl_compile, Predictor.train_model, get_device, get_benchmark, and GatesInBasis
references remain clear and reused.
In @tests/device_selection/test_predictor_ml.py:
- Line 131: The assertion using two equality checks should be simplified to use
membership testing; replace the expression comparing predicted.description with
"ibm_falcon_127" or "quantinuum_h2_56" (the assert in
tests/device_selection/test_predictor_ml.py referencing predicted.description)
with an assertion that predicted.description is in the list or tuple of allowed
values to make the test clearer and more idiomatic.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (4)
src/mqt/predictor/ml/helper.pysrc/mqt/predictor/ml/predictor.pytests/compilation/test_predictor_rl.pytests/device_selection/test_predictor_ml.py
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-12-25T13:28:10.402Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: tests/compilation/test_predictor_rl.py:225-225
Timestamp: 2025-12-25T13:28:10.402Z
Learning: In the munich-quantum-toolkit/predictor repository, Ruff is configured with the "SLF" (flake8-self) rule category enabled in extend-select. SLF001 (private member access) is active, so `# noqa: SLF001` directives are necessary when accessing private methods/attributes (e.g., `_ensure_device_averages_cached()`), even if Ruff reports RUF100 warnings suggesting the directive is unused.
Applied to files:
src/mqt/predictor/ml/helper.pysrc/mqt/predictor/ml/predictor.py
📚 Learning: 2025-12-25T13:28:19.850Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: src/mqt/predictor/rl/predictorenv.py:271-271
Timestamp: 2025-12-25T13:28:19.850Z
Learning: In the munich-quantum-toolkit/predictor repository, Ruff has a broad rule set including FLake8-SLF (slf) in extend-select. For private member access (e.g., self.state._layout = self.layout) in src/mqt/predictor/rl/predictorenv.py, include a per-file noqa directive: # noqa: SLF001. This is appropriate even if Ruff reports RUF100 as unused, to acknowledge intentional private attribute access and to avoid false positives in this specific code path. Apply this directive only to the files where private-member access is intentionally used and where SLF001 is the correct rationale.
Applied to files:
src/mqt/predictor/ml/helper.pysrc/mqt/predictor/ml/predictor.py
📚 Learning: 2025-12-04T06:59:40.314Z
Learnt from: MatthiasReumann
Repo: munich-quantum-toolkit/core PR: 1301
File: mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp:84-85
Timestamp: 2025-12-04T06:59:40.314Z
Learning: In the MQTOpt MLIR routing passes (NaiveRoutingPassSC, AStarRoutingPassSC), the input IR is guaranteed to contain only 1-qubit and 2-qubit gates. All 3+-qubit gates must be decomposed before routing; otherwise the input IR is invalid. This invariant allows skipTwoQubitBlock in LayeredUnit.cpp to safely assert wires.size() == 2.
Applied to files:
src/mqt/predictor/ml/helper.py
📚 Learning: 2025-12-25T13:28:02.860Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: tests/compilation/test_predictor_rl.py:225-225
Timestamp: 2025-12-25T13:28:02.860Z
Learning: In the munich-quantum-toolkit/predictor repository, when using Ruff with the SLF (flake8-self) rules enabled, explicitly add a noqa: SLF001 directive when accessing private members (e.g., _ensure_device_averages_cached()) in tests under tests/, even if Ruff would report RUF100 that the directive is unused. This applies to Python test files (and related code) that access private attributes or methods, to ensure maintainability and clarity of the intent.
Applied to files:
tests/device_selection/test_predictor_ml.pytests/compilation/test_predictor_rl.py
📚 Learning: 2025-12-21T22:35:08.572Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/fomac/fomac.cpp:348-364
Timestamp: 2025-12-21T22:35:08.572Z
Learning: In the munich-quantum-toolkit/core repository's nanobind bindings, use `.sig("...")` on parameter arguments that have vector or container defaults (e.g., `"sites"_a.sig("...") = std::vector<fomac::Session::Device::Site>{}`) to prevent exposing mutable defaults in the Python API, which would be flagged as a code smell by Python linters. This pattern is preferred over removing `.sig("...")` even though it shows `...` in the stub signature.
Applied to files:
src/mqt/predictor/ml/predictor.py
🧬 Code graph analysis (3)
src/mqt/predictor/ml/helper.py (1)
src/mqt/predictor/utils.py (1)
calc_supermarq_features(94-146)
tests/device_selection/test_predictor_ml.py (3)
src/mqt/predictor/ml/helper.py (1)
get_path_training_data(36-38)src/mqt/predictor/rl/helper.py (1)
get_path_training_data(97-99)src/mqt/predictor/ml/predictor.py (3)
predict_device_for_figure_of_merit(847-926)setup_device_predictor(103-167)Predictor(170-844)
src/mqt/predictor/ml/predictor.py (3)
src/mqt/predictor/ml/gnn.py (1)
GNN(191-295)src/mqt/predictor/hellinger/utils.py (1)
get_hellinger_model_path(135-144)src/mqt/predictor/ml/helper.py (11)
TrainingData(616-626)create_dag(192-309)create_feature_vector(164-189)evaluate_classification_model(351-430)evaluate_regression_model(433-494)get_gnn_input_features(312-324)get_path_trained_model(48-50)get_path_trained_model_gnn(53-57)get_path_training_circuits(60-62)get_path_training_circuits_compiled(65-67)get_path_training_data(36-38)
🪛 Ruff (0.14.10)
tests/device_selection/test_predictor_ml.py
42-42: Boolean-typed positional argument in function definition
(FBT001)
42-42: Boolean-typed positional argument in function definition
(FBT001)
90-90: Boolean-typed positional argument in function definition
(FBT001)
90-90: Boolean-typed positional argument in function definition
(FBT001)
170-170: Boolean-typed positional argument in function definition
(FBT001)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
- GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
- GitHub Check: 🐍 Test (macos-14) / 🐍 macos-14
🔇 Additional comments (15)
src/mqt/predictor/ml/predictor.py (5)
84-88: LGTM!The type aliases are well-defined and properly distinguish between GNN samples (with graph data) and feature samples (with classical feature vectors). The union type
TrainingSamplecleanly supports both paths.
94-101: LGTM!The
TrainGNNKwargsTypedDict withtotal=Falsecorrectly defines optional GNN training parameters that can be passed through**gnn_kwargs.
347-372: LGTM! Safetensors serialization is a good choice.The GNN data serialization using safetensors is secure and efficient. The separate
.labelfile handling for string labels is a reasonable approach since safetensors only supports tensor data.
517-524: LGTM! k-folds guard correctly handles edge cases.The logic properly ensures
2 <= k_folds <= len(dataset)and raises a clear error message for insufficient samples.
883-919: LGTM! GNN inference path is well-structured.The GNN prediction path correctly:
- Loads model metadata from JSON
- Reconstructs the GNN architecture with saved hyperparameters
- Uses
weights_only=Trueandmap_locationfor secure model loading- Properly squeezes the batch dimension from output
tests/device_selection/test_predictor_ml.py (3)
38-43: Boolean parameters in parameterized tests are acceptable.The Ruff FBT001 warnings about boolean-typed positional arguments can be safely ignored here. In pytest parameterized tests, boolean parameters are idiomatic and the
idsparameter makes the test output clear.
148-156: LGTM! Cleanup logic properly handles new GNN artifacts.The cleanup correctly removes:
.npy,.pt,.safetensors,.labelfilesgraph_dataset_*directories with their contents
68-78: LGTM! Artifact assertions properly distinguish GNN and RF paths.The test correctly validates different expected artifacts:
- GNN:
graph_dataset_expected_fidelity/directory with.safetensorsfiles- RF:
training_data_expected_fidelity.npyBoth paths verify
names_listandscores_listexist.src/mqt/predictor/ml/helper.py (7)
119-152: LGTM! OpenQASM 3.0 gate list with documentation link.The gate list includes a reference to the OpenQASM 3.0 specification and notes about verification, which aids future maintenance.
192-216: LGTM! DAG creation with proper error handling.The function correctly:
- Removes barriers before processing
- Wraps transpilation in try-except with descriptive error message
- Uses
_safe_floatto handle symbolic parameters gracefully
278-309: LGTM! Critical path detection is robust.The implementation:
- Uses defensive
.get(node, 0)access consistently- Handles empty
topo_nodesedge case- Correctly computes longest path using forward/backward passes
327-345: LGTM!get_results_classescorrectly handles both encoding types.The function properly distinguishes:
- 1D integer labels: uses directly as
.long()- [N, 1] shaped: views as 1D and converts to
.long()- One-hot encoded: uses
argmax(dim=1)This addresses the past review concern about corrupted target labels.
405-407: LGTM! Empty loader guard preventstorch.catcrash.The early return when
all_predsorall_targetsis empty correctly prevents the runtime error that would occur when concatenating empty lists.
588-600: LGTM! Verbose output correctly distinguishes task types.Classification displays accuracy; regression displays R² — no longer mixing irrelevant metrics.
615-626: LGTM! TrainingData dataclass supports both classical and GNN data.The widened type annotations correctly support:
- Classical:
NDArray[np.float64]for features- GNN:
list[torch_geometric.data.Data]for graph dataOptional fields with
Nonedefaults handle cases where test data isn't available.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In @tests/device_selection/test_helper_ml.py:
- Around line 41-58: The test misses asserting the feature dimension for node
vectors; update test_empty_circuit_dag to also verify node_vector.shape[1]
equals the expected feature size used by create_dag (referencing the constant or
value used by create_dag, e.g., FEATURE_DIM or the hardcoded feature length in
the DAG feature construction). Locate create_dag and confirm the exact feature
dimension symbol or value, then add an assertion using that symbol (or the
literal expected integer) to ensure the second dimension of node_vector matches
the DAG feature dimension.
- Around line 34-38: The test test_create_dag incorrectly treats the return of
create_dag as a single value and uses a weak assertion; unpack the returned
tuple into node_vector, edge_index, number_nodes (from create_dag(qc)) and
replace the trivial assert with meaningful checks: ensure node_vector is not
None and has expected shape/length, edge_index is a sequence/array of edges
(non-empty if expected), and number_nodes equals the length of node_vector (or a
computed expected count from get_benchmark(...).decompose()). Use the existing
get_benchmark and BenchmarkLevel.INDEP/decompose call to derive any expected
size for stronger assertions.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (1)
tests/device_selection/test_helper_ml.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-25T13:28:02.860Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: tests/compilation/test_predictor_rl.py:225-225
Timestamp: 2025-12-25T13:28:02.860Z
Learning: In the munich-quantum-toolkit/predictor repository, when using Ruff with the SLF (flake8-self) rules enabled, explicitly add a noqa: SLF001 directive when accessing private members (e.g., _ensure_device_averages_cached()) in tests under tests/, even if Ruff would report RUF100 that the directive is unused. This applies to Python test files (and related code) that access private attributes or methods, to ensure maintainability and clarity of the intent.
Applied to files:
tests/device_selection/test_helper_ml.py
🧬 Code graph analysis (1)
tests/device_selection/test_helper_ml.py (1)
src/mqt/predictor/ml/helper.py (1)
create_dag(192-309)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
- GitHub Check: 🐍 Test (macos-14) / 🐍 macos-14
- GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
🔇 Additional comments (1)
tests/device_selection/test_helper_ml.py (1)
13-13: LGTM!The new imports are necessary for the DAG tests and are used appropriately.
Also applies to: 15-15, 18-18
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/mqt/predictor/ml/predictor.py (1)
374-450: Align_generate_training_sampletype hints with actual usage
_generate_training_sampleis annotated withfile: Path, butgenerate_training_datapassesfilename.name(astr). Inside the method you treatfileas bothstrandPath(str(file),path_uncompiled_circuit / file), so the runtime code works but the signature is misleading for type checkers.Consider relaxing the annotation to reflect actual usage:
Suggested diff
- def _generate_training_sample( - self, - file: Path, + def _generate_training_sample( + self, + file: Path | str,This avoids confusing static analysis without changing behavior.
🤖 Fix all issues with AI agents
In @src/mqt/predictor/ml/predictor.py:
- Around line 332-343: The GNN training Data is created with an extra leading
dimension because y is built using .unsqueeze(0) which produces shape [1,
num_devices] per sample and later batches to [batch_size, 1, num_devices],
causing a mismatch with the model output; fix this by constructing the target
tensor for gnn_training_sample without the unsqueeze so y is a 1-D tensor of
device scores (use torch.tensor(value_device, dtype=torch.float32)) when
building Data in the block that creates gnn_training_sample (the Data(...) call
referenced by variable gnn_training_sample and guarded by self.gnn) so batched
targets match the GNN output shape expected by evaluate_classification_model.
In @tests/device_selection/test_predictor_ml.py:
- Around line 86-128: The test function
test_setup_multidevice_predictor_with_prediction_gnn uses an undefined variable
gnn in the if/else block; since the test calls setup_device_predictor(...,
gnn=True, ...) and predict_device_for_figure_of_merit(..., gnn=True), remove the
conditional and always assert the GNN-specific artifacts (check
graph_dataset_expected_fidelity exists and contains .safetensors and the
names/scores numpy files) and keep the final prediction call asserting
predicted.description; update the assertions in the function body accordingly to
reference the GNN artifact paths.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (4)
src/mqt/predictor/ml/helper.pysrc/mqt/predictor/ml/predictor.pytests/device_selection/test_predictor_ml.pytests/hellinger_distance/test_estimated_hellinger_distance.py
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-12-25T13:28:02.860Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: tests/compilation/test_predictor_rl.py:225-225
Timestamp: 2025-12-25T13:28:02.860Z
Learning: In the munich-quantum-toolkit/predictor repository, when using Ruff with the SLF (flake8-self) rules enabled, explicitly add a noqa: SLF001 directive when accessing private members (e.g., _ensure_device_averages_cached()) in tests under tests/, even if Ruff would report RUF100 that the directive is unused. This applies to Python test files (and related code) that access private attributes or methods, to ensure maintainability and clarity of the intent.
Applied to files:
tests/device_selection/test_predictor_ml.pytests/hellinger_distance/test_estimated_hellinger_distance.py
📚 Learning: 2025-12-25T13:28:25.619Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: src/mqt/predictor/rl/predictorenv.py:271-271
Timestamp: 2025-12-25T13:28:25.619Z
Learning: In the munich-quantum-toolkit/predictor repository, Ruff is configured with an extensive set of rules including SLF (flake8-self) in the extend-select list. The `# noqa: SLF001` directive for private member access (e.g., `self.state._layout = self.layout` in src/mqt/predictor/rl/predictorenv.py) is necessary and appropriate, even if Ruff reports RUF100 warnings suggesting the directive is unused.
Applied to files:
tests/hellinger_distance/test_estimated_hellinger_distance.py
📚 Learning: 2025-11-26T10:29:36.435Z
Learnt from: marcelwa
Repo: munich-quantum-toolkit/core PR: 1243
File: test/python/fomac/test_fomac.py:54-66
Timestamp: 2025-11-26T10:29:36.435Z
Learning: In test/python/fomac/test_fomac.py, the `ddsim_device` fixture returns `Device | None` even though it only returns a `Device` or calls `pytest.skip()`. The `| None` annotation is required for the type checker to pass, as type checkers may not recognize `pytest.skip()` as a function that never returns.
Applied to files:
tests/hellinger_distance/test_estimated_hellinger_distance.py
📚 Learning: 2025-11-04T14:26:25.420Z
Learnt from: marcelwa
Repo: munich-quantum-toolkit/core PR: 1243
File: test/python/qdmi/qiskit/conftest.py:11-19
Timestamp: 2025-11-04T14:26:25.420Z
Learning: In the munich-quantum-toolkit/core repository, Qiskit is always available as a dependency during testing, so import guards for qiskit-dependent imports in test files (e.g., test/python/qdmi/qiskit/*.py) are not necessary.
Applied to files:
tests/hellinger_distance/test_estimated_hellinger_distance.py
📚 Learning: 2025-12-25T13:28:10.402Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: tests/compilation/test_predictor_rl.py:225-225
Timestamp: 2025-12-25T13:28:10.402Z
Learning: In the munich-quantum-toolkit/predictor repository, Ruff is configured with the "SLF" (flake8-self) rule category enabled in extend-select. SLF001 (private member access) is active, so `# noqa: SLF001` directives are necessary when accessing private methods/attributes (e.g., `_ensure_device_averages_cached()`), even if Ruff reports RUF100 warnings suggesting the directive is unused.
Applied to files:
src/mqt/predictor/ml/helper.pysrc/mqt/predictor/ml/predictor.py
📚 Learning: 2025-12-25T13:28:19.850Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: src/mqt/predictor/rl/predictorenv.py:271-271
Timestamp: 2025-12-25T13:28:19.850Z
Learning: In the munich-quantum-toolkit/predictor repository, Ruff has a broad rule set including FLake8-SLF (slf) in extend-select. For private member access (e.g., self.state._layout = self.layout) in src/mqt/predictor/rl/predictorenv.py, include a per-file noqa directive: # noqa: SLF001. This is appropriate even if Ruff reports RUF100 as unused, to acknowledge intentional private attribute access and to avoid false positives in this specific code path. Apply this directive only to the files where private-member access is intentionally used and where SLF001 is the correct rationale.
Applied to files:
src/mqt/predictor/ml/helper.pysrc/mqt/predictor/ml/predictor.py
📚 Learning: 2025-12-04T06:59:40.314Z
Learnt from: MatthiasReumann
Repo: munich-quantum-toolkit/core PR: 1301
File: mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp:84-85
Timestamp: 2025-12-04T06:59:40.314Z
Learning: In the MQTOpt MLIR routing passes (NaiveRoutingPassSC, AStarRoutingPassSC), the input IR is guaranteed to contain only 1-qubit and 2-qubit gates. All 3+-qubit gates must be decomposed before routing; otherwise the input IR is invalid. This invariant allows skipTwoQubitBlock in LayeredUnit.cpp to safely assert wires.size() == 2.
Applied to files:
src/mqt/predictor/ml/helper.py
📚 Learning: 2025-12-21T22:35:08.572Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/fomac/fomac.cpp:348-364
Timestamp: 2025-12-21T22:35:08.572Z
Learning: In the munich-quantum-toolkit/core repository's nanobind bindings, use `.sig("...")` on parameter arguments that have vector or container defaults (e.g., `"sites"_a.sig("...") = std::vector<fomac::Session::Device::Site>{}`) to prevent exposing mutable defaults in the Python API, which would be flagged as a code smell by Python linters. This pattern is preferred over removing `.sig("...")` even though it shows `...` in the stub signature.
Applied to files:
src/mqt/predictor/ml/predictor.py
🧬 Code graph analysis (4)
tests/device_selection/test_predictor_ml.py (1)
src/mqt/predictor/ml/predictor.py (1)
predict_device_for_figure_of_merit(842-921)
tests/hellinger_distance/test_estimated_hellinger_distance.py (5)
src/mqt/predictor/ml/helper.py (3)
TrainingData(614-624)create_dag(192-309)get_path_training_data(36-38)src/mqt/predictor/rl/helper.py (1)
get_path_training_data(97-99)src/mqt/predictor/ml/predictor.py (1)
Predictor(170-839)tests/compilation/test_reward.py (1)
device(38-40)src/mqt/predictor/hellinger/utils.py (2)
calc_device_specific_features(37-132)hellinger_distance(29-34)
src/mqt/predictor/ml/helper.py (1)
src/mqt/predictor/utils.py (1)
calc_supermarq_features(94-146)
src/mqt/predictor/ml/predictor.py (3)
src/mqt/predictor/ml/gnn.py (1)
GNN(191-295)src/mqt/predictor/hellinger/utils.py (1)
get_hellinger_model_path(135-144)src/mqt/predictor/ml/helper.py (8)
TrainingData(614-624)create_dag(192-309)create_feature_vector(164-189)evaluate_classification_model(351-430)evaluate_regression_model(433-494)get_gnn_input_features(312-324)get_path_trained_model(48-50)get_path_trained_model_gnn(53-57)
🪛 Ruff (0.14.10)
tests/device_selection/test_predictor_ml.py
42-42: Boolean-typed positional argument in function definition
(FBT001)
42-42: Boolean-typed positional argument in function definition
(FBT001)
113-113: Undefined name gnn
(F821)
167-167: Boolean-typed positional argument in function definition
(FBT001)
tests/hellinger_distance/test_estimated_hellinger_distance.py
171-171: Boolean-typed positional argument in function definition
(FBT001)
240-240: Boolean-typed positional argument in function definition
(FBT001)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: 🐍 Test (macos-14) / 🐍 macos-14
- GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
- GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
🔇 Additional comments (2)
tests/hellinger_distance/test_estimated_hellinger_distance.py (1)
37-47: GNN optional-dependency handling and cleanup fixture look solidThe
_HAS_GNN_DEPSflag plus earlypytest.skipin the GNN parametrizations, together with thecleanup_artifactsautouse fixture, make the Hellinger tests robust both with and without torch/torch-geometric installed and avoid test-order dependencies. No issues from a functional or maintainability perspective.Also applies to: 168-176, 236-247, 346-393
src/mqt/predictor/ml/helper.py (1)
192-309: DAG featurization and GNN input-dimension helper are internally consistent
create_dag’s feature layout (one‑hot overget_openqasm3_gates()+"measure", 6 param features, arity, controls, num_params, critical flag, fan_in, fan_out) matchesget_gnn_input_features(), and the transpilation/critical-path logic handles empty/degenerate circuits defensively. This provides a solid, well-defined input space for the GNN models.Also applies to: 312-325
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
tests/device_selection/test_predictor_ml.py (1)
126-149: Cleanup test handles new GNN artifacts but still relies on test orderingThe extended cleanup in
test_remove_files(including.safetensors/.labelandgraph_dataset_*directories) matches the new artifact layout, but as a standalone test it still depends on execution order and-kselection. Consider migrating this logic into an autouse fixture (as done in the Hellinger tests) so cleanup runs regardless of which tests are selected or in what order.tests/hellinger_distance/test_estimated_hellinger_distance.py (1)
333-343: Minor type alignment forTrainingDataconstruction in negative testIn
test_predict_device_for_estimated_hellinger_distance_no_device_provided,TrainingData.y_trainis passed as a plain Python list. This works with scikit‑learn, but is slightly out of sync with the annotated type (NDArray[np.float64] | torch.Tensor). For consistency with other tests and stricter type-checking, consider wrappinglabels_listinnp.asarray(..., dtype=np.float64)here as well.src/mqt/predictor/ml/predictor.py (3)
842-915: Address PyTorch version dependency and missing error handling forweights_only=TrueThe state_dict + model reconstruction approach is sound, but the code has critical gaps:
PyTorch version constraint:
weights_only=Trueis not a PyTorch 2.1 feature; its behavior is only documented consistently in PyTorch >=2.6. If the project targets earlier versions, this parameter will fail silently or error at load time.No error handling for checkpoint incompatibility:
weights_only=Trueraises "Weights only load failed" if the checkpoint contains non-tensor objects or custom tensor subclasses. The code has no try/except aroundtorch.load, so it will crash if the saved GNN checkpoint is incompatible.Missing post-load validation: After loading the state dict, validate that tensor shapes and dtypes match expectations before inference.
Add a PyTorch version check (require >=2.6 for
weights_only=True), add error handling aroundtorch.loadwith fallback toweights_only=Falseand logging, and validate tensor properties after load. Alternatively, considersafetensorsfor more robust, format-agnostic loading.
709-751: The CV clamping logic risks failure with min_class < 2; adjust to handle extreme class imbalanceThe CV guard:
num_cv = min(len(training_data.y_train), 5) min_class = min(Counter(training_data.y_train).values()) num_cv = max(2, min(num_cv, min_class))enforces a minimum of
cv=2, which causesGridSearchCVto fail when the smallest class has only 1 sample. SinceGridSearchCVusesStratifiedKFoldby default for classifiers and requiresn_splits ≤ min_class_count, the current logic will raise aValueErrorin these edge cases.Consider either:
- Removing the
max(2, ...)guard and raising a clearValueErrorwhenmin_class < 2, or- Skipping stratified CV (e.g.,
cv=1) whenmin_class < 2and documenting this limitation.This ensures robustness for datasets with rare classes while maintaining stratified validation when sufficient samples exist.
318-346: Critical: extraunsqueeze(0)on GNN targets breaks shape compatibility during batchingIn
generate_training_data, the GNN branch stores targets as:gnn_training_sample = Data( x=x, y=torch.tensor(value_device, dtype=torch.float32).unsqueeze(0), edge_index=edge_idx, num_nodes=n_nodes, target_label=target_label, )This creates each
Data.ywith shape(1, num_devices). When batched viatorch_geometric.loader.DataLoader,batch.ybecomes(batch_size, 1, num_devices), whereas the GNN model outputs shape(batch_size, num_devices). Sinceevaluate_classification_modelenforces shape equality (line 391 in helper.py), MSELoss and any loss computation will fail withValueError: Shape mismatch ...during Optuna trials and final training.Remove the
unsqueeze(0)to store targets as 1D per-device score vectors, and add backward-compatibility handling for legacy(1, N)tensors when loading safetensors:Proposed fix
if self.gnn: x, _y, edge_idx, n_nodes, target_label = training_sample value_device = [scores.get(dev.description, -1.0) for dev in self.devices] - gnn_training_sample = Data( - x=x, - y=torch.tensor(value_device, dtype=torch.float32).unsqueeze(0), - edge_index=edge_idx, - num_nodes=n_nodes, - target_label=target_label, - ) + # Store targets as 1D per-device score vectors so batched shapes + # match model outputs: (batch_size, num_devices). + gnn_training_sample = Data( + x=x, + y=torch.tensor(value_device, dtype=torch.float32), + edge_index=edge_idx, + num_nodes=n_nodes, + target_label=target_label, + )In
_get_prepared_training_data, add backward-compatibility squeeze:tensors = load_file(str(sf)) + y = tensors["y"] + # Backward compatibility: squeeze legacy (1, N) targets to (N,) + if y.dim() == 2 and y.size(0) == 1: + y = y.squeeze(0) data = Data( x=tensors["x"], - y=tensors["y"], + y=y, edge_index=tensors["edge_index"], num_nodes=int(tensors["num_nodes"][0].item()), )
🤖 Fix all issues with AI agents
In @tests/hellinger_distance/test_estimated_hellinger_distance.py:
- Around line 26-47: Replace the try/except block that sets torch, Batch, Data
and _HAS_GNN_DEPS with pytest.importorskip calls: call
pytest.importorskip("torch", exc_type=ModuleNotFoundError) and
pytest.importorskip("torch_geometric", exc_type=ModuleNotFoundError) (or the
specific subpackage names you need) at the top of the test module, then import
torch and torch_geometric symbols (Batch, Data) normally and remove the
_HAS_GNN_DEPS flag and its usages; this surfaces real import failures while
making the tests skip cleanly when the optional GNN deps are missing.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (4)
src/mqt/predictor/ml/helper.pysrc/mqt/predictor/ml/predictor.pytests/device_selection/test_predictor_ml.pytests/hellinger_distance/test_estimated_hellinger_distance.py
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-12-25T13:28:02.860Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: tests/compilation/test_predictor_rl.py:225-225
Timestamp: 2025-12-25T13:28:02.860Z
Learning: In the munich-quantum-toolkit/predictor repository, when using Ruff with the SLF (flake8-self) rules enabled, explicitly add a noqa: SLF001 directive when accessing private members (e.g., _ensure_device_averages_cached()) in tests under tests/, even if Ruff would report RUF100 that the directive is unused. This applies to Python test files (and related code) that access private attributes or methods, to ensure maintainability and clarity of the intent.
Applied to files:
tests/device_selection/test_predictor_ml.pytests/hellinger_distance/test_estimated_hellinger_distance.py
📚 Learning: 2025-12-25T13:28:25.619Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: src/mqt/predictor/rl/predictorenv.py:271-271
Timestamp: 2025-12-25T13:28:25.619Z
Learning: In the munich-quantum-toolkit/predictor repository, Ruff is configured with an extensive set of rules including SLF (flake8-self) in the extend-select list. The `# noqa: SLF001` directive for private member access (e.g., `self.state._layout = self.layout` in src/mqt/predictor/rl/predictorenv.py) is necessary and appropriate, even if Ruff reports RUF100 warnings suggesting the directive is unused.
Applied to files:
tests/hellinger_distance/test_estimated_hellinger_distance.py
📚 Learning: 2025-11-26T10:29:36.435Z
Learnt from: marcelwa
Repo: munich-quantum-toolkit/core PR: 1243
File: test/python/fomac/test_fomac.py:54-66
Timestamp: 2025-11-26T10:29:36.435Z
Learning: In test/python/fomac/test_fomac.py, the `ddsim_device` fixture returns `Device | None` even though it only returns a `Device` or calls `pytest.skip()`. The `| None` annotation is required for the type checker to pass, as type checkers may not recognize `pytest.skip()` as a function that never returns.
Applied to files:
tests/hellinger_distance/test_estimated_hellinger_distance.py
📚 Learning: 2025-11-04T14:26:25.420Z
Learnt from: marcelwa
Repo: munich-quantum-toolkit/core PR: 1243
File: test/python/qdmi/qiskit/conftest.py:11-19
Timestamp: 2025-11-04T14:26:25.420Z
Learning: In the munich-quantum-toolkit/core repository, Qiskit is always available as a dependency during testing, so import guards for qiskit-dependent imports in test files (e.g., test/python/qdmi/qiskit/*.py) are not necessary.
Applied to files:
tests/hellinger_distance/test_estimated_hellinger_distance.py
📚 Learning: 2025-12-25T13:28:10.402Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: tests/compilation/test_predictor_rl.py:225-225
Timestamp: 2025-12-25T13:28:10.402Z
Learning: In the munich-quantum-toolkit/predictor repository, Ruff is configured with the "SLF" (flake8-self) rule category enabled in extend-select. SLF001 (private member access) is active, so `# noqa: SLF001` directives are necessary when accessing private methods/attributes (e.g., `_ensure_device_averages_cached()`), even if Ruff reports RUF100 warnings suggesting the directive is unused.
Applied to files:
src/mqt/predictor/ml/helper.pysrc/mqt/predictor/ml/predictor.py
📚 Learning: 2025-12-25T13:28:19.850Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: src/mqt/predictor/rl/predictorenv.py:271-271
Timestamp: 2025-12-25T13:28:19.850Z
Learning: In the munich-quantum-toolkit/predictor repository, Ruff has a broad rule set including FLake8-SLF (slf) in extend-select. For private member access (e.g., self.state._layout = self.layout) in src/mqt/predictor/rl/predictorenv.py, include a per-file noqa directive: # noqa: SLF001. This is appropriate even if Ruff reports RUF100 as unused, to acknowledge intentional private attribute access and to avoid false positives in this specific code path. Apply this directive only to the files where private-member access is intentionally used and where SLF001 is the correct rationale.
Applied to files:
src/mqt/predictor/ml/helper.pysrc/mqt/predictor/ml/predictor.py
📚 Learning: 2025-12-04T06:59:40.314Z
Learnt from: MatthiasReumann
Repo: munich-quantum-toolkit/core PR: 1301
File: mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp:84-85
Timestamp: 2025-12-04T06:59:40.314Z
Learning: In the MQTOpt MLIR routing passes (NaiveRoutingPassSC, AStarRoutingPassSC), the input IR is guaranteed to contain only 1-qubit and 2-qubit gates. All 3+-qubit gates must be decomposed before routing; otherwise the input IR is invalid. This invariant allows skipTwoQubitBlock in LayeredUnit.cpp to safely assert wires.size() == 2.
Applied to files:
src/mqt/predictor/ml/helper.py
📚 Learning: 2025-12-21T22:35:08.572Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/fomac/fomac.cpp:348-364
Timestamp: 2025-12-21T22:35:08.572Z
Learning: In the munich-quantum-toolkit/core repository's nanobind bindings, use `.sig("...")` on parameter arguments that have vector or container defaults (e.g., `"sites"_a.sig("...") = std::vector<fomac::Session::Device::Site>{}`) to prevent exposing mutable defaults in the Python API, which would be flagged as a code smell by Python linters. This pattern is preferred over removing `.sig("...")` even though it shows `...` in the stub signature.
Applied to files:
src/mqt/predictor/ml/predictor.py
🧬 Code graph analysis (3)
tests/device_selection/test_predictor_ml.py (2)
src/mqt/predictor/ml/helper.py (1)
get_path_training_data(36-38)src/mqt/predictor/ml/predictor.py (3)
predict_device_for_figure_of_merit(842-921)setup_device_predictor(103-167)Predictor(170-839)
tests/hellinger_distance/test_estimated_hellinger_distance.py (5)
src/mqt/predictor/ml/helper.py (3)
TrainingData(614-624)create_dag(192-309)get_path_training_data(36-38)src/mqt/predictor/rl/helper.py (1)
get_path_training_data(97-99)src/mqt/predictor/ml/predictor.py (4)
Predictor(170-839)train_gnn_model(581-707)train_random_forest_model(709-756)compile_training_circuits(252-283)src/mqt/predictor/rl/predictor.py (1)
Predictor(32-127)src/mqt/predictor/hellinger/utils.py (1)
hellinger_distance(29-34)
src/mqt/predictor/ml/helper.py (2)
src/mqt/predictor/utils.py (1)
calc_supermarq_features(94-146)src/mqt/predictor/rl/helper.py (1)
get_path_training_data(97-99)
🪛 Ruff (0.14.10)
tests/device_selection/test_predictor_ml.py
42-42: Boolean-typed positional argument in function definition
(FBT001)
42-42: Boolean-typed positional argument in function definition
(FBT001)
162-162: Boolean-typed positional argument in function definition
(FBT001)
tests/hellinger_distance/test_estimated_hellinger_distance.py
171-171: Boolean-typed positional argument in function definition
(FBT001)
240-240: Boolean-typed positional argument in function definition
(FBT001)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: 🐍 Test (macos-14) / 🐍 macos-14
- GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
- GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
🔇 Additional comments (13)
src/mqt/predictor/ml/helper.py (3)
53-57: GNN path/file helpers look consistent and well-scoped
get_path_trained_model_gnnandget_openqasm3_gatesare straightforward and match how the GNN artifacts and OpenQASM 3 basis are used elsewhere. Path construction is stable (reusingget_path_training_data) and the gate list is clearly tied to the spec URL and comment, which should make future maintenance easier.Also applies to: 119-152
192-310: DAG featurization and input-dimension helper are internally consistentThe
create_dagpipeline (barrier removal, controlled transpile with a clear error, op-node enumeration, feature tensors, edge_index, and critical-path tagging) is coherent. The finalnode_vectordimension matchesget_gnn_input_features()(one‑hot overget_openqasm3_gates() + ["measure"], 6 sin/cos params, and 6 scalar DAG attributes), including empty‑DAG and no‑topo‑nodes edge cases. The use of.get(..., 0)fordist_in/dist_outand an explicit empty-edge tensor avoids KeyErrors and shape issues.Also applies to: 312-324
327-346: Evaluation helpers andTrainingDatatyping align with mixed RF/GNN use
get_results_classes,evaluate_classification_model,evaluate_regression_model, andtrain_modelhandle both regression and classification cleanly, including:
- robust device handling (string vs
torch.device),- empty-loader guards before
torch.cat,- consistent metrics dict typing (
dict[str, float | str]),- and early‑stopping logic that always initializes
val_loss/val_metricsbefore use.The updated
TrainingDataannotation matches how RF (numpy arrays) and GNN (list oftorch_geometric.data.Data) flows actually consumeX_*/y_*. This should also satisfy the earlier static-analysis concerns about potentially uninitialized locals.Also applies to: 351-430, 433-495, 497-611, 613-620
tests/device_selection/test_predictor_ml.py (2)
38-64: End‑to‑end RF/GNN setup test correctly exercises both backendsThe parametrized
test_setup_device_predictor_with_predictionwiresgnnandverbosethrough tosetup_device_predictorandpredict_device_for_figure_of_meritas intended, and the artifact assertions match the RF vs GNN storage layout (training_data_expected_fidelity.npyvsgraph_dataset_expected_fidelitywith.safetensorsplusnames_list/scores_list). The final device prediction check against"ibm_falcon_127"gives a clear functional smoke test for both backends.Also applies to: 67-84
86-124: Multi‑device GNN test validates graph dataset layout and prediction
test_setup_multidevice_predictor_with_prediction_gnncorrectly:
- trains with
gnn=Trueon two devices,- asserts that the
graph_dataset_expected_fidelitydirectory exists and contains at least one.safetensorsfile plus the sharednames_list/scores_list,- and verifies that the predicted device is one of the two configured backends.
This gives good coverage of the multi‑device GNN path.
tests/hellinger_distance/test_estimated_hellinger_distance.py (3)
168-234: RF/GNN Hellinger training test is consistent; tolerance is intentionally loose
test_train_model_and_predictcorrectly:
- uses
model_typeto avoid boolean parameter lint and to derivegnn,- skips the GNN branch when
_HAS_GNN_DEPSis false,- builds RF training data from device‑specific feature vectors,
- builds GNN training data from
create_daggraphs with scalarytargets (suitable for regression),- and exercises both
train_random_forest_modelandtrain_gnn_model.The relatively loose
atol=5e-1check for the GNN branch is well‑documented in the comments as a smoke‑test tolerance given very small training sets, which is acceptable here.
236-323: End‑to‑end estimated‑Hellinger toolchain test covers RF and GNN paths
test_train_and_qcompile_with_hellinger_modelnow:
- parametrizes over RF vs GNN backends and verbosity,
- skips GNN variants when GNN deps are missing,
- trains a small RL model for compilation, then uses
ml_Predictorwithgnnwired through to compile circuits, generate training data, and train the RF/GNN models,- asserts graph datasets (
graph_dataset_estimated_hellinger_distance) and safetensors layout for GNN, or the legacy.npyfiles for RF,- and validates that
predict_device_for_figure_of_meritreturns a device fromget_available_device_names().This gives good coverage of the full RL+ML pipeline across both backends.
346-393: Autouse cleanup fixture matches new artifact layout and is reasonably conservativeThe
cleanup_artifactsfixture:
- runs once per module (autouse) and performs teardown after all tests,
- removes only
.qasmfiles in the per-test source/target directories and then attempts to rmdir them,- prunes training data artifacts matching known extensions plus
graph_dataset_*directories (with a small safeguard for nested dirs),- and cleans up trained model files in
trained_model.This keeps test artifacts under control without resorting to unbounded
rmtree, and the NOTE about potential flakiness is appropriate.src/mqt/predictor/ml/predictor.py (5)
18-24: Type aliases and GNN training kwargs are well-structured
GNNSample/FeatureSample/TrainingSampleclearly describe the two training-sample shapes, andTrainGNNKwargsgives a typed surface for GNN‑only options. Makinggnnkeyword‑only in bothsetup_device_predictorandPredictor.__init__, and forwarding**gnn_kwargsintotrain_gnn_model, keeps the public API explicit without cluttering the core signature.Also applies to: 25-26, 77-88, 94-101
374-449: GNN training sample generation and DAG path integration are structurally sound aside from target shapeApart from the
unsqueeze(0)issue ony, the GNN branch of_generate_training_sampleandgenerate_training_datacorrectly:
- compute scores per device via the configured figure of merit,
- skip samples where all devices are
-1.0(no compiled circuits),- derive a canonical device ordering from
self.devices,- build
create_dag(qc)features and wrap them intorch_geometric.data.Dataobjects annotated withtarget_label,- and serialize graph datasets plus
.labelfiles undergraph_dataset_{figure_of_merit}.Once the target shape is fixed as per the other comment, the end-to-end GNN dataset pipeline should be consistent with
create_dagandget_gnn_input_features().
581-707:train_gnn_modelend-to-end flow is coherent (data prep, Optuna, final training, metadata, checkpointing)
train_gnn_modelcorrectly:
- chooses
num_outputsand save path based on the figure of merit (single-output regression for Hellinger vs multi-output for device scoring),- falls back to
_get_prepared_training_data()whentraining_datais not provided,- uses
get_gnn_input_features()andnn.MSELoss()consistently withcreate_dag,- runs an Optuna study via the
objectivehelper,- rebuilds the best GNN with parsed
mlp_unitsandnum_outputs,- writes a JSON config containing hyperparameters and
class_labels,- performs a second training run with early stopping on a held-out validation split,
- optionally evaluates on a test split when present, and
- saves only the model
state_dict(good for later secure loading).Once the GNN target shape in the dataset is corrected, this orchestration should train and persist models suitable for the downstream prediction path.
758-839: Prepared-data loader cleanly supports both RF and GNN, including legacy scores format
_get_prepared_training_datanow:
- branches correctly between RF (
training_data_{figure}.npy) and GNN (graph_dataset_{figure}safetensors),- reconstructs
Dataobjects with integernum_nodesand reattachestarget_labelstrings,- normalizes
scores_listso both legacy dict entries and new list/array entries becomelist[list[float]],- and returns a
TrainingDatainstance whose fields match the updated dataclass types for both dense and graph training.Once the safetensors loader squeezes any legacy
(1, N)targets (per the earlier suggestion), this loader provides a solid bridge between stored artifacts and the new GNN training code.
758-839: RF/GNNTrainingDataunpacking matches dataclass expectationsThe RF branch unpacks
training_datainto(feature_vec, target_label)pairs and converts them into dense numpy arrays, while the GNN branch returnsxas a list ofDataandyas an array oftarget_labelstrings. The subsequent 70/30 split and reconstruction ofTrainingData(includingnames_listand normalizedscores_list) is consistent with both the RF and GNN training code paths.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/mqt/predictor/ml/predictor.py (1)
843-921: Wrap single Data object withBatch.from_data_list()for proper batching.At line 907, the single
Dataobject is passed directly to the GNN model without a batch attribute, which will beNone. While this may work incidentally, GraphNorm expects batch indices and the pattern is not documented or tested. UseBatch.from_data_list([feature_vector])to ensure proper graph batching, matching the approach used elsewhere in the codebase.
🤖 Fix all issues with AI agents
In @src/mqt/predictor/ml/predictor.py:
- Around line 880-882: The code incorrectly calls the instance method Path.open
as a class method (Path.open(path.with_suffix(".json"), ...)); change this to
call open on the Path instance: replace that line with
path.with_suffix(".json").open(encoding="utf-8") (or use the builtin
open(path.with_suffix(".json"), encoding="utf-8")) so json.load(f) reads from
the correctly opened file handle; the symbols to update are Path.open and
path.with_suffix(".json") where json_dict is assigned.
In @tests/hellinger_distance/test_estimated_hellinger_distance.py:
- Around line 292-293: The inline comment above the assertion using
dataset_dir.iterdir()/f.suffix/f.stem.isdigit() contains a typo: change
"asdefined" to "as defined" so the comment reads that the name starts with a
number and ends with .safetensors "as defined" in the predictor; update only the
comment text near that assert in test_estimated_hellinger_distance.py.
- Around line 186-225: The variable labels is reassigned inside the GNN
evaluation block which shadows the outer labels; rename the inner variable
(e.g., expected_labels) where you currently do labels = np.asarray(labels_list,
dtype=np.float32) and update subsequent references (the assert that compares
predicted_values to labels -> expected_labels) so the outer labels remain
unchanged and the intent is clearer; ensure you only change occurrences in the
GNN evaluation block (the scope around trained_model.eval(), model_device
retrieval, Batch.from_data_list(...) and the final np.allclose assertion).
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (4)
src/mqt/predictor/ml/predictor.pytests/compilation/test_predictor_rl.pytests/device_selection/test_helper_ml.pytests/hellinger_distance/test_estimated_hellinger_distance.py
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-11-04T14:26:25.420Z
Learnt from: marcelwa
Repo: munich-quantum-toolkit/core PR: 1243
File: test/python/qdmi/qiskit/conftest.py:11-19
Timestamp: 2025-11-04T14:26:25.420Z
Learning: In the munich-quantum-toolkit/core repository, Qiskit is always available as a dependency during testing, so import guards for qiskit-dependent imports in test files (e.g., test/python/qdmi/qiskit/*.py) are not necessary.
Applied to files:
tests/device_selection/test_helper_ml.pytests/hellinger_distance/test_estimated_hellinger_distance.py
📚 Learning: 2025-12-25T13:28:02.860Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: tests/compilation/test_predictor_rl.py:225-225
Timestamp: 2025-12-25T13:28:02.860Z
Learning: In the munich-quantum-toolkit/predictor repository, when using Ruff with the SLF (flake8-self) rules enabled, explicitly add a noqa: SLF001 directive when accessing private members (e.g., _ensure_device_averages_cached()) in tests under tests/, even if Ruff would report RUF100 that the directive is unused. This applies to Python test files (and related code) that access private attributes or methods, to ensure maintainability and clarity of the intent.
Applied to files:
tests/device_selection/test_helper_ml.pytests/compilation/test_predictor_rl.pytests/hellinger_distance/test_estimated_hellinger_distance.py
📚 Learning: 2025-12-25T13:28:25.619Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: src/mqt/predictor/rl/predictorenv.py:271-271
Timestamp: 2025-12-25T13:28:25.619Z
Learning: In the munich-quantum-toolkit/predictor repository, Ruff is configured with an extensive set of rules including SLF (flake8-self) in the extend-select list. The `# noqa: SLF001` directive for private member access (e.g., `self.state._layout = self.layout` in src/mqt/predictor/rl/predictorenv.py) is necessary and appropriate, even if Ruff reports RUF100 warnings suggesting the directive is unused.
Applied to files:
tests/hellinger_distance/test_estimated_hellinger_distance.py
📚 Learning: 2025-11-26T10:29:36.435Z
Learnt from: marcelwa
Repo: munich-quantum-toolkit/core PR: 1243
File: test/python/fomac/test_fomac.py:54-66
Timestamp: 2025-11-26T10:29:36.435Z
Learning: In test/python/fomac/test_fomac.py, the `ddsim_device` fixture returns `Device | None` even though it only returns a `Device` or calls `pytest.skip()`. The `| None` annotation is required for the type checker to pass, as type checkers may not recognize `pytest.skip()` as a function that never returns.
Applied to files:
tests/hellinger_distance/test_estimated_hellinger_distance.py
📚 Learning: 2025-11-04T14:28:32.371Z
Learnt from: marcelwa
Repo: munich-quantum-toolkit/core PR: 1243
File: test/python/qdmi/qiskit/test_qdmi_qiskit_backend.py:0-0
Timestamp: 2025-11-04T14:28:32.371Z
Learning: In the munich-quantum-toolkit/core repository, at least one FoMaC device is always available during testing, so skip logic for missing devices in QDMI Qiskit backend tests is not necessary.
Applied to files:
tests/hellinger_distance/test_estimated_hellinger_distance.py
📚 Learning: 2025-12-21T22:35:08.572Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1383
File: bindings/fomac/fomac.cpp:348-364
Timestamp: 2025-12-21T22:35:08.572Z
Learning: In the munich-quantum-toolkit/core repository's nanobind bindings, use `.sig("...")` on parameter arguments that have vector or container defaults (e.g., `"sites"_a.sig("...") = std::vector<fomac::Session::Device::Site>{}`) to prevent exposing mutable defaults in the Python API, which would be flagged as a code smell by Python linters. This pattern is preferred over removing `.sig("...")` even though it shows `...` in the stub signature.
Applied to files:
src/mqt/predictor/ml/predictor.py
📚 Learning: 2025-12-25T13:28:10.402Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: tests/compilation/test_predictor_rl.py:225-225
Timestamp: 2025-12-25T13:28:10.402Z
Learning: In the munich-quantum-toolkit/predictor repository, Ruff is configured with the "SLF" (flake8-self) rule category enabled in extend-select. SLF001 (private member access) is active, so `# noqa: SLF001` directives are necessary when accessing private methods/attributes (e.g., `_ensure_device_averages_cached()`), even if Ruff reports RUF100 warnings suggesting the directive is unused.
Applied to files:
src/mqt/predictor/ml/predictor.py
📚 Learning: 2025-12-25T13:28:19.850Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/predictor PR: 526
File: src/mqt/predictor/rl/predictorenv.py:271-271
Timestamp: 2025-12-25T13:28:19.850Z
Learning: In the munich-quantum-toolkit/predictor repository, Ruff has a broad rule set including FLake8-SLF (slf) in extend-select. For private member access (e.g., self.state._layout = self.layout) in src/mqt/predictor/rl/predictorenv.py, include a per-file noqa directive: # noqa: SLF001. This is appropriate even if Ruff reports RUF100 as unused, to acknowledge intentional private attribute access and to avoid false positives in this specific code path. Apply this directive only to the files where private-member access is intentionally used and where SLF001 is the correct rationale.
Applied to files:
src/mqt/predictor/ml/predictor.py
🧬 Code graph analysis (3)
tests/device_selection/test_helper_ml.py (1)
src/mqt/predictor/ml/helper.py (1)
create_dag(192-309)
tests/compilation/test_predictor_rl.py (1)
src/mqt/predictor/rl/predictor.py (1)
rl_compile(151-182)
tests/hellinger_distance/test_estimated_hellinger_distance.py (3)
src/mqt/predictor/hellinger/utils.py (1)
calc_device_specific_features(37-132)src/mqt/predictor/ml/predictor.py (3)
Predictor(170-840)predict_device_for_figure_of_merit(843-922)compile_training_circuits(252-283)src/mqt/predictor/ml/helper.py (3)
TrainingData(614-624)create_dag(192-309)get_path_training_data(36-38)
🪛 Ruff (0.14.10)
tests/hellinger_distance/test_estimated_hellinger_distance.py
165-165: Boolean-typed positional argument in function definition
(FBT001)
232-232: Boolean-typed positional argument in function definition
(FBT001)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
- GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
🔇 Additional comments (15)
tests/device_selection/test_helper_ml.py (3)
13-24: LGTM!The new imports for
torch,QuantumCircuit, andcreate_dagare correctly added to support the new DAG creation tests.
34-44: LGTM!The test properly validates DAG creation with assertions for tensor types, shapes, node count consistency, and edge index properties. Using
BenchmarkLevel.INDEPwith.decompose()ensures the circuit has actual gates for meaningful DAG testing.
47-65: LGTM!Excellent coverage of the empty circuit edge case. The assertions correctly verify that
create_dagreturns properly shaped empty tensors while maintaining the expected dimensions for batch compatibility.tests/compilation/test_predictor_rl.py (2)
65-71: LGTM!The test correctly validates that
rl_compileraises appropriateValueErrorexceptions whenfigure_of_meritordeviceisNonewhilepredictor_singletonis alsoNone. This aligns with the function's documented behavior insrc/mqt/predictor/rl/predictor.py.
74-110: No action needed. The device name "ibm_falcon_127" is valid and correctly used throughout the test suite. It is imported and used successfully viaget_device()frommqt.bench.targetsin multiple test files (test_predictor_ml.py, test_estimated_hellinger_distance.py, and others), with assertions verifying the device name works correctly. The parameterization is appropriate.Likely an incorrect or invalid review comment.
tests/hellinger_distance/test_estimated_hellinger_distance.py (2)
39-40: LGTM!Using
pytest.importorskipfortorchandtorch_geometricis appropriate to skip tests gracefully when optional GNN dependencies are not installed.
336-383: LGTM!The cleanup fixture is well-designed with appropriate safety measures:
- Module-scoped and autouse for automatic cleanup after all tests in the module
- Conservative deletion (only specific file extensions) to avoid accidental data loss
- Helpful comment explaining the rmdir limitation and suggesting shutil.rmtree if needed
- Proper handling of nested directories within graph_dataset_* folders
src/mqt/predictor/ml/predictor.py (8)
18-50: LGTM!The new imports are well-organized and necessary for GNN support. The separation between runtime imports and TYPE_CHECKING imports is correctly maintained.
85-100: LGTM!The type aliases
GNNSample,FeatureSample, andTrainingSampleprovide clear documentation of the data structures used in both RF and GNN paths. TheTrainGNNKwargsTypedDict withtotal=Falsecorrectly allows optional keyword arguments.
103-167: LGTM!The
setup_device_predictorfunction is correctly extended to support GNN training while maintaining backward compatibility with the defaultgnn=False. The conditional training logic and updated logging are appropriate.
327-373: LGTM!The training data generation correctly handles both RF and GNN paths:
- GNN samples are properly constructed as
torch_geometric.data.Dataobjects- The
unsqueeze(0)on line 338 ensures proper batch dimension handling- Using safetensors for tensor serialization and separate
.labelfiles for string labels is a clean approach
453-580: LGTM!The Optuna objective function is well-designed:
- Comprehensive hyperparameter search space covering architecture (layers, hidden dims, MLP) and regularization (dropout, pooling)
- Proper k-fold validation with early stopping
- Good resource management with explicit cleanup (
del,cuda.empty_cache(),gc.collect())- Appropriate validation of minimum fold count
582-708: LGTM!The
train_gnn_modelmethod implements a complete GNN training pipeline:
- Optuna-based hyperparameter optimization with TPE sampler
- JSON configuration saved alongside the model for reproducible inference
- Final model training with early stopping after HPO
- Optional test set evaluation when
verbose=TrueThe architecture is well-structured and follows ML best practices.
749-752: LGTM!Good defensive addition to handle class imbalance in cross-validation. Capping
num_cvat the minimum class count preventsGridSearchCVfrom failing when some classes have fewer samples than the number of folds.
759-840: LGTM!The
_get_prepared_training_datamethod correctly handles both RF and GNN data formats:
- Proper file path resolution based on
gnnflag- GNN data reconstructed from safetensors with label files
- Backward compatibility for legacy dict-based scores format
- Consistent train/test split for both paths
Signed-off-by: Antonio Tudisco <anto.tu98@hotmail.it>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@pyproject.toml`:
- Around line 172-173: The mypy override for Optuna currently lists "optuna.*"
but omits the root module, so imports like the direct "import optuna" in
src/mqt/predictor/ml/predictor.py will still trigger missing-stub errors; update
the module list in pyproject.toml (the line containing module = [... "optuna.*",
...]) to include "optuna" alongside "optuna.*" to mirror how "torch" and
"torch_geometric" are configured so direct imports are covered by
ignore_missing_imports.
In `@tests/device_selection/test_predictor_ml.py`:
- Line 101: Rename the local variable named `device` (which holds a list) to
`devices` to match the parameter name used by `setup_device_predictor` and avoid
confusion; update all references in this test (including the list assignment
`device = [get_device(...), ...]` and any subsequent calls or assertions that
pass it into `setup_device_predictor`) to use `devices` so naming is consistent
and clearer.
♻️ Duplicate comments (6)
pyproject.toml (1)
47-50: Update the stale torch version comment.Line 49’s comment still says “Restrict torch v2.3.0” while the constraint is
torch>=2.7.0,<2.8.0. This will confuse maintainers.📝 Suggested fix
- "torch>=2.7.0,<2.8.0; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict torch v2.3.0 for macOS x86 since it is not supported anymore. + "torch>=2.7.0,<2.8.0; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict torch to 2.7.x for macOS x86 since 2.8.0+ is not supported on this platform.src/mqt/predictor/_version.py (1)
1-39: Remove auto-generated file from version control.This file is auto-generated by setuptools-scm (as indicated on lines 9-10: "file generated by setuptools-scm / don't change, don't track in version control"). It should be added to
.gitignoreand removed from the repository.Based on learnings, this file should not be manually modified or reviewed, and should be excluded from version control via
.gitignore.src/mqt/predictor/hellinger/utils.py (1)
135-144: Consider makinggnnkeyword-only to prevent accidental positional use.The Ruff linter flags FBT001/FBT002 for boolean positional arguments. Making
gnnkeyword-only enforces explicit intent at call sites and silences these warnings.♻️ Proposed fix
-def get_hellinger_model_path(device: Target, gnn: bool = False) -> Path: +def get_hellinger_model_path(device: Target, *, gnn: bool = False) -> Path:tests/hellinger_distance/test_estimated_hellinger_distance.py (1)
162-166: Makeverbosekeyword‑only to satisfy FBT001.Both tests still accept a positional
bool, which Ruff flags. Making it keyword‑only keeps pytest parametrization working and removes the lint warning.♻️ Suggested change
-def test_train_model_and_predict(device: Target, model_type: str, verbose: bool) -> None: +def test_train_model_and_predict(device: Target, model_type: str, *, verbose: bool) -> None: @@ -def test_train_and_qcompile_with_hellinger_model( - source_path: Path, target_path: Path, device: Target, model_type: str, verbose: bool -) -> None: +def test_train_and_qcompile_with_hellinger_model( + source_path: Path, target_path: Path, device: Target, model_type: str, *, verbose: bool +) -> None:Also applies to: 228-233
src/mqt/predictor/ml/predictor.py (2)
332-339: Fix GNN target shape to match model outputs.
yis stored with an extra leading dimension, so batched targets become[batch, 1, num_devices]while the model outputs[batch, num_devices], causing a mismatch in training/evaluation. Remove theunsqueeze(0).🐛 Proposed fix
- y=torch.tensor(value_device, dtype=torch.float32).unsqueeze(0), + y=torch.tensor(value_device, dtype=torch.float32),
880-882: Fix incorrectPath.openusage.
Path.openis an instance method; calling it as a class method will raise. Open the JSON file via the Path instance.🐛 Proposed fix
- with Path.open(path.with_suffix(".json"), encoding="utf-8") as f: + with path.with_suffix(".json").open(encoding="utf-8") as f:
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
tests/hellinger_distance/test_estimated_hellinger_distance.py (1)
269-281: Windows timeout warning should apply to both RF and GNN paths.The condition at line 270 only expects the timeout warning for the RF path (
sys.platform == "win32" and not gnn), butcompile_training_circuitsuses the timeout feature regardless of thegnnflag. On Windows with GNN enabled, this test would fail if the warning is raised unexpectedly.🐛 Proposed fix
# Note: On Windows, the timeout feature is not supported and raises a warning. - if sys.platform == "win32" and not gnn: + if sys.platform == "win32": with pytest.warns(RuntimeWarning, match=re.escape("Timeout is not supported on Windows.")): ml_predictor.compile_training_circuits( timeout=600, path_compiled_circuits=target_path, path_uncompiled_circuits=source_path, num_workers=1, ) else: ml_predictor.compile_training_circuits( timeout=600, path_compiled_circuits=target_path, path_uncompiled_circuits=source_path, num_workers=1 )
🤖 Fix all issues with AI agents
In `@src/mqt/predictor/ml/predictor.py`:
- Around line 647-670: The model is being moved to CUDA/CPU twice: once inline
when constructing model via the .to("cuda" if torch.cuda.is_available() else
"cpu") on the GNN(...) expression and again with model.to(device) after creating
device; remove the redundancy by constructing model = GNN(...) (without
.to(...)) and then call model.to(device) using the existing device variable, or
alternatively keep the inline .to(...) and delete the later model.to(device)
call so only one transfer (reference symbols: GNN, model, device).
♻️ Duplicate comments (1)
src/mqt/predictor/ml/predictor.py (1)
335-342: The.unsqueeze(0)on target tensor may still cause shape issues.The comment at line 337 explains this is to "avoid concatenation issues," but PyTorch Geometric's
DataLoaderbatching already handles concatenation. When batched, targets will have shape[batch_size, 1, num_devices]while the GNN model outputs[batch_size, num_devices]. The training loop at line 551-564 inobjective()and similar code intrain_gnn_modelmay need to handle this dimension mismatch.If this is intentional and working correctly, consider expanding the comment to explain how the shape is reconciled during training/evaluation.
#!/bin/bash # Verify how y tensor dimensions are handled during training rg -n "batch\.y|batch_device\.y" src/mqt/predictor/ml --type py -B 2 -A 4
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/mqt/predictor/_logging.py`:
- Around line 13-19: The module currently configures a StreamHandler and forces
DEBUG at import (logger, console_handler, logger_formatter), which can duplicate
handlers and override app logging; change this to only attach a handler when
none exist (use logger.hasHandlers() or check root handlers) and stop forcing
logger.setLevel(logging.DEBUG) — leave the logger level as NOTSET so the host
app controls it, do not set console_handler level to DEBUG by default, and
ensure logger.propagate remains True so external logging configuration is
respected.
In `@tests/device_selection/test_predictor_ml.py`:
- Around line 41-43: The test functions
test_setup_device_predictor_with_prediction and the other test at lines around
161–163 use boolean positional parameters (gnn: bool, verbose: bool) which trip
Ruff's FBT001; add a trailing comment "# noqa: FBT001" to each def line (e.g.,
the def for test_setup_device_predictor_with_prediction and the other test
function definition) to suppress the flake8-boolean-trap warning while leaving
signatures unchanged.
♻️ Duplicate comments (2)
pyproject.toml (1)
49-49: Update stale torch version comment.Line 49 references
torch v2.3.0in the comment, but the actual constraint istorch>=2.7.0,<2.8.0. Update the comment to reflect the correct version.📝 Suggested fix
- "torch>=2.7.0,<2.8.0; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict torch v2.3.0 for macOS x86 since it is not supported anymore. + "torch>=2.7.0,<2.8.0; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict torch to 2.7.x for macOS x86 where newer versions are unsupported.src/mqt/predictor/ml/predictor.py (1)
332-339: Remove extra target dimension to avoid GNN training shape mismatch.Line 338 adds an unsqueeze dimension that causes shape misalignment. The model outputs shape
[batch, num_devices], but addingunsqueeze(0)produces targets with shape[batch, 1, num_devices]after batching, which will raise a mismatch error during loss computation. Store targets as 1D tensors instead.🐛 Suggested fix
gnn_training_sample = Data( x=x, - # unsqueeze to avoid concatenation issues later on - y=torch.tensor(value_device, dtype=torch.float32).unsqueeze(0), + y=torch.tensor(value_device, dtype=torch.float32), edge_index=edge_idx, num_nodes=n_nodes, target_label=target_label, )
| logger = logging.getLogger("mqt-predictor") | ||
| console_handler = logging.StreamHandler() | ||
| console_handler.setLevel(logging.DEBUG) | ||
| logger_formatter = logging.Formatter("%(name)s - %(levelname)s - %(message)s") | ||
| console_handler.setFormatter(logger_formatter) | ||
| logger.addHandler(console_handler) | ||
| logger.setLevel(logging.DEBUG) |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Avoid configuring handlers and DEBUG level at import time.
Lines 13–19 configure a StreamHandler and force DEBUG on import, which can duplicate handlers on reload and override application logging. Prefer adding a handler only if none exist and avoid forcing DEBUG so host apps can configure logging.
♻️ Suggested adjustment
logger = logging.getLogger("mqt-predictor")
-console_handler = logging.StreamHandler()
-console_handler.setLevel(logging.DEBUG)
-logger_formatter = logging.Formatter("%(name)s - %(levelname)s - %(message)s")
-console_handler.setFormatter(logger_formatter)
-logger.addHandler(console_handler)
-logger.setLevel(logging.DEBUG)
+if not logger.handlers:
+ console_handler = logging.StreamHandler()
+ logger_formatter = logging.Formatter("%(name)s - %(levelname)s - %(message)s")
+ console_handler.setFormatter(logger_formatter)
+ logger.addHandler(console_handler)
+# Let the application decide the level; avoid forcing DEBUG here.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| logger = logging.getLogger("mqt-predictor") | |
| console_handler = logging.StreamHandler() | |
| console_handler.setLevel(logging.DEBUG) | |
| logger_formatter = logging.Formatter("%(name)s - %(levelname)s - %(message)s") | |
| console_handler.setFormatter(logger_formatter) | |
| logger.addHandler(console_handler) | |
| logger.setLevel(logging.DEBUG) | |
| logger = logging.getLogger("mqt-predictor") | |
| if not logger.handlers: | |
| console_handler = logging.StreamHandler() | |
| logger_formatter = logging.Formatter("%(name)s - %(levelname)s - %(message)s") | |
| console_handler.setFormatter(logger_formatter) | |
| logger.addHandler(console_handler) | |
| # Let the application decide the level; avoid forcing DEBUG here. |
🤖 Prompt for AI Agents
In `@src/mqt/predictor/_logging.py` around lines 13 - 19, The module currently
configures a StreamHandler and forces DEBUG at import (logger, console_handler,
logger_formatter), which can duplicate handlers and override app logging; change
this to only attach a handler when none exist (use logger.hasHandlers() or check
root handlers) and stop forcing logger.setLevel(logging.DEBUG) — leave the
logger level as NOTSET so the host app controls it, do not set console_handler
level to DEBUG by default, and ensure logger.propagate remains True so external
logging configuration is respected.
There was a problem hiding this comment.
It was already present in previous versions. Leave it as it is.
There was a problem hiding this comment.
@antotu, understood! I'll respect the existing pattern for consistency with previous versions. Thanks for clarifying. 🐰
/)_/)
(•ᴗ•)
/>📋
🧠 Learnings used
Learnt from: antotu
Repo: munich-quantum-toolkit/predictor PR: 430
File: src/mqt/predictor/_version.py:21-27
Timestamp: 2026-01-19T08:34:51.732Z
Learning: The file `src/mqt/predictor/_version.py` is auto-generated by setuptools-scm and should not be manually modified or reviewed. It should be excluded from version control via .gitignore.
Description
This PR introduces a Graph Neural Network (GNN) as an alternative to the Random Forest model for predicting the best device to run a quantum circuit.
To support this, the preprocessing pipeline was redesigned: instead of manually extracting features from the circuit, the model now directly takes as input the Directed Acyclic Graph (DAG) representation of the quantum circuit.
🚀 Major Changes
Graph Neural Network Integration
🎯 Motivation
🔧 Fixes and Enhancements
📦 Dependency Updates
optuna>=4.5.0torch-geometric>=2.6.1Checklist: