Your First Quantum Layers
Merlin provides three approaches to create quantum neural networks, from simple to advanced:
Simple API : Quick start with minimal configuration
Factory Method : More control with pre-built architectures
Direct Circuit Definition : Full control over quantum circuit design
We’ll start with the easiest approach and progress to more advanced methods.
Method 1: Simple API (Easiest)
The simplest way to create a quantum layer is using the .simple()
method. This is perfect for beginners who want to quickly integrate quantum processing into their neural networks.
Basic Example
import torch
import torch.nn as nn
import merlin as ML
class HybridIrisClassifier(nn.Module):
"""
Hybrid model for Iris classification:
- Classical layer reduces 4 features to 3
- Quantum layer processes the 3 features
- Classical output layer for 3-class classification
"""
def __init__(self):
super(HybridIrisClassifier, self).__init__()
# Classical preprocessing layer
self.classical_in = nn.Sequential(
nn.Linear(4, 8),
nn.ReLU(),
nn.Linear(8, 3),
nn.Tanh() # Normalize to [-1, 1] for quantum layer
)
# Quantum layer
# Just specify input size and approximate parameter count
self.quantum = ML.QuantumLayer.simple(
input_size=3, # 3 input features
n_params=100, # ~100 quantum parameters
shots=10000 # Number of measurements
)
# Classical output layer
self.classical_out = nn.Sequential(
nn.Linear(self.quantum.output_size, 8),
nn.ReLU(),
nn.Dropout(0.1),
nn.Linear(8, 3) # 3 classes
)
print(f"\nModel Architecture:")
print(f" Input: 4 features")
print(f" Classical preprocessing: 4 → 3")
print(f" Quantum layer: 3 → {self.quantum.output_size}")
print(f" Classical output: {self.quantum.output_size} → 3 classes")
def forward(self, x):
# Preprocess with classical layer
x = self.classical_in(x)
# Process through quantum layer
# Note: quantum layer expects inputs in [0, 1], so we shift tanh output
x = (x + 1) / 2 # Convert from [-1, 1] to [0, 1]
x = self.quantum(x)
# Final classification
x = self.classical_out(x)
return x
Training the Simple Model
# Create model
model = HybridIrisClassifier()
# Standard PyTorch training
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
criterion = nn.CrossEntropyLoss()
# Training loop (standard PyTorch)
for epoch in range(50):
optimizer.zero_grad()
outputs = model(X_train)
loss = criterion(outputs, y_train)
loss.backward()
optimizer.step()
Method 2: Factory Method (Intermediate)
For more control over the quantum architecture while still using pre-built circuits, use the factory method approach.
Creating Quantum Layers with Factories
from merlin import PhotonicBackend, CircuitType, StatePattern, AnsatzFactory, QuantumLayer
from merlin import OutputMappingStrategy
# Define the quantum experiment configuration
n_modes = 6
n_photons = 3
input_size = 4
# Create experiment with Series circuit
photonicbackend = PhotonicBackend(
circuit_type=CircuitType.SERIES,
n_modes=n_modes,
n_photons=n_photons,
reservoir_mode=True, # Non-trainable quantum layer
use_bandwidth_tuning=False, # No bandwidth tuning
state_pattern=StatePattern.PERIODIC
)
# Create ansatz with automatic output size
ansatz = AnsatzFactory.create(
PhotonicBackend=photonicbackend,
input_size=input_size,
# output_size not specified - will be calculated automatically unless specified
output_mapping_strategy=OutputMappingStrategy.NONE
)
# Create quantum layer
quantum_layer = QuantumLayer(
input_size=input_size,
ansatz=ansatz,
shots=10000, # Number of measurement shots
no_bunching=False
)
Available Circuit Types
merlin provides several pre-built circuit architectures:
CircuitType.SERIES
: Gan et al paper circuit design implementationOther circuit types available in the library
State Patterns
Control how photons are distributed:
StatePattern.PERIODIC
: 1 photon every 2 modes i.e 101010StatePattern.SEQUENTIAL
: photons injected into the first modes seen i.e 111000
Using Factory-Created Layers in Models
class FactoryHybridModel(nn.Module):
def __init__(self):
super().__init__()
# Classical preprocessing
self.preprocess = nn.Linear(10, 4)
# Quantum layer from factory
photonicbackend = PhotonicBackend(
circuit_type=CircuitType.SERIES,
n_modes=8,
n_photons=4,
state_pattern=StatePattern.PERIODIC
)
ansatz = AnsatzFactory.create(
PhotonicBackend=photonicbackend,
input_size=4,
output_size = 10,
output_mapping_strategy=OutputMappingStrategy.LINEAR
)
self.quantum = QuantumLayer(
input_size=4,
ansatz=ansatz,
shots=5000
)
# Classical output
self.output = nn.Linear(self.quantum.output_size, 3)
def forward(self, x):
x = torch.relu(self.preprocess(x))
x = torch.sigmoid(x) # Normalize to [0, 1]
x = self.quantum(x)
return self.output(x)
Method 3: Direct Circuit Definition (Advanced)
For complete control over the quantum circuit, define it directly using Perceval.
Circuit Definition
The quantum circuit consists of three main parts:
import perceval as pcvl
def create_quantum_circuit(m):
# 1. Left interferometer - trainable transformation
wl = pcvl.GenericInterferometer(
m,
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
)
# 2. Input encoding - maps classical data to quantum parameters
c_var = pcvl.Circuit(m)
for i in range(4): # 4 input features
px = pcvl.P(f"px{i + 1}")
c_var.add(i + (m - 4) // 2, pcvl.PS(px))
# 3. Right interferometer - trainable transformation
wr = pcvl.GenericInterferometer(
m,
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
)
# Combine all components
return wl // c_var // wr
Key Components:
pcvl.BS()
: Beam splitter for quantum interferencepcvl.PS(pcvl.P("name"))
: Phase shifter with trainable parameterpcvl.GenericInterferometer
: Creates complex interference patternspcvl.Circuit
: Container for quantum components
Create Quantum Layer from Circuit
# Create quantum layer
m = 6 # 6 optical modes
circuit = create_quantum_circuit(m)
quantum_layer = ML.QuantumLayer(
input_size=4, # 4 input features
output_size=3, # 3 output classes
circuit=circuit, # Quantum circuit
trainable_parameters=["theta"], # Parameters to train
input_parameters=["px"], # Input encoding parameters
input_state=[1, 0, 1, 0, 1, 0], # Initial photon state
output_mapping_strategy=ML.OutputMappingStrategy.LINEAR # Output mapping
)
# Test the layer
x = torch.rand(10, 4) # Batch of 10 samples, 4 features each
output = quantum_layer(x)
print(f"Input shape: {x.shape}") # [10, 4]
print(f"Output shape: {output.shape}") # [10, 3]
Understanding Parameters
quantum_layer = ML.QuantumLayer(
input_size=4, # Classical input features
output_size=3, # Desired output size
circuit=circuit, # Quantum circuit
trainable_parameters=["theta"], # Which parameters to train
input_parameters=["px"], # Input encoding parameters
input_state=[1, 0] * (m // 2) + [0] * (m % 2), # Initial photon distribution
no_bunching=False, # Allow photon bunching
output_mapping_strategy=ML.OutputMappingStrategy.LINEAR # How to map quantum output
)
Parameter Explanation:
trainable_parameters
: Parameters updated during backpropagationinput_parameters
: Parameters that encode classical input datainput_state
: Initial photon configuration (e.g., [1,0,1,0,0,0] = photons in modes 0,2)no_bunching
: Whether multiple photons can occupy the same modeoutput_mapping_strategy
: How quantum probabilities become classical outputs
Complete Hybrid Network Example
class AdvancedHybridClassifier(nn.Module):
def __init__(self, input_dim=8, n_classes=3, n_modes=6):
super().__init__()
# Classical preprocessing
self.classical_input = nn.Sequential(
nn.Linear(input_dim, 6),
nn.ReLU(),
nn.Linear(6, 4) # Reduce to quantum layer input size
)
# Create quantum circuit
circuit = create_quantum_circuit(n_modes)
# Quantum processing layer
self.quantum_layer = ML.QuantumLayer(
input_size=4,
output_size=6,
circuit=circuit,
trainable_parameters=["theta"],
input_parameters=["px"],
input_state=[1, 0] * (n_modes // 2) + [0] * (n_modes % 2),
output_mapping_strategy=ML.OutputMappingStrategy.LINEAR
)
# Classical output layer
self.classifier = nn.Sequential(
nn.Linear(6, n_classes),
nn.Softmax(dim=1)
)
def forward(self, x):
# Classical preprocessing
x = self.classical_input(x)
# Normalize for quantum layer (required: inputs must be in [0,1])
x = torch.sigmoid(x)
# Quantum transformation
x = self.quantum_layer(x)
# Classical output
return self.classifier(x)
# Create and test model
model = AdvancedHybridClassifier(input_dim=8, n_classes=3, n_modes=6)
x = torch.rand(16, 8) # Batch of 16 samples
output = model(x)
print(f"Model output shape: {output.shape}") # [16, 3]
Training the Advanced Model
Train your hybrid model with standard PyTorch workflows:
import torch.optim as optim
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
# Generate synthetic dataset
X, y = make_classification(
n_samples=1000, n_features=8, n_classes=3,
n_informative=6, random_state=42
)
# Prepare data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
X_train = torch.FloatTensor(X_train)
X_test = torch.FloatTensor(X_test)
y_train = torch.LongTensor(y_train)
y_test = torch.LongTensor(y_test)
# Setup training
model = AdvancedHybridClassifier()
optimizer = optim.Adam(model.parameters(), lr=0.01)
criterion = nn.CrossEntropyLoss()
# Training loop
model.train()
for epoch in range(50):
# Forward pass
optimizer.zero_grad()
outputs = model(X_train)
loss = criterion(outputs, y_train)
# Backward pass
loss.backward()
optimizer.step()
# Evaluation
if epoch % 10 == 0:
model.eval()
with torch.no_grad():
test_outputs = model(X_test)
test_loss = criterion(test_outputs, y_test)
test_acc = (test_outputs.argmax(1) == y_test).float().mean()
print(f"Epoch {epoch}: Loss={loss:.4f}, Test Loss={test_loss:.4f}, Test Acc={test_acc:.4f}")
model.train()