merlin.bridge.quantum_bridge module
Passive bridge between qubit statevectors and Merlin photonic computation spaces.
- class merlin.bridge.quantum_bridge.QuantumBridge(n_photons, n_modes, *, qubit_groups=None, wires_order='little', computation_space=ComputationSpace.UNBUNCHED, normalize=True, device=None, dtype=torch.float32)
Bases:
ModulePassive 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 spreadover
2**2modes and another over2**1modes.wires_order: Endianness used to interpret computational basis strings. computation_space: Target photonic computation space. Accepts a
ComputationSpaceenum 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:
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
psiis not atorch.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.
Overview
The QuantumBridge provides a passive interface between PyTorch-compatible qubit statevector simulators (PennyLane, Qiskit, custom modules, …) and Merlin’s photonic quantum processors. It encodes computational-basis amplitudes into photonic spaces according to a one-photon-per-group scheme, enabling hybrid quantum machine learning workflows that combine qubit and photonic paradigms.
Key Features
Automatic State Conversion: Applies a predetermined transition matrix that maps qubit statevectors to photonic amplitudes ordered according to the selected computation space
Flexible Qubit Grouping: Supports arbitrary partitioning of qubits into groups for photonic encoding
Differentiable Pipeline: Maintains gradient flow from Merlin back to PennyLane for end-to-end training
Batch Processing: Handles batched inputs efficiently
Endianness Control: Supports both little-endian and big-endian qubit wire ordering
Inspectable Mapping:
QuantumBridge.qubit_to_fock_state()reveals how individual bitstrings map to photonic occupancies for debugging or educational purposes
Architecture
The bridge operates in three stages:
State Preparation: A PennyLane circuit or function generates a qubit statevector ψ ∈ ℂ^(2^n)
Encoding: Each computational basis state |bitstring⟩ is mapped to a Fock state with one photon per qubit group
Photonic Processing: The encoded superposition is fed to a Merlin QuantumLayer for photonic computation
Encoding Scheme
For a qubit system partitioned into groups of sizes [g₁, g₂, …, gₖ]:
Total qubits: n = g₁ + g₂ + … + gₖ
Total photonic modes: m = 2^g₁ + 2^g₂ + … + 2^gₖ
Total photons: k (one per group)
Each group of gᵢ qubits is encoded as one photon distributed across 2^gᵢ modes in a one-hot fashion.
Parameters
- class merlin.bridge.quantum_bridge.QuantumBridge(*, qubit_groups, n_modes, n_photons, computation_space='unbunched', device=None, dtype=torch.float32, wires_order='little', normalize=True)
- Parameters:
qubit_groups (list[int]) – List specifying the size of each qubit group. For example, [2, 2] splits 4 qubits into two groups of 2.
n_modes (int) – Total number of photonic modes (must equal Σ 2^group_size).
n_photons (int) – Number of photons in the photonic layer (must equal len(qubit_groups)).
computation_space (str) – Target computation space (
"fock","unbunched", or"dual_rail").device (torch.device) – Target device for computation (default: None, infers from input).
dtype (torch.dtype) – Data type for real-valued tensors (default: torch.float32).
wires_order (str) – Qubit ordering convention - ‘little’ (LSB first) or ‘big’ (MSB first) (default: ‘little’).
normalize (bool) – Whether to normalize the input statevector (default: True).
Helper Functions
- QuantumBridge.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.
Converts a bitstring to a photonic BasicState using one-photon-per-group encoding.
- param str qubit_state:
Binary string representing the qubit state (e.g., “1011”)
- param list[int] group_sizes:
List of qubit group sizes
- returns:
Perceval BasicState with one photon per group
- rtype:
pcvl.BasicState
Usage Examples
Basic Example: Identity Circuit
This example demonstrates the bridge with a simple identity circuit, mapping basis states through the photonic layer:
import torch
import perceval as pcvl
from merlin import MeasurementStrategy, QuantumLayer
from merlin.bridge.quantum_bridge import ComputationSpace, QuantumBridge
# Create a simple identity circuit (m=4 modes, 2 photons)
circuit = pcvl.Circuit(4)
merlin_layer = QuantumLayer(
input_size=0,
circuit=circuit,
n_photons=2,
measurement_strategy=MeasurementStrategy.PROBABILITIES,
no_bunching=True,
)
# Create the bridge
bridge = QuantumBridge(
qubit_groups=[1, 1], # Two 1-qubit groups
n_modes=4,
n_photons=2,
wires_order='little',
computation_space=ComputationSpace.UNBUNCHED,
normalize=True,
)
# PennyLane-like state preparation module
class StatePrep(torch.nn.Module):
def forward(self, _x):
psi = torch.zeros(4, dtype=torch.complex64)
psi[0] = 1.0 + 0.0j # |00⟩ state
return psi
state_prep = StatePrep()
model = torch.nn.Sequential(state_prep, bridge, merlin_layer)
dummy_input = torch.zeros(1, 1)
output = model(dummy_input)
print(f"Output shape: {output.shape}")
print(f"Output probabilities: {output}")
The bridge returns a complex amplitude tensor in the exact ordering expected by the
QuantumLayer.
Hybrid Classification Task
A complete example showing hybrid qubit-photonic classification:
import torch
import torch.nn as nn
import pennylane as qml
import perceval as pcvl
from merlin import MeasurementStrategy, QuantumLayer
from merlin.bridge.quantum_bridge import ComputationSpace, QuantumBridge
class HybridQuantumClassifier(nn.Module):
def __init__(self, n_qubits=3, n_classes=2):
super().__init__()
self.n_qubits = n_qubits
# Classical pre-processing
self.pre_net = nn.Sequential(
nn.Linear(4, 8),
nn.ReLU(),
nn.Linear(8, n_qubits)
)
# PennyLane quantum circuit
self.dev = qml.device('default.qubit', wires=n_qubits, shots=None)
self.weights = nn.Parameter(torch.randn(n_qubits, 3))
# Photonic processor
m = 2 ** n_qubits # One large group
circuit = pcvl.Circuit(m)
for i in range(m - 1):
circuit.add(i, pcvl.BS())
self.merlin_layer = QuantumLayer(
input_size=0,
circuit=circuit,
n_photons=1,
measurement_strategy=MeasurementStrategy.PROBABILITIES,
no_bunching=True,
)
# Quantum bridge
self.bridge = QuantumBridge(
qubit_groups=[n_qubits],
n_modes=m,
n_photons=1,
computation_space=ComputationSpace.UNBUNCHED,
normalize=True,
)
# Classical post-processing
self.post_net = nn.Linear(m, n_classes)
def _quantum_state(self, x):
@qml.qnode(self.dev, interface='torch', diff_method='backprop')
def circuit(weights, features):
qml.AngleEmbedding(features, wires=range(self.n_qubits))
for i in range(self.n_qubits):
qml.Rot(*weights[i], wires=i)
for i in range(self.n_qubits - 1):
qml.CNOT(wires=[i, i + 1])
return qml.state()
# Handle batching
if x.ndim > 1:
states = []
for i in range(x.shape[0]):
states.append(circuit(self.weights, x[i]))
return torch.stack(states)
return circuit(self.weights, x)
def forward(self, x):
# Classical preprocessing
features = self.pre_net(x)
psi = self._quantum_state(features)
payload = self.bridge(psi)
distribution = self.merlin_layer(payload)
logits = self.post_net(distribution)
return logits
# Usage
model = HybridQuantumClassifier(n_qubits=3, n_classes=2)
inputs = torch.randn(8, 4) # Batch of 8 samples
outputs = model(inputs)
print(f"Classification outputs: {outputs.shape}")
Notes and Best Practices
Design Considerations
No Trainable Mapping: The bridge itself contains no trainable parameters. All variational behavior should be implemented in the PennyLane circuit.
Mode Requirements: The Merlin layer must be configured with m = Σ 2^group_size modes and n_photons = len(qubit_groups). No ancilla or post-selected modes are supported.
Differentiability: The bridge uses Merlin’s tensor superposition path to maintain gradient flow. Always call with apply_sampling=False (handled internally).
Performance Tips
Use normalize=False if your PennyLane circuit already outputs normalized states
For large qubit systems, consider using multiple smaller groups rather than one large group
Batch multiple samples together for better GPU utilization
See Also
merlin.QuantumLayer: The underlying photonic processor interfacePennyLane Documentation: For quantum circuit design
Perceval Documentation: For photonic circuit details