mirror of
https://github.com/K-Dense-AI/claude-scientific-skills.git
synced 2026-01-26 16:58:56 +08:00
Add support for PennyLane: a cross-platform library for quantum computing, quantum machine learning, and quantum chemistry.
This commit is contained in:
220
scientific-skills/pennylane/SKILL.md
Normal file
220
scientific-skills/pennylane/SKILL.md
Normal file
@@ -0,0 +1,220 @@
|
||||
---
|
||||
name: pennylane
|
||||
description: Cross-platform Python library for quantum computing, quantum machine learning, and quantum chemistry. Enables building and training quantum circuits with automatic differentiation, seamless integration with PyTorch/JAX/TensorFlow, and device-independent execution across simulators and quantum hardware (IBM, Amazon Braket, Google, Rigetti, IonQ, etc.). Use when working with quantum circuits, variational quantum algorithms (VQE, QAOA), quantum neural networks, hybrid quantum-classical models, molecular simulations, quantum chemistry calculations, or any quantum computing tasks requiring gradient-based optimization, hardware-agnostic programming, or quantum machine learning workflows.
|
||||
---
|
||||
|
||||
# PennyLane
|
||||
|
||||
## Overview
|
||||
|
||||
PennyLane is a quantum computing library that enables training quantum computers like neural networks. It provides automatic differentiation of quantum circuits, device-independent programming, and seamless integration with classical machine learning frameworks.
|
||||
|
||||
## Installation
|
||||
|
||||
Install using uv:
|
||||
|
||||
```bash
|
||||
uv pip install pennylane
|
||||
```
|
||||
|
||||
For quantum hardware access, install device plugins:
|
||||
|
||||
```bash
|
||||
# IBM Quantum
|
||||
uv pip install pennylane-qiskit
|
||||
|
||||
# Amazon Braket
|
||||
uv pip install amazon-braket-pennylane-plugin
|
||||
|
||||
# Google Cirq
|
||||
uv pip install pennylane-cirq
|
||||
|
||||
# Rigetti Forest
|
||||
uv pip install pennylane-rigetti
|
||||
|
||||
# IonQ
|
||||
uv pip install pennylane-ionq
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
Build a quantum circuit and optimize its parameters:
|
||||
|
||||
```python
|
||||
import pennylane as qml
|
||||
from pennylane import numpy as np
|
||||
|
||||
# Create device
|
||||
dev = qml.device('default.qubit', wires=2)
|
||||
|
||||
# Define quantum circuit
|
||||
@qml.qnode(dev)
|
||||
def circuit(params):
|
||||
qml.RX(params[0], wires=0)
|
||||
qml.RY(params[1], wires=1)
|
||||
qml.CNOT(wires=[0, 1])
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Optimize parameters
|
||||
opt = qml.GradientDescentOptimizer(stepsize=0.1)
|
||||
params = np.array([0.1, 0.2], requires_grad=True)
|
||||
|
||||
for i in range(100):
|
||||
params = opt.step(circuit, params)
|
||||
```
|
||||
|
||||
## Core Capabilities
|
||||
|
||||
### 1. Quantum Circuit Construction
|
||||
|
||||
Build circuits with gates, measurements, and state preparation. See `references/quantum_circuits.md` for:
|
||||
- Single and multi-qubit gates
|
||||
- Controlled operations and conditional logic
|
||||
- Mid-circuit measurements and adaptive circuits
|
||||
- Various measurement types (expectation, probability, samples)
|
||||
- Circuit inspection and debugging
|
||||
|
||||
### 2. Quantum Machine Learning
|
||||
|
||||
Create hybrid quantum-classical models. See `references/quantum_ml.md` for:
|
||||
- Integration with PyTorch, JAX, TensorFlow
|
||||
- Quantum neural networks and variational classifiers
|
||||
- Data encoding strategies (angle, amplitude, basis, IQP)
|
||||
- Training hybrid models with backpropagation
|
||||
- Transfer learning with quantum circuits
|
||||
|
||||
### 3. Quantum Chemistry
|
||||
|
||||
Simulate molecules and compute ground state energies. See `references/quantum_chemistry.md` for:
|
||||
- Molecular Hamiltonian generation
|
||||
- Variational Quantum Eigensolver (VQE)
|
||||
- UCCSD ansatz for chemistry
|
||||
- Geometry optimization and dissociation curves
|
||||
- Molecular property calculations
|
||||
|
||||
### 4. Device Management
|
||||
|
||||
Execute on simulators or quantum hardware. See `references/devices_backends.md` for:
|
||||
- Built-in simulators (default.qubit, lightning.qubit, default.mixed)
|
||||
- Hardware plugins (IBM, Amazon Braket, Google, Rigetti, IonQ)
|
||||
- Device selection and configuration
|
||||
- Performance optimization and caching
|
||||
- GPU acceleration and JIT compilation
|
||||
|
||||
### 5. Optimization
|
||||
|
||||
Train quantum circuits with various optimizers. See `references/optimization.md` for:
|
||||
- Built-in optimizers (Adam, gradient descent, momentum, RMSProp)
|
||||
- Gradient computation methods (backprop, parameter-shift, adjoint)
|
||||
- Variational algorithms (VQE, QAOA)
|
||||
- Training strategies (learning rate schedules, mini-batches)
|
||||
- Handling barren plateaus and local minima
|
||||
|
||||
### 6. Advanced Features
|
||||
|
||||
Leverage templates, transforms, and compilation. See `references/advanced_features.md` for:
|
||||
- Circuit templates and layers
|
||||
- Transforms and circuit optimization
|
||||
- Pulse-level programming
|
||||
- Catalyst JIT compilation
|
||||
- Noise models and error mitigation
|
||||
- Resource estimation
|
||||
|
||||
## Common Workflows
|
||||
|
||||
### Train a Variational Classifier
|
||||
|
||||
```python
|
||||
# 1. Define ansatz
|
||||
@qml.qnode(dev)
|
||||
def classifier(x, weights):
|
||||
# Encode data
|
||||
qml.AngleEmbedding(x, wires=range(4))
|
||||
|
||||
# Variational layers
|
||||
qml.StronglyEntanglingLayers(weights, wires=range(4))
|
||||
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# 2. Train
|
||||
opt = qml.AdamOptimizer(stepsize=0.01)
|
||||
weights = np.random.random((3, 4, 3)) # 3 layers, 4 wires
|
||||
|
||||
for epoch in range(100):
|
||||
for x, y in zip(X_train, y_train):
|
||||
weights = opt.step(lambda w: (classifier(x, w) - y)**2, weights)
|
||||
```
|
||||
|
||||
### Run VQE for Molecular Ground State
|
||||
|
||||
```python
|
||||
from pennylane import qchem
|
||||
|
||||
# 1. Build Hamiltonian
|
||||
symbols = ['H', 'H']
|
||||
coords = np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.74])
|
||||
H, n_qubits = qchem.molecular_hamiltonian(symbols, coords)
|
||||
|
||||
# 2. Define ansatz
|
||||
@qml.qnode(dev)
|
||||
def vqe_circuit(params):
|
||||
qml.BasisState(qchem.hf_state(2, n_qubits), wires=range(n_qubits))
|
||||
qml.UCCSD(params, wires=range(n_qubits))
|
||||
return qml.expval(H)
|
||||
|
||||
# 3. Optimize
|
||||
opt = qml.AdamOptimizer(stepsize=0.1)
|
||||
params = np.zeros(10, requires_grad=True)
|
||||
|
||||
for i in range(100):
|
||||
params, energy = opt.step_and_cost(vqe_circuit, params)
|
||||
print(f"Step {i}: Energy = {energy:.6f} Ha")
|
||||
```
|
||||
|
||||
### Switch Between Devices
|
||||
|
||||
```python
|
||||
# Same circuit, different backends
|
||||
circuit_def = lambda dev: qml.qnode(dev)(circuit_function)
|
||||
|
||||
# Test on simulator
|
||||
dev_sim = qml.device('default.qubit', wires=4)
|
||||
result_sim = circuit_def(dev_sim)(params)
|
||||
|
||||
# Run on quantum hardware
|
||||
dev_hw = qml.device('qiskit.ibmq', wires=4, backend='ibmq_manila')
|
||||
result_hw = circuit_def(dev_hw)(params)
|
||||
```
|
||||
|
||||
## Detailed Documentation
|
||||
|
||||
For comprehensive coverage of specific topics, consult the reference files:
|
||||
|
||||
- **Getting started**: `references/getting_started.md` - Installation, basic concepts, first steps
|
||||
- **Quantum circuits**: `references/quantum_circuits.md` - Gates, measurements, circuit patterns
|
||||
- **Quantum ML**: `references/quantum_ml.md` - Hybrid models, framework integration, QNNs
|
||||
- **Quantum chemistry**: `references/quantum_chemistry.md` - VQE, molecular Hamiltonians, chemistry workflows
|
||||
- **Devices**: `references/devices_backends.md` - Simulators, hardware plugins, device configuration
|
||||
- **Optimization**: `references/optimization.md` - Optimizers, gradients, variational algorithms
|
||||
- **Advanced**: `references/advanced_features.md` - Templates, transforms, JIT compilation, noise
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Start with simulators** - Test on `default.qubit` before deploying to hardware
|
||||
2. **Use parameter-shift for hardware** - Backpropagation only works on simulators
|
||||
3. **Choose appropriate encodings** - Match data encoding to problem structure
|
||||
4. **Initialize carefully** - Use small random values to avoid barren plateaus
|
||||
5. **Monitor gradients** - Check for vanishing gradients in deep circuits
|
||||
6. **Cache devices** - Reuse device objects to reduce initialization overhead
|
||||
7. **Profile circuits** - Use `qml.specs()` to analyze circuit complexity
|
||||
8. **Test locally** - Validate on simulators before submitting to hardware
|
||||
9. **Use templates** - Leverage built-in templates for common circuit patterns
|
||||
10. **Compile when possible** - Use Catalyst JIT for performance-critical code
|
||||
|
||||
## Resources
|
||||
|
||||
- Official documentation: https://docs.pennylane.ai
|
||||
- Codebook (tutorials): https://pennylane.ai/codebook
|
||||
- QML demonstrations: https://pennylane.ai/qml/demonstrations
|
||||
- Community forum: https://discuss.pennylane.ai
|
||||
- GitHub: https://github.com/PennyLaneAI/pennylane
|
||||
667
scientific-skills/pennylane/references/advanced_features.md
Normal file
667
scientific-skills/pennylane/references/advanced_features.md
Normal file
@@ -0,0 +1,667 @@
|
||||
# Advanced Features in PennyLane
|
||||
|
||||
## Table of Contents
|
||||
1. [Templates and Layers](#templates-and-layers)
|
||||
2. [Transforms](#transforms)
|
||||
3. [Pulse Programming](#pulse-programming)
|
||||
4. [Catalyst and JIT Compilation](#catalyst-and-jit-compilation)
|
||||
5. [Adaptive Circuits](#adaptive-circuits)
|
||||
6. [Noise Models](#noise-models)
|
||||
7. [Resource Estimation](#resource-estimation)
|
||||
|
||||
## Templates and Layers
|
||||
|
||||
### Built-in Templates
|
||||
|
||||
```python
|
||||
import pennylane as qml
|
||||
from pennylane.templates import *
|
||||
from pennylane import numpy as np
|
||||
|
||||
dev = qml.device('default.qubit', wires=4)
|
||||
|
||||
# Strongly Entangling Layers
|
||||
@qml.qnode(dev)
|
||||
def circuit_sel(weights):
|
||||
StronglyEntanglingLayers(weights, wires=range(4))
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Generate appropriately shaped weights
|
||||
n_layers = 3
|
||||
n_wires = 4
|
||||
shape = StronglyEntanglingLayers.shape(n_layers, n_wires)
|
||||
weights = np.random.random(shape)
|
||||
|
||||
result = circuit_sel(weights)
|
||||
```
|
||||
|
||||
### Basic Entangler Layers
|
||||
|
||||
```python
|
||||
@qml.qnode(dev)
|
||||
def circuit_bel(weights):
|
||||
# Simple entangling layer
|
||||
BasicEntanglerLayers(weights, wires=range(4))
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
n_layers = 2
|
||||
weights = np.random.random((n_layers, 4))
|
||||
```
|
||||
|
||||
### Random Layers
|
||||
|
||||
```python
|
||||
@qml.qnode(dev)
|
||||
def circuit_random(weights):
|
||||
# Random circuit structure
|
||||
RandomLayers(weights, wires=range(4))
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
n_layers = 5
|
||||
weights = np.random.random((n_layers, 4))
|
||||
```
|
||||
|
||||
### Simplified Two Design
|
||||
|
||||
```python
|
||||
@qml.qnode(dev)
|
||||
def circuit_s2d(weights):
|
||||
# Simplified two-design
|
||||
SimplifiedTwoDesign(initial_layer_weights=weights[0],
|
||||
weights=weights[1:],
|
||||
wires=range(4))
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
```
|
||||
|
||||
### Particle-Conserving Layers
|
||||
|
||||
```python
|
||||
@qml.qnode(dev)
|
||||
def circuit_particle_conserving(weights):
|
||||
# Preserve particle number (useful for chemistry)
|
||||
ParticleConservingU1(weights, wires=range(4))
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
shape = ParticleConservingU1.shape(n_layers=2, n_wires=4)
|
||||
weights = np.random.random(shape)
|
||||
```
|
||||
|
||||
### Embedding Templates
|
||||
|
||||
```python
|
||||
# Angle embedding
|
||||
@qml.qnode(dev)
|
||||
def angle_embed(features):
|
||||
AngleEmbedding(features, wires=range(4))
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
features = np.array([0.1, 0.2, 0.3, 0.4])
|
||||
|
||||
# Amplitude embedding
|
||||
@qml.qnode(dev)
|
||||
def amplitude_embed(features):
|
||||
AmplitudeEmbedding(features, wires=range(2), normalize=True)
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
features = np.array([0.5, 0.5, 0.5, 0.5])
|
||||
|
||||
# IQP embedding
|
||||
@qml.qnode(dev)
|
||||
def iqp_embed(features):
|
||||
IQPEmbedding(features, wires=range(4), n_repeats=2)
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
```
|
||||
|
||||
### Custom Templates
|
||||
|
||||
```python
|
||||
def custom_layer(weights, wires):
|
||||
"""Define custom template."""
|
||||
n_wires = len(wires)
|
||||
|
||||
# Rotation layer
|
||||
for i, wire in enumerate(wires):
|
||||
qml.RY(weights[i], wires=wire)
|
||||
|
||||
# Entanglement pattern
|
||||
for i in range(0, n_wires-1, 2):
|
||||
qml.CNOT(wires=[wires[i], wires[i+1]])
|
||||
|
||||
for i in range(1, n_wires-1, 2):
|
||||
qml.CNOT(wires=[wires[i], wires[i+1]])
|
||||
|
||||
@qml.qnode(dev)
|
||||
def circuit_custom(weights, n_layers):
|
||||
for i in range(n_layers):
|
||||
custom_layer(weights[i], wires=range(4))
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
```
|
||||
|
||||
## Transforms
|
||||
|
||||
### Circuit Transformations
|
||||
|
||||
```python
|
||||
# Cancel adjacent inverse operations
|
||||
from pennylane import transforms
|
||||
|
||||
@transforms.cancel_inverses
|
||||
@qml.qnode(dev)
|
||||
def circuit():
|
||||
qml.Hadamard(wires=0)
|
||||
qml.Hadamard(wires=0) # These cancel
|
||||
qml.RX(0.5, wires=1)
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Merge rotations
|
||||
@transforms.merge_rotations
|
||||
@qml.qnode(dev)
|
||||
def circuit():
|
||||
qml.RX(0.1, wires=0)
|
||||
qml.RX(0.2, wires=0) # These merge into single RX(0.3)
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Commute measurements to end
|
||||
@transforms.commute_controlled
|
||||
@qml.qnode(dev)
|
||||
def circuit():
|
||||
qml.Hadamard(wires=0)
|
||||
qml.CNOT(wires=[0, 1])
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
```
|
||||
|
||||
### Parameter Broadcasting
|
||||
|
||||
```python
|
||||
# Execute circuit with multiple parameter sets
|
||||
@qml.qnode(dev)
|
||||
def circuit(x):
|
||||
qml.RX(x, wires=0)
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Broadcast over parameters
|
||||
params = np.array([0.1, 0.2, 0.3, 0.4])
|
||||
results = circuit(params) # Returns array of results
|
||||
```
|
||||
|
||||
### Metric Tensor
|
||||
|
||||
```python
|
||||
# Compute quantum geometric tensor
|
||||
@qml.qnode(dev)
|
||||
def variational_circuit(params):
|
||||
for i, param in enumerate(params):
|
||||
qml.RY(param, wires=i % 4)
|
||||
for i in range(3):
|
||||
qml.CNOT(wires=[i, i+1])
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
params = np.array([0.1, 0.2, 0.3, 0.4], requires_grad=True)
|
||||
|
||||
# Get metric tensor (useful for quantum natural gradient)
|
||||
metric_tensor = qml.metric_tensor(variational_circuit)(params)
|
||||
```
|
||||
|
||||
### Tape Manipulation
|
||||
|
||||
```python
|
||||
with qml.tape.QuantumTape() as tape:
|
||||
qml.Hadamard(wires=0)
|
||||
qml.CNOT(wires=[0, 1])
|
||||
qml.RX(0.5, wires=1)
|
||||
qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Inspect tape
|
||||
print("Operations:", tape.operations)
|
||||
print("Observables:", tape.observables)
|
||||
|
||||
# Transform tape
|
||||
expanded_tape = transforms.expand_tape(tape)
|
||||
optimized_tape = transforms.cancel_inverses(tape)
|
||||
```
|
||||
|
||||
### Decomposition
|
||||
|
||||
```python
|
||||
# Decompose operations into native gate set
|
||||
@qml.qnode(dev)
|
||||
def circuit():
|
||||
qml.U3(0.1, 0.2, 0.3, wires=0) # Arbitrary single-qubit gate
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Decompose U3 into RZ, RY
|
||||
decomposed = qml.transforms.decompose(circuit, gate_set={qml.RZ, qml.RY, qml.CNOT})
|
||||
```
|
||||
|
||||
## Pulse Programming
|
||||
|
||||
### Pulse-Level Control
|
||||
|
||||
```python
|
||||
from pennylane import pulse
|
||||
|
||||
# Define pulse envelope
|
||||
def gaussian_pulse(t, amplitude, sigma):
|
||||
return amplitude * np.exp(-(t**2) / (2 * sigma**2))
|
||||
|
||||
# Create pulse program
|
||||
dev_pulse = qml.device('default.qubit', wires=2)
|
||||
|
||||
@qml.qnode(dev_pulse)
|
||||
def pulse_circuit():
|
||||
# Apply pulse to qubit
|
||||
pulse.drive(
|
||||
amplitude=lambda t: gaussian_pulse(t, 1.0, 0.5),
|
||||
phase=0.0,
|
||||
freq=5.0,
|
||||
wires=0,
|
||||
duration=2.0
|
||||
)
|
||||
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
```
|
||||
|
||||
### Pulse Sequences
|
||||
|
||||
```python
|
||||
@qml.qnode(dev_pulse)
|
||||
def pulse_sequence():
|
||||
# Sequence of pulses
|
||||
duration = 1.0
|
||||
|
||||
# X pulse
|
||||
pulse.drive(
|
||||
amplitude=lambda t: np.sin(np.pi * t / duration),
|
||||
phase=0.0,
|
||||
freq=5.0,
|
||||
wires=0,
|
||||
duration=duration
|
||||
)
|
||||
|
||||
# Y pulse
|
||||
pulse.drive(
|
||||
amplitude=lambda t: np.sin(np.pi * t / duration),
|
||||
phase=np.pi/2,
|
||||
freq=5.0,
|
||||
wires=0,
|
||||
duration=duration
|
||||
)
|
||||
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
```
|
||||
|
||||
### Optimal Control
|
||||
|
||||
```python
|
||||
def optimize_pulse(target_gate):
|
||||
"""Optimize pulse to implement target gate."""
|
||||
|
||||
def pulse_fn(t, params):
|
||||
# Parameterized pulse
|
||||
return params[0] * np.sin(params[1] * t + params[2])
|
||||
|
||||
@qml.qnode(dev_pulse)
|
||||
def pulse_circuit(params):
|
||||
pulse.drive(
|
||||
amplitude=lambda t: pulse_fn(t, params),
|
||||
phase=0.0,
|
||||
freq=5.0,
|
||||
wires=0,
|
||||
duration=2.0
|
||||
)
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Cost: fidelity with target
|
||||
def cost(params):
|
||||
result_state = pulse_circuit(params)
|
||||
target_state = target_gate()
|
||||
return 1 - np.abs(np.vdot(result_state, target_state))**2
|
||||
|
||||
# Optimize
|
||||
opt = qml.AdamOptimizer(stepsize=0.01)
|
||||
params = np.random.random(3, requires_grad=True)
|
||||
|
||||
for i in range(100):
|
||||
params = opt.step(cost, params)
|
||||
|
||||
return params
|
||||
```
|
||||
|
||||
## Catalyst and JIT Compilation
|
||||
|
||||
### Basic JIT Compilation
|
||||
|
||||
```python
|
||||
from catalyst import qjit
|
||||
|
||||
dev = qml.device('lightning.qubit', wires=4)
|
||||
|
||||
@qjit # Just-in-time compile
|
||||
@qml.qnode(dev)
|
||||
def compiled_circuit(x):
|
||||
qml.RX(x, wires=0)
|
||||
qml.Hadamard(wires=1)
|
||||
qml.CNOT(wires=[0, 1])
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# First call compiles, subsequent calls are fast
|
||||
result = compiled_circuit(0.5)
|
||||
```
|
||||
|
||||
### Compiled Control Flow
|
||||
|
||||
```python
|
||||
@qjit
|
||||
@qml.qnode(dev)
|
||||
def circuit_with_loops(n):
|
||||
qml.Hadamard(wires=0)
|
||||
|
||||
# Compiled for loop
|
||||
@qml.for_loop(0, n, 1)
|
||||
def loop_body(i):
|
||||
qml.RX(0.1 * i, wires=0)
|
||||
|
||||
loop_body()
|
||||
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
result = circuit_with_loops(10)
|
||||
```
|
||||
|
||||
### Compiled While Loops
|
||||
|
||||
```python
|
||||
@qjit
|
||||
@qml.qnode(dev)
|
||||
def circuit_while():
|
||||
qml.Hadamard(wires=0)
|
||||
|
||||
# Compiled while loop
|
||||
@qml.while_loop(lambda i: i < 10)
|
||||
def loop_body(i):
|
||||
qml.RX(0.1, wires=0)
|
||||
return i + 1
|
||||
|
||||
loop_body(0)
|
||||
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
```
|
||||
|
||||
### Autodiff with JIT
|
||||
|
||||
```python
|
||||
@qjit
|
||||
@qml.qnode(dev)
|
||||
def circuit(params):
|
||||
qml.RX(params[0], wires=0)
|
||||
qml.RY(params[1], wires=1)
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Compiled gradient
|
||||
grad_fn = qjit(qml.grad(circuit))
|
||||
|
||||
params = np.array([0.1, 0.2])
|
||||
gradients = grad_fn(params)
|
||||
```
|
||||
|
||||
## Adaptive Circuits
|
||||
|
||||
### Mid-Circuit Measurements with Feedback
|
||||
|
||||
```python
|
||||
dev = qml.device('default.qubit', wires=3)
|
||||
|
||||
@qml.qnode(dev)
|
||||
def adaptive_circuit():
|
||||
# Prepare state
|
||||
qml.Hadamard(wires=0)
|
||||
qml.CNOT(wires=[0, 1])
|
||||
|
||||
# Mid-circuit measurement
|
||||
m0 = qml.measure(0)
|
||||
|
||||
# Conditional operation based on measurement
|
||||
qml.cond(m0, qml.PauliX)(wires=2)
|
||||
|
||||
# Another measurement
|
||||
m1 = qml.measure(1)
|
||||
|
||||
# More complex conditional
|
||||
qml.cond(m0 & m1, qml.Hadamard)(wires=2)
|
||||
|
||||
return qml.expval(qml.PauliZ(2))
|
||||
```
|
||||
|
||||
### Dynamic Circuit Depth
|
||||
|
||||
```python
|
||||
@qml.qnode(dev)
|
||||
def dynamic_depth_circuit(max_depth):
|
||||
qml.Hadamard(wires=0)
|
||||
|
||||
converged = False
|
||||
depth = 0
|
||||
|
||||
while not converged and depth < max_depth:
|
||||
# Apply layer
|
||||
qml.RX(0.1 * depth, wires=0)
|
||||
|
||||
# Check convergence via measurement
|
||||
m = qml.measure(0, reset=True)
|
||||
|
||||
if m == 1:
|
||||
converged = True
|
||||
|
||||
depth += 1
|
||||
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
```
|
||||
|
||||
### Quantum Error Correction
|
||||
|
||||
```python
|
||||
def bit_flip_code():
|
||||
"""3-qubit bit flip error correction."""
|
||||
|
||||
@qml.qnode(dev)
|
||||
def circuit():
|
||||
# Encode logical qubit
|
||||
qml.CNOT(wires=[0, 1])
|
||||
qml.CNOT(wires=[0, 2])
|
||||
|
||||
# Simulate error
|
||||
qml.PauliX(wires=1) # Bit flip on qubit 1
|
||||
|
||||
# Syndrome measurement
|
||||
qml.CNOT(wires=[0, 3])
|
||||
qml.CNOT(wires=[1, 3])
|
||||
s1 = qml.measure(3)
|
||||
|
||||
qml.CNOT(wires=[1, 4])
|
||||
qml.CNOT(wires=[2, 4])
|
||||
s2 = qml.measure(4)
|
||||
|
||||
# Correction
|
||||
qml.cond(s1 & ~s2, qml.PauliX)(wires=0)
|
||||
qml.cond(s1 & s2, qml.PauliX)(wires=1)
|
||||
qml.cond(~s1 & s2, qml.PauliX)(wires=2)
|
||||
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
return circuit()
|
||||
```
|
||||
|
||||
## Noise Models
|
||||
|
||||
### Built-in Noise Channels
|
||||
|
||||
```python
|
||||
dev_noisy = qml.device('default.mixed', wires=2)
|
||||
|
||||
@qml.qnode(dev_noisy)
|
||||
def noisy_circuit():
|
||||
qml.Hadamard(wires=0)
|
||||
|
||||
# Depolarizing noise
|
||||
qml.DepolarizingChannel(0.1, wires=0)
|
||||
|
||||
qml.CNOT(wires=[0, 1])
|
||||
|
||||
# Amplitude damping (energy loss)
|
||||
qml.AmplitudeDamping(0.05, wires=0)
|
||||
|
||||
# Phase damping (dephasing)
|
||||
qml.PhaseDamping(0.05, wires=1)
|
||||
|
||||
# Bit flip error
|
||||
qml.BitFlip(0.01, wires=0)
|
||||
|
||||
# Phase flip error
|
||||
qml.PhaseFlip(0.01, wires=1)
|
||||
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
```
|
||||
|
||||
### Custom Noise Models
|
||||
|
||||
```python
|
||||
def custom_noise(p):
|
||||
"""Custom noise channel."""
|
||||
# Kraus operators for custom noise
|
||||
K0 = np.sqrt(1 - p) * np.eye(2)
|
||||
K1 = np.sqrt(p/3) * np.array([[0, 1], [1, 0]]) # X
|
||||
K2 = np.sqrt(p/3) * np.array([[0, -1j], [1j, 0]]) # Y
|
||||
K3 = np.sqrt(p/3) * np.array([[1, 0], [0, -1]]) # Z
|
||||
|
||||
return [K0, K1, K2, K3]
|
||||
|
||||
@qml.qnode(dev_noisy)
|
||||
def circuit_custom_noise():
|
||||
qml.Hadamard(wires=0)
|
||||
|
||||
# Apply custom noise
|
||||
qml.QubitChannel(custom_noise(0.1), wires=0)
|
||||
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
```
|
||||
|
||||
### Noise-Aware Training
|
||||
|
||||
```python
|
||||
def train_with_noise(circuit, params, noise_level):
|
||||
"""Train considering hardware noise."""
|
||||
|
||||
dev_ideal = qml.device('default.qubit', wires=4)
|
||||
dev_noisy = qml.device('default.mixed', wires=4)
|
||||
|
||||
@qml.qnode(dev_noisy)
|
||||
def noisy_circuit(p):
|
||||
circuit(p)
|
||||
|
||||
# Add noise after each gate
|
||||
for wire in range(4):
|
||||
qml.DepolarizingChannel(noise_level, wires=wire)
|
||||
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Optimize noisy circuit
|
||||
opt = qml.AdamOptimizer(stepsize=0.01)
|
||||
|
||||
for i in range(100):
|
||||
params = opt.step(noisy_circuit, params)
|
||||
|
||||
return params
|
||||
```
|
||||
|
||||
## Resource Estimation
|
||||
|
||||
### Count Operations
|
||||
|
||||
```python
|
||||
@qml.qnode(dev)
|
||||
def circuit(params):
|
||||
for i, param in enumerate(params):
|
||||
qml.RY(param, wires=i % 4)
|
||||
for i in range(3):
|
||||
qml.CNOT(wires=[i, i+1])
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
params = np.random.random(10)
|
||||
|
||||
# Get resource information
|
||||
specs = qml.specs(circuit)(params)
|
||||
|
||||
print(f"Total gates: {specs['num_operations']}")
|
||||
print(f"Circuit depth: {specs['depth']}")
|
||||
print(f"Gate types: {specs['gate_types']}")
|
||||
print(f"Gate sizes: {specs['gate_sizes']}")
|
||||
print(f"Trainable params: {specs['num_trainable_params']}")
|
||||
```
|
||||
|
||||
### Estimate Execution Time
|
||||
|
||||
```python
|
||||
import time
|
||||
|
||||
def estimate_runtime(circuit, params, n_runs=10):
|
||||
"""Estimate circuit execution time."""
|
||||
|
||||
times = []
|
||||
for _ in range(n_runs):
|
||||
start = time.time()
|
||||
result = circuit(params)
|
||||
times.append(time.time() - start)
|
||||
|
||||
mean_time = np.mean(times)
|
||||
std_time = np.std(times)
|
||||
|
||||
print(f"Mean execution time: {mean_time*1000:.2f} ms")
|
||||
print(f"Std deviation: {std_time*1000:.2f} ms")
|
||||
|
||||
return mean_time
|
||||
```
|
||||
|
||||
### Resource Requirements
|
||||
|
||||
```python
|
||||
def estimate_resources(n_qubits, depth):
|
||||
"""Estimate computational resources."""
|
||||
|
||||
# Classical simulation cost
|
||||
state_vector_size = 2**n_qubits * 16 # bytes (complex128)
|
||||
|
||||
# Number of operations
|
||||
n_operations = depth * n_qubits
|
||||
|
||||
print(f"Qubits: {n_qubits}")
|
||||
print(f"Circuit depth: {depth}")
|
||||
print(f"State vector size: {state_vector_size / 1e9:.2f} GB")
|
||||
print(f"Number of operations: {n_operations}")
|
||||
|
||||
# Approximate simulation time (very rough)
|
||||
gate_time = 1e-6 # seconds per gate (varies by device)
|
||||
total_time = n_operations * gate_time * 2**n_qubits
|
||||
|
||||
print(f"Estimated simulation time: {total_time:.4f} seconds")
|
||||
|
||||
return {
|
||||
'memory': state_vector_size,
|
||||
'operations': n_operations,
|
||||
'time': total_time
|
||||
}
|
||||
|
||||
estimate_resources(n_qubits=20, depth=100)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use templates** - Leverage built-in templates for common patterns
|
||||
2. **Apply transforms** - Optimize circuits with transforms before execution
|
||||
3. **Compile with JIT** - Use Catalyst for performance-critical code
|
||||
4. **Consider noise** - Include noise models for realistic hardware simulation
|
||||
5. **Estimate resources** - Profile circuits before running on hardware
|
||||
6. **Use adaptive circuits** - Implement mid-circuit measurements for flexibility
|
||||
7. **Optimize pulses** - Fine-tune pulse parameters for hardware control
|
||||
8. **Cache compilations** - Reuse compiled circuits
|
||||
9. **Monitor performance** - Track execution times and resource usage
|
||||
10. **Test thoroughly** - Validate on simulators before hardware deployment
|
||||
596
scientific-skills/pennylane/references/devices_backends.md
Normal file
596
scientific-skills/pennylane/references/devices_backends.md
Normal file
@@ -0,0 +1,596 @@
|
||||
# Devices and Backends in PennyLane
|
||||
|
||||
## Table of Contents
|
||||
1. [Built-in Simulators](#built-in-simulators)
|
||||
2. [Hardware Plugins](#hardware-plugins)
|
||||
3. [Device Selection](#device-selection)
|
||||
4. [Device Configuration](#device-configuration)
|
||||
5. [Custom Devices](#custom-devices)
|
||||
6. [Performance Optimization](#performance-optimization)
|
||||
|
||||
## Built-in Simulators
|
||||
|
||||
### default.qubit
|
||||
|
||||
General-purpose state vector simulator:
|
||||
|
||||
```python
|
||||
import pennylane as qml
|
||||
|
||||
# Basic initialization
|
||||
dev = qml.device('default.qubit', wires=4)
|
||||
|
||||
# With shots (sampling mode)
|
||||
dev = qml.device('default.qubit', wires=4, shots=1000)
|
||||
|
||||
# Specify wire labels
|
||||
dev = qml.device('default.qubit', wires=['a', 'b', 'c', 'd'])
|
||||
```
|
||||
|
||||
### default.mixed
|
||||
|
||||
Mixed-state simulator for noisy quantum systems:
|
||||
|
||||
```python
|
||||
# Supports density matrix simulation
|
||||
dev = qml.device('default.mixed', wires=2)
|
||||
|
||||
@qml.qnode(dev)
|
||||
def noisy_circuit():
|
||||
qml.Hadamard(wires=0)
|
||||
|
||||
# Apply noise
|
||||
qml.DepolarizingChannel(0.1, wires=0)
|
||||
|
||||
qml.CNOT(wires=[0, 1])
|
||||
|
||||
# Amplitude damping
|
||||
qml.AmplitudeDamping(0.05, wires=1)
|
||||
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
```
|
||||
|
||||
### default.qubit.torch, default.qubit.tf, default.qubit.jax
|
||||
|
||||
Framework-specific simulators with better integration:
|
||||
|
||||
```python
|
||||
# PyTorch
|
||||
dev = qml.device('default.qubit.torch', wires=4)
|
||||
|
||||
# TensorFlow
|
||||
dev = qml.device('default.qubit.tf', wires=4)
|
||||
|
||||
# JAX
|
||||
dev = qml.device('default.qubit.jax', wires=4)
|
||||
```
|
||||
|
||||
### lightning.qubit
|
||||
|
||||
High-performance C++ simulator:
|
||||
|
||||
```python
|
||||
# Faster than default.qubit
|
||||
dev = qml.device('lightning.qubit', wires=20)
|
||||
|
||||
# Supports larger systems efficiently
|
||||
@qml.qnode(dev)
|
||||
def large_circuit():
|
||||
for i in range(20):
|
||||
qml.Hadamard(wires=i)
|
||||
|
||||
for i in range(19):
|
||||
qml.CNOT(wires=[i, i+1])
|
||||
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
```
|
||||
|
||||
### default.clifford
|
||||
|
||||
Efficient simulator for Clifford circuits:
|
||||
|
||||
```python
|
||||
# Only supports Clifford gates (H, S, CNOT, etc.)
|
||||
dev = qml.device('default.clifford', wires=100)
|
||||
|
||||
@qml.qnode(dev)
|
||||
def clifford_circuit():
|
||||
qml.Hadamard(wires=0)
|
||||
qml.CNOT(wires=[0, 1])
|
||||
qml.S(wires=1)
|
||||
# Cannot use RX, RY, RZ, etc.
|
||||
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
```
|
||||
|
||||
## Hardware Plugins
|
||||
|
||||
### IBM Quantum (Qiskit)
|
||||
|
||||
```bash
|
||||
# Install plugin
|
||||
uv pip install pennylane-qiskit
|
||||
```
|
||||
|
||||
```python
|
||||
import pennylane as qml
|
||||
|
||||
# Use IBM simulator
|
||||
dev = qml.device('qiskit.aer', wires=2)
|
||||
|
||||
# Use IBM quantum hardware
|
||||
dev = qml.device(
|
||||
'qiskit.ibmq',
|
||||
wires=2,
|
||||
backend='ibmq_manila', # Specify backend
|
||||
shots=1024
|
||||
)
|
||||
|
||||
# With API token
|
||||
dev = qml.device(
|
||||
'qiskit.ibmq',
|
||||
wires=2,
|
||||
backend='ibmq_manila',
|
||||
ibmqx_token='YOUR_API_TOKEN'
|
||||
)
|
||||
|
||||
@qml.qnode(dev)
|
||||
def circuit():
|
||||
qml.Hadamard(wires=0)
|
||||
qml.CNOT(wires=[0, 1])
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
```
|
||||
|
||||
### Amazon Braket
|
||||
|
||||
```bash
|
||||
# Install plugin
|
||||
uv pip install amazon-braket-pennylane-plugin
|
||||
```
|
||||
|
||||
```python
|
||||
# Use Braket simulators
|
||||
dev = qml.device(
|
||||
'braket.local.qubit',
|
||||
wires=2
|
||||
)
|
||||
|
||||
# Use AWS simulators
|
||||
dev = qml.device(
|
||||
'braket.aws.qubit',
|
||||
device_arn='arn:aws:braket:::device/quantum-simulator/amazon/sv1',
|
||||
wires=4,
|
||||
s3_destination_folder=('amazon-braket-outputs', 'outputs')
|
||||
)
|
||||
|
||||
# Use quantum hardware (IonQ, Rigetti, etc.)
|
||||
dev = qml.device(
|
||||
'braket.aws.qubit',
|
||||
device_arn='arn:aws:braket:us-east-1::device/qpu/ionq/Harmony',
|
||||
wires=11,
|
||||
shots=1000,
|
||||
s3_destination_folder=('amazon-braket-outputs', 'outputs')
|
||||
)
|
||||
```
|
||||
|
||||
### Google Cirq
|
||||
|
||||
```bash
|
||||
# Install plugin
|
||||
uv pip install pennylane-cirq
|
||||
```
|
||||
|
||||
```python
|
||||
# Use Cirq simulator
|
||||
dev = qml.device('cirq.simulator', wires=2)
|
||||
|
||||
# Use Cirq with qsim (faster)
|
||||
dev = qml.device('cirq.qsim', wires=20)
|
||||
|
||||
# Use Google quantum hardware (if you have access)
|
||||
dev = qml.device(
|
||||
'cirq.pasqal',
|
||||
wires=2,
|
||||
device='rainbow',
|
||||
shots=1000
|
||||
)
|
||||
```
|
||||
|
||||
### Rigetti Forest
|
||||
|
||||
```bash
|
||||
# Install plugin
|
||||
uv pip install pennylane-rigetti
|
||||
```
|
||||
|
||||
```python
|
||||
# Use QVM (Quantum Virtual Machine)
|
||||
dev = qml.device('rigetti.qvm', device='4q-qvm', shots=1000)
|
||||
|
||||
# Use Rigetti QPU
|
||||
dev = qml.device('rigetti.qpu', device='Aspen-M-3', shots=1000)
|
||||
```
|
||||
|
||||
### Microsoft Azure Quantum
|
||||
|
||||
```bash
|
||||
# Install plugin
|
||||
uv pip install pennylane-azure
|
||||
```
|
||||
|
||||
```python
|
||||
# Use Azure simulators
|
||||
dev = qml.device(
|
||||
'azure.simulator',
|
||||
wires=4,
|
||||
workspace='your-workspace',
|
||||
resource_group='your-resource-group',
|
||||
subscription_id='your-subscription-id'
|
||||
)
|
||||
|
||||
# Use IonQ on Azure
|
||||
dev = qml.device(
|
||||
'azure.ionq',
|
||||
wires=11,
|
||||
workspace='your-workspace',
|
||||
resource_group='your-resource-group',
|
||||
subscription_id='your-subscription-id',
|
||||
shots=500
|
||||
)
|
||||
```
|
||||
|
||||
### IonQ
|
||||
|
||||
```bash
|
||||
# Install plugin
|
||||
uv pip install pennylane-ionq
|
||||
```
|
||||
|
||||
```python
|
||||
# Use IonQ hardware
|
||||
dev = qml.device(
|
||||
'ionq.simulator', # or 'ionq.qpu'
|
||||
wires=11,
|
||||
shots=1024,
|
||||
api_key='your_api_key'
|
||||
)
|
||||
```
|
||||
|
||||
### Xanadu Hardware (Borealis)
|
||||
|
||||
```python
|
||||
# Photonic quantum computer
|
||||
dev = qml.device(
|
||||
'strawberryfields.remote',
|
||||
backend='borealis',
|
||||
shots=10000
|
||||
)
|
||||
```
|
||||
|
||||
## Device Selection
|
||||
|
||||
### Choosing the Right Device
|
||||
|
||||
```python
|
||||
def select_device(n_qubits, use_hardware=False, noise_model=None):
|
||||
"""Select appropriate device based on requirements."""
|
||||
|
||||
if use_hardware:
|
||||
# Use real quantum hardware
|
||||
if n_qubits <= 11:
|
||||
return qml.device('ionq.qpu', wires=n_qubits, shots=1000)
|
||||
elif n_qubits <= 127:
|
||||
return qml.device('qiskit.ibmq', wires=n_qubits, shots=1024)
|
||||
else:
|
||||
raise ValueError(f"No hardware available for {n_qubits} qubits")
|
||||
|
||||
elif noise_model:
|
||||
# Use noisy simulator
|
||||
return qml.device('default.mixed', wires=n_qubits)
|
||||
|
||||
else:
|
||||
# Use ideal simulator
|
||||
if n_qubits <= 20:
|
||||
return qml.device('lightning.qubit', wires=n_qubits)
|
||||
else:
|
||||
return qml.device('default.qubit', wires=n_qubits)
|
||||
|
||||
# Usage
|
||||
dev = select_device(n_qubits=10, use_hardware=False)
|
||||
```
|
||||
|
||||
### Device Capabilities
|
||||
|
||||
```python
|
||||
# Check device capabilities
|
||||
dev = qml.device('default.qubit', wires=4)
|
||||
|
||||
print("Device name:", dev.name)
|
||||
print("Number of wires:", dev.num_wires)
|
||||
print("Supports shots:", dev.shots is not None)
|
||||
|
||||
# Check supported operations
|
||||
print("Supported gates:", dev.operations)
|
||||
|
||||
# Check supported observables
|
||||
print("Supported observables:", dev.observables)
|
||||
```
|
||||
|
||||
## Device Configuration
|
||||
|
||||
### Setting Shots
|
||||
|
||||
```python
|
||||
# Exact simulation (no shots)
|
||||
dev = qml.device('default.qubit', wires=2)
|
||||
|
||||
@qml.qnode(dev)
|
||||
def exact_circuit():
|
||||
qml.Hadamard(wires=0)
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
result = exact_circuit() # Returns exact expectation
|
||||
|
||||
# Sampling mode (with shots)
|
||||
dev_sampled = qml.device('default.qubit', wires=2, shots=1000)
|
||||
|
||||
@qml.qnode(dev_sampled)
|
||||
def sampled_circuit():
|
||||
qml.Hadamard(wires=0)
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
result = sampled_circuit() # Estimated from samples
|
||||
```
|
||||
|
||||
### Dynamic Shots
|
||||
|
||||
```python
|
||||
# Change shots per execution
|
||||
dev = qml.device('default.qubit', wires=2)
|
||||
|
||||
@qml.qnode(dev)
|
||||
def circuit():
|
||||
qml.Hadamard(wires=0)
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Different shot numbers
|
||||
result_100 = circuit(shots=100)
|
||||
result_1000 = circuit(shots=1000)
|
||||
result_exact = circuit(shots=None) # Exact
|
||||
```
|
||||
|
||||
### Analytic Mode vs Finite Shots
|
||||
|
||||
```python
|
||||
# Compare analytic vs sampled
|
||||
dev_analytic = qml.device('default.qubit', wires=2)
|
||||
dev_sampled = qml.device('default.qubit', wires=2, shots=1000)
|
||||
|
||||
@qml.qnode(dev_analytic)
|
||||
def circuit_analytic(x):
|
||||
qml.RX(x, wires=0)
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
@qml.qnode(dev_sampled)
|
||||
def circuit_sampled(x):
|
||||
qml.RX(x, wires=0)
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
import numpy as np
|
||||
x = np.pi / 4
|
||||
|
||||
print(f"Analytic: {circuit_analytic(x)}")
|
||||
print(f"Sampled: {circuit_sampled(x)}")
|
||||
print(f"Exact value: {np.cos(x)}")
|
||||
```
|
||||
|
||||
### Seed for Reproducibility
|
||||
|
||||
```python
|
||||
# Set random seed
|
||||
dev = qml.device('default.qubit', wires=2, shots=1000, seed=42)
|
||||
|
||||
@qml.qnode(dev)
|
||||
def circuit():
|
||||
qml.Hadamard(wires=0)
|
||||
return qml.sample(qml.PauliZ(0))
|
||||
|
||||
# Reproducible results
|
||||
samples1 = circuit()
|
||||
samples2 = circuit() # Same as samples1 if seed is set
|
||||
```
|
||||
|
||||
## Custom Devices
|
||||
|
||||
### Creating a Custom Device
|
||||
|
||||
```python
|
||||
from pennylane.devices import DefaultQubit
|
||||
|
||||
class CustomDevice(DefaultQubit):
|
||||
"""Custom quantum device with additional features."""
|
||||
|
||||
name = 'Custom device'
|
||||
short_name = 'custom'
|
||||
pennylane_requires = '>=0.30.0'
|
||||
version = '0.1.0'
|
||||
author = 'Your Name'
|
||||
|
||||
def __init__(self, wires, shots=None, **kwargs):
|
||||
super().__init__(wires=wires, shots=shots)
|
||||
# Custom initialization
|
||||
|
||||
def apply(self, operations, **kwargs):
|
||||
"""Apply operations with custom logic."""
|
||||
# Custom operation handling
|
||||
for op in operations:
|
||||
# Log or modify operations
|
||||
print(f"Applying: {op.name}")
|
||||
|
||||
# Call parent implementation
|
||||
super().apply(operations, **kwargs)
|
||||
|
||||
# Use custom device
|
||||
dev = CustomDevice(wires=4)
|
||||
```
|
||||
|
||||
### Plugin Development
|
||||
|
||||
```python
|
||||
# Define custom plugin operations
|
||||
class CustomGate(qml.operation.Operation):
|
||||
"""Custom quantum gate."""
|
||||
|
||||
num_wires = 1
|
||||
num_params = 1
|
||||
par_domain = 'R'
|
||||
|
||||
def decomposition(self):
|
||||
"""Decompose into standard gates."""
|
||||
theta = self.parameters[0]
|
||||
wires = self.wires
|
||||
|
||||
return [
|
||||
qml.RY(theta / 2, wires=wires),
|
||||
qml.RZ(theta, wires=wires),
|
||||
qml.RY(-theta / 2, wires=wires)
|
||||
]
|
||||
|
||||
# Register with device
|
||||
qml.ops.CustomGate = CustomGate
|
||||
```
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Batch Execution
|
||||
|
||||
```python
|
||||
# Execute multiple parameter sets efficiently
|
||||
dev = qml.device('default.qubit', wires=2)
|
||||
|
||||
@qml.qnode(dev)
|
||||
def circuit(params):
|
||||
qml.RX(params[0], wires=0)
|
||||
qml.RY(params[1], wires=1)
|
||||
qml.CNOT(wires=[0, 1])
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Batch parameters
|
||||
params_batch = np.random.random((100, 2))
|
||||
|
||||
# Vectorized execution (faster)
|
||||
results = [circuit(p) for p in params_batch]
|
||||
```
|
||||
|
||||
### Device Caching
|
||||
|
||||
```python
|
||||
# Cache device for reuse
|
||||
_device_cache = {}
|
||||
|
||||
def get_device(n_qubits, device_type='default.qubit'):
|
||||
"""Get or create cached device."""
|
||||
key = (device_type, n_qubits)
|
||||
|
||||
if key not in _device_cache:
|
||||
_device_cache[key] = qml.device(device_type, wires=n_qubits)
|
||||
|
||||
return _device_cache[key]
|
||||
|
||||
# Reuse devices
|
||||
dev1 = get_device(4)
|
||||
dev2 = get_device(4) # Returns same device
|
||||
```
|
||||
|
||||
### JIT Compilation with Catalyst
|
||||
|
||||
```python
|
||||
# Install Catalyst
|
||||
# uv pip install pennylane-catalyst
|
||||
|
||||
import pennylane as qml
|
||||
from catalyst import qjit
|
||||
|
||||
dev = qml.device('lightning.qubit', wires=4)
|
||||
|
||||
@qjit # Just-in-time compilation
|
||||
@qml.qnode(dev)
|
||||
def compiled_circuit(x):
|
||||
qml.RX(x, wires=0)
|
||||
qml.Hadamard(wires=1)
|
||||
qml.CNOT(wires=[0, 1])
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# First call compiles, subsequent calls are fast
|
||||
result = compiled_circuit(0.5)
|
||||
```
|
||||
|
||||
### Parallel Execution
|
||||
|
||||
```python
|
||||
from multiprocessing import Pool
|
||||
|
||||
def run_circuit(params):
|
||||
"""Run circuit with given parameters."""
|
||||
dev = qml.device('default.qubit', wires=4)
|
||||
|
||||
@qml.qnode(dev)
|
||||
def circuit(p):
|
||||
# Circuit definition
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
return circuit(params)
|
||||
|
||||
# Parallel execution
|
||||
param_list = [np.random.random(10) for _ in range(100)]
|
||||
|
||||
with Pool(processes=4) as pool:
|
||||
results = pool.map(run_circuit, param_list)
|
||||
```
|
||||
|
||||
### GPU Acceleration
|
||||
|
||||
```python
|
||||
# Use GPU-accelerated devices if available
|
||||
try:
|
||||
dev = qml.device('lightning.gpu', wires=20)
|
||||
except:
|
||||
dev = qml.device('lightning.qubit', wires=20)
|
||||
|
||||
@qml.qnode(dev)
|
||||
def gpu_circuit():
|
||||
# Large circuit benefits from GPU
|
||||
for i in range(20):
|
||||
qml.Hadamard(wires=i)
|
||||
|
||||
for i in range(19):
|
||||
qml.CNOT(wires=[i, i+1])
|
||||
|
||||
return [qml.expval(qml.PauliZ(i)) for i in range(20)]
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Start with simulators** - Test on `default.qubit` before hardware
|
||||
2. **Use lightning for speed** - Switch to `lightning.qubit` for larger circuits
|
||||
3. **Match device to task** - Use `default.mixed` for noise studies
|
||||
4. **Cache devices** - Reuse device objects to avoid initialization overhead
|
||||
5. **Set appropriate shots** - Balance accuracy vs speed
|
||||
6. **Check capabilities** - Verify device supports required operations
|
||||
7. **Handle hardware errors** - Implement retries and error mitigation
|
||||
8. **Monitor costs** - Track hardware usage and costs
|
||||
9. **Use JIT when possible** - Compile circuits with Catalyst for speedup
|
||||
10. **Test locally first** - Validate on simulators before submitting to hardware
|
||||
|
||||
## Device Comparison
|
||||
|
||||
| Device | Type | Max Qubits | Speed | Noise | Use Case |
|
||||
|--------|------|-----------|-------|-------|----------|
|
||||
| default.qubit | Simulator | ~25 | Medium | No | General purpose |
|
||||
| lightning.qubit | Simulator | ~30 | Fast | No | Large circuits |
|
||||
| default.mixed | Simulator | ~15 | Slow | Yes | Noise studies |
|
||||
| default.clifford | Simulator | 100+ | Very fast | No | Clifford circuits |
|
||||
| IBM Quantum | Hardware | 127 | Slow | Yes | Real experiments |
|
||||
| IonQ | Hardware | 11 | Slow | Low | High fidelity |
|
||||
| Rigetti | Hardware | 80 | Slow | Yes | Research |
|
||||
| Borealis | Hardware | 216 | Slow | Yes | Photonic QC |
|
||||
227
scientific-skills/pennylane/references/getting_started.md
Normal file
227
scientific-skills/pennylane/references/getting_started.md
Normal file
@@ -0,0 +1,227 @@
|
||||
# Getting Started with PennyLane
|
||||
|
||||
## What is PennyLane?
|
||||
|
||||
PennyLane is a cross-platform Python library for quantum computing, quantum machine learning, and quantum chemistry. It enables training quantum computers like neural networks through automatic differentiation and seamless integration with classical machine learning frameworks.
|
||||
|
||||
## Installation
|
||||
|
||||
Install PennyLane using uv:
|
||||
|
||||
```bash
|
||||
uv pip install pennylane
|
||||
```
|
||||
|
||||
For specific device plugins (IBM, Amazon Braket, Google, Rigetti, etc.):
|
||||
|
||||
```bash
|
||||
# IBM Qiskit
|
||||
uv pip install pennylane-qiskit
|
||||
|
||||
# Amazon Braket
|
||||
uv pip install amazon-braket-pennylane-plugin
|
||||
|
||||
# Google Cirq
|
||||
uv pip install pennylane-cirq
|
||||
|
||||
# Rigetti
|
||||
uv pip install pennylane-rigetti
|
||||
```
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Quantum Nodes (QNodes)
|
||||
|
||||
A QNode is a quantum function that can be evaluated on a quantum device. It combines a quantum circuit definition with a device:
|
||||
|
||||
```python
|
||||
import pennylane as qml
|
||||
|
||||
# Define a device
|
||||
dev = qml.device('default.qubit', wires=2)
|
||||
|
||||
# Create a QNode
|
||||
@qml.qnode(dev)
|
||||
def circuit(params):
|
||||
qml.RX(params[0], wires=0)
|
||||
qml.RY(params[1], wires=1)
|
||||
qml.CNOT(wires=[0, 1])
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
```
|
||||
|
||||
### Devices
|
||||
|
||||
Devices execute quantum circuits. PennyLane supports:
|
||||
- **Simulators**: `default.qubit`, `default.mixed`, `lightning.qubit`
|
||||
- **Hardware**: Access through plugins (IBM, Amazon Braket, Rigetti, etc.)
|
||||
|
||||
```python
|
||||
# Local simulator
|
||||
dev = qml.device('default.qubit', wires=4)
|
||||
|
||||
# Lightning high-performance simulator
|
||||
dev = qml.device('lightning.qubit', wires=10)
|
||||
```
|
||||
|
||||
### Measurements
|
||||
|
||||
PennyLane supports various measurement types:
|
||||
|
||||
```python
|
||||
@qml.qnode(dev)
|
||||
def measure_circuit():
|
||||
qml.Hadamard(wires=0)
|
||||
# Expectation value
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
@qml.qnode(dev)
|
||||
def measure_probs():
|
||||
qml.Hadamard(wires=0)
|
||||
# Probability distribution
|
||||
return qml.probs(wires=[0, 1])
|
||||
|
||||
@qml.qnode(dev)
|
||||
def measure_samples():
|
||||
qml.Hadamard(wires=0)
|
||||
# Sample measurements
|
||||
return qml.sample(qml.PauliZ(0))
|
||||
```
|
||||
|
||||
## Basic Workflow
|
||||
|
||||
### 1. Build a Circuit
|
||||
|
||||
```python
|
||||
import pennylane as qml
|
||||
import numpy as np
|
||||
|
||||
dev = qml.device('default.qubit', wires=3)
|
||||
|
||||
@qml.qnode(dev)
|
||||
def quantum_circuit(weights):
|
||||
# Apply gates
|
||||
qml.RX(weights[0], wires=0)
|
||||
qml.RY(weights[1], wires=1)
|
||||
qml.CNOT(wires=[0, 1])
|
||||
qml.RZ(weights[2], wires=2)
|
||||
|
||||
# Measure
|
||||
return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
|
||||
```
|
||||
|
||||
### 2. Compute Gradients
|
||||
|
||||
```python
|
||||
# Automatic differentiation
|
||||
grad_fn = qml.grad(quantum_circuit)
|
||||
weights = np.array([0.1, 0.2, 0.3])
|
||||
gradients = grad_fn(weights)
|
||||
```
|
||||
|
||||
### 3. Optimize Parameters
|
||||
|
||||
```python
|
||||
from pennylane import numpy as np
|
||||
|
||||
# Define optimizer
|
||||
opt = qml.GradientDescentOptimizer(stepsize=0.1)
|
||||
|
||||
# Optimization loop
|
||||
weights = np.array([0.1, 0.2, 0.3], requires_grad=True)
|
||||
for i in range(100):
|
||||
weights = opt.step(quantum_circuit, weights)
|
||||
if i % 20 == 0:
|
||||
print(f"Step {i}: Cost = {quantum_circuit(weights)}")
|
||||
```
|
||||
|
||||
## Device-Independent Programming
|
||||
|
||||
Write circuits once, run anywhere:
|
||||
|
||||
```python
|
||||
# Same circuit, different backends
|
||||
@qml.qnode(qml.device('default.qubit', wires=2))
|
||||
def circuit_simulator(x):
|
||||
qml.RX(x, wires=0)
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Switch to hardware (if available)
|
||||
@qml.qnode(qml.device('qiskit.ibmq', wires=2))
|
||||
def circuit_hardware(x):
|
||||
qml.RX(x, wires=0)
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Parameterized Circuits
|
||||
|
||||
```python
|
||||
@qml.qnode(dev)
|
||||
def parameterized_circuit(params, x):
|
||||
# Encode data
|
||||
qml.RX(x, wires=0)
|
||||
|
||||
# Apply parameterized layers
|
||||
for param in params:
|
||||
qml.RY(param, wires=0)
|
||||
qml.CNOT(wires=[0, 1])
|
||||
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
```
|
||||
|
||||
### Circuit Templates
|
||||
|
||||
Use built-in templates for common patterns:
|
||||
|
||||
```python
|
||||
from pennylane.templates import StronglyEntanglingLayers
|
||||
|
||||
@qml.qnode(dev)
|
||||
def template_circuit(weights):
|
||||
StronglyEntanglingLayers(weights, wires=range(3))
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Generate random weights for template
|
||||
n_layers = 2
|
||||
n_wires = 3
|
||||
shape = StronglyEntanglingLayers.shape(n_layers, n_wires)
|
||||
weights = np.random.random(shape)
|
||||
```
|
||||
|
||||
## Debugging and Visualization
|
||||
|
||||
### Print Circuit Structure
|
||||
|
||||
```python
|
||||
print(qml.draw(circuit)(params))
|
||||
print(qml.draw_mpl(circuit)(params)) # Matplotlib visualization
|
||||
```
|
||||
|
||||
### Inspect Operations
|
||||
|
||||
```python
|
||||
with qml.tape.QuantumTape() as tape:
|
||||
qml.Hadamard(wires=0)
|
||||
qml.CNOT(wires=[0, 1])
|
||||
|
||||
print(tape.operations)
|
||||
print(tape.measurements)
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
For detailed information on specific topics:
|
||||
- **Building circuits**: See `references/quantum_circuits.md`
|
||||
- **Quantum ML**: See `references/quantum_ml.md`
|
||||
- **Chemistry applications**: See `references/quantum_chemistry.md`
|
||||
- **Device management**: See `references/devices_backends.md`
|
||||
- **Optimization**: See `references/optimization.md`
|
||||
- **Advanced features**: See `references/advanced_features.md`
|
||||
|
||||
## Resources
|
||||
|
||||
- Official docs: https://docs.pennylane.ai
|
||||
- Codebook: https://pennylane.ai/codebook
|
||||
- QML demos: https://pennylane.ai/qml/demonstrations
|
||||
- Community forum: https://discuss.pennylane.ai
|
||||
671
scientific-skills/pennylane/references/optimization.md
Normal file
671
scientific-skills/pennylane/references/optimization.md
Normal file
@@ -0,0 +1,671 @@
|
||||
# Optimization in PennyLane
|
||||
|
||||
## Table of Contents
|
||||
1. [Built-in Optimizers](#built-in-optimizers)
|
||||
2. [Gradient Computation](#gradient-computation)
|
||||
3. [Variational Algorithms](#variational-algorithms)
|
||||
4. [QAOA](#qaoa-quantum-approximate-optimization-algorithm)
|
||||
5. [Training Strategies](#training-strategies)
|
||||
6. [Optimization Challenges](#optimization-challenges)
|
||||
|
||||
## Built-in Optimizers
|
||||
|
||||
### Gradient Descent Optimizer
|
||||
|
||||
```python
|
||||
import pennylane as qml
|
||||
from pennylane import numpy as np
|
||||
|
||||
dev = qml.device('default.qubit', wires=2)
|
||||
|
||||
@qml.qnode(dev)
|
||||
def cost_function(params):
|
||||
qml.RX(params[0], wires=0)
|
||||
qml.RY(params[1], wires=1)
|
||||
qml.CNOT(wires=[0, 1])
|
||||
return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
|
||||
|
||||
# Initialize optimizer
|
||||
opt = qml.GradientDescentOptimizer(stepsize=0.1)
|
||||
|
||||
# Initialize parameters
|
||||
params = np.array([0.1, 0.2], requires_grad=True)
|
||||
|
||||
# Training loop
|
||||
for i in range(100):
|
||||
params = opt.step(cost_function, params)
|
||||
|
||||
if i % 10 == 0:
|
||||
print(f"Step {i}: Cost = {cost_function(params):.6f}")
|
||||
```
|
||||
|
||||
### Adam Optimizer
|
||||
|
||||
```python
|
||||
# Adaptive learning rate optimizer
|
||||
opt = qml.AdamOptimizer(stepsize=0.01, beta1=0.9, beta2=0.999)
|
||||
|
||||
params = np.random.random(10, requires_grad=True)
|
||||
|
||||
for i in range(100):
|
||||
params, cost = opt.step_and_cost(cost_function, params)
|
||||
|
||||
if i % 10 == 0:
|
||||
print(f"Step {i}: Cost = {cost:.6f}")
|
||||
```
|
||||
|
||||
### Momentum Optimizer
|
||||
|
||||
```python
|
||||
# Gradient descent with momentum
|
||||
opt = qml.MomentumOptimizer(stepsize=0.01, momentum=0.9)
|
||||
|
||||
params = np.random.random(5, requires_grad=True)
|
||||
|
||||
for i in range(100):
|
||||
params = opt.step(cost_function, params)
|
||||
```
|
||||
|
||||
### AdaGrad Optimizer
|
||||
|
||||
```python
|
||||
# Adaptive gradient algorithm
|
||||
opt = qml.AdagradOptimizer(stepsize=0.1)
|
||||
|
||||
params = np.random.random(8, requires_grad=True)
|
||||
|
||||
for i in range(100):
|
||||
params = opt.step(cost_function, params)
|
||||
```
|
||||
|
||||
### RMSProp Optimizer
|
||||
|
||||
```python
|
||||
# Root mean square propagation
|
||||
opt = qml.RMSPropOptimizer(stepsize=0.01, decay=0.9, eps=1e-8)
|
||||
|
||||
params = np.random.random(6, requires_grad=True)
|
||||
|
||||
for i in range(100):
|
||||
params = opt.step(cost_function, params)
|
||||
```
|
||||
|
||||
### Nesterov Momentum Optimizer
|
||||
|
||||
```python
|
||||
# Nesterov accelerated gradient
|
||||
opt = qml.NesterovMomentumOptimizer(stepsize=0.01, momentum=0.9)
|
||||
|
||||
params = np.random.random(4, requires_grad=True)
|
||||
|
||||
for i in range(100):
|
||||
params = opt.step(cost_function, params)
|
||||
```
|
||||
|
||||
## Gradient Computation
|
||||
|
||||
### Automatic Differentiation
|
||||
|
||||
```python
|
||||
# Backpropagation (for simulators)
|
||||
@qml.qnode(dev, diff_method='backprop')
|
||||
def circuit_backprop(params):
|
||||
qml.RX(params[0], wires=0)
|
||||
qml.RY(params[1], wires=1)
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Compute gradient
|
||||
grad_fn = qml.grad(circuit_backprop)
|
||||
params = np.array([0.1, 0.2], requires_grad=True)
|
||||
gradients = grad_fn(params)
|
||||
```
|
||||
|
||||
### Parameter-Shift Rule
|
||||
|
||||
```python
|
||||
# Hardware-compatible gradient method
|
||||
@qml.qnode(dev, diff_method='parameter-shift')
|
||||
def circuit_param_shift(params):
|
||||
qml.RX(params[0], wires=0)
|
||||
qml.RY(params[1], wires=1)
|
||||
qml.CNOT(wires=[0, 1])
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Works on quantum hardware
|
||||
grad_fn = qml.grad(circuit_param_shift)
|
||||
gradients = grad_fn(params)
|
||||
```
|
||||
|
||||
### Finite Differences
|
||||
|
||||
```python
|
||||
# Numerical gradient approximation
|
||||
@qml.qnode(dev, diff_method='finite-diff')
|
||||
def circuit_finite_diff(params):
|
||||
qml.RX(params[0], wires=0)
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
grad_fn = qml.grad(circuit_finite_diff)
|
||||
gradients = grad_fn(params)
|
||||
```
|
||||
|
||||
### Adjoint Method
|
||||
|
||||
```python
|
||||
# Efficient gradient for state vector simulators
|
||||
@qml.qnode(dev, diff_method='adjoint')
|
||||
def circuit_adjoint(params):
|
||||
qml.RX(params[0], wires=0)
|
||||
qml.RY(params[1], wires=1)
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
grad_fn = qml.grad(circuit_adjoint)
|
||||
gradients = grad_fn(params)
|
||||
```
|
||||
|
||||
### Custom Gradients
|
||||
|
||||
```python
|
||||
@qml.qnode(dev, diff_method='parameter-shift')
|
||||
def circuit(params):
|
||||
qml.RX(params[0], wires=0)
|
||||
qml.RY(params[1], wires=1)
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Compute Hessian
|
||||
hessian_fn = qml.jacobian(qml.grad(circuit))
|
||||
hessian = hessian_fn(params)
|
||||
```
|
||||
|
||||
### Stochastic Parameter-Shift
|
||||
|
||||
```python
|
||||
# For circuits with many parameters
|
||||
@qml.qnode(dev, diff_method='spsa') # Simultaneous Perturbation Stochastic Approximation
|
||||
def large_circuit(params):
|
||||
for i, param in enumerate(params):
|
||||
qml.RY(param, wires=i % 4)
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Efficient for high-dimensional parameter spaces
|
||||
opt = qml.SPSAOptimizer(maxiter=100)
|
||||
params = np.random.random(100, requires_grad=True)
|
||||
params = opt.minimize(large_circuit, params)
|
||||
```
|
||||
|
||||
## Variational Algorithms
|
||||
|
||||
### Variational Quantum Eigensolver (VQE)
|
||||
|
||||
```python
|
||||
# Ground state energy calculation
|
||||
def vqe(hamiltonian, ansatz, n_qubits):
|
||||
"""VQE implementation."""
|
||||
|
||||
dev = qml.device('default.qubit', wires=n_qubits)
|
||||
|
||||
@qml.qnode(dev)
|
||||
def cost_fn(params):
|
||||
ansatz(params, wires=range(n_qubits))
|
||||
return qml.expval(hamiltonian)
|
||||
|
||||
# Initialize parameters
|
||||
n_params = 10 # Depends on ansatz
|
||||
params = np.random.random(n_params, requires_grad=True)
|
||||
|
||||
# Optimize
|
||||
opt = qml.AdamOptimizer(stepsize=0.1)
|
||||
|
||||
energies = []
|
||||
for i in range(100):
|
||||
params, energy = opt.step_and_cost(cost_fn, params)
|
||||
energies.append(energy)
|
||||
|
||||
if i % 10 == 0:
|
||||
print(f"Step {i}: Energy = {energy:.6f}")
|
||||
|
||||
return params, energy, energies
|
||||
|
||||
# Example usage
|
||||
from pennylane import qchem
|
||||
|
||||
symbols = ['H', 'H']
|
||||
coords = np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.74])
|
||||
H, n_qubits = qchem.molecular_hamiltonian(symbols, coords)
|
||||
|
||||
def simple_ansatz(params, wires):
|
||||
qml.BasisState(qchem.hf_state(2, n_qubits), wires=wires)
|
||||
for i, param in enumerate(params):
|
||||
qml.RY(param, wires=i % len(wires))
|
||||
for i in range(len(wires)-1):
|
||||
qml.CNOT(wires=[i, i+1])
|
||||
|
||||
params, energy, history = vqe(H, simple_ansatz, n_qubits)
|
||||
```
|
||||
|
||||
### Quantum Natural Gradient
|
||||
|
||||
```python
|
||||
# More efficient optimization for variational circuits
|
||||
@qml.qnode(dev)
|
||||
def circuit(params):
|
||||
for i, param in enumerate(params):
|
||||
qml.RY(param, wires=i)
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Use quantum natural gradient
|
||||
opt = qml.QNGOptimizer(stepsize=0.01)
|
||||
params = np.random.random(4, requires_grad=True)
|
||||
|
||||
for i in range(100):
|
||||
params, cost = opt.step_and_cost(circuit, params)
|
||||
```
|
||||
|
||||
### Rotosolve
|
||||
|
||||
```python
|
||||
# Analytical parameter update
|
||||
opt = qml.RotosolveOptimizer()
|
||||
|
||||
@qml.qnode(dev)
|
||||
def cost_fn(params):
|
||||
qml.RX(params[0], wires=0)
|
||||
qml.RY(params[1], wires=1)
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
params = np.array([0.1, 0.2], requires_grad=True)
|
||||
|
||||
for i in range(20): # Converges quickly
|
||||
params = opt.step(cost_fn, params)
|
||||
```
|
||||
|
||||
### Quantum Analytic Descent
|
||||
|
||||
```python
|
||||
# Hybrid quantum-classical optimization
|
||||
opt = qml.QNSPSAOptimizer(stepsize=0.01)
|
||||
|
||||
params = np.random.random(10, requires_grad=True)
|
||||
|
||||
for i in range(100):
|
||||
params = opt.step(cost_function, params)
|
||||
```
|
||||
|
||||
## QAOA (Quantum Approximate Optimization Algorithm)
|
||||
|
||||
### Basic QAOA
|
||||
|
||||
```python
|
||||
from pennylane import qaoa
|
||||
|
||||
# Define problem: MaxCut on a graph
|
||||
edges = [(0, 1), (1, 2), (2, 0)]
|
||||
graph = [(edge[0], edge[1], 1.0) for edge in edges]
|
||||
|
||||
# Cost Hamiltonian
|
||||
cost_h = qaoa.maxcut(graph)
|
||||
|
||||
# Mixer Hamiltonian
|
||||
mixer_h = qaoa.x_mixer(range(3))
|
||||
|
||||
# QAOA circuit
|
||||
def qaoa_layer(gamma, alpha):
|
||||
"""Single QAOA layer."""
|
||||
qaoa.cost_layer(gamma, cost_h)
|
||||
qaoa.mixer_layer(alpha, mixer_h)
|
||||
|
||||
@qml.qnode(dev)
|
||||
def qaoa_circuit(params, depth):
|
||||
"""Full QAOA circuit."""
|
||||
# Initialize in superposition
|
||||
for wire in range(3):
|
||||
qml.Hadamard(wires=wire)
|
||||
|
||||
# Apply QAOA layers
|
||||
for i in range(depth):
|
||||
gamma = params[i]
|
||||
alpha = params[depth + i]
|
||||
qaoa_layer(gamma, alpha)
|
||||
|
||||
# Measure in computational basis
|
||||
return qml.expval(cost_h)
|
||||
|
||||
# Optimize
|
||||
depth = 3
|
||||
params = np.random.uniform(0, 2*np.pi, 2*depth, requires_grad=True)
|
||||
|
||||
opt = qml.AdamOptimizer(stepsize=0.1)
|
||||
|
||||
for i in range(100):
|
||||
params = opt.step(lambda p: -qaoa_circuit(p, depth), params) # Minimize negative = maximize
|
||||
|
||||
if i % 10 == 0:
|
||||
print(f"Step {i}: Cost = {-qaoa_circuit(params, depth):.4f}")
|
||||
```
|
||||
|
||||
### QAOA for MaxCut
|
||||
|
||||
```python
|
||||
import networkx as nx
|
||||
|
||||
# Create graph
|
||||
G = nx.cycle_graph(4)
|
||||
|
||||
# Generate cost Hamiltonian
|
||||
cost_h, mixer_h = qaoa.maxcut(G, constrained=False)
|
||||
|
||||
n_wires = len(G.nodes)
|
||||
dev = qml.device('default.qubit', wires=n_wires)
|
||||
|
||||
def qaoa_maxcut(params, depth):
|
||||
"""QAOA for MaxCut problem."""
|
||||
|
||||
@qml.qnode(dev)
|
||||
def circuit(gammas, betas):
|
||||
# Initialize
|
||||
for wire in range(n_wires):
|
||||
qml.Hadamard(wires=wire)
|
||||
|
||||
# QAOA layers
|
||||
for gamma, beta in zip(gammas, betas):
|
||||
# Cost layer
|
||||
for edge in G.edges:
|
||||
wire1, wire2 = edge
|
||||
qml.CNOT(wires=[wire1, wire2])
|
||||
qml.RZ(gamma, wires=wire2)
|
||||
qml.CNOT(wires=[wire1, wire2])
|
||||
|
||||
# Mixer layer
|
||||
for wire in range(n_wires):
|
||||
qml.RX(2 * beta, wires=wire)
|
||||
|
||||
return qml.expval(cost_h)
|
||||
|
||||
gammas = params[:depth]
|
||||
betas = params[depth:]
|
||||
return circuit(gammas, betas)
|
||||
|
||||
# Optimize
|
||||
depth = 3
|
||||
params = np.random.uniform(0, 2*np.pi, 2*depth, requires_grad=True)
|
||||
|
||||
opt = qml.AdamOptimizer(0.1)
|
||||
for i in range(100):
|
||||
params = opt.step(lambda p: -qaoa_maxcut(p, depth), params)
|
||||
```
|
||||
|
||||
### QAOA for QUBO
|
||||
|
||||
```python
|
||||
def qaoa_qubo(Q, depth):
|
||||
"""QAOA for Quadratic Unconstrained Binary Optimization."""
|
||||
|
||||
n = len(Q)
|
||||
dev = qml.device('default.qubit', wires=n)
|
||||
|
||||
# Build cost Hamiltonian from QUBO matrix
|
||||
coeffs = []
|
||||
obs = []
|
||||
|
||||
for i in range(n):
|
||||
for j in range(i, n):
|
||||
if Q[i][j] != 0:
|
||||
if i == j:
|
||||
coeffs.append(-Q[i][j] / 2)
|
||||
obs.append(qml.PauliZ(i))
|
||||
else:
|
||||
coeffs.append(-Q[i][j] / 4)
|
||||
obs.append(qml.PauliZ(i) @ qml.PauliZ(j))
|
||||
|
||||
cost_h = qml.Hamiltonian(coeffs, obs)
|
||||
|
||||
@qml.qnode(dev)
|
||||
def circuit(params):
|
||||
# Initialize
|
||||
for wire in range(n):
|
||||
qml.Hadamard(wires=wire)
|
||||
|
||||
# QAOA layers
|
||||
for i in range(depth):
|
||||
gamma = params[i]
|
||||
beta = params[depth + i]
|
||||
|
||||
# Cost layer
|
||||
for coeff, op in zip(coeffs, obs):
|
||||
qml.exp(op, -1j * gamma * coeff)
|
||||
|
||||
# Mixer layer
|
||||
for wire in range(n):
|
||||
qml.RX(2 * beta, wires=wire)
|
||||
|
||||
return qml.expval(cost_h)
|
||||
|
||||
return circuit
|
||||
|
||||
# Example QUBO
|
||||
Q = np.array([[1, -2], [-2, 1]])
|
||||
circuit = qaoa_qubo(Q, depth=2)
|
||||
|
||||
params = np.random.random(4, requires_grad=True)
|
||||
opt = qml.AdamOptimizer(0.1)
|
||||
|
||||
for i in range(100):
|
||||
params = opt.step(circuit, params)
|
||||
```
|
||||
|
||||
## Training Strategies
|
||||
|
||||
### Learning Rate Scheduling
|
||||
|
||||
```python
|
||||
def train_with_schedule(circuit, initial_params, n_epochs):
|
||||
"""Train with learning rate decay."""
|
||||
|
||||
params = initial_params
|
||||
base_lr = 0.1
|
||||
decay_rate = 0.95
|
||||
decay_steps = 10
|
||||
|
||||
for epoch in range(n_epochs):
|
||||
# Update learning rate
|
||||
lr = base_lr * (decay_rate ** (epoch // decay_steps))
|
||||
opt = qml.GradientDescentOptimizer(stepsize=lr)
|
||||
|
||||
# Training step
|
||||
params = opt.step(circuit, params)
|
||||
|
||||
if epoch % 10 == 0:
|
||||
print(f"Epoch {epoch}: LR = {lr:.4f}, Cost = {circuit(params):.4f}")
|
||||
|
||||
return params
|
||||
```
|
||||
|
||||
### Mini-Batch Training
|
||||
|
||||
```python
|
||||
def minibatch_train(circuit, X, y, batch_size=32, n_epochs=100):
|
||||
"""Mini-batch training for quantum circuits."""
|
||||
|
||||
params = np.random.random(10, requires_grad=True)
|
||||
opt = qml.AdamOptimizer(stepsize=0.01)
|
||||
|
||||
n_samples = len(X)
|
||||
|
||||
for epoch in range(n_epochs):
|
||||
# Shuffle data
|
||||
indices = np.random.permutation(n_samples)
|
||||
X_shuffled = X[indices]
|
||||
y_shuffled = y[indices]
|
||||
|
||||
# Mini-batch updates
|
||||
for i in range(0, n_samples, batch_size):
|
||||
X_batch = X_shuffled[i:i+batch_size]
|
||||
y_batch = y_shuffled[i:i+batch_size]
|
||||
|
||||
# Compute batch cost
|
||||
def batch_cost(p):
|
||||
predictions = np.array([circuit(x, p) for x in X_batch])
|
||||
return np.mean((predictions - y_batch) ** 2)
|
||||
|
||||
params = opt.step(batch_cost, params)
|
||||
|
||||
if epoch % 10 == 0:
|
||||
loss = batch_cost(params)
|
||||
print(f"Epoch {epoch}: Loss = {loss:.4f}")
|
||||
|
||||
return params
|
||||
```
|
||||
|
||||
### Early Stopping
|
||||
|
||||
```python
|
||||
def train_with_early_stopping(circuit, params, X_train, X_val, patience=10):
|
||||
"""Train with early stopping based on validation loss."""
|
||||
|
||||
opt = qml.AdamOptimizer(stepsize=0.01)
|
||||
|
||||
best_val_loss = float('inf')
|
||||
patience_counter = 0
|
||||
best_params = params.copy()
|
||||
|
||||
for epoch in range(1000):
|
||||
# Training step
|
||||
params = opt.step(lambda p: cost_fn(p, X_train), params)
|
||||
|
||||
# Validation
|
||||
val_loss = cost_fn(params, X_val)
|
||||
|
||||
if val_loss < best_val_loss:
|
||||
best_val_loss = val_loss
|
||||
best_params = params.copy()
|
||||
patience_counter = 0
|
||||
else:
|
||||
patience_counter += 1
|
||||
|
||||
if patience_counter >= patience:
|
||||
print(f"Early stopping at epoch {epoch}")
|
||||
break
|
||||
|
||||
return best_params
|
||||
```
|
||||
|
||||
### Gradient Clipping
|
||||
|
||||
```python
|
||||
def train_with_gradient_clipping(circuit, params, max_norm=1.0):
|
||||
"""Train with gradient clipping to prevent exploding gradients."""
|
||||
|
||||
opt = qml.GradientDescentOptimizer(stepsize=0.1)
|
||||
|
||||
for i in range(100):
|
||||
# Compute gradients
|
||||
grad_fn = qml.grad(circuit)
|
||||
grads = grad_fn(params)
|
||||
|
||||
# Clip gradients
|
||||
grad_norm = np.linalg.norm(grads)
|
||||
if grad_norm > max_norm:
|
||||
grads = grads * (max_norm / grad_norm)
|
||||
|
||||
# Manual update with clipped gradients
|
||||
params = params - opt.stepsize * grads
|
||||
|
||||
if i % 10 == 0:
|
||||
print(f"Step {i}: Grad norm = {grad_norm:.4f}")
|
||||
|
||||
return params
|
||||
```
|
||||
|
||||
## Optimization Challenges
|
||||
|
||||
### Barren Plateaus
|
||||
|
||||
```python
|
||||
def detect_barren_plateau(circuit, params, n_samples=100):
|
||||
"""Detect barren plateau by measuring gradient variance."""
|
||||
|
||||
grad_fn = qml.grad(circuit)
|
||||
grad_variances = []
|
||||
|
||||
for _ in range(n_samples):
|
||||
# Random initialization
|
||||
random_params = np.random.uniform(-np.pi, np.pi, len(params))
|
||||
|
||||
# Compute gradient
|
||||
grads = grad_fn(random_params)
|
||||
grad_variances.append(np.var(grads))
|
||||
|
||||
mean_var = np.mean(grad_variances)
|
||||
|
||||
print(f"Mean gradient variance: {mean_var:.6f}")
|
||||
|
||||
if mean_var < 1e-6:
|
||||
print("Warning: Barren plateau detected!")
|
||||
|
||||
return mean_var
|
||||
```
|
||||
|
||||
### Parameter Initialization
|
||||
|
||||
```python
|
||||
def initialize_params_smart(n_params, strategy='small_random'):
|
||||
"""Smart parameter initialization strategies."""
|
||||
|
||||
if strategy == 'small_random':
|
||||
# Small random values
|
||||
return np.random.uniform(-0.1, 0.1, n_params, requires_grad=True)
|
||||
|
||||
elif strategy == 'xavier':
|
||||
# Xavier initialization
|
||||
return np.random.normal(0, 1/np.sqrt(n_params), n_params, requires_grad=True)
|
||||
|
||||
elif strategy == 'identity':
|
||||
# Start near identity (zeros for rotations)
|
||||
return np.zeros(n_params, requires_grad=True)
|
||||
|
||||
elif strategy == 'layerwise':
|
||||
# Layer-dependent initialization
|
||||
return np.array([0.1 / (i+1) for i in range(n_params)], requires_grad=True)
|
||||
```
|
||||
|
||||
### Local Minima Escape
|
||||
|
||||
```python
|
||||
def train_with_restarts(circuit, n_restarts=5):
|
||||
"""Multiple random restarts to escape local minima."""
|
||||
|
||||
best_cost = float('inf')
|
||||
best_params = None
|
||||
|
||||
for restart in range(n_restarts):
|
||||
# Random initialization
|
||||
params = np.random.uniform(-np.pi, np.pi, 10, requires_grad=True)
|
||||
|
||||
# Optimize
|
||||
opt = qml.AdamOptimizer(stepsize=0.1)
|
||||
for i in range(100):
|
||||
params = opt.step(circuit, params)
|
||||
|
||||
# Check if better
|
||||
cost = circuit(params)
|
||||
if cost < best_cost:
|
||||
best_cost = cost
|
||||
best_params = params
|
||||
|
||||
print(f"Restart {restart}: Cost = {cost:.6f}")
|
||||
|
||||
return best_params, best_cost
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Choose appropriate optimizer** - Adam for general use, QNG for variational circuits
|
||||
2. **Use parameter-shift on hardware** - Backprop only works on simulators
|
||||
3. **Initialize carefully** - Avoid barren plateaus with smart initialization
|
||||
4. **Monitor gradients** - Check for vanishing/exploding gradients
|
||||
5. **Use learning rate schedules** - Decay learning rate over time
|
||||
6. **Try multiple restarts** - Escape local minima
|
||||
7. **Validate on test set** - Prevent overfitting
|
||||
8. **Profile optimization** - Identify bottlenecks
|
||||
9. **Clip gradients** - Prevent instability
|
||||
10. **Start shallow** - Use fewer layers initially, then grow
|
||||
567
scientific-skills/pennylane/references/quantum_chemistry.md
Normal file
567
scientific-skills/pennylane/references/quantum_chemistry.md
Normal file
@@ -0,0 +1,567 @@
|
||||
# Quantum Chemistry with PennyLane
|
||||
|
||||
## Table of Contents
|
||||
1. [Molecular Hamiltonians](#molecular-hamiltonians)
|
||||
2. [Variational Quantum Eigensolver (VQE)](#variational-quantum-eigensolver-vqe)
|
||||
3. [Molecular Structure](#molecular-structure)
|
||||
4. [Basis Sets and Mapping](#basis-sets-and-mapping)
|
||||
5. [Excited States](#excited-states)
|
||||
6. [Quantum Chemistry Workflows](#quantum-chemistry-workflows)
|
||||
|
||||
## Molecular Hamiltonians
|
||||
|
||||
### Building Molecular Hamiltonians
|
||||
|
||||
```python
|
||||
import pennylane as qml
|
||||
from pennylane import qchem
|
||||
import numpy as np
|
||||
|
||||
# Define molecule
|
||||
symbols = ['H', 'H']
|
||||
coordinates = np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.74]) # Angstroms
|
||||
|
||||
# Generate Hamiltonian
|
||||
hamiltonian, n_qubits = qchem.molecular_hamiltonian(
|
||||
symbols,
|
||||
coordinates,
|
||||
charge=0,
|
||||
mult=1, # Spin multiplicity
|
||||
basis='sto-3g',
|
||||
method='dhf' # Dirac-Hartree-Fock
|
||||
)
|
||||
|
||||
print(f"Hamiltonian: {hamiltonian}")
|
||||
print(f"Number of qubits needed: {n_qubits}")
|
||||
```
|
||||
|
||||
### Jordan-Wigner Transformation
|
||||
|
||||
```python
|
||||
# Hamiltonian is automatically in qubit form via Jordan-Wigner
|
||||
# Manual transformation:
|
||||
from pennylane import fermi
|
||||
|
||||
# Fermionic operators
|
||||
a_0 = fermi.FermiC(0) # Creation operator
|
||||
a_1 = fermi.FermiA(1) # Annihilation operator
|
||||
|
||||
# Convert to qubits
|
||||
qubit_op = qml.qchem.jordan_wigner(a_0 * a_1)
|
||||
```
|
||||
|
||||
### Bravyi-Kitaev Transformation
|
||||
|
||||
```python
|
||||
# Alternative mapping (more efficient for some systems)
|
||||
from pennylane.qchem import bravyi_kitaev
|
||||
|
||||
# Build Hamiltonian with Bravyi-Kitaev
|
||||
hamiltonian, n_qubits = qchem.molecular_hamiltonian(
|
||||
symbols,
|
||||
coordinates,
|
||||
mapping='bravyi_kitaev'
|
||||
)
|
||||
```
|
||||
|
||||
### Custom Hamiltonians
|
||||
|
||||
```python
|
||||
# Build Hamiltonian from coefficients and operators
|
||||
coeffs = [0.2, -0.8, 0.5]
|
||||
obs = [
|
||||
qml.PauliZ(0),
|
||||
qml.PauliZ(0) @ qml.PauliZ(1),
|
||||
qml.PauliX(0) @ qml.PauliX(1)
|
||||
]
|
||||
|
||||
H = qml.Hamiltonian(coeffs, obs)
|
||||
|
||||
# Or use simplified syntax
|
||||
H = 0.2 * qml.PauliZ(0) - 0.8 * qml.PauliZ(0) @ qml.PauliZ(1) + 0.5 * qml.PauliX(0) @ qml.PauliX(1)
|
||||
```
|
||||
|
||||
## Variational Quantum Eigensolver (VQE)
|
||||
|
||||
### Basic VQE Implementation
|
||||
|
||||
```python
|
||||
from pennylane import numpy as np
|
||||
|
||||
# Define device
|
||||
dev = qml.device('default.qubit', wires=n_qubits)
|
||||
|
||||
# Hartree-Fock state preparation
|
||||
hf_state = qchem.hf_state(electrons=2, orbitals=n_qubits)
|
||||
|
||||
def ansatz(params, wires):
|
||||
"""Variational ansatz."""
|
||||
qml.BasisState(hf_state, wires=wires)
|
||||
|
||||
for i in range(len(wires)):
|
||||
qml.RY(params[i], wires=i)
|
||||
|
||||
for i in range(len(wires)-1):
|
||||
qml.CNOT(wires=[i, i+1])
|
||||
|
||||
@qml.qnode(dev)
|
||||
def vqe_circuit(params):
|
||||
ansatz(params, wires=range(n_qubits))
|
||||
return qml.expval(hamiltonian)
|
||||
|
||||
# Optimize
|
||||
opt = qml.GradientDescentOptimizer(stepsize=0.4)
|
||||
params = np.random.normal(0, np.pi, n_qubits, requires_grad=True)
|
||||
|
||||
for n in range(100):
|
||||
params, energy = opt.step_and_cost(vqe_circuit, params)
|
||||
|
||||
if n % 20 == 0:
|
||||
print(f"Step {n}: Energy = {energy:.8f} Ha")
|
||||
|
||||
print(f"Final ground state energy: {energy:.8f} Ha")
|
||||
```
|
||||
|
||||
### UCCSD Ansatz
|
||||
|
||||
```python
|
||||
from pennylane.qchem import UCCSD
|
||||
|
||||
# Singles and doubles excitations
|
||||
singles, doubles = qchem.excitations(electrons=2, orbitals=n_qubits)
|
||||
|
||||
@qml.qnode(dev)
|
||||
def uccsd_circuit(params):
|
||||
# Hartree-Fock reference
|
||||
qml.BasisState(hf_state, wires=range(n_qubits))
|
||||
|
||||
# UCCSD ansatz
|
||||
UCCSD(params, wires=range(n_qubits), s_wires=singles, d_wires=doubles)
|
||||
|
||||
return qml.expval(hamiltonian)
|
||||
|
||||
# Initialize parameters
|
||||
n_params = len(singles) + len(doubles)
|
||||
params = np.zeros(n_params, requires_grad=True)
|
||||
|
||||
# Optimize
|
||||
opt = qml.AdamOptimizer(stepsize=0.1)
|
||||
for n in range(100):
|
||||
params, energy = opt.step_and_cost(uccsd_circuit, params)
|
||||
```
|
||||
|
||||
### Adaptive VQE
|
||||
|
||||
```python
|
||||
def adaptive_vqe(hamiltonian, n_qubits, max_gates=10):
|
||||
"""Adaptive VQE: Grow ansatz iteratively."""
|
||||
dev = qml.device('default.qubit', wires=n_qubits)
|
||||
|
||||
# Start with HF state
|
||||
operations = []
|
||||
params = []
|
||||
|
||||
hf_state = qchem.hf_state(electrons=2, orbitals=n_qubits)
|
||||
|
||||
@qml.qnode(dev)
|
||||
def circuit(p):
|
||||
qml.BasisState(hf_state, wires=range(n_qubits))
|
||||
|
||||
for op, param in zip(operations, p):
|
||||
op(param)
|
||||
|
||||
return qml.expval(hamiltonian)
|
||||
|
||||
# Iteratively add gates
|
||||
for _ in range(max_gates):
|
||||
# Find best gate to add
|
||||
best_op = None
|
||||
best_improvement = 0
|
||||
|
||||
for candidate_op in generate_candidates():
|
||||
# Test adding this operation
|
||||
test_ops = operations + [candidate_op]
|
||||
test_params = params + [0.0]
|
||||
|
||||
improvement = evaluate_improvement(test_ops, test_params)
|
||||
|
||||
if improvement > best_improvement:
|
||||
best_improvement = improvement
|
||||
best_op = candidate_op
|
||||
|
||||
if best_improvement < threshold:
|
||||
break
|
||||
|
||||
operations.append(best_op)
|
||||
params.append(0.0)
|
||||
|
||||
# Optimize current ansatz
|
||||
opt = qml.AdamOptimizer(stepsize=0.1)
|
||||
for _ in range(50):
|
||||
params = opt.step(circuit, params)
|
||||
|
||||
return circuit, params
|
||||
```
|
||||
|
||||
## Molecular Structure
|
||||
|
||||
### Defining Molecules
|
||||
|
||||
```python
|
||||
# Simple diatomic
|
||||
h2_symbols = ['H', 'H']
|
||||
h2_coords = np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.74])
|
||||
|
||||
# Water molecule
|
||||
h2o_symbols = ['O', 'H', 'H']
|
||||
h2o_coords = np.array([
|
||||
0.0, 0.0, 0.0, # O
|
||||
0.757, 0.586, 0.0, # H
|
||||
-0.757, 0.586, 0.0 # H
|
||||
])
|
||||
|
||||
# From XYZ format
|
||||
molecule = qchem.read_structure('molecule.xyz')
|
||||
symbols, coords = molecule
|
||||
```
|
||||
|
||||
### Geometry Optimization
|
||||
|
||||
```python
|
||||
def optimize_geometry(symbols, initial_coords, basis='sto-3g'):
|
||||
"""Optimize molecular geometry."""
|
||||
|
||||
def energy_surface(coords):
|
||||
H, n_qubits = qchem.molecular_hamiltonian(
|
||||
symbols, coords, basis=basis
|
||||
)
|
||||
|
||||
# Run VQE to get energy
|
||||
energy = run_vqe(H, n_qubits)
|
||||
return energy
|
||||
|
||||
# Classical optimization of nuclear coordinates
|
||||
from scipy.optimize import minimize
|
||||
|
||||
result = minimize(
|
||||
energy_surface,
|
||||
initial_coords,
|
||||
method='BFGS',
|
||||
options={'gtol': 1e-5}
|
||||
)
|
||||
|
||||
return result.x, result.fun
|
||||
|
||||
optimized_coords, min_energy = optimize_geometry(h2_symbols, h2_coords)
|
||||
print(f"Optimized geometry: {optimized_coords}")
|
||||
print(f"Energy: {min_energy} Ha")
|
||||
```
|
||||
|
||||
### Bond Dissociation Curves
|
||||
|
||||
```python
|
||||
def dissociation_curve(symbols, axis=2, distances=None):
|
||||
"""Calculate potential energy surface."""
|
||||
|
||||
if distances is None:
|
||||
distances = np.linspace(0.5, 3.0, 20)
|
||||
|
||||
energies = []
|
||||
|
||||
for d in distances:
|
||||
coords = np.zeros(6)
|
||||
coords[axis] = d # Set bond length
|
||||
|
||||
H, n_qubits = qchem.molecular_hamiltonian(
|
||||
symbols, coords, basis='sto-3g'
|
||||
)
|
||||
|
||||
energy = run_vqe(H, n_qubits)
|
||||
energies.append(energy)
|
||||
|
||||
print(f"Distance: {d:.2f} Å, Energy: {energy:.6f} Ha")
|
||||
|
||||
return distances, energies
|
||||
|
||||
# H2 dissociation
|
||||
distances, energies = dissociation_curve(['H', 'H'])
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
plt.plot(distances, energies)
|
||||
plt.xlabel('Bond length (Å)')
|
||||
plt.ylabel('Energy (Ha)')
|
||||
plt.title('H2 Dissociation Curve')
|
||||
plt.show()
|
||||
```
|
||||
|
||||
## Basis Sets and Mapping
|
||||
|
||||
### Basis Set Selection
|
||||
|
||||
```python
|
||||
# Minimal basis (fastest, least accurate)
|
||||
H_sto3g, n_qubits = qchem.molecular_hamiltonian(
|
||||
symbols, coords, basis='sto-3g'
|
||||
)
|
||||
|
||||
# Double-zeta basis
|
||||
H_631g, n_qubits = qchem.molecular_hamiltonian(
|
||||
symbols, coords, basis='6-31g'
|
||||
)
|
||||
|
||||
# Large basis (slower, more accurate)
|
||||
H_ccpvdz, n_qubits = qchem.molecular_hamiltonian(
|
||||
symbols, coords, basis='cc-pvdz'
|
||||
)
|
||||
```
|
||||
|
||||
### Active Space Selection
|
||||
|
||||
```python
|
||||
# Select active orbitals
|
||||
active_electrons = 2
|
||||
active_orbitals = 2
|
||||
|
||||
H_active, n_qubits = qchem.molecular_hamiltonian(
|
||||
symbols,
|
||||
coords,
|
||||
active_electrons=active_electrons,
|
||||
active_orbitals=active_orbitals
|
||||
)
|
||||
|
||||
print(f"Full system: {len(symbols)} electrons")
|
||||
print(f"Active space: {active_electrons} electrons in {active_orbitals} orbitals")
|
||||
print(f"Qubits needed: {n_qubits}")
|
||||
```
|
||||
|
||||
### Fermion-to-Qubit Mappings
|
||||
|
||||
```python
|
||||
# Jordan-Wigner (default)
|
||||
H_jw, n_q_jw = qchem.molecular_hamiltonian(
|
||||
symbols, coords, mapping='jordan_wigner'
|
||||
)
|
||||
|
||||
# Bravyi-Kitaev
|
||||
H_bk, n_q_bk = qchem.molecular_hamiltonian(
|
||||
symbols, coords, mapping='bravyi_kitaev'
|
||||
)
|
||||
|
||||
# Parity
|
||||
H_parity, n_q_parity = qchem.molecular_hamiltonian(
|
||||
symbols, coords, mapping='parity'
|
||||
)
|
||||
|
||||
print(f"Jordan-Wigner terms: {len(H_jw.ops)}")
|
||||
print(f"Bravyi-Kitaev terms: {len(H_bk.ops)}")
|
||||
```
|
||||
|
||||
## Excited States
|
||||
|
||||
### Quantum Subspace Expansion
|
||||
|
||||
```python
|
||||
def quantum_subspace_expansion(hamiltonian, ground_state_params, excitations):
|
||||
"""Calculate excited states via subspace expansion."""
|
||||
|
||||
@qml.qnode(dev)
|
||||
def ground_state():
|
||||
ansatz(ground_state_params, wires=range(n_qubits))
|
||||
return qml.state()
|
||||
|
||||
# Get ground state
|
||||
psi_0 = ground_state()
|
||||
|
||||
# Generate excited state basis
|
||||
basis = [psi_0]
|
||||
|
||||
for exc in excitations:
|
||||
@qml.qnode(dev)
|
||||
def excited_state():
|
||||
ansatz(ground_state_params, wires=range(n_qubits))
|
||||
# Apply excitation
|
||||
apply_excitation(exc)
|
||||
return qml.state()
|
||||
|
||||
psi_exc = excited_state()
|
||||
basis.append(psi_exc)
|
||||
|
||||
# Build Hamiltonian matrix in subspace
|
||||
n_basis = len(basis)
|
||||
H_matrix = np.zeros((n_basis, n_basis))
|
||||
|
||||
for i in range(n_basis):
|
||||
for j in range(n_basis):
|
||||
H_matrix[i, j] = np.vdot(basis[i], hamiltonian @ basis[j])
|
||||
|
||||
# Diagonalize
|
||||
eigenvalues, eigenvectors = np.linalg.eigh(H_matrix)
|
||||
|
||||
return eigenvalues, eigenvectors
|
||||
```
|
||||
|
||||
### SSVQE (Subspace-Search VQE)
|
||||
|
||||
```python
|
||||
def ssvqe(hamiltonian, n_states=3):
|
||||
"""Calculate multiple states simultaneously."""
|
||||
|
||||
def cost_function(params):
|
||||
states = []
|
||||
|
||||
for i in range(n_states):
|
||||
@qml.qnode(dev)
|
||||
def state_i():
|
||||
ansatz(params[i], wires=range(n_qubits))
|
||||
return qml.state()
|
||||
|
||||
states.append(state_i())
|
||||
|
||||
# Energy expectation
|
||||
energies = [np.vdot(s, hamiltonian @ s) for s in states]
|
||||
|
||||
# Orthogonality penalty
|
||||
penalty = 0
|
||||
for i in range(n_states):
|
||||
for j in range(i+1, n_states):
|
||||
overlap = np.abs(np.vdot(states[i], states[j]))
|
||||
penalty += overlap ** 2
|
||||
|
||||
return sum(energies) + 1000 * penalty
|
||||
|
||||
# Initialize parameters for all states
|
||||
params = [np.random.random(n_params) for _ in range(n_states)]
|
||||
|
||||
opt = qml.AdamOptimizer(stepsize=0.01)
|
||||
for _ in range(100):
|
||||
params = opt.step(cost_function, params)
|
||||
|
||||
return params
|
||||
```
|
||||
|
||||
## Quantum Chemistry Workflows
|
||||
|
||||
### Full VQE Workflow
|
||||
|
||||
```python
|
||||
def full_chemistry_workflow(symbols, coords, basis='sto-3g'):
|
||||
"""Complete quantum chemistry calculation."""
|
||||
|
||||
print("1. Building molecular Hamiltonian...")
|
||||
H, n_qubits = qchem.molecular_hamiltonian(
|
||||
symbols, coords, basis=basis
|
||||
)
|
||||
|
||||
print(f" Molecule: {' '.join(symbols)}")
|
||||
print(f" Qubits: {n_qubits}")
|
||||
print(f" Hamiltonian terms: {len(H.ops)}")
|
||||
|
||||
print("\n2. Preparing Hartree-Fock state...")
|
||||
n_electrons = sum(qchem.atomic_numbers[s] for s in symbols)
|
||||
hf_state = qchem.hf_state(n_electrons, n_qubits)
|
||||
|
||||
print("\n3. Running VQE...")
|
||||
energy, params = run_vqe(H, n_qubits, hf_state)
|
||||
|
||||
print(f"\n4. Results:")
|
||||
print(f" Ground state energy: {energy:.8f} Ha")
|
||||
|
||||
print("\n5. Computing properties...")
|
||||
dipole = compute_dipole_moment(symbols, coords, params)
|
||||
print(f" Dipole moment: {dipole:.4f} D")
|
||||
|
||||
return {
|
||||
'energy': energy,
|
||||
'params': params,
|
||||
'dipole': dipole
|
||||
}
|
||||
|
||||
results = full_chemistry_workflow(['H', 'H'], h2_coords)
|
||||
```
|
||||
|
||||
### Molecular Property Calculation
|
||||
|
||||
```python
|
||||
def compute_molecular_properties(symbols, coords, vqe_params):
|
||||
"""Calculate molecular properties from VQE solution."""
|
||||
|
||||
# Energy
|
||||
H, n_qubits = qchem.molecular_hamiltonian(symbols, coords)
|
||||
energy = vqe_circuit(vqe_params)
|
||||
|
||||
# Dipole moment
|
||||
dipole_obs = qchem.dipole_moment(symbols, coords)
|
||||
|
||||
@qml.qnode(dev)
|
||||
def dipole_circuit(axis):
|
||||
ansatz(vqe_params, wires=range(n_qubits))
|
||||
return qml.expval(dipole_obs[axis])
|
||||
|
||||
dipole = [dipole_circuit(i) for i in range(3)]
|
||||
dipole_magnitude = np.linalg.norm(dipole)
|
||||
|
||||
# Particle number (sanity check)
|
||||
@qml.qnode(dev)
|
||||
def particle_number():
|
||||
ansatz(vqe_params, wires=range(n_qubits))
|
||||
N_op = qchem.particle_number(n_qubits)
|
||||
return qml.expval(N_op)
|
||||
|
||||
n_particles = particle_number()
|
||||
|
||||
return {
|
||||
'energy': energy,
|
||||
'dipole_moment': dipole_magnitude,
|
||||
'dipole_vector': dipole,
|
||||
'particle_number': n_particles
|
||||
}
|
||||
```
|
||||
|
||||
### Reaction Energy Calculation
|
||||
|
||||
```python
|
||||
def reaction_energy(reactants, products):
|
||||
"""Calculate energy of chemical reaction."""
|
||||
|
||||
# Calculate energies of reactants
|
||||
E_reactants = 0
|
||||
for molecule in reactants:
|
||||
symbols, coords = molecule
|
||||
H, n_qubits = qchem.molecular_hamiltonian(symbols, coords)
|
||||
E_reactants += run_vqe(H, n_qubits)
|
||||
|
||||
# Calculate energies of products
|
||||
E_products = 0
|
||||
for molecule in products:
|
||||
symbols, coords = molecule
|
||||
H, n_qubits = qchem.molecular_hamiltonian(symbols, coords)
|
||||
E_products += run_vqe(H, n_qubits)
|
||||
|
||||
# Reaction energy
|
||||
delta_E = E_products - E_reactants
|
||||
|
||||
print(f"Reactant energy: {E_reactants:.6f} Ha")
|
||||
print(f"Product energy: {E_products:.6f} Ha")
|
||||
print(f"Reaction energy: {delta_E:.6f} Ha ({delta_E * 627.5:.2f} kcal/mol)")
|
||||
|
||||
return delta_E
|
||||
|
||||
# Example: H2 dissociation
|
||||
reactants = [((['H', 'H'], h2_coords_bonded))]
|
||||
products = [((['H'], [0, 0, 0]), (['H'], [10, 0, 0]))] # Separated atoms
|
||||
|
||||
delta_E = reaction_energy(reactants, products)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Start with small basis sets** - Use STO-3G for testing, upgrade for production
|
||||
2. **Use active space** - Reduce qubits by selecting relevant orbitals
|
||||
3. **Choose appropriate mapping** - Bravyi-Kitaev often reduces circuit depth
|
||||
4. **Initialize with HF** - Start VQE from Hartree-Fock state
|
||||
5. **Validate results** - Compare with classical methods (FCI, CCSD)
|
||||
6. **Consider symmetries** - Exploit molecular symmetries to reduce complexity
|
||||
7. **Use UCCSD for accuracy** - UCCSD ansatz is chemically motivated
|
||||
8. **Monitor convergence** - Check gradient norms and energy variance
|
||||
9. **Account for correlation** - Ensure ansatz captures electron correlation
|
||||
10. **Benchmark thoroughly** - Test on known systems before novel molecules
|
||||
437
scientific-skills/pennylane/references/quantum_circuits.md
Normal file
437
scientific-skills/pennylane/references/quantum_circuits.md
Normal file
@@ -0,0 +1,437 @@
|
||||
# Quantum Circuits in PennyLane
|
||||
|
||||
## Table of Contents
|
||||
1. [Basic Gates and Operations](#basic-gates-and-operations)
|
||||
2. [Multi-Qubit Gates](#multi-qubit-gates)
|
||||
3. [Controlled Operations](#controlled-operations)
|
||||
4. [Measurements](#measurements)
|
||||
5. [Circuit Construction Patterns](#circuit-construction-patterns)
|
||||
6. [Dynamic Circuits](#dynamic-circuits)
|
||||
7. [Circuit Inspection](#circuit-inspection)
|
||||
|
||||
## Basic Gates and Operations
|
||||
|
||||
### Single-Qubit Gates
|
||||
|
||||
```python
|
||||
import pennylane as qml
|
||||
|
||||
# Pauli gates
|
||||
qml.PauliX(wires=0) # X gate (bit flip)
|
||||
qml.PauliY(wires=0) # Y gate
|
||||
qml.PauliZ(wires=0) # Z gate (phase flip)
|
||||
|
||||
# Hadamard gate (superposition)
|
||||
qml.Hadamard(wires=0)
|
||||
|
||||
# Phase gates
|
||||
qml.S(wires=0) # S gate (π/2 phase)
|
||||
qml.T(wires=0) # T gate (π/4 phase)
|
||||
qml.PhaseShift(phi, wires=0) # Arbitrary phase
|
||||
|
||||
# Rotation gates (parameterized)
|
||||
qml.RX(theta, wires=0) # Rotation around X-axis
|
||||
qml.RY(theta, wires=0) # Rotation around Y-axis
|
||||
qml.RZ(theta, wires=0) # Rotation around Z-axis
|
||||
|
||||
# General single-qubit rotation
|
||||
qml.Rot(phi, theta, omega, wires=0)
|
||||
|
||||
# Universal gate (any single-qubit unitary)
|
||||
qml.U3(theta, phi, delta, wires=0)
|
||||
```
|
||||
|
||||
### Basis State Preparation
|
||||
|
||||
```python
|
||||
# Computational basis state
|
||||
qml.BasisState([1, 0, 1], wires=[0, 1, 2]) # |101⟩
|
||||
|
||||
# Amplitude encoding
|
||||
amplitudes = [0.5, 0.5, 0.5, 0.5] # Must be normalized
|
||||
qml.MottonenStatePreparation(amplitudes, wires=[0, 1])
|
||||
```
|
||||
|
||||
## Multi-Qubit Gates
|
||||
|
||||
### Two-Qubit Gates
|
||||
|
||||
```python
|
||||
# CNOT (Controlled-NOT)
|
||||
qml.CNOT(wires=[0, 1]) # control=0, target=1
|
||||
|
||||
# CZ (Controlled-Z)
|
||||
qml.CZ(wires=[0, 1])
|
||||
|
||||
# SWAP gate
|
||||
qml.SWAP(wires=[0, 1])
|
||||
|
||||
# Controlled rotations
|
||||
qml.CRX(theta, wires=[0, 1])
|
||||
qml.CRY(theta, wires=[0, 1])
|
||||
qml.CRZ(theta, wires=[0, 1])
|
||||
|
||||
# Ising coupling gates
|
||||
qml.IsingXX(phi, wires=[0, 1])
|
||||
qml.IsingYY(phi, wires=[0, 1])
|
||||
qml.IsingZZ(phi, wires=[0, 1])
|
||||
```
|
||||
|
||||
### Multi-Qubit Gates
|
||||
|
||||
```python
|
||||
# Toffoli gate (CCNOT)
|
||||
qml.Toffoli(wires=[0, 1, 2]) # control=0,1, target=2
|
||||
|
||||
# Multi-controlled X
|
||||
qml.MultiControlledX(control_wires=[0, 1, 2], wires=3)
|
||||
|
||||
# Multi-qubit Pauli rotations
|
||||
qml.MultiRZ(theta, wires=[0, 1, 2])
|
||||
```
|
||||
|
||||
## Controlled Operations
|
||||
|
||||
### General Controlled Operations
|
||||
|
||||
```python
|
||||
# Apply controlled version of any operation
|
||||
qml.ctrl(qml.RX(0.5, wires=1), control=0)
|
||||
|
||||
# Multiple control qubits
|
||||
qml.ctrl(qml.RY(0.3, wires=2), control=[0, 1])
|
||||
|
||||
# Negative controls (activate when control is |0⟩)
|
||||
qml.ctrl(qml.Hadamard(wires=2), control=0, control_values=[0])
|
||||
```
|
||||
|
||||
### Conditional Operations
|
||||
|
||||
```python
|
||||
@qml.qnode(dev)
|
||||
def conditional_circuit():
|
||||
qml.Hadamard(wires=0)
|
||||
|
||||
# Mid-circuit measurement
|
||||
m = qml.measure(0)
|
||||
|
||||
# Apply gate conditionally
|
||||
qml.cond(m, qml.PauliX)(wires=1)
|
||||
|
||||
return qml.expval(qml.PauliZ(1))
|
||||
```
|
||||
|
||||
## Measurements
|
||||
|
||||
### Expectation Values
|
||||
|
||||
```python
|
||||
@qml.qnode(dev)
|
||||
def measure_expectation():
|
||||
qml.Hadamard(wires=0)
|
||||
|
||||
# Single observable
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
@qml.qnode(dev)
|
||||
def measure_tensor():
|
||||
qml.Hadamard(wires=0)
|
||||
qml.Hadamard(wires=1)
|
||||
|
||||
# Tensor product of observables
|
||||
return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
|
||||
```
|
||||
|
||||
### Probability Distributions
|
||||
|
||||
```python
|
||||
@qml.qnode(dev)
|
||||
def measure_probabilities():
|
||||
qml.Hadamard(wires=0)
|
||||
qml.CNOT(wires=[0, 1])
|
||||
|
||||
# Probabilities of all basis states
|
||||
return qml.probs(wires=[0, 1]) # Returns [p(|00⟩), p(|01⟩), p(|10⟩), p(|11⟩)]
|
||||
```
|
||||
|
||||
### Samples and Counts
|
||||
|
||||
```python
|
||||
@qml.qnode(dev)
|
||||
def measure_samples(shots=1000):
|
||||
qml.Hadamard(wires=0)
|
||||
|
||||
# Raw samples
|
||||
return qml.sample(qml.PauliZ(0))
|
||||
|
||||
@qml.qnode(dev)
|
||||
def measure_counts(shots=1000):
|
||||
qml.Hadamard(wires=0)
|
||||
qml.CNOT(wires=[0, 1])
|
||||
|
||||
# Count occurrences
|
||||
return qml.counts(wires=[0, 1])
|
||||
```
|
||||
|
||||
### Variance
|
||||
|
||||
```python
|
||||
@qml.qnode(dev)
|
||||
def measure_variance():
|
||||
qml.RX(0.5, wires=0)
|
||||
|
||||
# Variance of observable
|
||||
return qml.var(qml.PauliZ(0))
|
||||
```
|
||||
|
||||
### Mid-Circuit Measurements
|
||||
|
||||
```python
|
||||
@qml.qnode(dev)
|
||||
def mid_circuit_measure():
|
||||
qml.Hadamard(wires=0)
|
||||
|
||||
# Measure qubit 0 during circuit
|
||||
m0 = qml.measure(0)
|
||||
|
||||
# Use measurement result
|
||||
qml.cond(m0, qml.PauliX)(wires=1)
|
||||
|
||||
# Final measurement
|
||||
return qml.expval(qml.PauliZ(1))
|
||||
```
|
||||
|
||||
## Circuit Construction Patterns
|
||||
|
||||
### Layer-Based Construction
|
||||
|
||||
```python
|
||||
def layer(weights, wires):
|
||||
"""Single layer of parameterized gates."""
|
||||
for i, wire in enumerate(wires):
|
||||
qml.RY(weights[i], wires=wire)
|
||||
|
||||
for wire in wires[:-1]:
|
||||
qml.CNOT(wires=[wire, wire+1])
|
||||
|
||||
@qml.qnode(dev)
|
||||
def layered_circuit(weights):
|
||||
n_layers = len(weights)
|
||||
wires = range(4)
|
||||
|
||||
for i in range(n_layers):
|
||||
layer(weights[i], wires)
|
||||
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
```
|
||||
|
||||
### Data Encoding
|
||||
|
||||
```python
|
||||
def angle_encoding(x, wires):
|
||||
"""Encode classical data as rotation angles."""
|
||||
for i, wire in enumerate(wires):
|
||||
qml.RX(x[i], wires=wire)
|
||||
|
||||
def amplitude_encoding(x, wires):
|
||||
"""Encode data as quantum state amplitudes."""
|
||||
qml.MottonenStatePreparation(x, wires=wires)
|
||||
|
||||
def basis_encoding(x, wires):
|
||||
"""Encode binary data in computational basis."""
|
||||
for i, val in enumerate(x):
|
||||
if val:
|
||||
qml.PauliX(wires=i)
|
||||
```
|
||||
|
||||
### Ansatz Patterns
|
||||
|
||||
```python
|
||||
# Hardware-efficient ansatz
|
||||
def hardware_efficient_ansatz(weights, wires):
|
||||
n_layers = len(weights) // len(wires)
|
||||
|
||||
for layer in range(n_layers):
|
||||
# Rotation layer
|
||||
for i, wire in enumerate(wires):
|
||||
qml.RY(weights[layer * len(wires) + i], wires=wire)
|
||||
|
||||
# Entanglement layer
|
||||
for wire in wires[:-1]:
|
||||
qml.CNOT(wires=[wire, wire+1])
|
||||
|
||||
# Alternating layered ansatz
|
||||
def alternating_ansatz(weights, wires):
|
||||
for w in weights:
|
||||
for wire in wires:
|
||||
qml.RX(w[wire], wires=wire)
|
||||
for wire in wires[:-1]:
|
||||
qml.CNOT(wires=[wire, wire+1])
|
||||
```
|
||||
|
||||
## Dynamic Circuits
|
||||
|
||||
### For Loops
|
||||
|
||||
```python
|
||||
@qml.qnode(dev)
|
||||
def dynamic_for_loop(n_iterations):
|
||||
qml.Hadamard(wires=0)
|
||||
|
||||
# Dynamic for loop
|
||||
for i in range(n_iterations):
|
||||
qml.RX(0.1 * i, wires=0)
|
||||
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
```
|
||||
|
||||
### While Loops (with Catalyst)
|
||||
|
||||
```python
|
||||
@qml.qjit # Just-in-time compilation
|
||||
@qml.qnode(dev)
|
||||
def dynamic_while_loop():
|
||||
qml.Hadamard(wires=0)
|
||||
|
||||
# Dynamic while loop
|
||||
@qml.while_loop(lambda i: i < 5)
|
||||
def loop(i):
|
||||
qml.RX(0.1, wires=0)
|
||||
return i + 1
|
||||
|
||||
loop(0)
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
```
|
||||
|
||||
### Adaptive Circuits
|
||||
|
||||
```python
|
||||
@qml.qnode(dev)
|
||||
def adaptive_circuit():
|
||||
qml.Hadamard(wires=0)
|
||||
|
||||
# Measure and adapt
|
||||
m = qml.measure(0)
|
||||
|
||||
# Different paths based on measurement
|
||||
if m:
|
||||
qml.RX(0.5, wires=1)
|
||||
else:
|
||||
qml.RY(0.5, wires=1)
|
||||
|
||||
return qml.expval(qml.PauliZ(1))
|
||||
```
|
||||
|
||||
## Circuit Inspection
|
||||
|
||||
### Drawing Circuits
|
||||
|
||||
```python
|
||||
# Text representation
|
||||
print(qml.draw(circuit)(params))
|
||||
|
||||
# ASCII art
|
||||
print(qml.draw(circuit, wire_order=[0,1,2])(params))
|
||||
|
||||
# Matplotlib visualization
|
||||
fig, ax = qml.draw_mpl(circuit)(params)
|
||||
```
|
||||
|
||||
### Analyzing Circuit Structure
|
||||
|
||||
```python
|
||||
# Get circuit specs
|
||||
specs = qml.specs(circuit)(params)
|
||||
print(f"Gates: {specs['gate_sizes']}")
|
||||
print(f"Depth: {specs['depth']}")
|
||||
print(f"Parameters: {specs['num_trainable_params']}")
|
||||
|
||||
# Resource estimation
|
||||
resources = qml.resource.resource_estimation(circuit)(params)
|
||||
print(f"Total gates: {resources['num_gates']}")
|
||||
```
|
||||
|
||||
### Tape Inspection
|
||||
|
||||
```python
|
||||
# Record operations
|
||||
with qml.tape.QuantumTape() as tape:
|
||||
qml.Hadamard(wires=0)
|
||||
qml.CNOT(wires=[0, 1])
|
||||
qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Inspect tape contents
|
||||
print("Operations:", tape.operations)
|
||||
print("Measurements:", tape.measurements)
|
||||
print("Wires used:", tape.wires)
|
||||
```
|
||||
|
||||
### Circuit Transformations
|
||||
|
||||
```python
|
||||
# Expand composite operations
|
||||
expanded = qml.transforms.expand_tape(tape)
|
||||
|
||||
# Cancel adjacent operations
|
||||
optimized = qml.transforms.cancel_inverses(tape)
|
||||
|
||||
# Commute measurements to end
|
||||
commuted = qml.transforms.commute_controlled(tape)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use native gates** - Prefer gates supported by target device
|
||||
2. **Minimize circuit depth** - Reduce decoherence effects
|
||||
3. **Encode efficiently** - Choose encoding matching data structure
|
||||
4. **Reuse circuits** - Cache compiled circuits when possible
|
||||
5. **Validate measurements** - Ensure observables are Hermitian
|
||||
6. **Check qubit count** - Verify device has sufficient wires
|
||||
7. **Profile circuits** - Use `qml.specs()` to analyze complexity
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Bell State Preparation
|
||||
|
||||
```python
|
||||
@qml.qnode(dev)
|
||||
def bell_state():
|
||||
qml.Hadamard(wires=0)
|
||||
qml.CNOT(wires=[0, 1])
|
||||
return qml.state() # Returns |Φ+⟩ = (|00⟩ + |11⟩)/√2
|
||||
```
|
||||
|
||||
### GHZ State
|
||||
|
||||
```python
|
||||
@qml.qnode(dev)
|
||||
def ghz_state(n_qubits):
|
||||
qml.Hadamard(wires=0)
|
||||
for i in range(n_qubits-1):
|
||||
qml.CNOT(wires=[0, i+1])
|
||||
return qml.state()
|
||||
```
|
||||
|
||||
### Quantum Fourier Transform
|
||||
|
||||
```python
|
||||
def qft(wires):
|
||||
"""Quantum Fourier Transform."""
|
||||
n_wires = len(wires)
|
||||
for i in range(n_wires):
|
||||
qml.Hadamard(wires=wires[i])
|
||||
for j in range(i+1, n_wires):
|
||||
qml.CRZ(np.pi / (2**(j-i)), wires=[wires[j], wires[i]])
|
||||
```
|
||||
|
||||
### Inverse QFT
|
||||
|
||||
```python
|
||||
def inverse_qft(wires):
|
||||
"""Inverse Quantum Fourier Transform."""
|
||||
n_wires = len(wires)
|
||||
for i in range(n_wires-1, -1, -1):
|
||||
for j in range(n_wires-1, i, -1):
|
||||
qml.CRZ(-np.pi / (2**(j-i)), wires=[wires[j], wires[i]])
|
||||
qml.Hadamard(wires=wires[i])
|
||||
```
|
||||
571
scientific-skills/pennylane/references/quantum_ml.md
Normal file
571
scientific-skills/pennylane/references/quantum_ml.md
Normal file
@@ -0,0 +1,571 @@
|
||||
# Quantum Machine Learning with PennyLane
|
||||
|
||||
## Table of Contents
|
||||
1. [Hybrid Quantum-Classical Models](#hybrid-quantum-classical-models)
|
||||
2. [Framework Integration](#framework-integration)
|
||||
3. [Quantum Neural Networks](#quantum-neural-networks)
|
||||
4. [Variational Classifiers](#variational-classifiers)
|
||||
5. [Training and Optimization](#training-and-optimization)
|
||||
6. [Data Encoding Strategies](#data-encoding-strategies)
|
||||
7. [Transfer Learning](#transfer-learning)
|
||||
|
||||
## Hybrid Quantum-Classical Models
|
||||
|
||||
### Basic Hybrid Model
|
||||
|
||||
```python
|
||||
import pennylane as qml
|
||||
import numpy as np
|
||||
|
||||
dev = qml.device('default.qubit', wires=4)
|
||||
|
||||
@qml.qnode(dev)
|
||||
def quantum_layer(inputs, weights):
|
||||
# Encode classical data
|
||||
for i, inp in enumerate(inputs):
|
||||
qml.RY(inp, wires=i)
|
||||
|
||||
# Parameterized quantum circuit
|
||||
for wire in range(4):
|
||||
qml.RX(weights[wire], wires=wire)
|
||||
|
||||
for wire in range(3):
|
||||
qml.CNOT(wires=[wire, wire+1])
|
||||
|
||||
# Measure
|
||||
return [qml.expval(qml.PauliZ(i)) for i in range(4)]
|
||||
|
||||
# Use in classical workflow
|
||||
inputs = np.array([0.1, 0.2, 0.3, 0.4])
|
||||
weights = np.random.random(4)
|
||||
output = quantum_layer(inputs, weights)
|
||||
```
|
||||
|
||||
### Quantum-Classical Pipeline
|
||||
|
||||
```python
|
||||
def hybrid_model(x, quantum_weights, classical_weights):
|
||||
# Classical preprocessing
|
||||
x_preprocessed = np.tanh(classical_weights['pre'] @ x)
|
||||
|
||||
# Quantum layer
|
||||
quantum_out = quantum_layer(x_preprocessed, quantum_weights)
|
||||
|
||||
# Classical postprocessing
|
||||
output = classical_weights['post'] @ quantum_out
|
||||
|
||||
return output
|
||||
```
|
||||
|
||||
## Framework Integration
|
||||
|
||||
### PyTorch Integration
|
||||
|
||||
```python
|
||||
import torch
|
||||
import pennylane as qml
|
||||
|
||||
dev = qml.device('default.qubit', wires=2)
|
||||
|
||||
@qml.qnode(dev, interface='torch')
|
||||
def quantum_circuit(inputs, weights):
|
||||
qml.RY(inputs[0], wires=0)
|
||||
qml.RY(inputs[1], wires=1)
|
||||
qml.RX(weights[0], wires=0)
|
||||
qml.RX(weights[1], wires=1)
|
||||
qml.CNOT(wires=[0, 1])
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Create PyTorch layer
|
||||
class QuantumLayer(torch.nn.Module):
|
||||
def __init__(self, n_qubits):
|
||||
super().__init__()
|
||||
self.n_qubits = n_qubits
|
||||
self.weights = torch.nn.Parameter(torch.randn(n_qubits))
|
||||
|
||||
def forward(self, x):
|
||||
return torch.stack([quantum_circuit(xi, self.weights) for xi in x])
|
||||
|
||||
# Use in PyTorch model
|
||||
class HybridModel(torch.nn.Module):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.classical_1 = torch.nn.Linear(10, 2)
|
||||
self.quantum = QuantumLayer(2)
|
||||
self.classical_2 = torch.nn.Linear(1, 2)
|
||||
|
||||
def forward(self, x):
|
||||
x = torch.relu(self.classical_1(x))
|
||||
x = self.quantum(x)
|
||||
x = self.classical_2(x.unsqueeze(1))
|
||||
return x
|
||||
|
||||
# Training loop
|
||||
model = HybridModel()
|
||||
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
|
||||
criterion = torch.nn.CrossEntropyLoss()
|
||||
|
||||
for epoch in range(100):
|
||||
optimizer.zero_grad()
|
||||
outputs = model(inputs)
|
||||
loss = criterion(outputs, labels)
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
```
|
||||
|
||||
### JAX Integration
|
||||
|
||||
```python
|
||||
import jax
|
||||
import jax.numpy as jnp
|
||||
import pennylane as qml
|
||||
|
||||
dev = qml.device('default.qubit', wires=2)
|
||||
|
||||
@qml.qnode(dev, interface='jax')
|
||||
def quantum_circuit(inputs, weights):
|
||||
qml.RY(inputs[0], wires=0)
|
||||
qml.RY(inputs[1], wires=1)
|
||||
qml.RX(weights[0], wires=0)
|
||||
qml.RX(weights[1], wires=1)
|
||||
qml.CNOT(wires=[0, 1])
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# JAX-compatible training
|
||||
@jax.jit
|
||||
def loss_fn(weights, x, y):
|
||||
predictions = quantum_circuit(x, weights)
|
||||
return jnp.mean((predictions - y) ** 2)
|
||||
|
||||
# Compute gradients with JAX
|
||||
grad_fn = jax.grad(loss_fn)
|
||||
|
||||
# Training
|
||||
weights = jnp.array([0.1, 0.2])
|
||||
for i in range(100):
|
||||
grads = grad_fn(weights, x_train, y_train)
|
||||
weights = weights - 0.01 * grads
|
||||
```
|
||||
|
||||
### TensorFlow Integration
|
||||
|
||||
```python
|
||||
import tensorflow as tf
|
||||
import pennylane as qml
|
||||
|
||||
dev = qml.device('default.qubit', wires=2)
|
||||
|
||||
@qml.qnode(dev, interface='tf')
|
||||
def quantum_circuit(inputs, weights):
|
||||
qml.RY(inputs[0], wires=0)
|
||||
qml.RY(inputs[1], wires=1)
|
||||
qml.RX(weights[0], wires=0)
|
||||
qml.RX(weights[1], wires=1)
|
||||
qml.CNOT(wires=[0, 1])
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Keras layer
|
||||
class QuantumLayer(tf.keras.layers.Layer):
|
||||
def __init__(self, n_qubits):
|
||||
super().__init__()
|
||||
self.n_qubits = n_qubits
|
||||
weight_init = tf.random_uniform_initializer()
|
||||
self.weights = tf.Variable(
|
||||
initial_value=weight_init(shape=(n_qubits,), dtype=tf.float32),
|
||||
trainable=True
|
||||
)
|
||||
|
||||
def call(self, inputs):
|
||||
return tf.stack([quantum_circuit(x, self.weights) for x in inputs])
|
||||
|
||||
# Keras model
|
||||
model = tf.keras.Sequential([
|
||||
tf.keras.layers.Dense(2, activation='relu'),
|
||||
QuantumLayer(2),
|
||||
tf.keras.layers.Dense(2, activation='softmax')
|
||||
])
|
||||
|
||||
model.compile(
|
||||
optimizer=tf.keras.optimizers.Adam(0.01),
|
||||
loss='sparse_categorical_crossentropy',
|
||||
metrics=['accuracy']
|
||||
)
|
||||
|
||||
model.fit(x_train, y_train, epochs=100, batch_size=32)
|
||||
```
|
||||
|
||||
## Quantum Neural Networks
|
||||
|
||||
### Variational Quantum Circuit (VQC)
|
||||
|
||||
```python
|
||||
from pennylane import numpy as np
|
||||
|
||||
dev = qml.device('default.qubit', wires=4)
|
||||
|
||||
def variational_block(weights, wires):
|
||||
"""Single layer of variational circuit."""
|
||||
for i, wire in enumerate(wires):
|
||||
qml.RY(weights[i, 0], wires=wire)
|
||||
qml.RZ(weights[i, 1], wires=wire)
|
||||
|
||||
for i in range(len(wires)-1):
|
||||
qml.CNOT(wires=[wires[i], wires[i+1]])
|
||||
|
||||
@qml.qnode(dev)
|
||||
def quantum_neural_network(inputs, weights):
|
||||
# Encode inputs
|
||||
for i, inp in enumerate(inputs):
|
||||
qml.RY(inp, wires=i)
|
||||
|
||||
# Apply variational layers
|
||||
n_layers = len(weights)
|
||||
for layer_weights in weights:
|
||||
variational_block(layer_weights, wires=range(4))
|
||||
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Initialize weights
|
||||
n_layers = 3
|
||||
n_wires = 4
|
||||
weights_shape = (n_layers, n_wires, 2)
|
||||
weights = np.random.random(weights_shape, requires_grad=True)
|
||||
```
|
||||
|
||||
### Quantum Convolutional Neural Network
|
||||
|
||||
```python
|
||||
def conv_layer(weights, wires):
|
||||
"""Quantum convolutional layer."""
|
||||
n_wires = len(wires)
|
||||
|
||||
# Apply local unitaries
|
||||
for i in range(n_wires):
|
||||
qml.RY(weights[i], wires=wires[i])
|
||||
|
||||
# Nearest-neighbor entanglement
|
||||
for i in range(0, n_wires-1, 2):
|
||||
qml.CNOT(wires=[wires[i], wires[i+1]])
|
||||
|
||||
def pooling_layer(wires):
|
||||
"""Quantum pooling (measure and discard)."""
|
||||
measurements = []
|
||||
for i in range(0, len(wires), 2):
|
||||
measurements.append(qml.measure(wires[i]))
|
||||
return measurements
|
||||
|
||||
@qml.qnode(dev)
|
||||
def qcnn(inputs, weights):
|
||||
# Encode image data
|
||||
for i, pixel in enumerate(inputs):
|
||||
qml.RY(pixel, wires=i)
|
||||
|
||||
# Convolutional layers
|
||||
conv_layer(weights[0], wires=range(8))
|
||||
pooling_layer(wires=range(0, 8, 2))
|
||||
|
||||
conv_layer(weights[1], wires=range(1, 8, 2))
|
||||
pooling_layer(wires=range(1, 8, 4))
|
||||
|
||||
return qml.expval(qml.PauliZ(1))
|
||||
```
|
||||
|
||||
### Quantum Recurrent Neural Network
|
||||
|
||||
```python
|
||||
def qrnn_cell(x, hidden, weights):
|
||||
"""Single QRNN cell."""
|
||||
@qml.qnode(dev)
|
||||
def cell(x, h, w):
|
||||
# Encode input and hidden state
|
||||
qml.RY(x, wires=0)
|
||||
qml.RY(h, wires=1)
|
||||
|
||||
# Apply recurrent transformation
|
||||
qml.RX(w[0], wires=0)
|
||||
qml.RX(w[1], wires=1)
|
||||
qml.CNOT(wires=[0, 1])
|
||||
qml.RY(w[2], wires=1)
|
||||
|
||||
return qml.expval(qml.PauliZ(1))
|
||||
|
||||
return cell(x, hidden, weights)
|
||||
|
||||
def qrnn_sequence(sequence, weights):
|
||||
"""Process sequence with QRNN."""
|
||||
hidden = 0.0
|
||||
outputs = []
|
||||
|
||||
for x in sequence:
|
||||
hidden = qrnn_cell(x, hidden, weights)
|
||||
outputs.append(hidden)
|
||||
|
||||
return outputs
|
||||
```
|
||||
|
||||
## Variational Classifiers
|
||||
|
||||
### Binary Classification
|
||||
|
||||
```python
|
||||
dev = qml.device('default.qubit', wires=2)
|
||||
|
||||
@qml.qnode(dev)
|
||||
def variational_classifier(x, weights):
|
||||
# Feature map
|
||||
qml.RY(x[0], wires=0)
|
||||
qml.RY(x[1], wires=1)
|
||||
|
||||
# Variational layers
|
||||
for w in weights:
|
||||
qml.RX(w[0], wires=0)
|
||||
qml.RX(w[1], wires=1)
|
||||
qml.CNOT(wires=[0, 1])
|
||||
qml.RY(w[2], wires=0)
|
||||
qml.RY(w[3], wires=1)
|
||||
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
def cost_function(weights, X, y):
|
||||
"""Binary cross-entropy loss."""
|
||||
predictions = np.array([variational_classifier(x, weights) for x in X])
|
||||
predictions = (predictions + 1) / 2 # Map [-1, 1] to [0, 1]
|
||||
return -np.mean(y * np.log(predictions) + (1 - y) * np.log(1 - predictions))
|
||||
|
||||
# Training
|
||||
n_layers = 2
|
||||
n_params_per_layer = 4
|
||||
weights = np.random.random((n_layers, n_params_per_layer), requires_grad=True)
|
||||
|
||||
opt = qml.GradientDescentOptimizer(stepsize=0.1)
|
||||
for i in range(100):
|
||||
weights = opt.step(lambda w: cost_function(w, X_train, y_train), weights)
|
||||
```
|
||||
|
||||
### Multi-Class Classification
|
||||
|
||||
```python
|
||||
@qml.qnode(dev)
|
||||
def multiclass_circuit(x, weights):
|
||||
# Encode input
|
||||
for i, val in enumerate(x):
|
||||
qml.RY(val, wires=i)
|
||||
|
||||
# Variational circuit
|
||||
for layer_weights in weights:
|
||||
for i, w in enumerate(layer_weights):
|
||||
qml.RY(w, wires=i)
|
||||
for i in range(len(x)-1):
|
||||
qml.CNOT(wires=[i, i+1])
|
||||
|
||||
# Multiple outputs for classes
|
||||
return [qml.expval(qml.PauliZ(i)) for i in range(3)]
|
||||
|
||||
def softmax(x):
|
||||
exp_x = np.exp(x - np.max(x))
|
||||
return exp_x / exp_x.sum()
|
||||
|
||||
def predict_class(x, weights):
|
||||
logits = multiclass_circuit(x, weights)
|
||||
return softmax(logits)
|
||||
```
|
||||
|
||||
## Training and Optimization
|
||||
|
||||
### Gradient-Based Training
|
||||
|
||||
```python
|
||||
# Automatic differentiation
|
||||
@qml.qnode(dev, diff_method='backprop')
|
||||
def circuit_backprop(x, weights):
|
||||
# ... circuit definition
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Parameter shift rule
|
||||
@qml.qnode(dev, diff_method='parameter-shift')
|
||||
def circuit_param_shift(x, weights):
|
||||
# ... circuit definition
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Finite differences
|
||||
@qml.qnode(dev, diff_method='finite-diff')
|
||||
def circuit_finite_diff(x, weights):
|
||||
# ... circuit definition
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
```
|
||||
|
||||
### Mini-Batch Training
|
||||
|
||||
```python
|
||||
def batch_cost(weights, X_batch, y_batch):
|
||||
predictions = np.array([variational_classifier(x, weights) for x in X_batch])
|
||||
return np.mean((predictions - y_batch) ** 2)
|
||||
|
||||
# Mini-batch training
|
||||
batch_size = 32
|
||||
n_epochs = 100
|
||||
|
||||
for epoch in range(n_epochs):
|
||||
for i in range(0, len(X_train), batch_size):
|
||||
X_batch = X_train[i:i+batch_size]
|
||||
y_batch = y_train[i:i+batch_size]
|
||||
|
||||
weights = opt.step(lambda w: batch_cost(w, X_batch, y_batch), weights)
|
||||
```
|
||||
|
||||
### Learning Rate Scheduling
|
||||
|
||||
```python
|
||||
def train_with_schedule(weights, X, y, n_epochs):
|
||||
initial_lr = 0.1
|
||||
decay = 0.95
|
||||
|
||||
for epoch in range(n_epochs):
|
||||
lr = initial_lr * (decay ** epoch)
|
||||
opt = qml.GradientDescentOptimizer(stepsize=lr)
|
||||
|
||||
weights = opt.step(lambda w: cost_function(w, X, y), weights)
|
||||
|
||||
if epoch % 10 == 0:
|
||||
print(f"Epoch {epoch}, Loss: {cost_function(weights, X, y)}")
|
||||
|
||||
return weights
|
||||
```
|
||||
|
||||
## Data Encoding Strategies
|
||||
|
||||
### Angle Encoding
|
||||
|
||||
```python
|
||||
def angle_encoding(x, wires):
|
||||
"""Encode features as rotation angles."""
|
||||
for i, feature in enumerate(x):
|
||||
qml.RY(feature, wires=wires[i])
|
||||
```
|
||||
|
||||
### Amplitude Encoding
|
||||
|
||||
```python
|
||||
def amplitude_encoding(x, wires):
|
||||
"""Encode features as state amplitudes."""
|
||||
# Normalize
|
||||
x_norm = x / np.linalg.norm(x)
|
||||
qml.MottonenStatePreparation(x_norm, wires=wires)
|
||||
```
|
||||
|
||||
### Basis Encoding
|
||||
|
||||
```python
|
||||
def basis_encoding(x, wires):
|
||||
"""Encode binary features in computational basis."""
|
||||
for i, bit in enumerate(x):
|
||||
if bit:
|
||||
qml.PauliX(wires=wires[i])
|
||||
```
|
||||
|
||||
### IQP Encoding
|
||||
|
||||
```python
|
||||
def iqp_encoding(x, wires):
|
||||
"""Instantaneous Quantum Polynomial encoding."""
|
||||
# Hadamard layer
|
||||
for wire in wires:
|
||||
qml.Hadamard(wires=wire)
|
||||
|
||||
# Encode features
|
||||
for i, feature in enumerate(x):
|
||||
qml.RZ(feature, wires=wires[i])
|
||||
|
||||
# Entanglement
|
||||
for i in range(len(wires)-1):
|
||||
qml.IsingZZ(x[i] * x[i+1], wires=[wires[i], wires[i+1]])
|
||||
```
|
||||
|
||||
### Hamiltonian Encoding
|
||||
|
||||
```python
|
||||
def hamiltonian_encoding(x, wires, time=1.0):
|
||||
"""Encode via Hamiltonian evolution."""
|
||||
# Build Hamiltonian from features
|
||||
coeffs = x
|
||||
obs = [qml.PauliZ(i) for i in wires]
|
||||
|
||||
H = qml.Hamiltonian(coeffs, obs)
|
||||
|
||||
# Apply time evolution
|
||||
qml.ApproxTimeEvolution(H, time, n=10)
|
||||
```
|
||||
|
||||
## Transfer Learning
|
||||
|
||||
### Pre-trained Quantum Model
|
||||
|
||||
```python
|
||||
# Train on large dataset
|
||||
pretrained_weights = train_quantum_model(large_dataset)
|
||||
|
||||
# Fine-tune on specific task
|
||||
def fine_tune(pretrained_weights, small_dataset, n_epochs=50):
|
||||
# Freeze early layers
|
||||
frozen_weights = pretrained_weights[:-1] # All but last layer
|
||||
trainable_weights = pretrained_weights[-1:] # Only last layer
|
||||
|
||||
@qml.qnode(dev)
|
||||
def transfer_circuit(x, trainable):
|
||||
# Apply frozen layers
|
||||
for layer_w in frozen_weights:
|
||||
variational_block(layer_w, wires=range(4))
|
||||
|
||||
# Apply trainable layer
|
||||
variational_block(trainable, wires=range(4))
|
||||
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Train only last layer
|
||||
opt = qml.AdamOptimizer(stepsize=0.01)
|
||||
for epoch in range(n_epochs):
|
||||
trainable_weights = opt.step(
|
||||
lambda w: cost_function(w, small_dataset),
|
||||
trainable_weights
|
||||
)
|
||||
|
||||
return np.concatenate([frozen_weights, trainable_weights])
|
||||
```
|
||||
|
||||
### Classical-to-Quantum Transfer
|
||||
|
||||
```python
|
||||
# Use classical network for feature extraction
|
||||
import torch.nn as nn
|
||||
|
||||
classical_extractor = nn.Sequential(
|
||||
nn.Conv2d(3, 16, 3),
|
||||
nn.ReLU(),
|
||||
nn.MaxPool2d(2),
|
||||
nn.Flatten(),
|
||||
nn.Linear(16*13*13, 4) # Output 4 features for quantum circuit
|
||||
)
|
||||
|
||||
# Quantum classifier
|
||||
@qml.qnode(dev)
|
||||
def quantum_classifier(features, weights):
|
||||
angle_encoding(features, wires=range(4))
|
||||
variational_block(weights, wires=range(4))
|
||||
return qml.expval(qml.PauliZ(0))
|
||||
|
||||
# Combined model
|
||||
def hybrid_transfer_model(image, classical_weights, quantum_weights):
|
||||
features = classical_extractor(image)
|
||||
return quantum_classifier(features, quantum_weights)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Start simple** - Begin with small circuits and scale up
|
||||
2. **Choose encoding wisely** - Match encoding to data structure
|
||||
3. **Use appropriate interfaces** - Select interface matching your ML framework
|
||||
4. **Monitor gradients** - Check for vanishing/exploding gradients (barren plateaus)
|
||||
5. **Regularize** - Add L2 regularization to prevent overfitting
|
||||
6. **Validate hardware compatibility** - Test on simulators before hardware
|
||||
7. **Batch efficiently** - Use vectorization when possible
|
||||
8. **Cache compilations** - Reuse compiled circuits for inference
|
||||
Reference in New Issue
Block a user