merlin.core.partial_measurement
===============================

.. currentmodule:: merlin.core.partial_measurement

PartialMeasurementBranch
------------------------

.. autoclass:: PartialMeasurementBranch
   :members:
   :member-order: bysource
   :show-inheritance:

PartialMeasurement
------------------

.. autoclass:: PartialMeasurement
   :members:
   :member-order: bysource
   :show-inheritance:

Notes and Examples
------------------

Basic Structure
^^^^^^^^^^^^^^^

A :class:`PartialMeasurement` represents the outcome of measuring a subset of modes in a quantum system.
It consists of a collection of :class:`PartialMeasurementBranch` objects, each corresponding to a specific
measurement outcome on the measured modes, along with the conditional quantum state on the unmeasured modes.

.. code-block:: python

   from merlin.core.partial_measurement import PartialMeasurement, PartialMeasurementBranch
   from merlin.core.state_vector import StateVector
   import torch

   # Create branches manually
   outcome_1 = (1, 0)  # measurement result on measured modes 0 and 1
   prob_1 = torch.tensor([0.5])  # probability for this outcome
   amps_1 = StateVector.from_basic_state([1, 0], sparse=False)  # conditional state on unmeasured modes
   
   branch_1 = PartialMeasurementBranch(outcome_1, prob_1, amps_1)
   branch_2 = PartialMeasurementBranch((0, 1), torch.tensor([0.5]), StateVector.from_basic_state([0, 1], sparse=False))
   
   # Combine into PartialMeasurement
   pm = PartialMeasurement(
       branches=(branch_1, branch_2),
       measured_modes=(0, 1),
       unmeasured_modes=(2, 3)
   )

Accessing Measurement Results
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The :class:`PartialMeasurement` class provides convenient access to the measurement outcomes and their associated probabilities.

.. code-block:: python

   # Access properties
   print(f"Measured modes: {pm.measured_modes}")
   print(f"Unmeasured modes: {pm.unmeasured_modes}")
   print(f"Number of branches: {len(pm.branches)}")
   
   # Get probability distribution across all outcomes
   prob_tensor = pm.tensor  # shape: (batch_size, n_branches)
   
   # Get individual outcomes and amplitudes
   for outcome, branch in zip(pm.outcomes, pm.branches):
       print(f"Outcome {outcome}: probability={branch.probability}, amplitude shape={branch.amplitudes.shape}")

Working with Grouped Probabilities
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The :class:`PartialMeasurement` supports optional grouping of probabilities, which allows you to aggregate outcomes
according to a custom grouping function (e.g., for classical post-processing or symmetry-based grouping).

.. code-block:: python

   from merlin.utils.grouping import ModGrouping
   
   # Define a grouping function
   grouping = ModGrouping(input_size = 4,output_size=2)  # example grouping
   
   # Apply grouping to PartialMeasurement
   pm.set_grouping(grouping)
   
   # Grouped probabilities now have shape (batch_size, output_size) instead of (batch_size, n_branches)
   grouped_probs = pm.probabilities
   print(grouped_probs.shape)  # (batch_size, 2)

Creating from DetectorTransform Output
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

:class:`PartialMeasurement` objects are typically created from the output of a detector transformation in the measurement pipeline.

.. code-block:: python

   from merlin.core.partial_measurement import PartialMeasurement
   
   # When DetectorTransform produces partial measurement output (partial_measurement=True),
   # it returns a structure that can be converted to PartialMeasurement
   detector_output = [...]  # output from DetectorTransform
   
   pm = PartialMeasurement.from_detector_transform_output(
       detector_output,
       grouping=None
   )

Batch Processing
^^^^^^^^^^^^^^^^

Probabilities are stored per-batch in each branch, allowing for efficient handling of batch-processed quantum circuits.

.. code-block:: python

   import torch
   
   # Batch-wise probabilities
   batch_probs = torch.tensor([[0.3, 0.7], [0.6, 0.4]])  # shape: (batch_size=2, n_outcomes=2)
   
   # When creating a branch with batched probabilities
   branch = PartialMeasurementBranch(
       outcome=(1, 0),
       probability=batch_probs[:, 0],  # shape: (batch_size,)
       amplitudes=StateVector.from_basic_state([1, 0])
   )
