mirror of
https://github.com/K-Dense-AI/claude-scientific-skills.git
synced 2026-01-26 16:58:56 +08:00
534 lines
13 KiB
Markdown
534 lines
13 KiB
Markdown
# Qiskit Patterns: The Four-Step Workflow
|
||
|
||
Qiskit Patterns provide a general framework for solving domain-specific quantum computing problems in four stages: Map, Optimize, Execute, and Post-process.
|
||
|
||
## Overview
|
||
|
||
The patterns framework enables seamless composition of quantum capabilities and supports heterogeneous computing infrastructure (CPU/GPU/QPU). Execute locally, through cloud services, or via Qiskit Serverless.
|
||
|
||
## The Four Steps
|
||
|
||
```
|
||
Problem → [Map] → [Optimize] → [Execute] → [Post-process] → Solution
|
||
```
|
||
|
||
### 1. Map
|
||
Translate classical problems into quantum circuits and operators
|
||
|
||
### 2. Optimize
|
||
Prepare circuits for target hardware through transpilation
|
||
|
||
### 3. Execute
|
||
Run circuits on quantum hardware using primitives
|
||
|
||
### 4. Post-process
|
||
Extract and refine results with classical computation
|
||
|
||
## Step 1: Map
|
||
|
||
### Goal
|
||
Transform domain-specific problems into quantum representations (circuits, operators, Hamiltonians).
|
||
|
||
### Key Decisions
|
||
|
||
**Choose Output Type:**
|
||
- **Sampler**: For bitstring outputs (optimization, search)
|
||
- **Estimator**: For expectation values (chemistry, physics)
|
||
|
||
**Design Circuit Structure:**
|
||
```python
|
||
from qiskit import QuantumCircuit
|
||
from qiskit.circuit import Parameter
|
||
import numpy as np
|
||
|
||
# Example: Parameterized circuit for VQE
|
||
def create_ansatz(num_qubits, depth):
|
||
qc = QuantumCircuit(num_qubits)
|
||
params = []
|
||
|
||
for d in range(depth):
|
||
# Rotation layer
|
||
for i in range(num_qubits):
|
||
theta = Parameter(f'θ_{d}_{i}')
|
||
params.append(theta)
|
||
qc.ry(theta, i)
|
||
|
||
# Entanglement layer
|
||
for i in range(num_qubits - 1):
|
||
qc.cx(i, i + 1)
|
||
|
||
return qc, params
|
||
|
||
ansatz, params = create_ansatz(num_qubits=4, depth=2)
|
||
```
|
||
|
||
### Considerations
|
||
|
||
- **Hardware topology**: Design with backend coupling map in mind
|
||
- **Gate efficiency**: Minimize two-qubit gates
|
||
- **Measurement basis**: Determine required measurements
|
||
|
||
### Domain-Specific Examples
|
||
|
||
**Chemistry: Molecular Hamiltonian**
|
||
```python
|
||
from qiskit_nature.second_q.drivers import PySCFDriver
|
||
from qiskit_nature.second_q.mappers import JordanWignerMapper
|
||
|
||
# Define molecule
|
||
driver = PySCFDriver(atom='H 0 0 0; H 0 0 0.735', basis='sto3g')
|
||
problem = driver.run()
|
||
|
||
# Map to qubit Hamiltonian
|
||
mapper = JordanWignerMapper()
|
||
hamiltonian = mapper.map(problem.hamiltonian)
|
||
```
|
||
|
||
**Optimization: QAOA Circuit**
|
||
```python
|
||
from qiskit.circuit import QuantumCircuit, Parameter
|
||
|
||
def qaoa_circuit(graph, p):
|
||
"""Create QAOA circuit for MaxCut problem"""
|
||
num_qubits = len(graph.nodes())
|
||
qc = QuantumCircuit(num_qubits)
|
||
|
||
# Initial superposition
|
||
qc.h(range(num_qubits))
|
||
|
||
# Alternating layers
|
||
betas = [Parameter(f'β_{i}') for i in range(p)]
|
||
gammas = [Parameter(f'γ_{i}') for i in range(p)]
|
||
|
||
for i in range(p):
|
||
# Problem Hamiltonian
|
||
for edge in graph.edges():
|
||
qc.cx(edge[0], edge[1])
|
||
qc.rz(2 * gammas[i], edge[1])
|
||
qc.cx(edge[0], edge[1])
|
||
|
||
# Mixer Hamiltonian
|
||
qc.rx(2 * betas[i], range(num_qubits))
|
||
|
||
return qc
|
||
```
|
||
|
||
## Step 2: Optimize
|
||
|
||
### Goal
|
||
Transform abstract circuits to hardware-compatible ISA (Instruction Set Architecture) circuits.
|
||
|
||
### Transpilation
|
||
|
||
```python
|
||
from qiskit import transpile
|
||
|
||
# Basic transpilation
|
||
qc_isa = transpile(qc, backend=backend, optimization_level=3)
|
||
|
||
# With specific initial layout
|
||
qc_isa = transpile(
|
||
qc,
|
||
backend=backend,
|
||
optimization_level=3,
|
||
initial_layout=[0, 2, 4, 6], # Map to specific physical qubits
|
||
seed_transpiler=42 # Reproducibility
|
||
)
|
||
```
|
||
|
||
### Pre-optimization Tips
|
||
|
||
1. **Test with simulators first**:
|
||
```python
|
||
from qiskit_aer import AerSimulator
|
||
|
||
sim = AerSimulator.from_backend(backend)
|
||
qc_test = transpile(qc, sim, optimization_level=3)
|
||
print(f"Estimated depth: {qc_test.depth()}")
|
||
```
|
||
|
||
2. **Analyze transpilation results**:
|
||
```python
|
||
print(f"Original gates: {qc.size()}")
|
||
print(f"Transpiled gates: {qc_isa.size()}")
|
||
print(f"Two-qubit gates: {qc_isa.count_ops().get('cx', 0)}")
|
||
```
|
||
|
||
3. **Consider circuit cutting** for large circuits:
|
||
```python
|
||
# For circuits too large for available hardware
|
||
# Use circuit cutting techniques to split into smaller subcircuits
|
||
```
|
||
|
||
## Step 3: Execute
|
||
|
||
### Goal
|
||
Run ISA circuits on quantum hardware using primitives.
|
||
|
||
### Using Sampler
|
||
|
||
```python
|
||
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
|
||
|
||
service = QiskitRuntimeService()
|
||
backend = service.backend("ibm_brisbane")
|
||
|
||
# Transpile first
|
||
qc_isa = transpile(qc, backend=backend, optimization_level=3)
|
||
|
||
# Execute
|
||
sampler = Sampler(backend)
|
||
job = sampler.run([qc_isa], shots=10000)
|
||
result = job.result()
|
||
counts = result[0].data.meas.get_counts()
|
||
```
|
||
|
||
### Using Estimator
|
||
|
||
```python
|
||
from qiskit_ibm_runtime import EstimatorV2 as Estimator
|
||
from qiskit.quantum_info import SparsePauliOp
|
||
|
||
service = QiskitRuntimeService()
|
||
backend = service.backend("ibm_brisbane")
|
||
|
||
# Transpile
|
||
qc_isa = transpile(qc, backend=backend, optimization_level=3)
|
||
|
||
# Define observable
|
||
observable = SparsePauliOp(["ZZZZ", "XXXX"])
|
||
|
||
# Execute
|
||
estimator = Estimator(backend)
|
||
job = estimator.run([(qc_isa, observable)])
|
||
result = job.result()
|
||
expectation_value = result[0].data.evs
|
||
```
|
||
|
||
### Execution Modes
|
||
|
||
**Session Mode (Iterative):**
|
||
```python
|
||
from qiskit_ibm_runtime import Session
|
||
|
||
with Session(backend=backend) as session:
|
||
sampler = Sampler(session=session)
|
||
|
||
# Multiple iterations
|
||
for iteration in range(max_iterations):
|
||
qc_iteration = update_circuit(params[iteration])
|
||
qc_isa = transpile(qc_iteration, backend=backend)
|
||
|
||
job = sampler.run([qc_isa], shots=1000)
|
||
result = job.result()
|
||
|
||
# Update parameters
|
||
params[iteration + 1] = optimize_params(result)
|
||
```
|
||
|
||
**Batch Mode (Parallel):**
|
||
```python
|
||
from qiskit_ibm_runtime import Batch
|
||
|
||
with Batch(backend=backend) as batch:
|
||
sampler = Sampler(session=batch)
|
||
|
||
# Submit all jobs at once
|
||
jobs = []
|
||
for qc in circuit_list:
|
||
qc_isa = transpile(qc, backend=backend)
|
||
job = sampler.run([qc_isa], shots=1000)
|
||
jobs.append(job)
|
||
|
||
# Collect results
|
||
results = [job.result() for job in jobs]
|
||
```
|
||
|
||
### Error Mitigation
|
||
|
||
```python
|
||
from qiskit_ibm_runtime import Options
|
||
|
||
options = Options()
|
||
options.resilience_level = 2 # 0=none, 1=light, 2=moderate, 3=heavy
|
||
options.optimization_level = 3
|
||
|
||
sampler = Sampler(backend, options=options)
|
||
```
|
||
|
||
## Step 4: Post-process
|
||
|
||
### Goal
|
||
Extract meaningful results from quantum measurements using classical computation.
|
||
|
||
### Result Processing
|
||
|
||
**For Sampler (Bitstrings):**
|
||
```python
|
||
counts = result[0].data.meas.get_counts()
|
||
|
||
# Convert to probabilities
|
||
total_shots = sum(counts.values())
|
||
probabilities = {state: count/total_shots for state, count in counts.items()}
|
||
|
||
# Find most probable state
|
||
max_state = max(counts, key=counts.get)
|
||
print(f"Most probable state: {max_state} ({counts[max_state]}/{total_shots})")
|
||
```
|
||
|
||
**For Estimator (Expectation Values):**
|
||
```python
|
||
expectation_value = result[0].data.evs
|
||
std_dev = result[0].data.stds # Standard deviation
|
||
|
||
print(f"Energy: {expectation_value} ± {std_dev}")
|
||
```
|
||
|
||
### Domain-Specific Post-Processing
|
||
|
||
**Chemistry: Ground State Energy**
|
||
```python
|
||
def post_process_chemistry(result, nuclear_repulsion):
|
||
"""Extract ground state energy"""
|
||
electronic_energy = result[0].data.evs
|
||
total_energy = electronic_energy + nuclear_repulsion
|
||
return total_energy
|
||
```
|
||
|
||
**Optimization: MaxCut Solution**
|
||
```python
|
||
def post_process_maxcut(counts, graph):
|
||
"""Find best cut from measurement results"""
|
||
def compute_cut_value(bitstring, graph):
|
||
cut_value = 0
|
||
for edge in graph.edges():
|
||
if bitstring[edge[0]] != bitstring[edge[1]]:
|
||
cut_value += 1
|
||
return cut_value
|
||
|
||
# Find bitstring with maximum cut
|
||
best_cut = 0
|
||
best_string = None
|
||
|
||
for bitstring, count in counts.items():
|
||
cut = compute_cut_value(bitstring, graph)
|
||
if cut > best_cut:
|
||
best_cut = cut
|
||
best_string = bitstring
|
||
|
||
return best_string, best_cut
|
||
```
|
||
|
||
### Advanced Post-Processing
|
||
|
||
**Error Mitigation Post-Processing:**
|
||
```python
|
||
# Apply additional classical error mitigation
|
||
from qiskit.result import marginal_counts
|
||
|
||
# Marginalize to relevant qubits
|
||
relevant_qubits = [0, 1, 2]
|
||
marginal = marginal_counts(counts, indices=relevant_qubits)
|
||
```
|
||
|
||
**Statistical Analysis:**
|
||
```python
|
||
import numpy as np
|
||
|
||
def analyze_results(results_list):
|
||
"""Analyze multiple runs for statistics"""
|
||
energies = [r[0].data.evs for r in results_list]
|
||
|
||
mean_energy = np.mean(energies)
|
||
std_energy = np.std(energies)
|
||
confidence_interval = 1.96 * std_energy / np.sqrt(len(energies))
|
||
|
||
return {
|
||
'mean': mean_energy,
|
||
'std': std_energy,
|
||
'95% CI': (mean_energy - confidence_interval, mean_energy + confidence_interval)
|
||
}
|
||
```
|
||
|
||
**Visualization:**
|
||
```python
|
||
from qiskit.visualization import plot_histogram
|
||
import matplotlib.pyplot as plt
|
||
|
||
# Visualize results
|
||
plot_histogram(counts, figsize=(12, 6))
|
||
plt.title("Measurement Results")
|
||
plt.show()
|
||
```
|
||
|
||
## Complete Example: VQE for Chemistry
|
||
|
||
```python
|
||
from qiskit import QuantumCircuit, transpile
|
||
from qiskit_ibm_runtime import QiskitRuntimeService, EstimatorV2 as Estimator, Session
|
||
from qiskit.quantum_info import SparsePauliOp
|
||
from scipy.optimize import minimize
|
||
import numpy as np
|
||
|
||
# 1. MAP: Create parameterized circuit
|
||
def create_ansatz(num_qubits):
|
||
qc = QuantumCircuit(num_qubits)
|
||
params = []
|
||
|
||
for i in range(num_qubits):
|
||
theta = f'θ_{i}'
|
||
params.append(theta)
|
||
qc.ry(theta, i)
|
||
|
||
for i in range(num_qubits - 1):
|
||
qc.cx(i, i + 1)
|
||
|
||
return qc, params
|
||
|
||
# Define Hamiltonian (example: H2 molecule)
|
||
hamiltonian = SparsePauliOp(["IIZZ", "ZZII", "XXII", "IIXX"], coeffs=[0.3, 0.3, 0.1, 0.1])
|
||
|
||
# 2. OPTIMIZE: Connect and prepare
|
||
service = QiskitRuntimeService()
|
||
backend = service.backend("ibm_brisbane")
|
||
|
||
ansatz, param_names = create_ansatz(num_qubits=4)
|
||
|
||
# 3. EXECUTE: Run VQE
|
||
def cost_function(params):
|
||
# Bind parameters
|
||
bound_circuit = ansatz.assign_parameters({param_names[i]: params[i] for i in range(len(params))})
|
||
|
||
# Transpile
|
||
qc_isa = transpile(bound_circuit, backend=backend, optimization_level=3)
|
||
|
||
# Execute
|
||
job = estimator.run([(qc_isa, hamiltonian)])
|
||
result = job.result()
|
||
energy = result[0].data.evs
|
||
|
||
return energy
|
||
|
||
with Session(backend=backend) as session:
|
||
estimator = Estimator(session=session)
|
||
|
||
# Classical optimization loop
|
||
initial_params = np.random.random(len(param_names)) * 2 * np.pi
|
||
result = minimize(cost_function, initial_params, method='COBYLA')
|
||
|
||
# 4. POST-PROCESS: Extract ground state energy
|
||
ground_state_energy = result.fun
|
||
optimized_params = result.x
|
||
|
||
print(f"Ground state energy: {ground_state_energy}")
|
||
print(f"Optimized parameters: {optimized_params}")
|
||
```
|
||
|
||
## Best Practices
|
||
|
||
### 1. Iterate Locally First
|
||
Test the full workflow with simulators before using hardware:
|
||
```python
|
||
from qiskit.primitives import StatevectorEstimator
|
||
|
||
estimator = StatevectorEstimator()
|
||
# Test workflow locally
|
||
```
|
||
|
||
### 2. Use Sessions for Iterative Algorithms
|
||
VQE, QAOA, and other variational algorithms benefit from sessions.
|
||
|
||
### 3. Choose Appropriate Shots
|
||
- Development/testing: 100-1000 shots
|
||
- Production: 10,000+ shots
|
||
|
||
### 4. Monitor Convergence
|
||
```python
|
||
energies = []
|
||
|
||
def cost_function_with_tracking(params):
|
||
energy = cost_function(params)
|
||
energies.append(energy)
|
||
print(f"Iteration {len(energies)}: E = {energy}")
|
||
return energy
|
||
```
|
||
|
||
### 5. Save Results
|
||
```python
|
||
import json
|
||
|
||
results_data = {
|
||
'energy': float(ground_state_energy),
|
||
'parameters': optimized_params.tolist(),
|
||
'iterations': len(energies),
|
||
'backend': backend.name
|
||
}
|
||
|
||
with open('vqe_results.json', 'w') as f:
|
||
json.dump(results_data, f, indent=2)
|
||
```
|
||
|
||
## Qiskit Serverless
|
||
|
||
For large-scale workflows, use Qiskit Serverless for distributed computation:
|
||
|
||
```python
|
||
from qiskit_serverless import ServerlessClient, QiskitFunction
|
||
|
||
client = ServerlessClient()
|
||
|
||
# Define serverless function
|
||
@QiskitFunction()
|
||
def run_vqe_serverless(hamiltonian, ansatz):
|
||
# Your VQE implementation
|
||
pass
|
||
|
||
# Execute remotely
|
||
job = run_vqe_serverless(hamiltonian, ansatz)
|
||
result = job.result()
|
||
```
|
||
|
||
## Common Workflow Patterns
|
||
|
||
### Pattern 1: Parameter Sweep
|
||
```python
|
||
# Map → Optimize once → Execute many → Post-process
|
||
qc_isa = transpile(parameterized_circuit, backend=backend)
|
||
|
||
with Batch(backend=backend) as batch:
|
||
sampler = Sampler(session=batch)
|
||
results = []
|
||
|
||
for param_set in parameter_sweep:
|
||
bound_qc = qc_isa.assign_parameters(param_set)
|
||
job = sampler.run([bound_qc], shots=1000)
|
||
results.append(job.result())
|
||
```
|
||
|
||
### Pattern 2: Iterative Refinement
|
||
```python
|
||
# Map → (Optimize → Execute → Post-process) repeated
|
||
with Session(backend=backend) as session:
|
||
estimator = Estimator(session=session)
|
||
|
||
for iteration in range(max_iter):
|
||
qc = update_circuit(params)
|
||
qc_isa = transpile(qc, backend=backend)
|
||
|
||
result = estimator.run([(qc_isa, observable)]).result()
|
||
params = update_params(result)
|
||
```
|
||
|
||
### Pattern 3: Ensemble Measurement
|
||
```python
|
||
# Map → Optimize → Execute many observables → Post-process
|
||
qc_isa = transpile(qc, backend=backend)
|
||
|
||
observables = [obs1, obs2, obs3, obs4]
|
||
jobs = [(qc_isa, obs) for obs in observables]
|
||
|
||
estimator = Estimator(backend)
|
||
result = estimator.run(jobs).result()
|
||
expectation_values = [r.data.evs for r in result]
|
||
```
|