Source code for uniqc.algorithms.core.training.qcnn
"""Quantum Convolutional Neural Network (QCNN) with TorchQuantum backend.
Implements convolutional and pooling layers on qubits for quantum state
classification, using TorchQuantum's native PyTorch autograd.
"""
from __future__ import annotations
import torch
import torch.nn as nn
from uniqc.simulator.torchquantum_simulator import TorchQuantumSimulator
__all__ = ["QCNNClassifier"]
def _build_qcnn_circuit(
params: torch.Tensor,
n_qubits: int,
) -> tuple[list, int, dict]:
"""Build QCNN circuit with conv and pooling layers.
Architecture:
1. Conv layers: parameterized 2-qubit gates on neighboring pairs
2. Pool layers: CNOT-based reduction (halve active qubits)
3. Final measurement on remaining qubits
Returns (opcode_list, n_qubits, param_overrides).
"""
from uniqc.circuit_builder import Circuit
circuit = Circuit(n_qubits)
param_overrides = {}
param_idx = 0
active_qubits = list(range(n_qubits))
while len(active_qubits) > 1:
# Convolution layer: parameterized 2-qubit gates on pairs
for i in range(0, len(active_qubits) - 1, 2):
q1, q2 = active_qubits[i], active_qubits[i + 1]
# RZ on q1
opcode_idx = len(circuit.opcode_list)
circuit.rz(q1, 0.0)
param_overrides[opcode_idx] = params[param_idx].unsqueeze(0).unsqueeze(0)
param_idx += 1
# RY on q2
opcode_idx = len(circuit.opcode_list)
circuit.ry(q2, 0.0)
param_overrides[opcode_idx] = params[param_idx].unsqueeze(0).unsqueeze(0)
param_idx += 1
# CNOT entangle
circuit.cx(q1, q2)
# RZ on q2
opcode_idx = len(circuit.opcode_list)
circuit.rz(q2, 0.0)
param_overrides[opcode_idx] = params[param_idx].unsqueeze(0).unsqueeze(0)
param_idx += 1
# Pooling layer: CNOT to halve active qubits
new_active = []
for i in range(0, len(active_qubits) - 1, 2):
q1, q2 = active_qubits[i], active_qubits[i + 1]
circuit.cx(q2, q1)
new_active.append(q1) # keep q1, discard q2
if len(active_qubits) % 2 == 1:
new_active.append(active_qubits[-1])
active_qubits = new_active
return circuit.opcode_list, n_qubits, param_overrides
[docs]
class QCNNClassifier(nn.Module):
"""Quantum Convolutional Neural Network for classification.
Applies convolutional and pooling layers that progressively reduce
the number of active qubits, then measures the final qubit.
Args:
n_qubits: Number of qubits (preferably power of 2).
n_classes: Number of output classes.
"""
def __init__(self, n_qubits: int = 8, n_classes: int = 2):
super().__init__()
self.n_qubits = n_qubits
self.n_classes = n_classes
# Calculate number of parameters
# Each conv layer uses 3 params per pair, pooling uses 0
# Number of pairs halves each round
n_params = 0
active = n_qubits
while active > 1:
n_pairs = active // 2
n_params += n_pairs * 3
active = (active + 1) // 2
self.n_params = n_params
self.params = nn.Parameter(torch.randn(n_params) * 0.1)
# Measurement: <Z_0> for binary, or multi-qubit for multi-class
if n_classes == 2:
self.hamiltonian = [("Z" + "I" * (n_qubits - 1), 1.0)]
else:
self.hamiltonian = [("I" * n_qubits, 1.0)] # placeholder
self._sim = TorchQuantumSimulator(n_wires=n_qubits)
[docs]
def forward(self, x: torch.Tensor | None = None) -> torch.Tensor:
"""Classify quantum state.
Returns:
Probability-like output in [0, 1].
"""
opcode_list, n_qubits, param_overrides = _build_qcnn_circuit(self.params, self.n_qubits)
expval = self._sim.expectation(opcode_list, self.hamiltonian, param_overrides)
return (expval + 1.0) / 2.0