:github_url: https://github.com/merlinquantum/merlin

==========================
Measurement Strategy Guide
==========================

The type of output from a QuantumLayer depends on the measurement strategy. The quantum-to-classical conversion relies on two orthogonal concepts:

- :class:`~merlin.measurement.strategies.MeasurementStrategy` selects how results are extracted from the quantum simulation or hardware backend.
- :class:`~merlin.utils.grouping.LexGrouping` and :class:`~merlin.utils.grouping.ModGrouping` provide optional post-processing of outputs (see :doc:`./grouping`).

The quantum-to-classical conversion can also be handled by the user with a typed object being returned by a forward() call. Take a look at :doc:`../api_reference/api/merlin.algorithms.layer` for more details about returned typed objects per measurement strategy.

PROBABILITIES
-----------------------

Produces the probability of observing each Fock state. In the case of strong simulation (as detailed in the :doc:`../quantum_expert_area/SLOS` documentation), the SLOS algorithm returns complex amplitudes, which are internally converted into probabilities using the standard quantum operation: :math:`a \rightarrow |a|^2`.

The output vector contains real numbers and has a size of combination(m + n - 1, n) in the Fock space (many photons can arrive at the same mode), combination(m, n) in the unbunched space, and :math:`2^{(m/2)}` in the dual rail computation space where n is the number of photons and m the number of modes. See :doc:`./computation_space` for more details on computation spaces.

.. code-block:: python

    builder = CircuitBuilder(n_modes=5)
    # Add whatever to your builder

    quantum_layer = QuantumLayer(.
        input_size=4,
        builder=builder,
        n_photons=2,
        measurement_strategy=MeasurementStrategy.probs(),  # Optional because this is its default value
    )

Characteristics:

- Output shape matches the number of Fock states generated by the experiment.
- Values are real, non-negative, and sum to one.
- Ideal to exploit nearly all the quantum layer expressivity.

Return type (forward call):

- return_object=False: ``torch.Tensor``
- return_object=True: ``merlin.ProbabilityDistribution``

Extensions:

- Add ``torch.nn.Linear`` or any classical layer after the quantum layer to map the probabilities to logits/regression targets.
- Combine with :class:`~merlin.utils.grouping.LexGrouping` or :class:`~merlin.utils.grouping.ModGrouping` when you need fewer output features and don't want to increase the number of parameters.

MODE_EXPECTATIONS
----------------

The output vector is real and has a size equal to the number of modes. Each element represents the expected number of photons in the corresponding mode. In the unbunched computation space, the vector instead provides the probability of detecting at least one photon in each respective mode.

.. code-block:: python

    builder = CircuitBuilder(n_modes=5)
    # Add whatever to your builder

    quantum_layer = QuantumLayer(.
        input_size=4,
        builder=builder,
        n_photons=2,
        measurement_strategy=MeasurementStrategy.mode_expectations(),
    )

Use mode_expectations if you only need an m-sized vector that summarizes the quantum layer output by providing expectations values.

Key properties:

- ``ComputationSpace.FOCK`` returns the expected photon count per mode.
- ``ComputationSpace.UNBUNCHED`` reports occupancy probabilities per mode.
- Output size always equals the number of modes.

Return type (forward call):

- return_object=False: ``torch.Tensor``
- return_object=True: ``torch.Tensor`

AMPLITUDES
---------------

Returns the complex amplitudes directly. The output size is the same as with MeasurementStrategy.probs(), which is dependent on the computation space. This strategy is only meaningful in 
simulation, because amplitudes cannot be measured or obtained on hardware. Indeed,
returning amplitudes is physically non-realizable and should mainly be used for 
connecting two quantum layers (e.g. when the second one uses amplitude encoding) 
or for analytical purposes when studying quantum systems.

.. code-block:: python

    builder = CircuitBuilder(n_modes=5)
    # Add whatever to your builder

    quantum_layer = QuantumLayer(.
        input_size=4,
        builder=builder,
        n_photons=2,
        measurement_strategy=MeasurementStrategy.amplitudes(),
    )

Use this strategy for debugging, algorithmic research, or when a classical routine manipulates complex amplitudes directly. The output is a complex tensor normalised to unit norm.

Return type (forward call):

- return_object=False: ``torch.Tensor``
- return_object=True: ``merlin.StateVector`

.. note::
  Adding photon loss or detectors corresponds to performing a measurement of the quantum state, 
  which collapses it and is therefore incompatible with amplitude retrieval. Thus,
  this measurement strategy **requires that no noise model or detectors are defined**.

PARTIAL MEASUREMENT
---------------

Partial measurement is a specific kind of measurement strategy. With this measurement strategy, only some modes are measured while amplitudes are preserved on the other modes. For more details, checkout the :doc:`/quantum_expert_area/partial_measurement` page.

Photon Loss-aware & Detector-aware execution
============================================

When a :class:`perceval.Experiment` is provided, the :class:`~merlin.algorithms.layer.QuantumLayer`
derives a photon loss transform and a detector transform that remaps raw Fock-state probabilities to the classical
outcomes defined by the experiment. The photon loss mapping is applied first. Then, the detector mapping is applied. Lastly, the
measurement strategy converts the distribution into classical features. As a consequence:

* ``MeasurementStrategy.probs()`` and ``MeasurementStrategy.mode_expectations()`` transparently work with any noise model and detector setup.
* ``MeasurementStrategy.amplitudes()`` requires direct access to the complex amplitudes and therefore **cannot** be used when a noise model or custom detectors are defined (the layer will raise a ``RuntimeError``).

Computation space and grouping
============================================
The measurement strategy is now a first class object that contains all of the informations of the output of the layer for SLOS optimizations.
The MeasurementStrategy now gets two arguments in input.

* ``computation_space``: A ``ComputationSpace`` object that defines the output computation space.

  It will be the only way to define the computation space as the ``no_bunching`` flag is deprecated.
  
  .. warning:: *Deprecated since version 0.3:*
   The use of the ``no_bunching`` flag  is deprecated and is removed since version 0.3.0.
   Use the ``computation_space`` flag inside ``measurement_strategy`` instead. See :doc:`/user_guide/migration_guide`.


* ``grouping``: The grouping strategy to use between ``LexGrouping`` and ``ModGrouping``. By default, no grouping is applied.


Migrating from OutputMappingStrategy (legacy)
============================================

Earlier releases exposed :class:`~merlin.algorithms.layer.QuantumLayer` through
``OutputMappingStrategy``. Newc code should rely on :class:`~merlin.measurement.strategies.MeasurementStrategy`
instead. The mapping is:

- ``OutputMappingStrategy.NONE`` → ``MeasurementStrategy.probs()``
- ``OutputMappingStrategy.LINEAR`` → ``MeasurementStrategy.probs()`` followed by a ``torch.nn.Linear`` layer
- ``OutputMappingStrategy.LEXGROUPING`` or ``GROUPING`` → ``MeasurementStrategy.probs()`` + :class:`~merlin.utils.grouping.LexGrouping`
- ``OutputMappingStrategy.MODGROUPING`` → ``MeasurementStrategy.probs()`` + :class:`~merlin.utils.grouping.ModGrouping`

Selection Cheat Sheet
=====================

.. list-table::
   :header-rows: 1
   :widths: 25 35 40

   * - Scenario
     - Recommended Pipeline
     - Notes
   * - Classification / Regression
     - ``PROBABILITIES`` + classical linear-probing head
     - Obtain logits or arbitrary ranges with ``nn.Linear`` or deeper networks.
   * - Structured probability outputs
     - ``PROBABILITIES`` + ``LexGrouping`` / ``ModGrouping``
     - Preserves probability mass while reducing dimensionality.
   * - Hardware-friendly analytics
     - ``MODE_EXPECTATIONS`` (``ComputationSpace.UNBUNCHED`` to taste)
     - Outputs one feature per mode, easy to interpret.
   * - Algorithm debugging
     - ``AMPLITUDES``
     - Complex amplitudes, simulation only.

Next steps
==========

- Experiment with the different measurement strategies.
- Explore the two external grouping methods :class:`~merlin.utils.grouping.LexGrouping` and :class:`~merlin.utils.grouping.ModGrouping`.
- Take a look at the :doc:`./angle_amplitude_encoding` documentation to learn about data encoding using MerLin.