Angle Encoding and Amplitude Encoding
This guide shows how to use angle encoding and amplitude encoding with
Merlin’s QuantumLayer. You’ll find when to use each,
how to build circuits with CircuitBuilder or native Perceval, and complete, runnable snippets.
Prerequisites
Python, PyTorch, and Merlin installed.
Basic familiarity with Merlin’s
QuantumLayer.Optional: Perceval for custom circuits and experiments.
Conceptual Overview
Angle encoding maps a real feature vector into circuit parameters (e.g., phase shifter angles). The circuit unitary depends on your data. Data is encoded at specific points in the circuit using phase shifters.
Amplitude encoding feeds a complex statevector directly to the layer as input. Instead of turning features into angles, you supply the input quantum state’s amplitudes at the beginning of the circuit.
Angle Encoding
When to use
Use angle encoding for classical-quantum pipelines: feature maps, kernels, or hybrid neural networks where your inputs are real-valued tensors.
With CircuitBuilder
CircuitBuilder provides a declarative way to add an
angle-encoding stage into your photonic circuit.
Build a circuit with angle encoding:
import numpy as np
from merlin.builder import CircuitBuilder
# 1) Declare a circuit with 6 modes
builder = CircuitBuilder(n_modes=6)
# 2) Put trainable rotations (phase shifters) on every mode
builder.add_rotations(modes=[0, 1, 2, 3, 4, 5], trainable=True)
# 3) Add an angle-encoding layer; the 'name' will prefix the input parameters
builder.add_angle_encoding(
modes=[0, 1, 2, 3, 4, 5],
name="input",
scale=np.pi # optional global scaling of features -> angles
)
# 4) Entangle some modes (e.g., MZI block between modes 0 and 5)
builder.add_entangling_layer(modes=[0, 5], trainable=True, model="mzi")
# 5) Add superposition/BS layers (increase expressivity)
builder.add_superpositions(modes=[0, 1, 2, 3, 4, 5], trainable=True, depth=2)
Wrap it as a QuantumLayer and run a forward pass:
import torch
from merlin.algorithms import QuantumLayer
layer = QuantumLayer(
input_size=6, # number of *classical* features per sample
builder=builder, # the declarative circuit
input_state=[1, 0, 1, 0, 1, 0] # 5 photons in 10-mode equivalent => here 6 modes, so 3 photons example
)
x = torch.rand((4, 6)) # batch of 4 samples
probs = layer(x) # default MeasurementStrategy.PROBABILITIES
Parameter names and prefixes
The add_angle_encoding() call registers
parameters prefixed by name (e.g., "input"). Internally,
QuantumLayer will consume your real-valued input
tensor and map each feature to the corresponding prefixed angle(s).
Tips and constraints
Modes vs. features: By construction you typically shouldn’t encode more independent features than available modes in the encoding step.
Scaling and combinations: You can use
scale=...to rescale inputs before turning them into angles. If you create multiple encoding stages with different names (prefixes), the layer can split the input tensor across them.Kernels: For quantum kernels, consider
FeatureMapandFidelityKernelif you need a reusable feature map object.
Angle encoding using QuantumLayer.simple
If you want a quick start without designing the circuit:
import torch
from merlin.algorithms import QuantumLayer
layer = QuantumLayer.simple(
input_size=6, # number of classical features
n_params=100, # parameter budget for a 10-mode circuit
output_size=10 # output dimensionality
)
x = torch.rand((2, 6))
y = layer(x) # probability vector of size `output_size`
Angle encoding with Perceval circuits
For full control, create a Perceval circuit, then expose input-parameter prefix(es) that the layer will map features to:
import perceval as pcvl
from merlin.algorithms import QuantumLayer
# Build a 6-mode Perceval circuit
circuit = pcvl.Circuit(6)
# Example: add user-named input phase shifters (prefix 'input')
for mode in range(6):
circuit.add(mode, pcvl.PS(pcvl.P(f"input{mode}")))
circuit.add(mode, pcvl.PS(pcvl.P(f"theta{mode}")))
# (Add interferometers, MZIs, etc. as you like)
# ...
layer = QuantumLayer(
input_size=6,
circuit=circuit,
input_state=[1, 0, 1, 0, 1, 0],
input_parameters=["input"], # map features -> parameters named 'input*'
trainable_parameters=["theta"] # example trainable prefix used elsewhere in your circuit
)
import torch
x = torch.rand((1, 6))
probs = layer(x)
Amplitude Encoding
When to use
Choose amplitude encoding when you already have a prepared quantum state to
inject into the circuit (e.g., produced by an upstream simulator or another
photonic block). Here your input to forward is the statevector
amplitudes, not classical features. This is useful for providing a prepared quantum state as input to a photonic circuit.
How to use
To activate amplitude encoding, set
amplitude_encoding=TrueonQuantumLayer.Requires the parameter
n_photonsto define the computational subspace.Angle and amplitude encoding are mutually exclusive. Thus,
input_sizeorinput_parameterswill not be used and may cause errors if provided.The input tensor shape must match the layer’s state space size:
len(layer.output_keys)(or[batch, len(output_keys)]).
Key input/output dimensions
The principal difference is that in amplitude encoding, the number of inputs is conditioned by the number of photons and modes.
The input dimension must equal the number of Fock states, given by num_states = len(layer.output_keys).
Mathematically, the number of Fock states is:
(n_modes + n_photons - 1) choose n_photons
This combinatorial formula gives the dimension of the Hilbert space for the photonic system.
Minimal example (amplitudes out)
import torch
from merlin.algorithms import QuantumLayer
from merlin.measurement import MeasurementStrategy
# Suppose you already have a circuit or builder; here we assume `circuit`
# exists and is unitary with no post-selection.
# For amplitude encoding, the optical layout defines the evolution,
# but no classical input parameters are used.
layer = QuantumLayer(
circuit=circuit, # or builder=..., or experiment=...
n_photons=2, # required: defines the subspace
amplitude_encoding=True, # switch to amplitude input
measurement_strategy=MeasurementStrategy.AMPLITUDES
)
# Build (or sample) an input statevector compatible with the layer basis
num_states = len(layer.output_keys) # basis size for 2 photons over the modes
psi_in = torch.randn(num_states, dtype=torch.complex64)
psi_in = psi_in / psi_in.norm() # normalize - important to avoid exploding norms
# Forward: returns complex amplitudes after the circuit
psi_out = layer(psi_in)
Detectors, noise, and shots
With
AMPLITUDES, only strong simulation is possible. Hence, the layer bypasses detectors and noise;shotsmust be unset or zero.With probability-like strategies, detector/noise models (if present in a
perceval.Experiment) are applied after converting amplitudes to probabilities; shot sampling is supported when compatible.
Quantum systems interoperability requirements
The amplitude vector must be compatible with the basis used by the layer. Check
layer.output_keysto see state ordering.Always normalize your amplitude inputs to ensure proper probability mass and avoid unstable gradients.
Encodings Key Differences
Aspect |
Angle Encoding |
Amplitude Encoding |
|---|---|---|
Input to |
Real features |
Complex statevector amplitudes |
Number of inputs |
User-defined
( |
Fixed by n_modes and n_photons (combinatorial formula) |
Circuit dependence |
Features set parameters (phases/angles) |
State defines input quantum state to propagate |
Setup knobs |
|
|
Typical use |
Feature maps, kernels, hybrid NN layers |
Providing a prepared quantum state as input to a photonic circuit |
Measurement options |
Probabilities, modes, amplitudes (sim-only) |
Probabilities, modes, amplitudes (sim-only) |
Troubleshooting
Shape errors (angle encoding): Ensure
input_sizeequals the number of features you feed into the layer and matches the encoding specification (number of input phase shifters and prefixes).Too many features: If you attempt to encode more features than modes in your encoding stage, reduce features using dimensionality reduction techniques such as PCA or UMAP, or expand the circuit’s encoding modes.
Shape errors (amplitude encoding): The amplitude vector length must match the layer basis size:
len(layer.output_keys). For batching, use[batch, len(output_keys)].Incompatible measurement strategy: When
AMPLITUDESis selected, do not set nonzeroshotsor enable detectors/noise.Unnormalized amplitudes: Always normalize amplitude inputs to avoid unstable gradients and to ensure proper probability mass.
Complete Examples
Angle encoding with builder and probabilities out
import numpy as np
import torch
from merlin.builder import CircuitBuilder
from merlin.algorithms import QuantumLayer
from merlin.measurement import MeasurementStrategy
builder = CircuitBuilder(n_modes=6)
builder.add_angle_encoding(modes=list(range(6)), name="input", scale=np.pi)
builder.add_entangling_layer(trainable=True)
builder.add_superpositions(modes=list(range(6)), trainable=True, depth=1)
layer = QuantumLayer(
input_size=6,
builder=builder,
input_state=[1, 0, 1, 0, 1, 0],
measurement_strategy=MeasurementStrategy.PROBABILITIES
)
x = torch.rand((3, 6))
probs = layer(x) # shape: [3, layer.output_size]
Amplitude encoding with amplitudes out
import torch
import perceval as pcvl
from merlin.algorithms import QuantumLayer
from merlin.measurement import MeasurementStrategy
# Simple unitary circuit placeholder; customize as needed
circuit = pcvl.Circuit(4)
# ... populate circuit ...
layer = QuantumLayer(
circuit=circuit,
n_photons=2,
amplitude_encoding=True,
measurement_strategy=MeasurementStrategy.AMPLITUDES
)
num_states = len(layer.output_keys)
psi_in = torch.randn(num_states, dtype=torch.complex64)
psi_in = psi_in / psi_in.norm()
amps_out = layer(psi_in) # complex amplitudes
Amplitude encoding with probabilities out
import torch
from merlin.algorithms import QuantumLayer
from merlin.measurement import MeasurementStrategy
layer = QuantumLayer(
circuit=circuit, # same circuit as above
n_photons=2,
amplitude_encoding=True,
measurement_strategy=MeasurementStrategy.PROBABILITIES
)
psi_in = torch.randn(len(layer.output_keys), dtype=torch.complex64)
psi_in = psi_in / psi_in.norm()
probs = layer(psi_in) # classical probabilities
Measurement Strategies (Output Options)
Both angle and amplitude encoding support the following output measurement strategies. For more details, see Measurement Strategy Guide.
PROBABILITIES(default): returns a probability vector aligned withlayer.output_keys.MODE_EXPECTATIONS: returns per-mode expected photon counts.AMPLITUDES: returns complex amplitudes (simulation-only; bypasses detectors and noise).
References
Tak Hur et al., “Quantum convolutional neural network for classical data classification”, 2022. https://arxiv.org/abs/2108.00661