merlin.core.partial_measurement
PartialMeasurementBranch
- class merlin.core.partial_measurement.PartialMeasurementBranch(outcome, probability, amplitudes)
Bases:
objectSingle branch of a partial measurement for a specific measured-mode outcome.
- Parameters:
outcome (tuple[int, ...]) – Outcome restricted to measured modes, in detector order.
probability (torch.Tensor) – Per-batch probability for this outcome.
amplitudes (merlin.core.state_vector.StateVector) – Conditional state vector on unmeasured modes.
PartialMeasurement
- class merlin.core.partial_measurement.PartialMeasurement(branches, measured_modes, unmeasured_modes, grouping=None)
Bases:
objectCollection of partial-measurement branches and mode metadata.
- Parameters:
branches (tuple[PartialMeasurementBranch, ...]) – Branches ordered lexicographically by outcome.
measured_modes (tuple[int, ...]) – Indices of measured modes in the full system.
unmeasured_modes (tuple[int, ...]) – Indices of unmeasured modes in the full system.
grouping (Callable[[torch.Tensor], torch.Tensor] | None) – Optional callable used to group branch probabilities.
- Raises:
ValueError – If
measured_modesandunmeasured_modesare not disjoint, do not cover exactlyrange(n_modes), contain duplicate or invalid indices, or do not match the branch outcome/state metadata.
- verify_branches_order()
Verify that branches are ordered lexicographically by their outcomes.
- Return type:
- property probability_tensor_shape: tuple[int, int]
Return the expected (batch, n_outcomes) shape for the probability tensor.
- property tensor: Tensor
Returns branch probabilities as a stacked tensor. This property assumes that all branches are ordered lexicographically by their outcomes so the stacking of probabilities follows the same order.
- Returns:
Tensor of shape
(batch, n_branches). If a grouping is set, the returned tensor has shape(batch, grouping_output_size).- Return type:
- property amplitudes
Conditional amplitudes for each branch.
- set_grouping(grouping)
Set the grouping used to aggregate probabilities.
- Parameters:
grouping (Callable[[torch.Tensor], torch.Tensor] | None) – Callable used to group branch probabilities.
- Raises:
TypeError – If
groupingis not callable.- Return type:
- detach()
Return a detached
PartialMeasurementwith detached branches.- Returns:
Detached partial measurement with probability and amplitude tensors detached from the autograd graph.
- Return type:
- static from_detector_transform_output(detector_output, *, grouping=None)
Branch-based PartialMeasurement wrapper from DetectorTransform(partial_measurement=True) output.
- Parameters:
detector_output (
merlin.core.partial_measurement.DetectorTransformOutput) – Output ofDetectorTransform(partial_measurement=True).grouping (Callable[[torch.Tensor], torch.Tensor] | None) – Optional callable used to group branch probabilities.
- Returns:
Branch-based partial-measurement wrapper.
- Return type:
Notes and Examples
Basic Structure
A PartialMeasurement represents the outcome of measuring a subset of modes in a quantum system.
It consists of a collection of PartialMeasurementBranch objects, each corresponding to a specific
measurement outcome on the measured modes, along with the conditional quantum state on the unmeasured modes.
When constructing one manually, measured_modes and unmeasured_modes must be disjoint and together cover
the full contiguous mode range. Branch outcomes must have one entry per measured mode, and each conditional
state vector must use one mode per unmeasured mode.
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 PartialMeasurement class provides convenient access to the measurement outcomes and their associated probabilities.
# 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 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).
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
PartialMeasurement objects are typically created from the output of a detector transformation in the measurement pipeline.
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.
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])
)