merlin.algorithms.feed_forward module

class merlin.algorithms.feed_forward.ACircuit(m, name=None)

Bases: AParametrizedComponent, ABC

Abstract linear optics circuit class. A circuit is defined by a dimension m, and by parameters. Parameters can be fixed (value) or variables.

property U

get the symbolic unitary matrix

add(port_range, component, merge=None)
Return type:

Circuit

compute_unitary(assign=None, use_symbolic=False, use_polarization=None)

Compute the unitary matrix corresponding to the current circuit

Parameters:
  • use_polarization (Optional[bool]) –

  • assign (dict) – assign values to some parameters

  • use_symbolic (bool) – if the matrix should use symbolic calculation

Return type:

Matrix

Returns:

the unitary matrix, will be a MatrixS if symbolic, or a ~`MatrixN` if not.

definition()
depths()
abstract describe()
Return type:

str

identify(unitary_matrix, phases, precision=None, max_try=10, allow_error=False)

Identify an instance of the current circuit (should be parameterized) such as \(Q.C=U.P\) where \(Q\) and \(P\) are single-mode phase shifts (resp. \([q1, q2, ..., qn]\), and \([p1, p2, ...,pn]\)). This is solved through \(n^2\) equations: \(q_i * C_{i,j}(x,y, ...) = UP_{i,j} * p_j\)

Parameters:
  • unitary_matrix – the matrix to identify

  • phases – phase shift parameters

  • max_try – the resolution is using parameter search starting on a random points, it might fail, this parameter sets the maximal number of times to try

Return type:

None

inverse(v, h)
match(pattern, pos=None, pattern_pos=None, match=None, actual_pos=0, actual_pattern_pos=0)
Return type:

Optional[Match]

ncomponents()
property requires_polarization
transfer_from(c, force=False)

transfer parameters from a Circuit to another - should be the same circuit

class merlin.algorithms.feed_forward.BranchState(amplitudes, weight, remaining_n, measurement_key, basis_keys)

Bases: object

amplitudes: Tensor
basis_keys: tuple[tuple[int, ...], ...]
measurement_key: tuple[Optional[int], ...]
remaining_n: int
weight: Tensor
enum merlin.algorithms.feed_forward.ComputationSpace(value)

Bases: str, Enum

Enumeration of supported computational subspaces.

Member Type:

str

Valid values are as follows:

FOCK = <ComputationSpace.FOCK: 'fock'>
UNBUNCHED = <ComputationSpace.UNBUNCHED: 'unbunched'>
DUAL_RAIL = <ComputationSpace.DUAL_RAIL: 'dual_rail'>

The Enum and its members also have the following methods:

classmethod default(*, no_bunching)

Derive the default computation space from the legacy no_bunching flag.

Return type:

ComputationSpace

classmethod coerce(value)

Normalize user-provided values (enum instances or case-insensitive strings).

Return type:

ComputationSpace

class merlin.algorithms.feed_forward.DetectorTransform(simulation_keys, detectors, *, dtype=None, device=None, partial_measurement=False)

Bases: Module

Linear map applying per-mode detector rules to a Fock probability vector.

Args:
simulation_keys: Iterable describing the raw Fock states produced by the

simulator (as tuples or lists of integers).

detectors: One detector per optical mode. Each detector must expose the

detect() method from perceval.Detector.

dtype: Optional torch dtype for the transform matrix. Defaults to

torch.float32.

device: Optional device used to stage the transform matrix. partial_measurement: When True, only the modes whose detector entry is

not None are measured. The transform then operates on complex amplitudes and returns per-outcome dictionaries (see forward()).

forward(tensor)

Apply the detector transform.

Return type:

Tensor | list[dict[tuple[int, ...], list[tuple[Tensor, Tensor]]]]

Args:
tensor: Probability distribution (complete mode) or amplitudes

(partial measurement). The last dimension must match the simulator basis.

Returns:
  • Complete mode: real probability tensor expressed in the detector basis.

  • Partial mode: list indexed by remaining photon count. Each entry is a dictionary whose keys are full-length mode tuples (unmeasured modes set to None) and whose values are lists of (probability, normalized remaining-mode amplitudes) pairs – one per perfect measurement branch.

property is_identity: bool

Whether the transform reduces to the identity (ideal PNR detectors).

property output_keys: list[tuple[int, ...]]

Return the classical detection outcome keys.

property output_size: int

Number of classical outcomes produced by the detectors.

property partial_measurement: bool

Return True when the transform runs in partial measurement mode.

remaining_basis(remaining_n=None)

Return the ordered Fock-state basis for the unmeasured modes.

Return type:

list[tuple[int, ...]]

Args:
remaining_n: Optional photon count used to select a specific block.

When omitted, the method returns the concatenation of every remaining-mode basis enumerated during detector initialisation.

Returns:

List of tuples describing the photon distribution over the unmeasured modes.

row(index, *, dtype=None, device=None)

Return a single detector transform row as a dense tensor.

Return type:

Tensor

class merlin.algorithms.feed_forward.FFCircuitProvider(m, offset, default_circuit, name=None)

Bases: AFFConfigurator

DEFAULT_NAME = 'FFC'

For any measurement, FFCircuitProvider will return a circuit or a processor, picked from known mapping of configurations. Each configuration links a measurement to a circuit or processor. If a measurement is received and was not set in the mapping, a mandatory default circuit or processor is returned.

Parameters:
  • m – The number of classical modes that are detected (after a detector)

  • offset – The distance between the configurator and the first mode of the implemented circuits. For positive values, it is the number of empty modes between the configurator and the configured circuit below. For negative values, it is the same but the circuit is located above the configurator (the number of empty modes is abs(offset) - 1, so an offset of -1 means that there is no empty modes between the configurator and the circuit). All circuits are considered to have the size of the biggest possible circuit in this configurator.

  • default_circuit – The circuit to be used if the measured state does not befall into one of the declared cases

add_configuration(state, circuit)
Return type:

FFCircuitProvider

property circuit_map
circuit_template()

Return a fitting representation of the controlled circuit or processor.

Return type:

ACircuit

configure(measured_state)

Gives the circuit or processor that must be configured given the measured state

Parameters:

measured_state (FockState) – The state of size self.m that corresponds to the measurements of the modes on which the configurator is located.

Return type:

ACircuit

Returns:

The processor or circuit that must be set

reset_map()
class merlin.algorithms.feed_forward.FFStage(unitary, active_modes, measured_modes, detectors, provider)

Bases: object

active_modes: tuple[int, ...]
detectors: dict[int, Optional[Detector]]
measured_modes: tuple[int, ...]
provider: Optional[FFCircuitProvider]
unitary: Circuit
class merlin.algorithms.feed_forward.FeedForwardBlock(experiment, *, input_state=None, trainable_parameters=None, input_parameters=None, computation_space=ComputationSpace.FOCK, measurement_strategy=MeasurementStrategy.PROBABILITIES, device=None, dtype=None)

Bases: Module

Feed-forward photonic block constructed directly from a Perceval experiment.

The block introspects the provided pcvl.Experiment, splits it into unitary / detector / FFCircuitProvider stages and turns each segment into one or more QuantumLayer instances. At run time the block executes every stage, branching on every partial measurement outcome and accumulating the classical probability for each branch.

Parameters

experiment:

Perceval experiment containing the full feed-forward definition. The current implementation requires noise-free experiments (NoiseModel() or None).

input_state:

Initial quantum state. May be provided as a Fock occupation list, pcvl.BasicState, pcvl.StateVector, or a tensor whose components represent amplitudes in the experiment Fock basis (the tensor is only required for amplitude-encoding inputs).

trainable_parameters:

Optional list of Perceval parameter prefixes that should remain learnable across all stages.

input_parameters:

Perceval parameter prefixes that receive classical inputs. They are consumed by the first stage only; once the first detection happens all branches switch to amplitude encoding and the classical tensor is ignored.

computation_space:

Currently restricted to FOCK.

measurement_strategy:

Controls how classical outputs are produced:

  • MeasurementStrategy.PROBABILITIES (default) returns a tensor of shape (batch_size, num_output_keys) whose columns match the fully specified Fock states stored in :pyattr:`output_keys`.

  • MeasurementStrategy.MODE_EXPECTATIONS collapses every branch into a single tensor of shape (batch_size, num_modes) that contains the per-mode photon expectations aggregated across all measurement keys. The :pyattr:`output_keys` attribute is retained for metadata while :pyattr:`output_state_sizes` reports num_modes for every key.

  • MeasurementStrategy.AMPLITUDES yields a list of tuples (measurement_key, branch_probability, remaining_photons, amplitudes) so callers can reason about the mixed state left by each branch.

describe()

Return a multi-line description of the feed-forward stages.

The summary lists, in order, the global modes that remain active at each step, the subset of measured modes, and the type of feed-forward configurator attached to the stage. It is primarily intended for debugging or for logging experiment structure.

Return type:

str

forward(x=None)

Execute the feed-forward experiment.

Return type:

Tensor | list[tuple[tuple[int, ...], Tensor, int, Tensor]]

Parameters

x:

Classical feature tensor. Only the first stage consumes classical inputs; subsequent stages operate purely in amplitude-encoding mode. When the experiment does not expose classical inputs this argument may be omitted (or None), in which case an empty tensor is automatically supplied.

Returns

torch.Tensor | list

PROBABILITIES returns a tensor of shape (batch_size, len(output_keys)) aligned with the fully specified Fock states in :pyattr:`output_keys`. MODE_EXPECTATIONS produces a tensor of shape (batch_size, total_modes) where the columns already encode the per-mode expectations aggregated across all measurement keys (:pyattr:`output_state_sizes` stores total_modes for every key). AMPLITUDES yields a list of tuples (measurement_key, branch_probability, remaining_photons, amplitudes) describing every branch of the resulting mixed state.

property output_keys: list[tuple[int, ...]]

Return the measurement keys associated with the most recent classical forward pass.

The list is populated after forward() completes. For the PROBABILITIES strategy the list lines up with the tensor columns. For MODE_EXPECTATIONS it is retained for reference even though the returned tensor already aggregates all measurement outcomes. Calling the property before running the block raises RuntimeError.

property output_state_sizes: dict[tuple[int, ...], int]

Return the number of remaining Fock states represented by each entry in output_keys.

Only available when measurement_strategy is PROBABILITIES or MODE_EXPECTATIONS. For PROBABILITIES the value is always 1 because each key now denotes a fully specified Fock state, while for MODE_EXPECTATIONS it equals the total number of modes contributing to the expectation vector.

enum merlin.algorithms.feed_forward.MeasurementStrategy(value)

Bases: Enum

Strategy for measuring quantum states or counts and possibly apply mapping to classical outputs.

Valid values are as follows:

PROBABILITIES = <MeasurementStrategy.PROBABILITIES: 'probabilities'>
MODE_EXPECTATIONS = <MeasurementStrategy.MODE_EXPECTATIONS: 'mode_expectations'>
AMPLITUDES = <MeasurementStrategy.AMPLITUDES: 'amplitudes'>
class merlin.algorithms.feed_forward.NoiseModel(brightness=None, indistinguishability=None, g2=None, g2_distinguishable=None, transmittance=None, phase_imprecision=None, phase_error=None)

Bases: object

The NoiseModel class contains all noise parameters which are supported by Perceval. Default value of each parameter means “no noise”, so a NoiseModel constructed with all default parameters leads to a perfect simulation.

Parameters:
  • brightness (float) – first lens brightness of a quantum dot

  • indistinguishability (float) – chance two photons are indistinguishable

  • g2 (float) – g²(0) - second order intensity autocorrelation at zero time delay. This parameter is correlated with how often two photons are emitted by the source instead of a single one.

  • g2_distinguishable (bool) – g2-generated photons indistinguishability

  • transmittance (float) – system-wide transmittance (warning, can interfere with the brightness parameter)

  • phase_imprecision (float) – maximum precision of the phase shifters (0 means infinite precision)

  • phase_error (float) – maximum random noise on the phase shifters (in radian)

set_value(param_name, value)
class merlin.algorithms.feed_forward.OutputMapper

Bases: object

Handles mapping quantum state amplitudes or probabilities to classical outputs.

This class provides factory methods for creating different types of output mappers that convert quantum state amplitudes or probabilities to classical outputs.

static create_mapping(strategy, computation_space=ComputationSpace.FOCK, keys=None)

Create an output mapping based on the specified strategy.

Args:

strategy: The measurement mapping strategy to use no_bunching: (Only used for ModeExpectations measurement strategy) If True (default), the per-mode probability of finding at least one photon is returned.

Otherwise, it is the per-mode expected number of photons that is returned.

keys: (Only used for ModeExpectations measurement strategy) List of tuples that represent the possible quantum Fock states.

For example, keys = [(0,1,0,2), (1,0,1,0), …]

Returns:

A PyTorch module that maps the per state amplitudes or probabilities to the desired format.

Raises:

ValueError: If strategy is unknown

class merlin.algorithms.feed_forward.QuantumLayer(input_size=None, builder=None, circuit=None, experiment=None, input_state=None, n_photons=None, trainable_parameters=None, input_parameters=None, amplitude_encoding=False, computation_space=None, measurement_strategy=MeasurementStrategy.PROBABILITIES, device=None, dtype=None, **kwargs)

Bases: Module

Enhanced Quantum Neural Network Layer with factory-based architecture.

This layer can be created either from a CircuitBuilder instance or a pre-compiled pcvl.Circuit.

Merlin integration (optimal design):
  • merlin_leaf = True marks this module as an indivisible execution leaf.

  • force_simulation (bool) defaults to False. When True, the layer MUST run locally.

  • supports_offload() reports whether remote offload is possible (via export_config()).

  • should_offload(processor, shots) encapsulates the current offload policy:

    return supports_offload() and not force_local

angle_encoding_specs: dict[str, dict[str, Any]]
as_simulation()

Temporarily force local simulation within the context.

experiment: pcvl.Experiment | None
export_config()

Export a standalone configuration for remote execution.

Return type:

dict

property force_local: bool

When True, this layer must run locally (Merlin will not offload it).

forward(*input_parameters, shots=None, sampling_method=None, simultaneous_processes=None)

Forward pass through the quantum layer.

When self.amplitude_encoding is True the first positional argument must contain the amplitude-encoded input state (either [num_states] or [batch_size, num_states]). Remaining positional arguments are treated as classical inputs and processed via the standard encoding pipeline.

Return type:

tuple[Tensor, Tensor] | Tensor

Sampling is controlled by:
  • shots (int): number of samples; if 0 or None, return exact amplitudes/probabilities.

  • sampling_method (str): e.g. “multinomial”.

get_experiment()
Return type:

Optional[Experiment]

property has_custom_detectors: bool
input_parameters: list[str]
merlin_leaf: bool = True
noise_model: Any | None
property output_keys

Return the Fock basis associated with the layer outputs.

property output_size: int
prepare_parameters(input_parameters)

Prepare parameter list for circuit evaluation.

Return type:

list[Tensor]

set_force_simulation(value)
Return type:

None

set_input_state(input_state)
set_sampling_config(shots=None, method=None)

Deprecated: sampling configuration must be provided at call time in forward.

should_offload(_processor=None, _shots=None)

Return True if this layer should be offloaded under current policy.

Return type:

bool

classmethod simple(input_size, n_params=90, output_size=None, device=None, dtype=None, no_bunching=True, **kwargs)

Create a ready-to-train layer with a 10-mode, 5-photon architecture.

The circuit is assembled via CircuitBuilder with the following layout:

  1. A fully trainable entangling layer acting on all modes;

  2. A full input encoding layer spanning all encoded features;

  3. A non-trainable entangling layer that redistributes encoded information;

  4. Optional trainable Mach-Zehnder blocks (two parameters each) to reach the requested n_params budget;

  5. A final entangling layer prior to measurement.

Args:

input_size: Size of the classical input vector. n_params: Number of trainable parameters to allocate across the additional MZI blocks. Values

below the default entangling budget trigger a warning; values above it must differ by an even amount because each added MZI exposes two parameters.

output_size: Optional classical output width. device: Optional target device for tensors. dtype: Optional tensor dtype. no_bunching: Whether to restrict to states without photon bunching.

Returns:

QuantumLayer configured with the described architecture.

supports_offload()

Return True if this layer is technically offloadable.

Return type:

bool

to(*args, **kwargs)

Move and/or cast the parameters and buffers.

This can be called as

to(device=None, dtype=None, non_blocking=False)
to(dtype, non_blocking=False)
to(tensor, non_blocking=False)
to(memory_format=torch.channels_last)

Its signature is similar to torch.Tensor.to(), but only accepts floating point or complex dtypes. In addition, this method will only cast the floating point or complex parameters and buffers to dtype (if given). The integral parameters and buffers will be moved device, if that is given, but with dtypes unchanged. When non_blocking is set, it tries to convert/move asynchronously with respect to the host if possible, e.g., moving CPU Tensors with pinned memory to CUDA devices.

See below for examples.

Note

This method modifies the module in-place.

Args:
device (torch.device): the desired device of the parameters

and buffers in this module

dtype (torch.dtype): the desired floating point or complex dtype of

the parameters and buffers in this module

tensor (torch.Tensor): Tensor whose dtype and device are the desired

dtype and device for all parameters and buffers in this module

memory_format (torch.memory_format): the desired memory

format for 4D parameters and buffers in this module (keyword only argument)

Returns:

Module: self

Examples:

>>> # xdoctest: +IGNORE_WANT("non-deterministic")
>>> linear = nn.Linear(2, 2)
>>> linear.weight
Parameter containing:
tensor([[ 0.1913, -0.3420],
        [-0.5113, -0.2325]])
>>> linear.to(torch.double)
Linear(in_features=2, out_features=2, bias=True)
>>> linear.weight
Parameter containing:
tensor([[ 0.1913, -0.3420],
        [-0.5113, -0.2325]], dtype=torch.float64)
>>> # xdoctest: +REQUIRES(env:TORCH_DOCTEST_CUDA1)
>>> gpu1 = torch.device("cuda:1")
>>> linear.to(gpu1, dtype=torch.half, non_blocking=True)
Linear(in_features=2, out_features=2, bias=True)
>>> linear.weight
Parameter containing:
tensor([[ 0.1914, -0.3420],
        [-0.5112, -0.2324]], dtype=torch.float16, device='cuda:1')
>>> cpu = torch.device("cpu")
>>> linear.to(cpu)
Linear(in_features=2, out_features=2, bias=True)
>>> linear.weight
Parameter containing:
tensor([[ 0.1914, -0.3420],
        [-0.5112, -0.2324]], dtype=torch.float16)

>>> linear = nn.Linear(2, 2, bias=None).to(torch.cdouble)
>>> linear.weight
Parameter containing:
tensor([[ 0.3741+0.j,  0.2382+0.j],
        [ 0.5593+0.j, -0.4443+0.j]], dtype=torch.complex128)
>>> linear(torch.ones(3, 2, dtype=torch.cdouble))
tensor([[0.6122+0.j, 0.1150+0.j],
        [0.6122+0.j, 0.1150+0.j],
        [0.6122+0.j, 0.1150+0.j]], dtype=torch.complex128)
trainable_parameters: list[str]
training: bool
class merlin.algorithms.feed_forward.Sequence

Bases: Reversible, Collection

All the operations on a read-only sequence.

Concrete subclasses must override __new__ or __init__, __getitem__, and __len__.

count(value) integer -- return number of occurrences of value
index(value[, start[, stop]]) integer -- return first index of value.

Raises ValueError if the value is not present.

Supporting start and stop arguments is optional, but recommended.

class merlin.algorithms.feed_forward.StageRuntime(circuit, pre_layer, detector_transform, conditional_circuits, conditional_default_key, measured_modes, global_measured_modes, active_modes, detectors, provider, pre_layers=<factory>, detector_cache=<factory>, conditional_layer_cache=<factory>, trainable_parameters=None, initial_amplitudes=None, classical_input_size=0)

Bases: object

active_modes: tuple[int, ...]
circuit: Circuit
classical_input_size: int = 0
conditional_circuits: dict[tuple[int, ...], Circuit]
conditional_default_key: Optional[tuple[int, ...]]
conditional_layer_cache: dict[tuple[tuple[int, ...], int], QuantumLayer]
detector_cache: dict[int, DetectorTransform]
detector_transform: Optional[DetectorTransform]
detectors: dict[int, Optional[Detector]]
global_measured_modes: tuple[int, ...]
initial_amplitudes: Optional[Tensor] = None
measured_modes: tuple[int, ...]
pre_layer: Optional[QuantumLayer]
pre_layers: dict[int, QuantumLayer]
provider: Optional[FFCircuitProvider]
trainable_parameters: Optional[list[str]] = None
merlin.algorithms.feed_forward.dataclass(cls=None, /, *, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False, weakref_slot=False)

Add dunder methods based on the fields defined in the class.

Examines PEP 526 __annotations__ to determine fields.

If init is true, an __init__() method is added to the class. If repr is true, a __repr__() method is added. If order is true, rich comparison dunder methods are added. If unsafe_hash is true, a __hash__() method is added. If frozen is true, fields may not be assigned to after instance creation. If match_args is true, the __match_args__ tuple is added. If kw_only is true, then by default all fields are keyword-only. If slots is true, a new class with a __slots__ attribute is returned.

merlin.algorithms.feed_forward.field(*, default=<dataclasses._MISSING_TYPE object>, default_factory=<dataclasses._MISSING_TYPE object>, init=True, repr=True, hash=None, compare=True, metadata=None, kw_only=<dataclasses._MISSING_TYPE object>)

Return an object to identify dataclass fields.

default is the default value of the field. default_factory is a 0-argument function called to initialize a field’s value. If init is true, the field will be a parameter to the class’s __init__() function. If repr is true, the field will be included in the object’s repr(). If hash is true, the field will be included in the object’s hash(). If compare is true, the field will be used in comparison functions. metadata, if specified, must be a mapping which is stored but not otherwise examined by dataclass. If kw_only is true, the field will become a keyword-only parameter to __init__().

It is an error to specify both default and default_factory.

merlin.algorithms.feed_forward.pcvl_to_tensor(state_vector, computation_space=ComputationSpace.FOCK, dtype=torch.complex64, device=device(type='cpu'))

Convert a Perceval StateVector into a torch Tensor.

Return type:

Tensor

Args:

state_vector: Perceval StateVector. computation_space: Computation space of the state vector following combinadics ordering. dtype: Desired torch dtype of the output Tensor. device: Desired torch device of the output Tensor.

Returns:

Equivalent torch Tensor.

Raises:
ValueError: If the StateVector includes states with incompatible photon number for the specified computation space,

or non consistent number of photons across the states.