Skip to content

PML-282 Photonic Generator#220

Open
ben9871 wants to merge 8 commits into
merlinquantum:release/0.4from
ben9871:PML-282
Open

PML-282 Photonic Generator#220
ben9871 wants to merge 8 commits into
merlinquantum:release/0.4from
ben9871:PML-282

Conversation

@ben9871
Copy link
Copy Markdown
Contributor

@ben9871 ben9871 commented May 21, 2026

PML-282 Add QuantumLayer-backed PhotonicGenerator

Summary

This PR adds a PhotonicGenerator model that composes one or more existing QuantumLayer objects into a latent-to-sample generator. It keeps quantum execution inside QuantumLayer, exposes raw per-layer measurements for inspection, and uses output adapters to map measurements to vectors or images.

Related Issue

Related Jira: PML-282

Type of change

  • Bug fix
  • New feature
  • Documentation update
  • Refactor / Cleanup
  • Performance improvement
  • CI / Build / Tooling
  • Breaking change (requires migration notes)

Proposed changes

  • Add merlin.models.PhotonicGenerator.
  • Add GeneratorMeasurements to carry raw per-layer outputs and output-key metadata.
  • Add latent distribution helpers:
    • LatentDistribution
    • NormalLatent
  • Add output adapter API:
    • OutputAdapter
    • VectorAdapter
    • ImageAdapter
  • Export the new public objects from merlin.models and top-level merlin.
  • Add tests covering construction, validation, latent sampling, generation, adapter behaviour, parameter registration, and gradient flow.
  • Add the new API page under docs/source/api_reference/api/merlin.models.photonic_generator.rst.

API shape:

generator = ml.PhotonicGenerator(
    layers=[layer_0, layer_1],
    output_adapter=ml.ImageAdapter(shape=(1, 8, 8)),
)

z = generator.sample_latent(batch_size=16)
samples = generator(z)
measurements = generator.measure(z)
first_layer = generator[0]

How to test / How to run

  1. Run pre-commit on the touched Python files:
python -m pre_commit run --files merlin/models/photonic_generator.py merlin/models/__init__.py merlin/__init__.py tests/models/test_photonic_generator.py
  1. Run the focused test file:
python -m pytest tests/models/test_photonic_generator.py -q
  1. Run the strict docs build:
python -m sphinx -b html -W --keep-going -n docs/source docs/build/html

Observed locally:

ruff (legacy alias)......................................................Passed
ruff format..............................................................Passed
mypy.....................................................................Passed

16 passed

build succeeded.

Screenshots / Logs (optional)

N/A.

Performance considerations (optional)

The generator evaluates its underlying QuantumLayer heads sequentially and then adapts the collected outputs. Runtime is therefore approximately the sum of the runtimes of the configured quantum layers. No cross-layer batching or caching is introduced in this PR.

This is intentional for the first version because different generator heads may use different circuits, input states, output spaces, measurement strategies, and noise models.

Documentation

  • User docs updated (Sphinx)
  • Examples / notebooks updated
  • Docstrings updated
  • Updated the API

Checklist

  • PR title includes Jira issue key (e.g., PML-126)

  • "Related Jira ticket" section includes the Jira issue key (no URL)

  • Code formatted (ruff format)

  • Lint passes (ruff)

  • Static typing passes (mypy) if applicable

  • Unit tests added/updated (pytest)

  • Tests pass locally (pytest)

  • Tests pass on GPU (pytest)

  • Test coverage not decreased significantly

  • Docs build locally if affected (sphinx)

  • With this command:

      > SPHINXOPTS="-W --keep-going -n" make -C docs clean html
    

    the docs are built without any warning or errors.

  • New public classes/methods/packages are added in the API following the methodology presented in other files.

  • Dependencies updated (if needed) and pinned appropriately

  • PR description explains what changed and how to validate it

Copy link
Copy Markdown
Contributor

@LF-Vigneux LF-Vigneux left a comment

Choose a reason for hiding this comment

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

The form and structure of the code seems fine to me. This is not a complete review pending the remaining functions to implement.

Comment thread merlin/models/photonic_generator.py
Comment thread merlin/models/photonic_generator.py
Copy link
Copy Markdown
Contributor

@CassNot CassNot left a comment

Choose a reason for hiding this comment

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

There are some discrepancy with the orignal code

More of a product question: What happens if the subgenerators are all the same ? Do we need to create multiple times the same one or should there be a shortcut to have to only define it once, then through the API, we precise the circuit/layer structure and N ones would be initiated ? (as in the original one)

Lack of validation: to validate this implementation, I would test it in the same settings as the original work (implementation here https://github.com/merlinquantum/reproduced_papers/tree/main/papers/photonic_QGAN) in a dedicated notebook

Comment thread merlin/models/photonic_generator.py
raise NotImplementedError


class VectorAdapter(OutputAdapter):
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.

Both classes VectorAdapter and ImageAdapter do not preserve the original QGAN mapping
Here: they flatten the outputs and crop/pad them directly, ignoring the output_keys
In the original work: the orignal PatchGenerator regroups Fock states through output_map and sums collision with index_add_ (filters lossy/non-PNR states + renormalizes) then maps to pixels -> see method dist_to_image in https://github.com/merlinquantum/reproduced_papers/blob/main/papers/photonic_QGAN/lib/generators.py

Therefore, in this implementation, the mapping depends on the MerLin's combinadics and not the paper mapping (for which the code is the same in the reproduced_papers repo as in the original work, in utils/mappings.py)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

will be changed in next commit

return flattened


def _center_crop_or_pad(x: torch.Tensor, size: int) -> torch.Tensor:
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.

Here, you apply cropping and padding after concatenating all heads
But the orignal work applied it per batch before concatenation (line 173 in https://github.com/merlinquantum/reproduced_papers/blob/main/papers/photonic_QGAN/lib/generators.py)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

correct

If ``std`` is not positive.
"""

def __init__(self, dim: int, mean: float = 0.0, std: float = 1.0) -> None:
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.

Default latent sampling does not match the reproduction training loop (line 142 https://github.com/merlinquantum/reproduced_papers/blob/main/papers/photonic_QGAN/lib/qgan.py)
PhotonicGenerator.generate() defaults to NormalLatent(..., std=1.0), while the original QGAN samples latent inputs with std 2 * pi

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.

A regression test should also compare a fixed set of output_keys and synthetic probability tensors against the original regroup/crop/normalize behavior

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

correct, will do in next commit

@CassNot
Copy link
Copy Markdown
Contributor

CassNot commented May 25, 2026

@codex review with respect to original implementation https://github.com/merlinquantum/reproduced_papers/tree/main/papers/photonic_QGAN

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9c529552be

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +256 to +258
flattened = _flatten_tensor_outputs(measurements.outputs)
combined = torch.cat(flattened, dim=1)
return _center_crop_or_pad(combined, self.size)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reintroduce output-key remapping before image/vector adaptation

The new adapters flatten and crop raw tensor columns directly, but the original photonic_QGAN PatchGenerator path explicitly remaps probabilities with output_map (index_add_) before resizing each patch. In non-PNR/lossy settings, multiple Fock keys must be merged (and some keys filtered) to represent the same classical bin; skipping that remap changes the generated sample distribution rather than just reshaping it, so outputs are no longer faithful to the original implementation behavior.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

correct

Comment thread merlin/models/photonic_generator.py Outdated
Comment on lines +360 to +361
if latent is None:
latent = NormalLatent(inferred_dim)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Match default latent scale used by original photonic QGAN

When latent=None, the generator defaults to NormalLatent(..., std=1.0), but the original photonic_QGAN training code samples generator noise with torch.normal(0, 2 * torch.pi, ...). Any workflow using sample_latent()/generate() with defaults will therefore feed a much narrower latent distribution than the reference implementation, which can materially change training dynamics and reproduction results.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

correct, will be changed soon

@CassNot CassNot added this to the v0.4 milestone May 28, 2026
Copy link
Copy Markdown
Contributor

@CassNot CassNot left a comment

Choose a reason for hiding this comment

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

We still miss:

Comment thread merlin/utils/grouping.py
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.

This grouping strategy requires the keys and therefore breaks the nn.Sequential pipeline we can have with the other ones
Maybe you can move it to models/photonic_generator.py for now as we know it works for the GAN but we have not tested it on other models
This way, we can use it externally but it does not surprise users if it breaks nn.Sequential pipeline

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a new PhotonicGenerator model under merlin.models that composes one or more QuantumLayer heads into a latent-to-sample generator, and adds an OccupancyGrouping policy used by photonic QGAN-style image pipelines. It also wires up the necessary plumbing in QuantumLayer to bind key-aware groupings to layer output keys.

Changes:

  • Add PhotonicGenerator, GeneratorMeasurements, LatentDistribution/NormalLatent, and OutputAdapter/VectorAdapter/ImageAdapter in a new merlin/models/photonic_generator.py.
  • Add OccupancyGrouping in merlin/utils/grouping.py plus _bind_grouping_to_output_keys, and integrate output-key binding into QuantumLayer so probability groupings can refine output_keys/output_size.
  • Export new public APIs from merlin and merlin.models, and add Sphinx pages for the new grouping and generator modules.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated no comments.

Show a summary per file
File Description
merlin/models/photonic_generator.py New generator model, latent/output-adapter abstractions, and concrete vector/image adapters.
merlin/utils/grouping.py New OccupancyGrouping plus shared key-binding helper.
merlin/utils/init.py Exports OccupancyGrouping.
merlin/measurement/strategies.py Accepts OccupancyGrouping in probability/partial strategies.
merlin/algorithms/layer.py Binds key-aware groupings and uses the bound grouping in forward/output_keys.
merlin/models/init.py Re-exports new generator-related symbols.
merlin/init.py Top-level exports for new public API.
tests/models/test_photonic_generator.py New comprehensive test suite for the generator pipeline.
tests/utils/test_grouping.py Adds OccupancyGrouping test class covering binding, filtering, collapsing, gradients, dtype.
tests/measurement/test_measurement_strategy.py Adds an integration test verifying OccupancyGrouping binds to layer output keys.
docs/source/api_reference/api/merlin.utils.grouping.rst Adds OccupancyGrouping autoclass entry.
docs/source/api_reference/api/merlin.models.rst Adds toctree entry for the new generator page.
docs/source/api_reference/api/merlin.models.photonic_generator.rst New API reference page for the generator and adapters.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants