Merlin - Photonic Quantum Neural Networks for PyTorch

A comprehensive framework for integrating photonic quantum circuits into PyTorch neural networks with automatic differentiation support.

class merlin.Amplitudes

Bases: Module

Output the Fock state vector (also called amplitudes) directly. This can only be done with a simulator because amplitudes cannot be retrieved from the per state probabilities obtained with a QPU.

forward(x)

Return the fock state vector amplitudes.

Return type:

Tensor

class merlin.AutoDiffProcess(sampling_method='multinomial')

Bases: object

Handles automatic differentiation backend and sampling noise integration.

autodiff_backend(needs_gradient, apply_sampling, shots)

Determine sampling configuration based on gradient requirements.

Return type:

tuple[bool, int]

class merlin.CircuitBuilder(n_modes)

Bases: object

Builder for quantum circuits using a declarative API.

add_angle_encoding(modes=None, name=None, *, scale=1.0, subset_combinations=False, max_order=None)

Convenience method for angle-based input encoding.

Return type:

CircuitBuilder

Args:

modes: Optional list of circuit modes to target. Defaults to all modes. name: Prefix used for generated input parameters. Defaults to "px". scale: Global scaling factor applied before angle mapping. subset_combinations: When True, generate higher-order feature

combinations (up to max_order) similar to the legacy FeatureEncoder.

max_order: Optional cap on the size of feature combinations when

subset_combinations is enabled. None uses all orders.

Returns:

CircuitBuilder: self for fluent chaining.

add_entangling_layer(modes=None, *, trainable=True, model='mzi', name=None, trainable_inner=None, trainable_outer=None)

Add an entangling layer spanning a range of modes.

Return type:

CircuitBuilder

Args:
modes: Optional list describing the span. None targets all modes;

one element targets modes[0] through the final mode; two elements target the inclusive range [modes[0], modes[1]].

trainable: Whether internal phase shifters should be trainable. model: "mzi" or "bell" to select the internal interferometer template. name: Optional prefix used for generated parameter names. trainable_inner: Override for the internal (between-beam splitter) phase shifters. trainable_outer: Override for the output phase shifters at the exit of the interferometer.

Raises:

ValueError: If the provided modes are invalid or span fewer than two modes.

Returns:

CircuitBuilder: self for fluent chaining.

add_rotations(modes=None, *, axis='z', trainable=False, as_input=False, angle=None, value=None, name=None, role=None)

Add one or multiple rotations across the provided modes.

Return type:

CircuitBuilder

Args:

modes: Single mode, list of modes, module group or None (all modes). axis: Axis of rotation for each inserted phase shifter. trainable: Promote the rotations to trainable parameters (legacy flag). as_input: Mark the rotations as input-driven parameters (legacy flag). angle: Optional fixed value for the rotations (alias of value). value: Optional fixed value for the rotations (alias of angle). name: Optional stem used for generated parameter names. role: Explicit ParameterRole taking precedence over other flags.

Returns:

CircuitBuilder: self for fluent chaining.

add_superpositions(targets=None, *, depth=1, theta=0.785398, phi=0.0, trainable=None, trainable_theta=None, trainable_phi=None, modes=None, name=None)

Add one or more superposition (beam splitter) components.

Return type:

CircuitBuilder

Args:
targets: Tuple or list of tuples describing explicit mode pairs. When

omitted, nearest neighbours over modes (or all modes) are used.

depth: Number of sequential passes to apply (>=1). theta: Baseline mixing angle for fixed beam splitters. phi: Baseline relative phase for fixed beam splitters. trainable: Convenience flag to mark both theta and phi trainable. trainable_theta: Whether the mixing angle should be trainable. trainable_phi: Whether the relative phase should be trainable. modes: Optional mode list/module group used when targets is omitted. name: Optional stem used for generated parameter names.

Returns:

CircuitBuilder: self for fluent chaining.

property angle_encoding_specs: dict[str, dict[str, Any]]

Return metadata describing configured angle encodings.

Returns:

Dict[str, Dict[str, Any]]: Mapping from encoding prefix to combination metadata.

build()

Build and return the circuit.

Return type:

Circuit

Returns:

Circuit: Circuit instance populated with components.

classmethod from_circuit(circuit)

Create a builder from an existing circuit.

Return type:

CircuitBuilder

Args:

circuit: Circuit object whose components should seed the builder.

Returns:

CircuitBuilder: A new builder instance wrapping the provided circuit.

property input_parameter_prefixes: list[str]

Expose the order-preserving set of input prefixes.

Returns:

List[str]: Input parameter stems emitted during encoding.

to_pcvl_circuit(pcvl_module=None)

Convert the constructed circuit into a Perceval circuit.

Args:

pcvl_module: Optional Perceval module. If None, attempts to import perceval.

Returns:

A pcvl.Circuit instance mirroring the components tracked by this builder.

Raises:

ImportError: If perceval is not installed and no module is provided.

property trainable_parameter_prefixes: list[str]

Expose the unique set of trainable prefixes in insertion order.

Returns:

List[str]: Trainable parameter stems discovered so far.

class merlin.CircuitConverter(circuit, input_specs=None, dtype=torch.complex64, device=device(type='cpu'))

Bases: object

Convert a parameterized Perceval circuit into a differentiable PyTorch unitary matrix.

This class converts Perceval quantum circuits into PyTorch tensors that can be used in neural network training with automatic differentiation. It supports batch processing for efficient training and handles various quantum components like beam splitters, phase shifters, and unitary operations.

Supported Components:
  • PS (Phase Shifter)

  • BS (Beam Splitter)

  • PERM (Permutation)

  • Unitary (Generic unitary matrix)

  • Barrier (no-op, removed during compilation)

Attributes:

circuit: The Perceval circuit to convert param_mapping: Maps parameter names to tensor indices device: PyTorch device for tensor operations tensor_cdtype: Complex tensor dtype tensor_fdtype: Float tensor dtype

Example:

Basic usage with a single phase shifter:

>>> import torch
>>> import perceval as pcvl
>>> from merlin.pcvl_pytorch.locirc_to_tensor import CircuitConverter
>>>
>>> # Create a simple circuit with one phase shifter
>>> circuit = pcvl.Circuit(1) // pcvl.PS(pcvl.P("phi"))
>>>
>>> # Convert to PyTorch with gradient tracking
>>> converter = CircuitConverter(circuit, input_specs=["phi"])
>>> phi_params = torch.tensor([0.5], requires_grad=True)
>>> unitary = converter.to_tensor(phi_params)
>>> print(unitary.shape)  # torch.Size([1, 1])

Multiple parameters with grouping:

>>> # Circuit with multiple phase shifters
>>> circuit = (pcvl.Circuit(2)
...            // pcvl.PS(pcvl.P("theta1"))
...            // (1, pcvl.PS(pcvl.P("theta2"))))
>>>
>>> converter = CircuitConverter(circuit, input_specs=["theta"])
>>> theta_params = torch.tensor([0.1, 0.2], requires_grad=True)
>>> unitary = converter.to_tensor(theta_params)
>>> print(unitary.shape)  # torch.Size([2, 2])

Batch processing for training:

>>> # Batch of parameter values
>>> batch_params = torch.tensor([[0.1], [0.2], [0.3]], requires_grad=True)
>>> converter = CircuitConverter(circuit, input_specs=["phi"])
>>> batch_unitary = converter.to_tensor(batch_params)
>>> print(batch_unitary.shape)  # torch.Size([3, 1, 1])

Training integration:

>>> # Training loop with beam splitter
>>> circuit = pcvl.Circuit(2) // pcvl.BS.Rx(pcvl.P("theta"))
>>> converter = CircuitConverter(circuit, ["theta"])
>>> theta = torch.tensor([0.5], requires_grad=True)
>>> optimizer = torch.optim.Adam([theta], lr=0.01)
>>>
>>> for step in range(10):
...     optimizer.zero_grad()
...     unitary = converter.to_tensor(theta)
...     loss = some_loss_function(unitary)
...     loss.backward()
...     optimizer.step()
set_dtype(dtype)

Set the tensor data types for float and complex operations.

Args:

dtype: Target dtype (float32/complex64 or float64/complex128)

Raises:

TypeError: If dtype is not supported

to(dtype, device)

Move the converter to a specific device and dtype.

Args:

dtype: Target tensor dtype (float32/complex64 or float64/complex128) device: Target device (string or torch.device)

Returns:

Self for method chaining

Raises:

TypeError: If device type or dtype is not supported

to_tensor(*input_params, batch_size=None)

Convert the parameterized circuit to a PyTorch unitary tensor.

Return type:

Tensor

Args:

*input_params: Variable number of parameter tensors. Each tensor has shape (num_params,) or (batch_size, num_params) corresponding to input_specs order. batch_size: Explicit batch size. If None, inferred from input tensors.

Returns:
Complex unitary tensor of shape (circuit.m, circuit.m) for single samples

or (batch_size, circuit.m, circuit.m) for batched inputs.

Raises:

ValueError: If wrong number of input tensors provided. TypeError: If input_params is not a list or tuple.

class merlin.CircuitGenerator

Bases: object

Utility class for generating quantum photonic circuits.

static generate_circuit(circuit_type, n_modes, n_features, reservoir_mode=False)

Generate a quantum circuit based on specified type.

enum merlin.CircuitType(value)

Bases: Enum

Quantum circuit topology types.

Valid values are as follows:

PARALLEL_COLUMNS = <CircuitType.PARALLEL_COLUMNS: 'parallel_columns'>
SERIES = <CircuitType.SERIES: 'series'>
PARALLEL = <CircuitType.PARALLEL: 'parallel'>
class merlin.Combinadics(scheme, n, m)

Bases: object

Rank/unrank Fock states in descending lexicographic order.

Parameters

schemestr

Enumeration strategy. Supported values are "fock", "unbunched", and "dual_rail".

nint

Number of photons. Must be non-negative.

mint

Number of modes. Must be at least one.

Raises

ValueError

If an unsupported scheme is provided or the parameters violate the constraints of the selected scheme.

compute_space_size()

Return the number of admissible Fock states for this configuration.

Return type:

int

Returns

int

Cardinality of the state space.

enumerate_states()

Return all admissible states in descending lexicographic order.

Return type:

list[tuple[int, ...]]

Returns

list[Tuple[int, …]]

State list matching iter_states().

fock_to_index(counts)

Map a Fock state to its index under the configured scheme.

Return type:

int

Parameters

countsIterable[int]

Photon counts per mode.

Returns

int

Rank of the Fock state in descending lexicographic order.

Raises

ValueError

If counts violates the scheme-specific constraints.

index_to_fock(index)

Map an index to its corresponding Fock state.

Return type:

tuple[int, ...]

Parameters

indexint

Rank to decode.

Returns

Tuple[int, …]

Photon counts per mode.

Raises

ValueError

If index is outside the valid range for the configured scheme.

iter_states()

Yield admissible states in descending lexicographic order.

Return type:

Iterator[tuple[int, ...]]

Returns

Iterator[Tuple[int, …]]

Generator over states matching the configured scheme.

class merlin.ComputationProcess(circuit, input_state, trainable_parameters, input_parameters, n_photons=None, reservoir_mode=False, dtype=torch.float32, device=None, computation_space=None, no_bunching=None, output_map_func=None)

Bases: AbstractComputationProcess

Handles quantum circuit computation and state evolution.

compute(parameters)

Compute quantum output distribution.

Return type:

Tensor

compute_ebs_simultaneously(parameters, simultaneous_processes=1)

Evaluate a single circuit parametrisation against all superposed input states by chunking them in groups and delegating the heavy work to the TorchScript-enabled batch kernel.

The method converts the trainable parameters into a unitary matrix, normalises the input state (if it is not already normalised), filters out components with zero amplitude, and then queries the simulation graph for batches of Fock states. Each batch feeds SLOSComputeGraph.compute_batch(), producing a tensor that contains the amplitudes of all reachable output states for the selected input components. The partial results are accumulated into a preallocated tensor and finally weighted by the complex coefficients of self.input_state to produce the global output amplitudes.

Return type:

Tensor

Args:
parameters (list[torch.Tensor]): Differentiable parameters that

encode the photonic circuit. They are forwarded to self.converter to build the unitary matrix used during the simulation.

simultaneous_processes (int): Maximum number of non-zero input

components that are propagated in a single call to compute_batch. Tuning this value allows trading memory consumption for wall-clock time on GPU.

Returns:

torch.Tensor: The superposed output amplitudes with shape [batch_size, num_output_states] where batch_size corresponds to the number of independent input batches and num_output_states is the size of self.simulation_graph.mapped_keys.

Raises:

TypeError: If self.input_state is not a torch.Tensor. The simulation graph expects tensor inputs, therefore other sequence types (NumPy arrays, lists, etc.) cannot be used here.

Notes:
  • self.input_state is normalised in place to avoid an extra allocation.

  • Zero-amplitude components are skipped to minimise the number of calls to compute_batch.

  • The method is agnostic to the device: tensors remain on the device they already occupy, so callers should ensure parameters and self.input_state live on the same device.

compute_superposition_state(parameters, *, return_keys=False)
Return type:

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

compute_with_keys(parameters)

Compute quantum output distribution and return both keys and probabilities.

configure_computation_space(computation_space=ComputationSpace.UNBUNCHED, *, validate_input=True)

Reconfigure the logical basis according to the desired computation space.

Return type:

None

enum merlin.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.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.FeatureEncoder(feature_count)

Bases: object

Utility class for encoding classical features into quantum circuit parameters.

This class provides methods to encode normalized classical features into parameters that can be used to configure quantum circuits. Different encoding strategies are supported depending on the circuit type.

encode(X_norm, circuit_type, n_modes, bandwidth_coeffs=None, total_shifters=None)

Encode normalized features into quantum circuit parameters.

Return type:

Tensor

Args:

X_norm: Normalized input features of shape (batch_size, num_features) circuit_type: Type of quantum circuit architecture n_modes: Number of modes in the quantum circuit bandwidth_coeffs: Optional bandwidth tuning coefficients for each feature dimension total_shifters: Optional total number of phase shifters (unused in current implementation)

Returns:

Encoded parameters tensor of shape (batch_size, num_parameters)

Raises:

ValueError: If circuit_type is not supported

class merlin.FeatureMap(circuit=None, input_size=None, *, builder=None, experiment=None, input_parameters, trainable_parameters=None, dtype=torch.float32, device=None, encoder=None)

Bases: object

Quantum Feature Map

FeatureMap embeds a datapoint within a quantum circuit and computes the associated unitary for quantum kernel methods.

Args:

circuit: Pre-compiled pcvl.Circuit to encode features. input_size: Dimension of incoming classical data (required). builder: Optional CircuitBuilder to compile into a circuit. experiment: Optional pcvl.Experiment providing both the circuit and detector configuration.

Exactly one of circuit, builder, or experiment must be supplied.

input_parameters: Parameter prefix(es) that host the classical data. dtype: Torch dtype used when constructing the unitary. device: Torch device on which unitaries are evaluated.

compute_unitary(x, *training_parameters)

Generate the circuit unitary after encoding x and applying trainables.

Return type:

Tensor

Args:

x: Single datapoint to embed; accepts scalars, numpy arrays, or tensors. *training_parameters: Optional overriding trainable tensors.

Returns:

Tensor: Complex unitary matrix representing the prepared circuit.

is_datapoint(x)

Determine if x describes one sample or a batch.

Return type:

bool

Args:

x: Candidate input data.

Returns:

bool: True when x corresponds to a single datapoint.

classmethod simple(input_size, n_modes, n_photons=None, *, dtype=torch.float32, device=None, angle_encoding_scale=1.0, trainable=True, trainable_prefix='phi')

Simple factory method to create a FeatureMap with minimal configuration.

Return type:

FeatureMap

Args:

input_size: Classical feature dimension. n_modes: Number of photonic modes used by the helper circuit. n_photons: Optional photon count (defaults to input_size). dtype: Target dtype for internal tensors. device: Optional torch device handle. angle_encoding_scale: Global scaling applied to angle encoding features. trainable: Whether to expose a trainable rotation layer. trainable_prefix: Prefix used for the generated trainable parameter names.

Returns:

FeatureMap: Configured feature-map instance.

class merlin.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.

class merlin.FeedForwardBlockLegacy(input_size, n, m, depth=None, state_injection=False, conditional_modes=None, layers=None, circuit_type=None, device=None)

Bases: Module

Feed-forward quantum neural network for photonic computation.

This class models a conditional feed-forward architecture used in quantum photonic circuits. It connects multiple quantum layers in a branching tree structure — where each branch corresponds to a sequence of photon-detection outcomes on designated conditional modes.

Each node in this feedforward tree represents a QuantumLayer that acts on a quantum state conditioned on measurement results of previous layers.

The recursion continues until a specified depth, allowing the model to simulate complex conditional evolution of quantum systems.

Detector support:

The current feed-forward implementation expects amplitude access for every intermediate layer (MeasurementStrategy.AMPLITUDES) and therefore assumes ideal PNR detectors. Custom detector transforms or Perceval experiments with threshold / hybrid detectors are not yet supported inside this block.

— Args:

input_size (int):

Number of classical input features (used for hybrid quantum-classical computation).

n (int):

Number of photons in the system.

m (int):

Total number of photonic modes.

depth (int, optional):

Maximum depth of feed-forward recursion. Defaults to m - 1 if not specified.

state_injection (bool, optional):

If True, allows re-injecting quantum states at intermediate steps (useful for simulating sources or ancilla modes). Default = False.

conditional_modes (list[int], optional):

List of mode indices on which photon detection is performed. Determines the branching structure. Defaults to [0].

layers (list[QuantumLayer], optional):

Predefined list of quantum layers (if any). If not provided, layers are automatically generated.

circuit_type (str, optional):

Type of quantum circuit architecture used to build each layer. Acts as a “template selector” for circuit structure generation.

define_ff_layer(k, layers)

Replace quantum layers at a specific depth k.

Args:

k (int): Feed-forward layer depth index. layers (list[QuantumLayer]): List of replacement layers.

define_layers(circuit_type)

Define and instantiate all quantum layers for each measurement outcome path.

Each tuple (representing a branch of the feedforward tree) is mapped to a QuantumLayer object. Depending on whether the state injection mode is active, the number of modes/photons and the input size differ.

Args:

circuit_type (str): Template name or circuit architecture type.

Raises:

AssertionError: If total input size does not match after allocation.

forward(x)

Perform the full quantum-classical feedforward computation.

Args:

x (torch.Tensor): Classical input tensor of shape (batch_size, input_size).

Returns:
torch.Tensor: Final output tensor containing probabilities for each

terminal measurement configuration.

generate_possible_tuples()

Generate all possible conditional outcome tuples.

Each tuple represents one possible sequence of photon detection results across all conditional modes up to a given depth. For example, with n_cond = 2 and depth = 3, tuples correspond to binary sequences of length depth * n_cond.

Returns:
list[tuple[int]]:

List of tuples containing binary measurement outcomes (0/1).

get_output_size()

Compute the number of output channels (post-measurement outcomes).

input_size_ff_layer(k)

Return the list of input sizes for all layers at depth k.

iterate_feedforward(current_tuple, remaining_amplitudes, keys, accumulated_prob, intermediary, outputs, depth=0, x=None)

Recursive feedforward traversal of the quantum circuit tree.

At each step:
  1. Evaluate photon detection outcomes (0/1) on conditional modes.

  2. For each possible combination, compute probabilities.

  3. Apply the corresponding quantum layer and recurse deeper.

Args:

current_tuple (tuple[int]): Current measurement sequence path. remaining_amplitudes (torch.Tensor): Quantum amplitudes of current state. keys (list[tuple[int]]): Fock basis keys for amplitudes. accumulated_prob (torch.Tensor or float): Product of probabilities so far. intermediary (dict): Stores intermediate probabilities. outputs (dict): Stores final output probabilities for all branches. depth (int): Current recursion depth. x (torch.Tensor, optional): Classical input features.

property output_keys

Return cached output keys, or compute them via a dummy forward pass.

parameters()

Iterate over all trainable parameters from every quantum layer.

size_ff_layer(k)

Return number of feed-forward branches at layer depth k.

to(device)

Moves the FeedForwardBlock and all its QuantumLayers to the specified device.

Args:

device (str or torch.device): Target device (‘cpu’, ‘cuda’, ‘mps’, etc.)

class merlin.FidelityKernel(feature_map, input_state, *, shots=None, sampling_method='multinomial', no_bunching=False, force_psd=True, device=None, dtype=None)

Bases: Module

Fidelity Quantum Kernel

For a given input Fock state, \(|s \rangle\) and feature map, \(U\), the fidelity quantum kernel estimates the following inner product using SLOS: .. math:

|\langle s | U^{\dagger}(x_2) U(x_1) | s \rangle|^{2}

Transition probabilities are computed in parallel for each pair of datapoints in the input datasets.

Parameters:
  • feature_map (FeatureMap) – Feature map object that encodes a given datapoint within its circuit

  • input_state (list[int]) – Input state into circuit.

  • shots (Optional[int]) – Number of circuit shots. If None, the exact transition probabilities are returned. Default: None.

  • sampling_method (str) – Probability distributions are post- processed with some pseudo-sampling method: ‘multinomial’, ‘binomial’ or ‘gaussian’.

  • no_bunching (bool) – Whether or not to post-select out results with bunching. Default: False.

  • force_psd (bool) – Projects training kernel matrix to closest positive semi-definite. Default: True.

  • device (Optional[device]) – Device on which to perform SLOS

  • dtype (UnionType[str, dtype, None]) – Datatype with which to perform SLOS

Examples

For a given training and test datasets, one can construct the training and test kernel matrices in the following structure: .. code-block:: python

>>> circuit = Circuit(2) // PS(P("X0") // BS() // PS(P("X1") // BS()
>>> feature_map = FeatureMap(circuit, ["X"])
>>>
>>> quantum_kernel = FidelityKernel(
>>>     feature_map,
>>>     input_state=[0, 4],
>>>     no_bunching=False,
>>> )
>>> # Construct the training & test kernel matrices
>>> K_train = quantum_kernel(X_train)
>>> K_test = quantum_kernel(X_test, X_train)

Use with scikit-learn for kernel-based machine learning:. .. code-block:: python

>>> from sklearn import SVC
>>> # For a support vector classification problem
>>> svc = SVC(kernel='precomputed')
>>> svc.fit(K_train, y_train)
>>> y_pred = svc.predict(K_test)
forward(x1, x2=None)

Calculate the quantum kernel for input data x1 and x2. If x1 and x2 are datapoints, a scalar value is returned. For input datasets the kernel matrix is computed.

classmethod simple(input_size, n_modes, n_photons=None, input_state=None, *, shots=0, sampling_method='multinomial', no_bunching=False, force_psd=True, trainable=True, dtype=torch.float32, device=None, angle_encoding_scale=1.0)

Simple factory method to create a FidelityKernel with minimal configuration.

Return type:

FidelityKernel

class merlin.LexGrouping(input_size, output_size)

Bases: Module

Maps tensor to a lexical grouping of its components.

This mapper groups consecutive elements of the input tensor into equal-sized buckets and sums them to produce the output. If the input size is not evenly divisible by the output size, padding is applied.

forward(x)

Map the input tensor to the desired output_size utilizing lexical grouping.

Args:

x: Input tensor of shape (n_batch, input_size) or (input_size,)

Returns:

Grouped tensor of shape (batch_size, output_size) or (output_size,)

enum merlin.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.MerlinProcessor(remote_processor, microbatch_size=32, timeout=3600.0, max_shots_per_call=None, chunk_concurrency=1)

Bases: object

RPC-style processor for quantum execution with:
  • Torch-friendly async interface (Future[Torch.Tensor])

  • Cloud offload of QuantumLayer leaves

  • Batch chunking per quantum leaf with limited concurrency

  • Cancellation (per-future and global)

  • Global timeouts that cancel in-flight jobs

  • Per-call RemoteProcessor pooling (no shared RPC handlers across threads)

  • Descriptive cloud job names (<= 50 chars) for traceability

DEFAULT_MAX_SHOTS: int = 100000
DEFAULT_SHOTS_PER_CALL: int = 10000
cancel_all()

Cancel all in-flight jobs across all futures.

Return type:

None

clear_job_history()
Return type:

None

estimate_required_shots_per_input(layer, input, desired_samples_per_input)

Estimate required shots per input row for a QuantumLayer using the platform’s RemoteProcessor estimator (transmittance, filters, etc.). :rtype: list[int]

  • Accepts a single vector (shape: [D]) or a batch (shape: [B, D]).

  • Returns a list[int] with one entry per input row (0 means ‘not viable’).

NOTE: This does not submit any cloud jobs; it only uses the estimator.

forward(module, input, *, nsample=None, timeout=None)

Synchronous convenience wrapper around forward_async().

Return type:

Tensor

forward_async(module, input, *, nsample=None, timeout=None)
Return type:

Future

get_job_history()
Return type:

list[RemoteJob]

class merlin.ModGrouping(input_size, output_size)

Bases: Module

Maps tensor to a modulo grouping of its components.

This mapper groups elements of the input tensor based on their index modulo the output size. Elements with the same modulo value are summed together to produce the output.

forward(x)

Map the input tensor to the desired output_size utilizing modulo grouping.

Args:

x: Input tensor of shape (n_batch, input_size) or (input_size,)

Returns:

Grouped tensor of shape (batch_size, output_size) or (output_size,)

class merlin.ModeExpectations(computation_space, keys)

Bases: Module

Maps quantum state amplitudes or probabilities to the per mode expected number of photons.

forward(x)

Convert the per state amplitudes to per state probabilities if x are amplitudes. Then, marginalize the per state probability distribution into a per mode expected value.

Return type:

Tensor

Args:

x: Input Fock states amplitudes or probabilities of shape (n_batch, num_states) or (num_states,)

Returns:

Expected value tensor of shape (batch_size, num_modes)

marginalize_per_mode(probability_distribution)

Marginalize Fock state probabilities to get per-mode occupation expected values.

Return type:

Tensor

Args:
probability_distribution (torch.Tensor): Tensor of shape (N, num_keys) with probabilities

for each Fock state, with requires_grad=True

Returns:

torch.Tensor: Shape (N, num_modes) with marginal per mode expected number of photons

class merlin.NKernelAlignment

Bases: _Loss

Negative kernel-target alignment loss function for quantum kernel training.

Within quantum kernel alignment, the goal is to maximize the alignment between the quantum kernel matrix and the ideal target matrix given by \(K^{*} = y y^T\), where \(y \in \{-1, +1\}\) are the target labels.

The negative kernel alignment loss is given as:

\[\text{NKA}(K, K^{*}) = -\frac{\operatorname{Tr}(K K^{*})}{ \sqrt{\operatorname{Tr}(K^2)\operatorname{Tr}(K^{*2})}}\]
forward(input, target)

Define the computation performed at every call.

Should be overridden by all subclasses. :rtype: Tensor

Note

Although the recipe for forward pass needs to be defined within this function, one should call the Module instance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.

class merlin.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.PoolingFeedForwardLegacy(n_modes, n_photons, n_output_modes, pooling_modes=None, no_bunching=True)

Bases: Module

A quantum-inspired pooling module that aggregates amplitude information from an input quantum state representation into a lower-dimensional output space.

This module computes mappings between input and output Fock states (defined by keys_in and keys_out) based on a specified pooling scheme. It then aggregates the amplitudes according to these mappings, normalizing the result to preserve probabilistic consistency.

Parameters

n_modesint

Number of input modes in the quantum circuit.

n_photonsint

Number of photons used in the quantum simulation.

n_output_modesint

Number of output modes after pooling.

pooling_modeslist of list of int, optional

Specifies how input modes are grouped (pooled) into output modes. Each sublist contains the indices of input modes to pool together for one output mode. If None, an even pooling scheme is automatically generated.

no_bunchingbool, default=True

Whether to restrict to Fock states without photon bunching (i.e., no two photons occupy the same mode).

Attributes

match_indicestorch.Tensor

Tensor containing the indices mapping input states to output states.

exclude_indicestorch.Tensor

Tensor containing indices of input states that have no valid mapping to an output state.

keys_outlist

List of output Fock state keys (from Perceval simulation graph).

n_modesint

Number of input modes.

forward(amplitudes)

Forward pass that pools input quantum amplitudes into output modes.

Return type:

Tensor

Parameters

amplitudestorch.Tensor

Input tensor of shape (batch_size, n_input_states) containing the complex amplitudes (or real/imag parts) of quantum states.

Returns

torch.Tensor

Normalized pooled amplitudes of shape (batch_size, n_output_states).

match_tuples(keys_in, keys_out, pooling_modes)

Matches input and output Fock state tuples based on pooling configuration.

For each input Fock state (key_in), the corresponding pooled output state (key_out) is computed by summing the photon counts over each pooling group. Input states that do not correspond to a valid output state are marked for exclusion.

Parameters

keys_inlist

List of Fock state tuples representing input configurations.

keys_outlist

List of Fock state tuples representing output configurations.

pooling_modeslist of list of int

Grouping of input modes into output modes.

Returns

tuple[list[int], list[int]]

A pair (indices, exclude_indices) where: - indices are the matched indices from input to output keys. - exclude_indices are input indices with no valid match.

class merlin.Probabilities

Bases: Module

Maps quantum state amplitudes or probabilities to the complete Fock state probability distribution.

forward(x)

Compute the probability distribution of possible Fock states from amplitudes or probabilities.

Args:

x: Input Fock states amplitudes or probabilities of shape (n_batch, num_states) or (num_states,)

Returns:

Fock states probability tensor of shape (batch_size, num_states) or (num_states,)

class merlin.QuantumBridge(n_photons, n_modes, *, qubit_groups=None, wires_order='little', computation_space=ComputationSpace.UNBUNCHED, normalize=True, device=None, dtype=torch.float32)

Bases: Module

Passive bridge between a qubit statevector (PyTorch tensor) and a Merlin QuantumLayer.

The bridge applies a fixed transition matrix that maps computational-basis amplitudes into the selected photonic computation space (Fock, unbunched, or dual-rail).

Args:

n_photons: Number of logical photons (equals len(qubit_groups)). n_modes: Total number of photonic modes that will be simulated downstream. qubit_groups: Logical grouping of qubits; [2, 1] means one photon is spread

over 2**2 modes and another over 2**1 modes.

wires_order: Endianness used to interpret computational basis strings. computation_space: Target photonic computation space. Accepts a

ComputationSpace enum or a string ("fock", "unbunched", "dual_rail").

normalize: Whether to L2-normalise input statevectors before applying the

transition matrix.

device: Optional device on which to place the output tensor. dtype: Real dtype that determines the corresponding complex dtype for amplitudes.

property basis_occupancies: tuple[tuple[int, ...], ...]

QLOQ occupancies indexed like the computational basis.

forward(psi)

Project a qubit statevector onto the selected photonic computation space.

Return type:

Tensor

Parameters

psitorch.Tensor

Input statevector with shape (2**n_qubits,) or (batch, 2**n_qubits). The tensor must reside on the bridge device.

Returns

torch.Tensor

Amplitudes ordered according to the computation-space enumeration. A 1D input returns a 1D tensor; batched inputs preserve the leading batch dimension.

Raises

TypeError

If psi is not a torch.Tensor.

ValueError

If the tensor shape or device is inconsistent with the bridge configuration.

property n_modes: int
property n_photons: int
property output_basis

Iterator over occupancies enumerating the selected computation space.

property output_size: int
qubit_to_fock_state(bitstring)

Convenience helper mirroring qubit_to_fock_state() with the bridge configuration.

Return type:

FockState

Parameters

bitstringstr

Computational basis string. Its length must equal sum(self.group_sizes).

Returns

perceval.BasicState

Photonic Fock state produced by the current qubit grouping convention.

transition_matrix()

Return the precomputed transition matrix.

Return type:

Tensor

Returns

torch.Tensor

Sparse COO tensor of shape (output_size, 2**n_qubits) mapping the qubit computational basis onto the selected photonic computation space.

class merlin.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

as_simulation()

Temporarily force local simulation within the context.

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
merlin_leaf: bool = True
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)
class merlin.SamplingProcess(method='multinomial')

Bases: object

Handles quantum measurement sampling with different methods.

This class provides functionality to simulate quantum measurement noise by applying different sampling strategies to probability distributions.

pcvl_sampler(distribution, shots, method=None)

Apply sampling noise to a probability distribution.

Return type:

Tensor

Args:

distribution: Input probability distribution tensor method: Sampling method to use (‘multinomial’, ‘binomial’, or ‘gaussian’), defaults to the initialized method shots: Number of measurement shots to simulate

Returns:

Noisy probability distribution after sampling

Raises:

ValueError: If method is not one of the valid options

class merlin.StateGenerator

Bases: object

Utility class for generating photonic input states.

static generate_state(n_modes, n_photons, state_pattern)

Generate an input state based on specified pattern.

enum merlin.StatePattern(value)

Bases: Enum

Input photon state patterns.

Valid values are as follows:

DEFAULT = <StatePattern.DEFAULT: 'default'>
SPACED = <StatePattern.SPACED: 'spaced'>
SEQUENTIAL = <StatePattern.SEQUENTIAL: 'sequential'>
PERIODIC = <StatePattern.PERIODIC: 'periodic'>
merlin.build_slos_distribution_computegraph(m, n_photons, output_map_func=None, computation_space=None, no_bunching=None, keep_keys=True, device=None, dtype=torch.float32, index_photons=None)

Construct a reusable SLOS computation graph.

Return type:

SLOSComputeGraph

Parameters

mint

Number of modes in the circuit.

n_photonsint

Total number of photons injected in the circuit.

output_map_funccallable, optional

Mapping applied to each output Fock state, allowing post-processing.

computation_space : ComputationSpace, optional keep_keys : bool, optional

Whether to keep the list of mapped Fock states.

devicetorch.device, optional

Device on which tensors should be allocated.

dtypetorch.dtype, optional

Real dtype controlling numerical precision.

index_photonslist[tuple[int, …]], optional

Bounds for each photon placement.

Returns

SLOSComputeGraph

Pre-built computation graph ready for repeated evaluations.

merlin.create_circuit(M, input_size)

Create a quantum photonic circuit with beam splitters and phase shifters.

Args:

M (int): Number of modes in the circuit.

Returns:

pcvl.Circuit: A quantum photonic circuit with alternating beam splitter layers and phase shifters.

merlin.define_layer_no_input(n_modes, n_photons, circuit_type=None)

Define a quantum layer for feed-forward processing.

Args:

n_modes (int): Number of optical modes. n_photons (int): Number of photons in the layer.

Returns:

QuantumLayer: A configured quantum layer with trainable parameters.

merlin.define_layer_with_input(M, N, input_size, circuit_type=None)

Define the first layers of the feed-forward block, those with an input size > 0.

Args:

M (int): Number of modes in the circuit. N (int): Number of photons.

Returns:

QuantumLayer: The first quantum layer with input parameters.

merlin.resolve_detectors(experiment, n_modes)

Build a per-mode detector list from a Perceval experiment.

Return type:

tuple[list[Detector], bool]

Args:

experiment: Perceval experiment carrying detector configuration. n_modes: Number of photonic modes to cover.

Returns:
normalized: list[pcvl.Detector]

List of detectors (defaulting to ideal PNR where unspecified),

empty_detectors: bool

If True, no Detector was defined in experiment. If False, at least one Detector was defined in experiement.

Subpackages

Submodules