Skip to content

Add Photonic Subcircuit Compiler - #1059

Open
tobi-forster wants to merge 48 commits into
mainfrom
tobi/add-photonics-subcircuit-compiler
Open

Add Photonic Subcircuit Compiler#1059
tobi-forster wants to merge 48 commits into
mainfrom
tobi/add-photonics-subcircuit-compiler

Conversation

@tobi-forster

@tobi-forster tobi-forster commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Description

This PR introduces mqt.qmap.ph, a new Python subpackage implementing a photonic subcircuit compiler for MZI-mesh chips. The compiler routes photons to the computation zone by finding the lowest-loss path through the chip's MZI mesh, then fits the phase-shifter parameters to a target unitary via PyTorch gradient descent. The compiled circuit is evaluated using Perceval simulation, reporting coincidence rate and TVD against the ideal output distribution. A fixed-placement baseline is computed alongside every run to verify that routing improves performance under transmission loss.

Required dependencies:

  • perceval-quandela ≥ 1.1.0
  • pandas ≥ 2.3.3
  • numpy ≥ 1.26.0
  • torch ≥ 2.9.0

Checklist

  • The pull request only contains commits that are focused and relevant to this change.
  • I have added appropriate tests that cover the new/changed functionality.
  • I have updated the documentation to reflect these changes.
  • I have added entries to the changelog for any noteworthy additions, changes, fixes, or removals.
  • I have added migration instructions to the upgrade guide (if needed).
  • The changes follow the project's style guidelines and introduce no new warnings.
  • The changes are fully tested and pass the CI checks.
  • I have reviewed my own code changes.

If PR contains AI-assisted content:

  • I have disclosed the use of AI tools in the PR description as per our AI Usage Guidelines.
  • AI-assisted commits include an Assisted-by: [Model Name] via [Tool Name] footer.
  • I confirm that I have personally reviewed and understood all AI-generated content, and accept full responsibility for it.

@tobi-forster

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added a photonic subcircuit compiler for routing and optimizing unitary operations on MZI-mesh chips.
    • Added simulation and performance evaluation with configurable losses, phase errors, and hardware characteristics.
    • Added baseline comparison utilities and batch result collection with CSV export.
    • Added photonic hardware transmission datasets and an experimental evaluation notebook.
  • Documentation

    • Added comprehensive usage documentation and configuration guidance for the photonic compiler.
  • Chores

    • Added optional photonics tooling and expanded automated test coverage.

Walkthrough

Adds a photonic compiler package for routing and optimizing unitary subcircuits on MZI meshes, with Perceval simulation, baseline comparison, batch evaluation, hardware data, documentation, optional dependencies, and tests.

Changes

Photonic subcircuit compiler

Layer / File(s) Summary
Package and dependency wiring
.license-tools-config.json, .pre-commit-config.yaml, noxfile.py, pyproject.toml, python/mqt/qmap/ph/__init__.py
Adds the photonics package, optional dependencies, platform-specific test setup, type-checking overrides, and license exclusions.
Baseline and routing graph
python/mqt/qmap/ph/baseline.py, python/mqt/qmap/ph/graph.py, test/python/ph/test_baseline.py, test/python/ph/test_graph.py
Implements baseline placement, beam-splitter fidelity calculations, reflectivity generation, weighted routing DAG construction, and related tests.
Route and phase optimization
python/mqt/qmap/ph/routing.py, python/mqt/qmap/ph/routing_to_phases.py, python/mqt/qmap/ph/unitary_to_phase_compilation.py, test/python/ph/test_routing.py, test/python/ph/test_phases.py, test/python/ph/test_unitary_to_phase_compilation.py
Adds route selection, movement masks, routing-aware phase constraints, unitary construction, gradient optimization, and regression coverage.
Simulation and compilation orchestration
python/mqt/qmap/ph/perceval_simulation.py, python/mqt/qmap/ph/subcircuit_compilation.py, test/python/ph/test_subcircuit_compilation.py
Builds and simulates MZI chips with loss and phase noise, evaluates coincidence/TVD metrics, and exposes compilation and evaluation result contracts.
Batch evaluation and experiment assets
eval/ph/data_collection.py, eval/ph/hardware_data/*, eval/ph/subcircuit_compilation_data_collection.ipynb, test/python/ph/test_data_collection.py, test/python/ph/conftest.py
Adds hardware-aware sweeps, aggregated CSV export, transmission datasets, an execution notebook, fixtures, and data-collection tests.
Documentation and changelog
docs/ph_subcircuit_compiler.md, CHANGELOG.md
Documents the compiler workflow and records the photonic compiler addition.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant Compiler
  participant RoutingGraph
  participant PhaseOptimizer
  participant Perceval
  Caller->>Compiler: compile_subcircuit(...)
  Compiler->>RoutingGraph: construct_graph(...)
  RoutingGraph-->>Compiler: best route and ports
  Compiler->>PhaseOptimizer: optimize routed phases
  PhaseOptimizer-->>Compiler: CompilationResult
  Caller->>Compiler: evaluate_subcircuit(...)
  Compiler->>Perceval: simulate proposed and baseline chips
  Perceval-->>Compiler: performance metrics
Loading

Suggested labels: enhancement

Poem

I’m a rabbit with phases that hop,
Through mesh-routing paths non-stop.
With graphs I find routes,
With tests I sprout roots,
And notebooks make results pop!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding the photonic subcircuit compiler.
Description check ✅ Passed The description covers the summary, motivation, dependencies, checklist, and AI disclosure, matching the template well enough.
Docstring Coverage ✅ Passed Docstring coverage is 94.55% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch tobi/add-photonics-subcircuit-compiler

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 21

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@eval/ph/subcircuit_compilation_data_collection.ipynb`:
- Around line 46-54: The OptimizationConfig constructor in the notebook still
passes unsupported kwargs, causing a TypeError at runtime. Update the config
setup to remove num_restarts and restart_perturbation, keeping the remaining
OptimizationConfig parameters unchanged. Use the existing OptimizationConfig
call in the subcircuit_compilation_data_collection notebook as the target for
this fix.

In `@python/mqt/qmap/ph/baseline.py`:
- Around line 21-38: The nested loops in embed_target_unitary_into_chip are
performing per-element tensor writes for the top-left block copy; replace them
with a single sliced block assignment on the embedded tensor using the
target_dim region. Keep the same behavior in embed_target_unitary_into_chip, but
use direct indexing into embedded with the target_unitary block so the function
is clearer and avoids Python-level iteration overhead.
- Around line 21-38: Add an input validation guard in
embed_target_unitary_into_chip before the nested loops: if target_dim is greater
than chip_dim, raise a ValueError instead of indexing into embedded. Keep the
fix localized to this helper so the torch.eye allocation and assignment loop
only run when the target block fits within the chip-sized tensor.

In `@python/mqt/qmap/ph/data_collection.py`:
- Line 77: The collect_pipeline_results function is too complex and should be
split up to satisfy Ruff PLR0912/PLR0915. Extract the hardware-cache
construction logic and the per-repeat run loop into separate helper functions,
then have collect_pipeline_results orchestrate those helpers while keeping its
responsibilities minimal. Use the existing collect_pipeline_results entry point
and any new private helpers to preserve behavior and make the control flow
easier to follow.
- Around line 216-224: The std value returned by `_mean_std` in
`data_collection.py` is never used because each call site only keeps the mean,
so the second return is dead weight. Update the aggregation logic around
`_mean_std` and the result assembly in `data_collection` to either include the
standard deviation in the collected outputs (and any downstream consumers) or
simplify `_mean_std` to return only the mean and adjust all call sites
accordingly.
- Around line 220-223: The aggregated loss values are computed in the data
collection flow but never make it into the final grouped result. Update the
aggregation in data_collection.py, especially in the logic around the rows
assembly and the groupby(...).agg(...) call in the aggregation step, so
`mean_loss` and `mean_baseline_loss` are actually included in the aggregated
output (or remove the unused computations if they are not meant to be returned).
Keep the `rows` construction and `df_aggregated` schema consistent so the loss
fields are not silently dropped.
- Around line 72-74: The helper _mean_std currently has no Google-style
docstring, so add one directly above the function definition describing its
purpose, the values argument, and the returned mean/std tuple in Google format.
Keep the implementation unchanged and ensure the docstring follows the existing
Python docstring guidelines used elsewhere in data_collection.py.
- Around line 84-87: Make the boolean flags in data_collection.py keyword-only
to avoid positional boolean arguments. Update the affected function signature
around the parameters input_losses, output_losses, and ideal_beam_splitters
(along with custom_bs_data if needed) by inserting a keyword-only separator so
callers must pass these flags by name. Check all call sites of the function to
ensure they use the explicit keywords.
- Around line 173-178: The seed in data_collection.py’s unitary generation logic
is derived from target_dim + unitary_index, which can repeat across different
sweep points and create correlated RNG streams. Update the seeding in the loop
that calls get_haar_random_unitary so each (target_dim, unitary_index) pair maps
to a unique deterministic seed, for example by combining both values with a
collision-resistant formula or hash. Keep the change localized around
unitary_seed and target_unitary so the sweep remains reproducible but
independent across target_dim values.
- Around line 247-266: Handle the empty rows case before calling groupby in
data_collection.py: when rows is empty, df will not contain the grouping columns
and df.groupby(groupby_cols, ...) will raise a KeyError. Update the logic around
the df and df_aggregated निर्माण so that an empty setups list or
num_unitaries_per_setup=0 returns an empty DataFrame with the expected output
schema instead of grouping. Use the existing groupby_cols and aggregation fields
in the same block to define the return shape consistently.

In `@python/mqt/qmap/ph/subcircuit_compilation.py`:
- Around line 86-307: compile_subcircuit is doing too many orchestration steps
in one place, which is why it exceeds the statement-count threshold. Refactor
the flow in subcircuit_compilation.py by extracting the routing/setup,
optimization, ideal-distribution computation, chip construction, and
simulation/evaluation logic into small helpers such as
_compute_ideal_distributions and _optimize_and_build_chip, while keeping
compile_subcircuit as the top-level coordinator. Preserve the existing behavior
and data flow through unique symbols like get_best_route,
optimize_unitary_subcircuit_parameters, create_mzi_chip, simulate_with_loss, and
evaluate_chip_performance, and keep RunResult assembly in the main function.
- Around line 238-267: The ideal Perceval simulation is being run twice with
identical inputs in the ground-truth section, duplicating an expensive
deterministic step. Reuse the single result from the
`ground_truth_processor`/`algorithm.Sampler(...).probs()` computation for both
`ideal_probability_distribution` and `baseline_ideal_probability_distribution`,
and remove the redundant `baseline_ground_truth_processor` setup while keeping
the existing `pcvl_u`, `ground_truth_processor`, and sampler flow intact.

In `@python/mqt/qmap/ph/unitary_to_phase_compilation.py`:
- Around line 519-609: The optimization loop in unitary_to_phase_compilation is
tracking best_loss but never stores the matching parameter state, so the return
value currently reflects the final iterate instead of the best one. Update the
loop around best_loss/no_improve_steps to snapshot phase_shifter_params whenever
a new best loss is found, and return that saved best tensor from the final
dictionary instead of the current detached phase_shifter_params. Use the
existing symbols best_loss, phase_shifter_params, and the function’s return
block to locate the change.
- Line 586: The progress-check in unitary-to-phase compilation is a dead no-op
because it only evaluates a boolean and discards it, so the verbose flag never
triggers logging. Update the progress reporting logic in the loop that uses
index and verbose so that it actually emits a message every 100 iterations when
verbose is enabled, using the surrounding compilation method/function as the
place to hook the logging behavior.

In `@test/python/ph/conftest.py`:
- Around line 21-33: The current `conftest.py` setup in the `mqt`/`mqt.qmap`
import shim mutates `sys.modules` globally, which can leak the stubs into later
tests in the same interpreter. Scope the stub registration in the loop that
creates the `ModuleType` entries to the photonics tests by using a fixture or
adding teardown logic in `conftest.py` to remove or restore the `mqt` and
`mqt.qmap` modules after the tests finish, while keeping `sys.path.insert`
limited to the test session setup.

In `@test/python/ph/test_baseline.py`:
- Around line 23-118: Add explicit -> None return annotations to every flagged
staticmethod test in TestGetBaselineActiveCols, TestEmbedTargetUnitaryIntoChip,
and TestGetBaselineInputPorts (and any other test methods in test_baseline.py
that currently omit them) to satisfy Ruff ANN205; update the function signatures
directly on each test_* method rather than suppressing the warning.

In `@test/python/ph/test_data_collection.py`:
- Around line 55-62: The comment in test_valid_multiple_setups is inaccurate
about why (6,6) is not produced by build_setup_grid; update the test note to
reflect that target_dims_list only includes 2 and 4, so 6 is never a candidate
target dimension. Keep the assertions as-is and revise the explanatory text in
test_valid_multiple_setups to match the actual inputs and expected Cartesian
product.

In `@test/python/ph/test_graph.py`:
- Around line 26-212: Add explicit `-> None` return annotations to every
`@staticmethod` test in `TestBarFidelity`, `TestCrossFidelity`,
`TestGenerateBeamSplitterMatrix`, `TestDetermineRoutingFidelities`, and
`TestConstructGraph` in `test_graph.py`. The issue is Ruff ANN205 missing return
type annotations on these test methods. Update each test method signature to
include `-> None` so the file stays lint-clean without suppressions.

In `@test/python/ph/test_phases.py`:
- Around line 24-128: Annotate the test methods in TestPhases and
TestGetEffectiveParamsAndMask with explicit -> None return types to satisfy
ANN205, and add explicit torch.Tensor return annotations to the helper methods
_bar_mask and _cross_mask so the type warnings are cleared without suppressing
them.

In `@test/python/ph/test_routing.py`:
- Around line 30-232: The test methods in TestRouting and the related static
test classes are missing explicit return type annotations, triggering ANN205
warnings. Update each `@staticmethod` test method in this file to declare -> None,
including methods like test_straight_route_first_position,
test_first_mode_active, test_straight_route_chip4_target2, and the other test_*
functions, without changing the test logic or assertions.

In `@test/python/ph/test_subcircuit_compilation.py`:
- Around line 167-262: Add explicit `-> None` return annotations to each
`@staticmethod` test in `TestRunResultProperties` and `TestRunValueRanges` to
satisfy Ruff ANN205. Update the signatures of the test methods like
`test_returns_run_result`, `test_performance_dict_has_required_keys`, and the
other static test helpers in this file so they clearly return nothing; keep the
test logic unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: eb9671aa-8b70-45d2-a4c2-f01ce933bcdc

📥 Commits

Reviewing files that changed from the base of the PR and between 4db332e and ef3a439.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (20)
  • .license-tools-config.json
  • eval/ph/subcircuit_compilation_data_collection.ipynb
  • noxfile.py
  • pyproject.toml
  • python/mqt/qmap/ph/__init__.py
  • python/mqt/qmap/ph/baseline.py
  • python/mqt/qmap/ph/data_collection.py
  • python/mqt/qmap/ph/graph.py
  • python/mqt/qmap/ph/perceval_simulation.py
  • python/mqt/qmap/ph/routing.py
  • python/mqt/qmap/ph/routing_to_phases.py
  • python/mqt/qmap/ph/subcircuit_compilation.py
  • python/mqt/qmap/ph/unitary_to_phase_compilation.py
  • test/python/ph/conftest.py
  • test/python/ph/test_baseline.py
  • test/python/ph/test_data_collection.py
  • test/python/ph/test_graph.py
  • test/python/ph/test_phases.py
  • test/python/ph/test_routing.py
  • test/python/ph/test_subcircuit_compilation.py

Comment thread eval/ph/subcircuit_compilation_data_collection.ipynb
Comment thread python/mqt/qmap/ph/baseline.py
Comment thread python/mqt/qmap/ph/data_collection.py Outdated
Comment thread eval/ph/data_collection.py
Comment thread eval/ph/data_collection.py Outdated
Comment thread test/python/ph/test_data_collection.py Outdated
Comment thread test/python/ph/test_graph.py Outdated
Comment thread test/python/ph/test_phases.py
Comment thread test/python/ph/test_routing.py
Comment thread test/python/ph/test_subcircuit_compilation.py Outdated
@tobi-forster
tobi-forster force-pushed the tobi/add-photonics-subcircuit-compiler branch from ef3a439 to 0313d46 Compare July 6, 2026 14:51
@tobi-forster

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
CHANGELOG.md (1)

278-281: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Restore the missing PR reference definitions.

  • CHANGELOG.md#L278-L281: add [#1059] and restore [#1069] in the PR-link section.
  • CHANGELOG.md#L21-L24: retain both references once their definitions exist.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` around lines 278 - 281, In the PR-link definitions section near
lines 278-281 of CHANGELOG.md, add the missing [`#1059`] definition and restore
[`#1069`] with their corresponding PR URLs. In the references near lines 21-24,
retain both [`#1059`] and [`#1069`] entries once the definitions are present.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@eval/ph/data_collection.py`:
- Around line 228-247: Update the repeat loop around evaluate_subcircuit to pass
a deterministic phase_noise_seed derived from unitary_seed and repeat_idx,
ensuring each benchmark repeat reproduces its phase-noise realization while
remaining distinct across repeats. Preserve the existing torch.manual_seed
formula and evaluation arguments.

In `@python/mqt/qmap/ph/graph.py`:
- Around line 50-54: Make the new Boolean parameters keyword-only by inserting a
positional-argument separator in generate_beam_splitter_matrix at
python/mqt/qmap/ph/graph.py lines 50-54 before ideal_bs, in the routing function
at python/mqt/qmap/ph/routing_to_phases.py lines 68-73 before
optimize_routing_parameters, and in the function at
python/mqt/qmap/ph/routing_to_phases.py lines 183-187 before
exclude_edge_phase_shifters; preserve existing parameter behavior while
preventing ambiguous positional calls and Ruff FBT001/FBT002 warnings.
- Around line 241-243: Update the fidelity lookup helper covering the
movement-mask processing around graph layers 2–4 so an edge starting at graph
layer L reads fidelities from the preceding chip layer, L - 1, rather than L +
1. Preserve the existing layer and mode-pair mapping, and add a non-ideal
regression test with distinct per-layer fidelities that verifies shortest-path
selection uses the preceding routing layer.

In `@python/mqt/qmap/ph/perceval_simulation.py`:
- Around line 224-236: The simulation result should return the normalized
conditional distribution rather than the raw corrected weights. In the flow that
computes norm_sim, update the returned mapped_distribution field to use norm_sim
when compensated_weight_sum is positive, while preserving the existing empty or
zero-weight behavior.

In `@python/mqt/qmap/ph/routing_to_phases.py`:
- Around line 51-63: Update the MZI refinement logic around the mzi_pairs loop
to determine absent or virtual shifters from the movement_mask states rather
than embedded_params phase magnitudes. Preserve MaskState.MZI for valid pairs,
including pairs whose phases are zero or near zero, and ensure both MZI cells
remain trainable instead of being reclassified or having gradients zeroed in the
related routing logic.

In `@python/mqt/qmap/ph/subcircuit_compilation.py`:
- Around line 459-496: Update the paired create_mzi_chip calls in the
compilation flow to generate one phase-error offset matrix and pass that same
realization to both virtual_chip and baseline_virtual_chip. Preserve the
existing phase_noise_seed behavior and ensure the shared offsets are applied
consistently so only circuit placement and optimization differ.
- Around line 352-353: Validate target_dim immediately after it is derived from
target_unitary in the subcircuit compilation flow: require it to be positive,
even, and no greater than chip_dim, and raise a clear error when invalid. Keep
routing and Perceval input-state construction unchanged for valid dimensions.

In `@python/mqt/qmap/ph/unitary_to_phase_compilation.py`:
- Around line 616-621: The unitary compilation result must expose the loss
associated with best_params, and both subcircuit result paths must consume it
instead of the final history loss. In
python/mqt/qmap/ph/unitary_to_phase_compilation.py lines 616-621, add best_loss
to the returned mapping; in python/mqt/qmap/ph/subcircuit_compilation.py lines
219-220 and 276-277, update the proposed and baseline result construction to use
the returned best_loss.

---

Outside diff comments:
In `@CHANGELOG.md`:
- Around line 278-281: In the PR-link definitions section near lines 278-281 of
CHANGELOG.md, add the missing [`#1059`] definition and restore [`#1069`] with their
corresponding PR URLs. In the references near lines 21-24, retain both [`#1059`]
and [`#1069`] entries once the definitions are present.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c65fbd72-f7be-4d73-b2ce-c5db4f118ea4

📥 Commits

Reviewing files that changed from the base of the PR and between 9f5c54b and 91b3817.

⛔ Files ignored due to path filters (2)
  • eval/ph/results/pipeline_results.csv is excluded by !**/*.csv
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (28)
  • .license-tools-config.json
  • .pre-commit-config.yaml
  • CHANGELOG.md
  • docs/ph_subcircuit_compiler.md
  • eval/ph/data_collection.py
  • eval/ph/hardware_data/24_mode_input_transmissions.txt
  • eval/ph/hardware_data/24_mode_output_transmissions.txt
  • eval/ph/hardware_data/48_mode_input_transmissions.txt
  • eval/ph/hardware_data/48_mode_output_transmissions.txt
  • eval/ph/subcircuit_compilation_data_collection.ipynb
  • noxfile.py
  • pyproject.toml
  • python/mqt/qmap/ph/__init__.py
  • python/mqt/qmap/ph/baseline.py
  • python/mqt/qmap/ph/graph.py
  • python/mqt/qmap/ph/perceval_simulation.py
  • python/mqt/qmap/ph/routing.py
  • python/mqt/qmap/ph/routing_to_phases.py
  • python/mqt/qmap/ph/subcircuit_compilation.py
  • python/mqt/qmap/ph/unitary_to_phase_compilation.py
  • test/python/ph/conftest.py
  • test/python/ph/test_baseline.py
  • test/python/ph/test_data_collection.py
  • test/python/ph/test_graph.py
  • test/python/ph/test_phases.py
  • test/python/ph/test_routing.py
  • test/python/ph/test_subcircuit_compilation.py
  • test/python/ph/test_unitary_to_phase_compilation.py

Comment thread eval/ph/data_collection.py
Comment thread python/mqt/qmap/ph/graph.py
Comment thread python/mqt/qmap/ph/graph.py Outdated
Comment thread python/mqt/qmap/ph/perceval_simulation.py
Comment thread python/mqt/qmap/ph/routing_to_phases.py Outdated
Comment thread python/mqt/qmap/ph/subcircuit_compilation.py
Comment thread python/mqt/qmap/ph/subcircuit_compilation.py
Comment thread python/mqt/qmap/ph/unitary_to_phase_compilation.py Outdated
@tobi-forster

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@tobi-forster tobi-forster added documentation Improvements or additions to documentation feature New feature or request python Anything related to Python code labels Jul 15, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

♻️ Duplicate comments (3)
python/mqt/qmap/ph/unitary_to_phase_compilation.py (1)

601-601: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Dead statement — verbose progress logging still has no effect (unresolved from a prior review).

verbose and index % 100 == 0 evaluates a boolean and discards it. The verbose parameter (documented at Line 450 as "print progress every 100 iterations") has no observable effect anywhere in the function.

🐛 Proposed fix
-        verbose and index % 100 == 0
+        if verbose and index % 100 == 0:
+            print(f"Iteration {index}: loss={loop_loss:.6e}")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/mqt/qmap/ph/unitary_to_phase_compilation.py` at line 601, Replace the
discarded boolean expression in the unitary-to-phase compilation loop with an
observable progress action: when verbose is enabled and index is divisible by
100, print the documented progress information. Remove the standalone `verbose
and index % 100 == 0` statement and preserve the existing iteration behavior
otherwise.
python/mqt/qmap/ph/subcircuit_compilation.py (1)

354-355: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Validate target_dim before use (still open).

target_dim is derived from target_unitary.shape[0] in both compile_subcircuit (lines 354-355) and evaluate_subcircuit (lines 459-460) without checking it's positive, even, and ≤ chip_dim. Invalid values (odd, non-positive, or larger than chip_dim) will fail later in routing/Perceval input-state construction with opaque errors rather than a clear message.

Proposed validation
     chip_dim = len(input_transmissions)
     target_dim = int(target_unitary.shape[0])
+    if target_dim <= 0 or target_dim % 2 != 0:
+        msg = "target_unitary dimension must be a positive even number for dual-rail encoding."
+        raise ValueError(msg)
+    if target_dim > chip_dim:
+        msg = "target_unitary dimension cannot exceed the chip dimension."
+        raise ValueError(msg)

Apply the same check in both compile_subcircuit and evaluate_subcircuit (or factor into a shared helper).

Also applies to: 459-460

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/mqt/qmap/ph/subcircuit_compilation.py` around lines 354 - 355,
Validate target_dim immediately after it is derived in both compile_subcircuit
and evaluate_subcircuit, requiring it to be positive, even, and no greater than
chip_dim; raise a clear validation error before routing or input-state
construction when invalid. Reuse a shared helper if appropriate, while
preserving valid execution paths.
python/mqt/qmap/ph/baseline.py (1)

21-36: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Missing guard for target_dim > chip_dim still absent.

A prior review already asked for a ValueError guard here (marked resolved), but the current code still has none. With target_dim > chip_dim, embedded[:target_dim, :target_dim] silently clips to embedded's real shape, and assigning the larger target_unitary raises an opaque PyTorch shape-mismatch error instead of a clear validation message.

🛡️ Proposed fix
 def embed_target_unitary_into_chip(target_unitary: np.ndarray, chip_dim: int, target_dim: int) -> torch.Tensor:
     """Embed a target unitary into the top-left block of a chip-sized identity matrix.
...
     """
+    if target_dim > chip_dim:
+        msg = f"target_dim ({target_dim}) cannot exceed chip_dim ({chip_dim})."
+        raise ValueError(msg)
     embedded = torch.eye(chip_dim, dtype=torch.complex128)
     embedded[:target_dim, :target_dim] = torch.tensor(target_unitary, dtype=torch.complex128)
     return embedded
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/mqt/qmap/ph/baseline.py` around lines 21 - 36, Add an explicit
ValueError guard at the start of embed_target_unitary_into_chip when target_dim
exceeds chip_dim, before constructing or assigning to the embedded tensor, with
a clear message describing the invalid dimensions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Around line 21-22: Synchronize the changelog entry references with the PR
links definitions: ensure the new [`#1059`] reference has a matching definition
and restore or update the definition for [`#1069`] so all references in the
changelog resolve correctly. Keep the existing PR link formatting and author
attribution conventions unchanged.

In `@docs/ph_subcircuit_compiler.md`:
- Around line 24-26: Update the coincidence rate definition in the documentation
paragraph to describe the probability that all photons are detected
simultaneously in the computation zone, replacing the reference to correct
output ports while leaving the separate TVD metric distinction intact.
- Around line 87-90: Correct the calibration sentence in the documentation by
replacing the ungrammatical phrase “a calibration measurements” with
“calibration measurements” or “a calibration measurement,” while preserving the
surrounding meaning.

In `@python/mqt/qmap/ph/graph.py`:
- Around line 50-153: Refactor generate_beam_splitter_matrix and construct_graph
into focused named helpers to reduce their PLR0912/PLR0915 complexity. Extract
beam-splitter statistics generation, normalization, and pair assembly from
generate_beam_splitter_matrix, and extract layer indexing and edge construction
from construct_graph, preserving the existing ordering, parity guards, and
output behavior.
- Around line 406-424: The construct_graph validation must reject odd chip_dim -
target_dim values before layer sizing occurs. Add a ValueError guard alongside
the existing target_dim checks, update the Raises documentation, and add a
regression test confirming construct_graph(3, 2) raises ValueError.

In `@python/mqt/qmap/ph/perceval_simulation.py`:
- Around line 23-30: Make the boolean parameters keyword-only in both
create_mzi_chip and the related function containing
apply_output_transmission_correction by inserting a keyword-only separator
before those flags. Preserve existing parameter behavior and ensure their call
sites continue passing them by keyword.

In `@python/mqt/qmap/ph/routing_to_phases.py`:
- Around line 117-126: Remove the near-zero phase heuristic in the state ==
MaskState.MZI branch of routing_to_phases: do not overwrite raw_params with (0,
π) or zero grad_mask for compute-zone MZIs based on phase values. Preserve
legitimate (0, 0) phases and their gradients, relying on the structural
movement_mask state to distinguish routing from compute zones; add regression
coverage for a compute MZI with both phases near zero if the existing test suite
supports it.

In `@python/mqt/qmap/ph/routing.py`:
- Around line 279-303: Update the later-layer transition handling in
route_to_movement_mask so both the i % 2 == 0 and i % 2 == 1 branches validate
that route[i - 1] is the expected adjacent node before marking CROSS, raising
the same invalid-edge ValueError behavior used by the i == 2 branch for
non-adjacent transitions. Add or update route_to_movement_mask tests in
TestRouteToMovementMask to cover invalid later-layer transitions and verify
target_dim-wide CROSS slices for target_dim values larger than 2.

---

Duplicate comments:
In `@python/mqt/qmap/ph/baseline.py`:
- Around line 21-36: Add an explicit ValueError guard at the start of
embed_target_unitary_into_chip when target_dim exceeds chip_dim, before
constructing or assigning to the embedded tensor, with a clear message
describing the invalid dimensions.

In `@python/mqt/qmap/ph/subcircuit_compilation.py`:
- Around line 354-355: Validate target_dim immediately after it is derived in
both compile_subcircuit and evaluate_subcircuit, requiring it to be positive,
even, and no greater than chip_dim; raise a clear validation error before
routing or input-state construction when invalid. Reuse a shared helper if
appropriate, while preserving valid execution paths.

In `@python/mqt/qmap/ph/unitary_to_phase_compilation.py`:
- Line 601: Replace the discarded boolean expression in the unitary-to-phase
compilation loop with an observable progress action: when verbose is enabled and
index is divisible by 100, print the documented progress information. Remove the
standalone `verbose and index % 100 == 0` statement and preserve the existing
iteration behavior otherwise.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 83008b97-9eb4-483f-89f0-525db597af48

📥 Commits

Reviewing files that changed from the base of the PR and between 9f5c54b and 9cb575e.

⛔ Files ignored due to path filters (2)
  • eval/ph/results/pipeline_results.csv is excluded by !**/*.csv
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (28)
  • .license-tools-config.json
  • .pre-commit-config.yaml
  • CHANGELOG.md
  • docs/ph_subcircuit_compiler.md
  • eval/ph/data_collection.py
  • eval/ph/hardware_data/24_mode_input_transmissions.txt
  • eval/ph/hardware_data/24_mode_output_transmissions.txt
  • eval/ph/hardware_data/48_mode_input_transmissions.txt
  • eval/ph/hardware_data/48_mode_output_transmissions.txt
  • eval/ph/subcircuit_compilation_data_collection.ipynb
  • noxfile.py
  • pyproject.toml
  • python/mqt/qmap/ph/__init__.py
  • python/mqt/qmap/ph/baseline.py
  • python/mqt/qmap/ph/graph.py
  • python/mqt/qmap/ph/perceval_simulation.py
  • python/mqt/qmap/ph/routing.py
  • python/mqt/qmap/ph/routing_to_phases.py
  • python/mqt/qmap/ph/subcircuit_compilation.py
  • python/mqt/qmap/ph/unitary_to_phase_compilation.py
  • test/python/ph/conftest.py
  • test/python/ph/test_baseline.py
  • test/python/ph/test_data_collection.py
  • test/python/ph/test_graph.py
  • test/python/ph/test_phases.py
  • test/python/ph/test_routing.py
  • test/python/ph/test_subcircuit_compilation.py
  • test/python/ph/test_unitary_to_phase_compilation.py

Comment thread CHANGELOG.md Outdated
Comment thread docs/ph_subcircuit_compiler.md Outdated
Comment thread docs/ph_subcircuit_compiler.md
Comment thread python/mqt/qmap/ph/graph.py
Comment thread python/mqt/qmap/ph/graph.py
Comment thread python/mqt/qmap/ph/perceval_simulation.py
Comment thread python/mqt/qmap/ph/routing_to_phases.py Outdated
Comment thread python/mqt/qmap/ph/routing.py
@ystade

ystade commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

@coderabbitai There are some unresolved comments. Please, check whether they were already tackled and then resolve them, otherwise comment, why they are not resolved yet.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

gh api graphql \
  -F owner='munich-quantum-toolkit' \
  -F name='qmap' \
  -F number=1059 \
  -f query='
query($owner: String!, $name: String!, $number: Int!) {
  repository(owner: $owner, name: $name) {
    pullRequest(number: $number) {
      reviewThreads(first: 100) {
        nodes {
          id
          isResolved
          isOutdated
          path
          line
          originalLine
          comments(first: 20) {
            nodes {
              id
              author { login }
              body
              createdAt
              url
            }
          }
        }
      }
    }
  }
}' > /tmp/pr-1059-review-threads.json

jq -r '
  .data.repository.pullRequest.reviewThreads.nodes[]
  | select(.isResolved == false)
  | "THREAD \(.id)\nPATH \(.path // "general") LINE \(.line // .originalLine // 0) OUTDATED \(.isOutdated)\n"
    + (.comments.nodes[] | "AUTHOR \(.author.login)\nURL \(.url)\nBODY\n\(.body)\n")
    + "----"
' /tmp/pr-1059-review-threads.json

Length of output: 22494


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '== Structure =='
ast-grep outline eval/ph/data_collection.py --items all --view expanded
ast-grep outline python/mqt/qmap/ph/unitary_to_phase_compilation.py --items all --match 'optimize_unitary_subcircuit_parameters' --view expanded
ast-grep outline test/python/ph/conftest.py --items all --view expanded
ast-grep outline python/mqt/qmap/ph/routing_to_phases.py --items all --view expanded

printf '%s\n' '== Current relevant code =='
nl -ba eval/ph/data_collection.py | sed -n '1,330p'
nl -ba python/mqt/qmap/ph/unitary_to_phase_compilation.py | sed -n '500,660p'
nl -ba test/python/ph/conftest.py | sed -n '1,180p'
nl -ba python/mqt/qmap/ph/routing_to_phases.py | sed -n '1,180p'

printf '%s\n' '== Targeted test/usages search =='
rg -n -C 4 --glob '*.py' \
  'unitary_seed|base_seed|avg_loss|avg_baseline_loss|std|empty.*DataFrame|num_unitaries_per_setup|verbose|capsys|sys\.modules|monkeypatch|priority_map|mixed.*(?:BAR|CROSS)|MaskState\.(?:BAR|CROSS)' \
  eval/ph test/python/ph python/mqt/qmap/ph

Length of output: 3064


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '== eval/ph/data_collection.py =='
sed -n '70,460p' eval/ph/data_collection.py

printf '%s\n' '== optimizer loop =='
sed -n '400,660p' python/mqt/qmap/ph/unitary_to_phase_compilation.py

printf '%s\n' '== photonics conftest =='
sed -n '1,180p' test/python/ph/conftest.py

printf '%s\n' '== routing-to-phases =='
sed -n '1,190p' python/mqt/qmap/ph/routing_to_phases.py

printf '%s\n' '== Targeted tests/usages =='
rg -n -C 4 --glob '*.py' \
  'unitary_seed|base_seed|avg_loss|avg_baseline_loss|_mean\(|num_unitaries_per_setup|verbose|capsys|sys\.modules|priority_map|MaskState\.(BAR|CROSS)' \
  eval/ph test/python/ph python/mqt/qmap/ph || true

Length of output: 62874


🏁 Script executed:

#!/bin/bash
set -euo pipefail

resolve_thread() {
  local thread_id="$1"
  gh api graphql \
    -f query='
mutation($threadId: ID!) {
  resolveReviewThread(input: {threadId: $threadId}) {
    thread { id isResolved }
  }
}' \
    -F threadId="$thread_id" \
    --jq '.data.resolveReviewThread.thread | "\(.id) resolved=\(.isResolved)"'
}

reply_thread() {
  local thread_id="$1"
  local body="$2"
  gh api graphql \
    -f query='
mutation($threadId: ID!, $body: String!) {
  addPullRequestReviewThreadReply(input: {pullRequestReviewThreadId: $threadId, body: $body}) {
    comment { url body }
  }
}' \
    -F threadId="$thread_id" \
    -F body="$body" \
    --jq '.data.addPullRequestReviewThreadReply.comment.url'
}

# Addressed in the current branch.
resolve_thread 'PRRT_kwDOEgwI3c6OOnim'
resolve_thread 'PRRT_kwDOEgwI3c6OOnir'
resolve_thread 'PRRT_kwDOEgwI3c6OOnis'
resolve_thread 'PRRT_kwDOEgwI3c6OOniv'
resolve_thread 'PRRT_kwDOEgwI3c6QbIif'

# Still valid in the current branch; keep open and explain why.
reply_thread \
  'PRRT_kwDOEgwI3c6OOni2' \
  'Checked the current branch: this is not resolved. `optimize_unitary_subcircuit_parameters()` still evaluates `verbose and index % 100 == 0` without emitting output, while its docstring promises progress logging. The thread remains open until the condition performs logging (or the unused `verbose` API is removed and documented accordingly).'

reply_thread \
  'PRRT_kwDOEgwI3c6OOni9' \
  'Checked the current branch: this is not resolved. `test/python/ph/conftest.py` still registers synthetic `mqt` and `mqt.qmap` modules in `sys.modules` during conftest import and does not restore them. Those stubs can therefore remain visible to other tests in the same pytest process. The thread remains open pending scoped setup/teardown or an import strategy that does not mutate process-global module state.'

Length of output: 2342


@ystade Checked all seven unresolved CodeRabbit threads on PR #1059.

Resolved five that are addressed in the current branch:

  • unique deterministic unitary seeding,
  • removal of unused standard-deviation computation,
  • aggregation of proposed and baseline losses,
  • schema-preserving empty results,
  • documented routing-state precedence.

Kept two open and replied in their threads:

  • verbose still does not emit progress output in optimize_unitary_subcircuit_parameters();
  • test/python/ph/conftest.py still leaves synthetic mqt/mqt.qmap entries in sys.modules for the process lifetime.

@ystade
ystade marked this pull request as ready for review July 20, 2026 11:53

@ystade ystade left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tobi-forster I am very happy to see this kick-start of a compiler for photonics. Really, great work that resulted in quite some lines. Hence, I do not recall in detail anymore what all my comments were about but I do recall that there was nothing critical and mostly the comments are more of a remark. Feel free to ask any questions if anything is unclear and (opposed to the coding guidelines) please resolve every comment that is fixed. Otherwise I loose the overview.

Regarding the tests that are skipped: These are skipped because this PR does not touch the C++ code base, so this is all fine.

Comment thread CHANGELOG.md Outdated
Comment thread CHANGELOG.md Outdated
Comment thread noxfile.py Outdated
# the torch-free tests (e.g. test_graph.py) still run. Remove this block and
# restore the unconditional `"--extra", "photonics"` args below once torch
# publishes macOS x86_64 wheels again or Intel macOS runners leave the matrix.
photonics_extra: tuple[str, ...] = ("--extra", "photonics")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering whether photonics_extra there is a more meaningful name for this variable. (1) one could imagine to list other extensions than photonics in the future; (2) that is an entirely "me"-problem: I did not know that these extras officially called extras since I haven't worked with Python so much recently. However, sometimes, they are also called optional_dependencies, which in my opinion is a more expressive variable name and better suited here.

Comment thread noxfile.py
# extra there; the photonics tests self-skip via `pytest.importorskip`, and
# the torch-free tests (e.g. test_graph.py) still run. Remove this block and
# restore the unconditional `"--extra", "photonics"` args below once torch
# publishes macOS x86_64 wheels again or Intel macOS runners leave the matrix.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@burgholzer For me this solution is fine, just tagging you here for awareness when you review it.

Comment thread pyproject.toml Outdated
Comment thread python/mqt/qmap/ph/unitary_to_phase_compilation.py Outdated
Comment on lines +21 to +33
_PYTHON_SRC = Path(__file__).parents[3] / "python"

for _pkg, _path in [
("mqt", _PYTHON_SRC / "mqt"),
("mqt.qmap", _PYTHON_SRC / "mqt" / "qmap"),
]:
if _pkg not in sys.modules:
_mod = ModuleType(_pkg)
_mod.__path__ = [str(_path)]
_mod.__package__ = _pkg
sys.modules[_pkg] = _mod

sys.path.insert(0, str(_PYTHON_SRC))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai @tobi-forster Is it actually really necessary to stub mqt and qmt.qmap. Yes, indeed, the photonics compiler does not require a C++ build but we are building the C++ code anyways in the CI so I di not understand what one gains through stubbing it.

Comment thread test/python/ph/test_baseline.py
Comment thread test/python/ph/test_data_collection.py Outdated
assert torch.all(mask[4:8, 2] == MaskState.BAR)


class TestGetBestRoute:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not know where to comment that, I'll just do it here. All the tests form a very sophisticated test suite. Most of them test small details of the implementation (which is fine and also necessary). However, I am missing (or I have overlooked it) an end-to-end test that feeds in a certain phase matrix (or multiple to test multiple scenarios) and tests where the outcome is as expected. To this end, the input phase matrix should deterministically lead to one certain output.

This is more of a fundamental comment: In C++ one has a better separation of the public interface and functions that are only called internally. While it is quite useful to also test the internal functions from time to time, to verify that the internals work as expected, most of the test should through the user facing interface, i.e., calling the functions a user would call directly with a certain input and then verifying that the output is as expected. This allows in particular, to change the internals completely as long as the public facing interface remains the same.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation feature New feature or request python Anything related to Python code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants