merlin.algorithms.layer module
Main QuantumLayer implementation
- class merlin.algorithms.layer.Any(*args, **kwargs)
Bases:
objectSpecial type indicating an unconstrained type.
Any is compatible with every type.
Any assumed to have all methods.
All values assumed to be instances of Any.
Note that all the above statements are true from the point of view of static type checkers. At runtime, Any should not be used with instance checks.
- class merlin.algorithms.layer.AutoDiffProcess(sampling_method='multinomial')
Bases:
objectHandles 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.algorithms.layer.CircuitBuilder(n_modes)
Bases:
objectBuilder 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:
- 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: WhenTrue, generate higher-order featurecombinations (up to
max_order) matching the historical subset encoding utility.- max_order: Optional cap on the size of feature combinations when
subset_combinationsis enabled.Noneuses all orders.
- Returns:
CircuitBuilder:
selffor 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:
- Args:
- modes: Optional list describing the span.
Nonetargets 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.- modes: Optional list describing the span.
- Raises:
ValueError: If the provided modes are invalid or span fewer than two modes.
- Returns:
CircuitBuilder:
selffor 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:
- 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 ofvalue). value: Optional fixed value for the rotations (alias ofangle). name: Optional stem used for generated parameter names. role: ExplicitParameterRoletaking precedence over other flags.- Returns:
CircuitBuilder:
selffor 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:
- 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 boththetaandphitrainable. 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 whentargetsis omitted. name: Optional stem used for generated parameter names.- Returns:
CircuitBuilder:
selffor 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:
- Returns:
Circuit: Circuit instance populated with components.
- classmethod from_circuit(circuit)
Create a builder from an existing circuit.
- Return type:
- 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 importperceval.- Returns:
A
pcvl.Circuitinstance mirroring the components tracked by this builder.- Raises:
ImportError: If
percevalis 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.algorithms.layer.ComputationProcessFactory
Bases:
objectFactory for creating computation processes.
- static create(circuit, input_state, trainable_parameters, input_parameters, computation_space=None, **kwargs)
Create a computation process.
- Return type:
- enum merlin.algorithms.layer.ComputationSpace(value)
Bases:
str,EnumEnumeration 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
Enumand its members also have the following methods:- classmethod default(*, no_bunching)
Derive the default computation space from the legacy no_bunching flag.
- Return type:
- classmethod coerce(value)
Normalize user-provided values (enum instances or case-insensitive strings).
- Return type:
- class merlin.algorithms.layer.DetectorTransform(simulation_keys, detectors, *, dtype=None, device=None, partial_measurement=False)
Bases:
ModuleLinear 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 fromperceval.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 isnot
Noneare measured. The transform then operates on complex amplitudes and returns per-outcome dictionaries (seeforward()).
- forward(tensor)
Apply the detector transform.
- 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.
- class merlin.algorithms.layer.InitializationContext(device, dtype, complex_dtype, amplitude_encoding, input_size, circuit, experiment, noise_model, has_custom_noise, input_state, n_photons, trainable_parameters, input_parameters, angle_encoding_specs, photon_survival_probs, detectors, has_custom_detectors, computation_space, measurement_strategy, warnings, return_object)
Bases:
objectImmutable context for QuantumLayer initialization.
-
amplitude_encoding:
bool
-
complex_dtype:
dtype
-
computation_space:
ComputationSpace
-
detectors:
list[Detector]
-
device:
Optional[device]
-
dtype:
dtype
-
experiment:
Experiment
-
has_custom_detectors:
bool
-
has_custom_noise:
bool
-
input_parameters:
list[str]
-
input_size:
Optional[int]
-
input_state:
UnionType[StateVector,BasicState,Tensor,None]
-
measurement_strategy:
MeasurementStrategy|_LegacyMeasurementStrategy
-
n_photons:
Optional[int]
-
photon_survival_probs:
list[float]
-
return_object:
bool
-
trainable_parameters:
list[str]
-
warnings:
list[str]
-
amplitude_encoding:
- class merlin.algorithms.layer.Iterable
Bases:
object
- enum merlin.algorithms.layer.MeasurementKind(value)
Bases:
EnumNew API: internal measurement kinds used by MeasurementStrategy.
Valid values are as follows:
- PROBABILITIES = <MeasurementKind.PROBABILITIES: 'PROBABILITIES'>
- MODE_EXPECTATIONS = <MeasurementKind.MODE_EXPECTATIONS: 'MODE_EXPECTATIONS'>
- AMPLITUDES = <MeasurementKind.AMPLITUDES: 'AMPLITUDES'>
- PARTIAL = <MeasurementKind.PARTIAL: 'PARTIAL'>
- class merlin.algorithms.layer.MeasurementStrategy(type, measured_modes=(), computation_space=None, grouping=None)
Bases:
objectNew API: immutable definition of a measurement strategy for output post-processing.
-
AMPLITUDES:
ClassVar[_LegacyMeasurementStrategy] = 'amplitudes'
-
MODE_EXPECTATIONS:
ClassVar[_LegacyMeasurementStrategy] = 'mode_expectations'
-
NONE:
ClassVar[MeasurementStrategy] = MeasurementStrategy(type=<MeasurementKind.AMPLITUDES: 'AMPLITUDES'>, measured_modes=(), computation_space=<ComputationSpace.UNBUNCHED: 'unbunched'>, grouping=None)
-
PROBABILITIES:
ClassVar[_LegacyMeasurementStrategy] = 'probabilities'
- static amplitudes(computation_space=ComputationSpace.UNBUNCHED)
- Return type:
-
computation_space:
Optional[ComputationSpace]
- get_unmeasured_modes(n_modes)
Return the complement of the measured modes after validation.
- Return type:
tuple[int,...]
-
grouping:
UnionType[LexGrouping,ModGrouping,None]
-
measured_modes:
tuple[int,...]
- static mode_expectations(computation_space=ComputationSpace.UNBUNCHED)
- Return type:
- static partial(modes, computation_space=ComputationSpace.UNBUNCHED, grouping=None)
Create a partial measurement on the given mode indices. Note that the specified grouping only applies on the resulting probabilities, not on the amplitudes.
- Return type:
- static probs(computation_space=ComputationSpace.UNBUNCHED, grouping=None)
- Return type:
-
type:
MeasurementKind
- validate_modes(n_modes)
Validate mode indices and warn when the selection covers all modes.
- Return type:
None
-
AMPLITUDES:
- class merlin.algorithms.layer.MerlinModule
Bases:
ModuleGeneric MerLin module with shared utility functions
- Merlin remote execution policy:
_force_simulation (bool) defaults to False. When True, the layer MUST run locally. The variable is set with property (getter and setter): force_local.
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() provide local context forcing use as simulation
- as_simulation()
Temporarily force local simulation within the context.
- property force_local: bool
When True, this layer must run locally (Merlin will not offload it).
- static setup_device_and_dtype(device, dtype)
Normalize device/dtype to final forms.
- Return type:
tuple[Optional[device],dtype,dtype]
- should_offload()
Return True if this layer should be offloaded under current policy.
- Return type:
bool
- supports_offload()
Return True if this layer is technically offloadable.
- Return type:
bool
- class merlin.algorithms.layer.ModGrouping(input_size, output_size)
Bases:
ModuleMaps 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.algorithms.layer.OutputMapper
Bases:
objectHandles 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, dtype=None)
Create an output mapping based on the specified strategy.
- Args:
strategy: The measurement mapping strategy to use computation_space: The computation space for the measurement. 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), …]
dtype: Target dtype for internal tensors. Defaults to torch.float32.
- 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.layer.PartialMeasurement(branches, measured_modes, unmeasured_modes, grouping=None)
Bases:
objectCollection of measurement branches along with measured/unmeasured mode metadata.
- Args:
branches: Tuple of branches, ordered lexicographically by outcome. measured_modes: Indices of measured modes in the full system. unmeasured_modes: Indices of unmeasured modes in the full system. grouping: Optional grouping callable to group probabilities.
- property amplitudes
- static from_detector_transform_output(detector_output, *, grouping=None)
Branch-based PartialMeasurement wrapper from DetectorTransform(partial_measurement=True) output.
- Return type:
- property n_measured_modes: int
- property n_unmeasured_modes: int
- property outcomes
- property probabilities: Tensor
Return the probabilities of all branches as a tensor of shape (batch, n_branches) unless a grouping was set in which case, the probabilities are grouped and the returned tensor has shape (batch, grouping_output_size).
Same property as self.tensor.
- property probability_tensor_shape: tuple[int, int]
Return the expected (batch, n_outcomes) shape for the probability tensor.
- reorder_branches()
Reorder branches lexicographically by their outcomes.
- Return type:
None
- set_grouping(grouping)
Set the grouping used to group probabilities.
Once the grouping is set, the properties probabilities and tensor return grouped probabilities.
- Return type:
None
- Args:
grouping: Grouping object used to group probabilities.
- property tensor: Tensor
Return the probabilities of all branches as a tensor of shape (batch, n_branches). unless a grouping was set in which case, the probabilities are grouped and the returned tensor has shape (batch, grouping_output_size).
This property assumes that all branches are ordered lexicographically by their outcomes so the stacking of probabilities follows the same order.
- verify_branches_order()
Verify that branches are ordered lexicographically by their outcomes.
- Return type:
None
- class merlin.algorithms.layer.PhotonLossTransform(simulation_keys, survival_probs, *, dtype=None, device=None)
Bases:
ModuleLinear map applying per-mode photon loss to a Fock probability vector.
- Args:
- simulation_keys: Iterable describing the raw Fock states produced by the
simulator (as tuples or lists of integers).
survival_probs: One survival probability per optical mode. dtype: Optional torch dtype for the transform matrix. Defaults to
torch.float32.device: Optional device used to stage the transform matrix.
- forward(distribution)
Apply the photon loss transform to a Fock probability vector.
- Return type:
- Args:
distribution: A Fock probability vector as a 1D torch tensor.
- Returns:
A Fock probability vector after photon loss.
- property is_identity: bool
Whether the transform corresponds to perfect transmission.
- property output_keys: list[tuple[int, ...]]
Classical Fock keys after photon loss.
- property output_size: int
Number of classical outcomes after photon loss.
- 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 complexdtypes. In addition, this method will only cast the floating point or complex parameters and buffers todtype(if given). The integral parameters and buffers will be moveddevice, if that is given, but with dtypes unchanged. Whennon_blockingis 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)
- device (
- 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.algorithms.layer.ProbabilityDistribution(tensor, n_modes, n_photons, computation_space=ComputationSpace.FOCK, logical_performance=None, _custom_basis=None)
Bases:
objectProbability tensor bundled with Fock metadata and post-filter tracking.
Parameters
- tensor:
Dense or sparse probabilities; leading dimensions are treated as batch axes.
- n_modes:
Number of modes in the Fock space.
- n_photons:
Total photon number represented by the distribution.
- computation_space:
Basis enumeration used to order amplitudes (
fock,unbunched,dual_rail).- logical_performance:
Optional per-batch scalar tracking kept/total probability after filtering.
Notes
Instances are normalized on construction; arithmetic-style temporary unnormalized states are not supported (unlike
StateVector). Onlyshape,device,dtype, andrequires_gradare delegated to the underlyingtorch.Tensor; tensor-like helpersto,clone,detach, andrequires_grad_mirror tensor semantics while keeping metadata and logical performance aligned. Layout-changing tensor operations should be done ontensordirectly, then wrapped again viafrom_tensorto maintain a consistent basis.- property basis: Combinadics | FilteredBasis | tuple[tuple[int, ...], ...]
- property basis_size: int
- clone()
Return a cloned
ProbabilityDistributionwith metadata and logical performance copied.- Return type:
-
computation_space:
ComputationSpace= 'fock'
- detach()
Return a detached
ProbabilityDistributionsharing data without gradients.- Return type:
- filter(rule)
Apply post-selection filter and renormalize probabilities.
logical_performance records kept_mass / original_mass per batch.
- Return type:
Parameters
- rule:
Computation space alias (
fock,unbunched,dual_rail), a predicate, an explicit iterable of allowed states, or a tuple(space, predicate)to combine a computation-space constraint with an additional predicate.
Returns
- ProbabilityDistribution
A new, normalized distribution; may shrink its basis when filtering to
unbunchedordual_railin the dense case.
Raises
- ValueError
If
dual_railis selected with incompatiblen_modes/n_photonsor an unknown computation space is requested.
- classmethod from_perceval(distribution, *, dtype=None, device=None, sparse=None)
Construct from a Perceval
BSDistribution.Validates that all entries share the same photon number and mode count.
- Return type:
Parameters
- distribution:
Input Perceval distribution.
- dtype / device:
Optional overrides for output tensor placement and precision.
- sparse:
Force dense or sparse output; default auto-selects based on fill ratio.
Raises
- ValueError
If the distribution is empty or inconsistent in shape/photon number.
- classmethod from_state_vector(state_vector, *, dtype=None, device=None, computation_space=None)
Convert a
StateVectorto a probability distribution.- Return type:
Parameters
- state_vector:
Source amplitudes; must expose
to_dense,n_modes, andn_photons.- dtype / device:
Optional overrides for output tensor placement and precision.
- computation_space:
Optional basis scheme; defaults to
fock.
- classmethod from_tensor(tensor, *, n_modes, n_photons, computation_space=None, dtype=None, device=None)
Build a distribution from an explicit probability tensor.
- Return type:
Parameters
- tensor:
Dense or sparse probability tensor; last dimension must match the basis size.
- n_modes / n_photons:
Metadata for basis construction.
- computation_space:
Optional basis scheme; defaults to
fock.- dtype / device:
Optional overrides for output tensor placement and precision.
Raises
- ValueError
If the last dimension does not match the expected basis size.
- property is_normalized: bool
- property is_sparse: bool
- memory_bytes()
Return the tensor’s approximate memory footprint in bytes.
- Return type:
int
-
n_modes:
int
-
n_photons:
int
- normalize()
In-place normalization; safe for zero-mass batches.
- Return type:
Returns
- ProbabilityDistribution
The same instance, normalized along the basis dimension.
- probabilities()
Alias for
to_dense()for readability.- Return type:
- requires_grad_(requires_grad=True)
Set
requires_gradon underlying tensors and return self.- Return type:
- to(*args, **kwargs)
Return a new
ProbabilityDistributionwith tensor (and logical_performance) moved/cast viatorch.Tensor.to.- Return type:
- to_perceval()
Convert to Perceval
BSDistribution(single) or list for batches.
- class merlin.algorithms.layer.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=None, return_object=False, device=None, dtype=None)
Bases:
MerlinModuleQuantum Neural Network Layer with factory-based architecture.
This layer can be created either from a
CircuitBuilderinstance, a pre-compiledpcvl.Circuit, or an :class:Experiment`.- export_config()
Export a standalone configuration for remote execution.
- Return type:
dict
- forward(*input_parameters, shots=None, sampling_method=None, simultaneous_processes=None)
Forward pass through the quantum layer.
Encoding is inferred from the input type: :rtype:
Tensor|PartialMeasurement|StateVector|ProbabilityDistributiontorch.Tensor(float): angle encoding (compatible withnn.Sequential)torch.Tensor(complex): amplitude encodingStateVector: amplitude encoding (preferred for quantum state injection)
Parameters
- *input_parameterstorch.Tensor | StateVector
Input data. For angle encoding, pass float tensors. For amplitude encoding, pass a single
StateVectoror complex tensor.- shotsint | None, optional
Number of samples; if 0 or None, return exact amplitudes/probabilities.
- sampling_methodstr | None, optional
Sampling method, e.g. “multinomial”.
- simultaneous_processesint | None, optional
Batch size hint for parallel computation.
Returns
- torch.Tensor | PartialMeasurement | StateVector | ProbabilityDistribution
Output after measurement mapping. Depending on the return_object argument and measurement strategy defined in the input, the output type will be different. Check the constructor for more details.
Raises
- TypeError
If inputs mix
torch.TensorandStateVector, or if an unsupported input type is provided.- ValueError
If multiple
StateVectorinputs are provided.
- property has_custom_detectors: bool
- 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_input_state(input_state)
- set_sampling_config(shots=None, sampling_method=None)
Deprecated: sampling configuration must be provided at call time in forward.
- classmethod simple(cls, input_size, output_size=None, device=None, dtype=None, computation_space=ComputationSpace.UNBUNCHED)
Create a ready-to-train layer with a input_size-mode, (input_size//2)-photon architecture.
The circuit is assembled via
CircuitBuilderwith the following layout:A fully trainable entangling layer acting on all modes;
A full input encoding layer spanning all encoded features;
A fully trainable entangling layer acting on all modes.
- Args:
input_size: Size of the classical input vector. Must be 20 or lower. output_size: Optional classical output width. device: Optional target device for tensors. dtype: Optional tensor dtype. computation_space: Logical computation subspace; one of {“fock”, “unbunched”, “dual_rail”}.
- Returns:
QuantumLayer configured with the described architecture.
- 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 complexdtypes. In addition, this method will only cast the floating point or complex parameters and buffers todtype(if given). The integral parameters and buffers will be moveddevice, if that is given, but with dtypes unchanged. Whennon_blockingis 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)
- device (
- 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.algorithms.layer.Sequence
Bases:
Reversible,CollectionAll 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.
- enum merlin.algorithms.layer.StatePattern(value)
Bases:
str,EnumInput photon state patterns.
- Member Type:
str
Valid values are as follows:
- DEFAULT = <StatePattern.DEFAULT: 'default'>
- SPACED = <StatePattern.SPACED: 'spaced'>
- SEQUENTIAL = <StatePattern.SEQUENTIAL: 'sequential'>
- PERIODIC = <StatePattern.PERIODIC: 'periodic'>
- class merlin.algorithms.layer.StateVector(tensor, n_modes, n_photons, _normalized=False)
Bases:
objectAmplitude tensor bundled with its Fock metadata.
Keeps
n_modes/n_photonsand combinadics basis ordering alongside the underlying PyTorch tensor (dense or sparse).Parameters
- tensor:
Dense or sparse amplitude tensor; leading dimensions (if any) are treated as batch axes.
- n_modes:
Number of modes in the Fock space.
- n_photons:
Total photon number represented by the state.
- _normalized:
Internal flag tracking whether the stored tensor is normalized.
Notes
This is a thin wrapper over a
torch.Tensor: onlyshape,device,dtype, andrequires_gradare delegated automatically, and tensor-like helpersto,clone,detach, andrequires_grad_are provided to mirror common tensor workflows while preserving metadata. Layout-changing operations (e.g.,reshape/view) are intentionally not exposed; perform those ontensorexplicitly if needed and rebuild viafrom_tensor.- property basis: Combinadics
Lazy combinadics basis for
(n_modes, n_photons)in Fock ordering.
- property basis_size: int
Return the number of basis states for
(n_modes, n_photons).
- clone()
Return a cloned
StateVectorwith identical metadata and normalization flag.- Return type:
- detach()
Return a detached
StateVectorsharing data without gradients.- Return type:
- classmethod from_basic_state(state, *, dtype=None, device=None, sparse=True)
Create a one-hot state from a Fock occupation list/BasicState.
- Return type:
- Args:
state: Occupation numbers per mode. dtype: Optional target dtype. device: Optional target device. sparse: Build sparse layout when True.
- Returns:
StateVector: One-hot state.
- classmethod from_perceval(state_vector, *, dtype=None, device=None, sparse=None)
Build from a
pcvl.StateVector.- Return type:
- Args:
state_vector: Perceval state to wrap. dtype: Optional target dtype. device: Optional target device. sparse: Force sparse/dense; if None use density heuristic (<=30%).
- Returns:
StateVector: Merlin wrapper with metadata and preserved amplitudes.
- Raises:
ValueError: If the Perceval state is empty or has inconsistent photon/mode counts.
- classmethod from_tensor(tensor, *, n_modes, n_photons, dtype=None, device=None)
Wrap an existing tensor with explicit metadata.
- Return type:
- Args:
tensor: Dense or sparse amplitude tensor. n_modes: Number of modes. n_photons: Total photons. dtype: Optional target dtype. device: Optional target device.
- Returns:
StateVector: Wrapped tensor.
- Raises:
ValueError: If the last dimension does not match the basis size.
- index(state)
Return basis index for the given Fock state.
- Return type:
Optional[int]
- Args:
state: Occupation list or BasicState.
- Returns:
int | None: Basis index, or None if not present (or zero in sparse tensor).
- property is_normalized: bool
- property is_sparse: bool
Return True if the underlying tensor uses a sparse layout.
- memory_bytes()
Approximate memory footprint (bytes) of the underlying tensor data.
- Return type:
int
-
n_modes:
int
-
n_photons:
int
- normalize()
Normalize this state in-place and return self.
- Return type:
- normalized_str()
Human-friendly string of the normalized state (forces normalization for display).
- Return type:
str
- requires_grad_(requires_grad=True)
Set
requires_gradon the underlying tensor and return self.- Return type:
- tensor_product(other, *, sparse=None)
Tensor product of two states with metadata propagation.
If any operand is dense, the result is dense. Supports one-hot fast path. The resulting state is normalized before returning.
- Return type:
- Args:
other: Another StateVector or a BasicState/occupation list. sparse: Override sparsity of the result; default keeps dense if any input dense.
- Returns:
StateVector: Combined state with summed modes/photons (normalized).
- Raises:
ValueError: If tensors are not 1D.
- to(*args, **kwargs)
Return a new
StateVectorwith the tensor moved/cast viatorch.Tensor.to.- Return type:
- to_perceval()
Convert to
pcvl.StateVector.- Return type:
StateVector|list[StateVector]
- Args:
None
- Returns:
pcvl.StateVector | list[pcvl.StateVector]: A Perceval state for 1D tensors, or a list for batched tensors, with amplitudes preserved (no extra renormalization).
- merlin.algorithms.layer.apply_angle_encoding(x, spec)
Apply custom angle encoding using stored metadata.
- Return type:
- merlin.algorithms.layer.cast(typ, val)
Cast a value to a type.
This returns the value unchanged. To the type checker this signals that the return value has the designated type, but at runtime we intentionally don’t check anything (we want this to be as fast as possible).
- merlin.algorithms.layer.contextmanager(func)
@contextmanager decorator.
Typical usage:
@contextmanager def some_generator(<arguments>):
<setup> try:
yield <value>
- finally:
<cleanup>
This makes this:
- with some_generator(<arguments>) as <variable>:
<body>
equivalent to this:
<setup> try:
<variable> = <value> <body>
- finally:
<cleanup>
- merlin.algorithms.layer.feature_count_for_prefix(prefix, angle_encoding_specs, spec_mappings=None)
Infer the number of raw features associated with an encoding prefix.
- Return type:
Optional[int]
- merlin.algorithms.layer.generate_state(n_modes, n_photons, state_pattern=StatePattern.DEFAULT)
Generate a Perceval Fock input state as a
BasicState.- Return type:
BasicState
- Args:
n_modes: Number of photonic modes. n_photons: Total number of photons. state_pattern: Placement strategy for the photons.
- Returns:
A
perceval.BasicStateinstance.- Raises:
ValueError: If the inputs are inconsistent or the pattern is unknown.
- merlin.algorithms.layer.normalize_measurement_strategy(measurement_strategy, computation_space)
Normalize measurement strategy + computation space with deprecation warnings.
Enforces the v0.3 requirement that computation_space must live inside MeasurementStrategy when using the new factory methods (probs, mode_expectations, partial).
- Return type:
tuple[MeasurementStrategy|_LegacyMeasurementStrategy,ComputationSpace]
Rules: 1. If MeasurementStrategy instance (new API) + constructor computation_space provided
→ ERROR: user must move computation_space into the factory method
If measurement_strategy is None and computation_space provided → OK with deprecation warning (default to MeasurementStrategy.probs(computation_space))
If legacy enum (PROBABILITIES, etc) + constructor computation_space → OK with deprecation warning (backward compat)
If MeasurementStrategy instance only → use its computation_space
If legacy enum only → wrap with computation_space param
- merlin.algorithms.layer.normalize_probabilities_and_amplitudes(amplitudes, computation_space)
Return probabilities and renormalized amplitudes when required.
- merlin.algorithms.layer.prepare_input_encoding(x, prefix=None, angle_encoding_specs=None)
Prepare input encoding based on mode.
- Return type:
- merlin.algorithms.layer.prepare_input_state(input_state, n_photons, computation_space, device, complex_dtype, experiment=None, circuit_m=None, amplitude_encoding=False)
Normalize input_state to canonical form.
- Return type:
tuple[UnionType[StateVector,BasicState,Tensor,None],Optional[int]]
Parameters
- input_stateStateVector | pcvl.StateVector | pcvl.BasicState | list | tuple | torch.Tensor | None
The input state in various formats.
StateVectoris the canonical type. Legacy formats are auto-converted with deprecation warnings where appropriate.- n_photonsint | None
Number of photons (used for default state generation).
- computation_spaceComputationSpace
The computation space configuration.
- devicetorch.device | None
Target device for tensors.
- complex_dtypetorch.dtype
Complex dtype for tensor conversion.
- experimentpcvl.Experiment | None
Optional experiment whose input_state takes precedence.
- circuit_mint | None
Number of modes in the circuit (for default state generation).
- amplitude_encodingbool
Whether amplitude encoding is enabled.
Returns
- tuple[StateVector | pcvl.BasicState | torch.Tensor | None, int | None]
The normalized input state and resolved photon count.
Raises
- ValueError
If neither input_state nor n_photons is provided, or if StateVector is empty.
Warns
- DeprecationWarning
When
torch.Tensoris passed as input_state (deprecated in favor of StateVector).- UserWarning
When both experiment.input_state and input_state are provided.
- merlin.algorithms.layer.resolve_circuit(circuit_source, pcvl_module)
Convert builder/circuit/experiment to unified circuit form.
- Return type:
ResolvedCircuit
- merlin.algorithms.layer.resolve_measurement_strategy(measurement_strategy)
Return the concrete strategy implementation for the enum value.
- Return type:
- merlin.algorithms.layer.sanitize_parameters(*args, **_kw)
Decorator to centralize parameter sanitization for method calls.
Usage: - As a plain decorator: @sanitize_parameters (no parentheses) - As a factory with processors: @sanitize_parameters(proc1, proc2, …)
Behavior: - Emits standardized warnings/errors based on the global deprecation registry. - Applies converter functions registered for present deprecated params. - Applies any additional processors(qual, kwargs) provided, sequentially, each receiving and returning kwargs.
- Return type:
- merlin.algorithms.layer.setup_noise_and_detectors(experiment, circuit, computation_space, measurement_strategy)
Extract and validate noise/detectors.
- Return type:
NoiseAndDetectorConfig
- merlin.algorithms.layer.split_inputs_by_prefix(prefixes, tensor, angle_encoding_specs, spec_mappings=None)
Split a single logical input tensor into per-prefix chunks when possible.
- Return type:
Optional[list[Tensor]]
- merlin.algorithms.layer.validate_and_resolve_circuit_source(builder, circuit, experiment, trainable_parameters, input_parameters)
Enforce exactly one of (builder, circuit, experiment) is provided.
- Return type:
CircuitSource
- merlin.algorithms.layer.validate_encoding_mode(amplitude_encoding, input_size, n_photons, input_parameters)
Fail-fast validation for amplitude encoding constraints.
- Return type:
EncodingModeConfig
- merlin.algorithms.layer.vet_experiment(experiment)
Check experiment constraints.
- Return type:
dict[str,bool]
Note
Quantum layers built from a perceval.Experiment now apply the experiment’s per-mode detector configuration before returning classical outputs. When no detectors are specified, ideal photon-number resolving detectors are used by default.
If the experiment carries a perceval.NoiseModel (via experiment.noise), MerLin inserts a PhotonLossTransform ahead of any detector transform. The resulting output_keys and output_size therefore include every survival/loss configuration implied by the model, and amplitude read-out is disabled whenever custom detectors or photon loss are present.
Example: Quickstart QuantumLayer
import torch.nn as nn
from merlin import QuantumLayer
simple_layer = QuantumLayer.simple(
input_size=4,
)
model = nn.Sequential(
simple_layer,
nn.Linear(simple_layer.output_size, 3),
)
# Train and evaluate as a standard torch.nn.Module
Note
QuantumLayer.simple() returns a thin SimpleSequential wrapper that behaves like a standard
PyTorch module while exposing the inner quantum layer as .quantum_layer and any
post-processing (ModGrouping or Identity) as .post_processing.
The wrapper also forwards .circuit and .output_size so existing code that inspects these
attributes continues to work.
The simple quantum layer above implements a circuit of (input_size) modes and (input_size//2) photons. This circuit is made of: - A fully trainable entangling layer acting on all modes; - A full input encoding layer spanning all encoded features; - A fully trainable entangling layer acting on all modes.
Example: Declarative builder API
import torch.nn as nn
from merlin import LexGrouping, MeasurementStrategy, QuantumLayer
from merlin.builder import CircuitBuilder
builder = CircuitBuilder(n_modes=6)
builder.add_entangling_layer(trainable=True, name="U1")
builder.add_angle_encoding(modes=list(range(4)), name="input")
builder.add_rotations(trainable=True, name="theta")
builder.add_superpositions(depth=1)
builder_layer = QuantumLayer(
input_size=4,
builder=builder,
n_photons=3, # is equivalent to input_state=[1,1,1,0,0,0]
measurement_strategy=MeasurementStrategy.probs(),
)
model = nn.Sequential(
builder_layer,
LexGrouping(builder_layer.output_size, 3),
)
# Train and evaluate as a standard torch.nn.Module
The circuit builder allows you to build your circuit layer by layer, with a high-level API. The example above implements a circuit of 6 modes and 3 photons. This circuit is made of: - A first entangling layer (trainable) - Angle encoding on the first 4 modes (for 4 input parameters with the name “input”) - A trainable rotation layer to add more trainable parameters - An entangling layer to add more expressivity
Other building blocks in the CircuitBuilder include:
add_rotations: Add single or multiple phase shifters (rotations) to specific modes. Rotations can be fixed, trainable, or data-driven (input-encoded).
add_angle_encoding: Encode classical data as quantum rotation angles, supporting higher-order feature combinations for expressive input encoding.
add_entangling_layer: Insert a multi-mode entangling layer (implemented via a generic interferometer), optionally trainable, and tune its internal template with the
modelargument ("mzi"or"bell") for different mixing behaviours.add_superpositions: Add one or more beam splitters (superposition layers) with configurable targets, depth, and trainability.
Example: Manual Perceval circuit (more control)
import torch.nn as nn
import perceval as pcvl
from merlin import LexGrouping, MeasurementStrategy, QuantumLayer
modes = 6
wl = pcvl.GenericInterferometer(
modes,
lambda i: pcvl.BS() // pcvl.PS(pcvl.P(f"theta_li{i}")) //
pcvl.BS() // pcvl.PS(pcvl.P(f"theta_lo{i}")),
shape=pcvl.InterferometerShape.RECTANGLE,
)
circuit = pcvl.Circuit(modes)
circuit.add(0, wl)
for mode in range(4):
circuit.add(mode, pcvl.PS(pcvl.P(f"input{mode}")))
wr = pcvl.GenericInterferometer(
modes,
lambda i: pcvl.BS() // pcvl.PS(pcvl.P(f"theta_ri{i}")) //
pcvl.BS() // pcvl.PS(pcvl.P(f"theta_ro{i}")),
shape=pcvl.InterferometerShape.RECTANGLE,
)
circuit.add(0, wr)
manual_layer = QuantumLayer(
input_size=4, # matches the number of phase shifters named "input{mode}"
circuit=circuit,
input_state=[1, 0, 1, 0, 1, 0],
trainable_parameters=["theta"],
input_parameters=["input"],
measurement_strategy=MeasurementStrategy.probs(),
)
model = nn.Sequential(
manual_layer,
LexGrouping(manual_layer.output_size, 3),
)
# Train and evaluate as a standard torch.nn.Module
Here, the grouping can also be directly added to the MeasurementStrategy object used in the measurement_strategy parameter.
See the User guide and Notebooks for more advanced usage and training routines !
Input states and amplitude encoding
The input state of a photonic circuit specifies how the photons enter the device. Physically this can be a single
Fock state (a precise configuration of n_photons over m modes) or a superposed/entangled state within the same
computation space (for example Bell pairs or GHZ states). QuantumLayer accepts the
following representations:
perceval.BasicState– a single configuration such aspcvl.BasicState([1, 0, 1, 0]);perceval.StateVector– an arbitrary superposition of basic states with complex amplitudes;- Python lists/tuples, e.g.
[1, 0, 1, 0]. These are accepted as convenience inputs and are immediately converted to a Perceval
perceval.BasicState.
- Python lists/tuples, e.g.
Note
For Fock/occupation inputs, QuantumLayer stores .input_state as a Perceval
perceval.BasicState. If you need the raw occupation vector, use list(layer.input_state).
When input_state is passed, the layer always injects that photonic state. In more elaborate pipelines you may want
to cascade circuits and let the output amplitudes of the previous layer become the input state of the next. Merlin
calls this amplitude encoding: the probability amplitudes themselves carry information and are passed to the next
layer as a tensor. Enabling this behaviour is done with amplitude_encoding=True; in that mode the forward input of
QuantumLayer is the complex photonic state.
The snippet below prepares a dual-rail Bell state as the initial condition and evaluates a batch of classical parameters:
import torch
import perceval as pcvl
from merlin.algorithms.layer import QuantumLayer
from merlin.core import ComputationSpace
from merlin.measurement.strategies import MeasurementStrategy
from merlin.measurement.
circuit = pcvl.Unitary(pcvl.Matrix.random_unitary(4)) # some haar-random 4-mode circuit
bell = pcvl.StateVector()
bell += pcvl.BasicState([1, 0, 1, 0])
bell += pcvl.BasicState([0, 1, 0, 1])
print(bell) # bell is a state vector of 2 photons in 4 modes
layer = QuantumLayer(
circuit=circuit,
n_photons=2,
input_state=bell,
measurement_strategy=MeasurementStrategy.probs(computation_space=ComputationSpace.DUAL_RAIL),
)
x = torch.rand(10, circuit.m) # batch of classical parameters
amplitudes = layer(x)
assert amplitudes.shape == (10, 2**2)
For comparison, the amplitude_encoding variant supplies the photonic state during the forward pass:
import torch
import perceval as pcvl
from merlin.algorithms.layer import QuantumLayer
from merlin.core import MeasurementStrategy,ComputationSpace
circuit = pcvl.Circuit(3)
layer = QuantumLayer(
circuit=circuit,
n_photons=2,
amplitude_encoding=True,
measurement_strategy=MeasurementStrategy.probs(computation_space=ComputationSpace.UNBUNCHED),
dtype=torch.cdouble,
)
prepared_states = torch.tensor(
[[1.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j],
[0.0 + 0.0j, 0.0 + 0.0j, 1.0 + 0.0j]],
dtype=torch.cdouble,
)
out = layer(prepared_states)
In the first example the circuit always starts from bell; in the second, each row of prepared_states represents a
different logical photonic state that flows through the layer. This separation allows you to mix classical angle
encoding with fully quantum, amplitude-based data pipelines.
Returning typed objects
When return_object is set to True, the output of a forward() call depends of the measurement_strategy. By default,
it is set to False. See the following output matrix to size what to expect as the return of a forward call.
Most of the typed objects can give the torch.Tensor as an output with the .tensor parameter. Only the
PartialMeasurement object is a little different. See its according documentation.
These object could be quite useful to access metadata like the number of photons, modes and measurement_strategy behind the output tensors. For example, a better access to specific
states is available with StateVector and ProbabilityDistribution by indexing the desired state. The objects also have an interoperability
with Perceval making it easy interations to have an easy crossplay between the two libraries.
For more information on the typed output capabilities, follow the following links:
- StateVector : /api_reference/api/merlin.algorithms.core.state_vector
- ProbabilityDistribution : /api_reference/api/merlin.algorithms.core.probability_distribution
- PartialMeasurement : /api_reference/api/merlin.algorithms.core.partial_measurement
The snippet below prepares a basic quantum layer and returns a ProbabilityDistribution object:
import torch
import perceval as pcvl
from merlin.algorithms.layer import QuantumLayer
from merlin.core import ComputationSpace
from merlin.measurement.strategies import MeasurementStrategy
from merlin import ProbabilityDistribution
circuit = pcvl.Unitary(pcvl.Matrix.random_unitary(4)) # some haar-random 4-mode circuit
bell = pcvl.StateVector()
bell += pcvl.BasicState([1, 0, 1, 0])
bell += pcvl.BasicState([0, 1, 0, 1])
print(bell) # bell is a state vector of 2 photons in 4 modes
layer = QuantumLayer(
circuit=circuit,
n_photons=2,
input_state=bell,
measurement_strategy=MeasurementStrategy.probs(computation_space=ComputationSpace.DUAL_RAIL),
return_object=True,
)
x = torch.rand(10, circuit.m) # batch of classical parameters
probs = layer(x)
assert isinstance(probs,ProbabilityDistribution)
assert isinstance(probs.tensor,torch.Tensor)
Deprecations
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 Migration guide.