变分与混合算法¶
变分量子算法(VQE、QAOA)和量子机器学习模型是 NISQ 时代最实用的范式。 本节分别展示纯经典优化器驱动版本和 PyTorch 集成版本,以及量子‑经典混合模型 和量子神经网络 / 卷积分类器。
Variational Quantum Eigensolver (VQE) — complete example.¶
Source: examples/2_advanced/algorithms/vqe.py
Status: pass
Demonstrates:
Building a UCCSD ansatz for molecular Hamiltonians
Computing energy expectation values via Pauli decomposition
Classical optimisation loop (COBYLA) to find ground-state energy
Using uniqc ansatz + measurement modules
Usage: python vqe.py [–molecule NAME] [–maxiter N]
References: Peruzzo et al. (2014). “A variational eigenvalue solver on a photonic quantum processor.” Nature Communications 5, 4213. https://arxiv.org/abs/1304.3061
Source code
#!/usr/bin/env python
"""Variational Quantum Eigensolver (VQE) — complete example.
Demonstrates:
* Building a UCCSD ansatz for molecular Hamiltonians
* Computing energy expectation values via Pauli decomposition
* Classical optimisation loop (COBYLA) to find ground-state energy
* Using uniqc ansatz + measurement modules
Usage:
python vqe.py [--molecule NAME] [--maxiter N]
References:
Peruzzo et al. (2014). "A variational eigenvalue solver on a photonic
quantum processor." Nature Communications 5, 4213.
https://arxiv.org/abs/1304.3061
[doc-require: ]
"""
import argparse
import sys
import numpy as np
sys.path.insert(0, str(__file__).rsplit("/", 2)[0])
from uniqc import Circuit
from uniqc.simulator import Simulator
from uniqc import uccsd_ansatz
from uniqc import pauli_expectation
# ── Toy Hamiltonian: H₂ (STO-3G, 2-electron, 4 spin-orbital) ──────────
# Bravyi–Kitaev mapped, simplified for demonstration.
# E_nuclear ≈ 0.72 Ha
H2_HAMILTONIAN = [
("I0", -0.8105),
("Z0", +0.1720),
("Z1", -0.2258),
("Z2", +0.1720),
("Z3", -0.2258),
("Z0Z1", +0.1205),
("Z0Z2", +0.0455),
("Z0Z3", +0.1660),
("Z1Z2", +0.1660),
("Z1Z3", +0.1205),
("Z2Z3", +0.0455),
("X0X1Y2Y3", -0.0455),
("Y0Y1X2X3", +0.0455),
]
H2_NUCLEAR = 0.72
def vqe_energy(params, hamiltonian, n_qubits, n_electrons):
"""Evaluate ⟨ψ(θ)|H|ψ(θ)⟩ using the UCCSD ansatz.
For each Pauli string in the Hamiltonian, builds the measurement
circuit and computes the expectation value via the statevector simulator.
Args:
params: Variational parameters (array).
hamiltonian: List of (pauli_string, coefficient) tuples.
n_qubits: Number of qubits (spin-orbitals).
n_electrons: Number of electrons.
Returns:
Total energy (float).
"""
# Build the ansatz circuit; pad with identities on every qubit so that
# zero-parameter ansätze still keep the full ``n_qubits`` register
# (the circuit builder otherwise prunes unused qubits).
circuit = uccsd_ansatz(n_qubits, n_electrons, params=params)
for q in range(n_qubits):
circuit.identity(q)
# Simulate statevector
sim = Simulator(backend_type="statevector")
# For energy evaluation, we use direct statevector overlap
# (in practice, you'd use pauli_expectation with shot-based simulation)
sv = np.asarray(sim.simulate_statevector(circuit.originir), dtype=complex)
energy = 0.0
for pauli_str, coeff in hamiltonian:
# For demonstration: compute Pauli expectation via statevector
# Build observable matrix and compute <ψ|P|ψ>
n = n_qubits
obs = _pauli_matrix(pauli_str, n)
exp_val = np.real(sv.conj() @ obs @ sv)
energy += coeff * exp_val
return energy
def _pauli_matrix(pauli_str, n_qubits):
"""Build the full 2^n × 2^n matrix for a Pauli string."""
I = np.eye(2)
X = np.array([[0, 1], [1, 0]])
Y = np.array([[0, -1j], [1j, 0]])
Z = np.array([[1, 0], [0, -1]])
pauli_map = {"I": I, "X": X, "Y": Y, "Z": Z}
# Parse pauli_str like "Z0Z1" or "X0X1Y2Y3"
ops = {}
for ch in pauli_str:
if ch in "IXYZ":
current_op = ch
elif ch.isdigit():
ops[int(ch)] = current_op
matrices = []
for i in range(n_qubits):
matrices.append(pauli_map.get(ops.get(i, "I"), I))
result = matrices[0]
for m in matrices[1:]:
result = np.kron(result, m)
return result
def run_vqe(molecule="H2", maxiter=100):
"""Run the VQE optimisation loop.
Args:
molecule: Molecule name (currently only "H2").
maxiter: Maximum optimiser iterations.
Returns:
Optimised energy (float).
"""
if molecule != "H2":
raise ValueError(f"Unsupported molecule: {molecule}")
n_qubits = 4
n_electrons = 2
# UCCSD: 2 singles + 1 double = 3 parameters for H₂
from itertools import combinations
occupied = list(range(n_electrons))
virtual = list(range(n_electrons, n_qubits))
n_singles = len(occupied) * len(virtual)
n_doubles = len(list(combinations(occupied, 2))) * len(list(combinations(virtual, 2)))
n_params = n_singles + n_doubles
print(f"VQE for {molecule}")
print(f" Qubits: {n_qubits}, Electrons: {n_electrons}")
print(f" UCCSD parameters: {n_params} ({n_singles} singles + {n_doubles} doubles)")
print(f" Hamiltonian terms: {len(H2_HAMILTONIAN)}")
print(f" Nuclear repulsion: {H2_NUCLEAR:.4f} Ha")
print()
# Simple gradient-free optimisation (coordinate descent)
params = np.zeros(n_params)
best_energy = float("inf")
step = 0.1
for iteration in range(maxiter):
improved = False
for i in range(n_params):
# Try positive step
params[i] += step
e_plus = vqe_energy(params, H2_HAMILTONIAN, n_qubits, n_electrons)
# Try negative step
params[i] -= 2 * step
e_minus = vqe_energy(params, H2_HAMILTONIAN, n_qubits, n_electrons)
# Reset
params[i] += step
e_curr = vqe_energy(params, H2_HAMILTONIAN, n_qubits, n_electrons)
if e_plus < e_curr and e_plus < e_minus:
params[i] += step
improved = True
elif e_minus < e_curr:
params[i] -= step
improved = True
energy = vqe_energy(params, H2_HAMILTONIAN, n_qubits, n_electrons)
total = energy + H2_NUCLEAR
if total < best_energy:
best_energy = total
if iteration % 10 == 0:
print(f" Iter {iteration:3d}: E = {total:.6f} Ha")
if not improved:
step *= 0.5
if step < 1e-6:
break
total = vqe_energy(params, H2_HAMILTONIAN, n_qubits, n_electrons) + H2_NUCLEAR
print(f"\n Final energy: {total:.6f} Ha")
print(f" Parameters: {params}")
print(f" Exact FCI: -1.137274 Ha")
return total
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="VQE example")
parser.add_argument("--molecule", default="H2", help="Molecule (default: H2)")
parser.add_argument("--maxiter", type=int, default=100, help="Max iterations")
args = parser.parse_args()
run_vqe(molecule=args.molecule, maxiter=args.maxiter)
Stdout
VQE for H2
Qubits: 4, Electrons: 2
UCCSD parameters: 5 (4 singles + 1 doubles)
Hamiltonian terms: 13
Nuclear repulsion: 0.7200 Ha
Iter 0: E = -0.584500 Ha
Iter 10: E = -0.584500 Ha
Final energy: -0.584500 Ha
Parameters: [ 0. 0. 0. 0. -0.1]
Exact FCI: -1.137274 Ha
VQE for H2 molecule using TorchQuantum backend with native PyTorch autograd.¶
Source: examples/2_advanced/algorithms/vqe_pytorch.py
Status: skip — missing requirements: torchquantum (torch + torchquantum installed)
Demonstrates variational quantum eigensolver with Adam optimizer, using TorchQuantum’s differentiable simulation (no parameter-shift rule).
Source code
"""VQE for H2 molecule using TorchQuantum backend with native PyTorch autograd.
Demonstrates variational quantum eigensolver with Adam optimizer,
using TorchQuantum's differentiable simulation (no parameter-shift rule).
[doc-require: torchquantum]
"""
try:
import torch
from uniqc import VQESolver, build_h2_hamiltonian
except ImportError as e:
print(f"Required dependencies not available: {e}")
print("Install with: pip install unified-quantum[pytorch]")
print('Then install TorchQuantum manually: pip install "torchquantum @ git+https://github.com/Agony5757/torchquantum.git@fix/optional-qiskit-deps"')
raise SystemExit(1)
def main():
print("=" * 60)
print("VQE for H2 Molecule — TorchQuantum Backend")
print("=" * 60)
# Build H2 Hamiltonian
pauli_terms, nuclear_repulsion = build_h2_hamiltonian()
n_qubits = 4
depth = 2
n_params = 2 * n_qubits * depth # 16 params
print(f"\nMolecule: H2 (STO-3G, 4 qubits)")
print(f"Nuclear repulsion: {nuclear_repulsion:.4f}")
print(f"Pauli terms: {len(pauli_terms)}")
print(f"Ansatz: HEA depth={depth}, params={n_params}")
print(f"Exact FCI energy: -1.137274 Ha\n")
# Create solver
solver = VQESolver(
hamiltonian=pauli_terms,
nuclear_repulsion=nuclear_repulsion,
n_qubits=n_qubits,
n_params=n_params,
lr=0.05,
)
# Optimize
final_energy, optimal_params = solver.solve(n_iters=100, verbose=True)
print(f"\nFinal energy: {final_energy:.6f} Ha")
print(f"Optimal params: {optimal_params[:4].tolist()} ...")
print(f"\nExpected: ~-1.10 Ha (simplified Hamiltonian)")
if __name__ == "__main__":
main()
Note
Example skipped during pre-doc-execution: missing requirements: torchquantum (torch + torchquantum installed)
Quantum Approximate Optimization Algorithm (QAOA) — complete example.¶
Source: examples/2_advanced/algorithms/qaoa.py
Status: pass
Demonstrates:
Building a QAOA ansatz for MaxCut
Evaluating the cost function via Pauli measurements
Classical optimisation to find approximate solutions
Using uniqc ansatz + measurement modules
Usage: python qaoa.py [–p LAYERS] [–maxiter N] [–graph FILE]
References: Farhi, E. et al. (2014). “A Quantum Approximate Optimization Algorithm.” arXiv:1411.4028.
Source code
#!/usr/bin/env python
"""Quantum Approximate Optimization Algorithm (QAOA) — complete example.
Demonstrates:
* Building a QAOA ansatz for MaxCut
* Evaluating the cost function via Pauli measurements
* Classical optimisation to find approximate solutions
* Using uniqc ansatz + measurement modules
Usage:
python qaoa.py [--p LAYERS] [--maxiter N] [--graph FILE]
References:
Farhi, E. et al. (2014). "A Quantum Approximate Optimization Algorithm."
arXiv:1411.4028.
[doc-require: ]
"""
import argparse
import sys
import numpy as np
sys.path.insert(0, str(__file__).rsplit("/", 2)[0])
from uniqc import Circuit
from uniqc.simulator import Simulator
from uniqc import qaoa_ansatz
# ── Example graph: triangle (3-node ring) ──────────────────────────────
TRIANGLE_EDGES = [(0, 1), (1, 2), (0, 2)]
def maxcut_hamiltonian(edges, n_nodes):
"""Build the MaxCut cost Hamiltonian.
H_C = -½ Σ_{(i,j)∈E} (1 - Z_i Z_j)
Maximising -H_C ⟺ maximising the number of cut edges.
Args:
edges: List of (i, j) tuples.
n_nodes: Number of graph nodes.
Returns:
List of (pauli_string, coefficient) tuples.
"""
hamiltonian = [("I" + str(0), -len(edges) / 2.0)] # constant term (dummy)
# We'll handle the constant separately
terms = []
for i, j in edges:
terms.append((f"Z{i}Z{j}", 0.5)) # -½ × (-Z_i Z_j) = +½ Z_i Z_j
return terms, -len(edges) / 2.0 # (pauli_terms, constant_offset)
def qaoa_energy(betas_gammas, cost_terms, p, n_qubits):
"""Evaluate ⟨ψ(β,γ)|H_C|ψ(β,γ)⟩.
Args:
betas_gammas: Concatenated [β₁...βₚ, γ₁...γₚ].
cost_terms: Pauli terms from maxcut_hamiltonian.
p: Number of QAOA layers.
n_qubits: Number of qubits.
Returns:
Expected cut value (float).
"""
betas = betas_gammas[:p]
gammas = betas_gammas[p:]
circuit = qaoa_ansatz(cost_terms, p=p, betas=betas, gammas=gammas)
sim = Simulator(backend_type="statevector")
sv = np.asarray(sim.simulate_statevector(circuit.originir), dtype=complex)
# Compute expectation value of each Pauli term
energy = 0.0
for pauli_str, coeff in cost_terms:
obs = _pauli_matrix(pauli_str, n_qubits)
exp_val = np.real(sv.conj() @ obs @ sv)
energy += coeff * exp_val
return energy
def _pauli_matrix(pauli_str, n_qubits):
"""Build matrix for a Pauli string."""
I = np.eye(2)
Z = np.array([[1, 0], [0, -1]])
pauli_map = {"I": I, "Z": Z, "X": np.array([[0, 1], [1, 0]]),
"Y": np.array([[0, -1j], [1j, 0]])}
ops = {}
current_op = None
for ch in pauli_str:
if ch in "IXYZ":
current_op = ch
elif ch.isdigit():
ops[int(ch)] = current_op
matrices = [pauli_map.get(ops.get(i, "I"), I) for i in range(n_qubits)]
result = matrices[0]
for m in matrices[1:]:
result = np.kron(result, m)
return result
def run_qaoa(p=2, maxiter=80, edges=None):
"""Run QAOA for MaxCut.
Args:
p: Number of QAOA layers.
maxiter: Maximum optimiser iterations.
edges: Graph edges. Defaults to triangle.
Returns:
Best energy and parameters.
"""
if edges is None:
edges = TRIANGLE_EDGES
n_nodes = max(max(i, j) for i, j in edges) + 1
cost_terms, const_offset = maxcut_hamiltonian(edges, n_nodes)
print(f"QAOA for MaxCut")
print(f" Graph: {len(edges)} edges, {n_nodes} nodes")
print(f" Layers (p): {p}")
print(f" Max possible cut: {len(edges)}")
print()
# Coordinate-descent optimisation
rng = np.random.default_rng(42)
params = rng.uniform(0, np.pi, size=2 * p)
best_energy = float("inf")
best_params = params.copy()
step = 0.2
for iteration in range(maxiter):
improved = False
for i in range(2 * p):
original = params[i]
params[i] = original + step
e_plus = qaoa_energy(params, cost_terms, p, n_nodes)
params[i] = original - step
e_minus = qaoa_energy(params, cost_terms, p, n_nodes)
params[i] = original
e_curr = qaoa_energy(params, cost_terms, p, n_nodes)
if e_plus < e_curr and e_plus <= e_minus:
params[i] = original + step
improved = True
elif e_minus < e_curr:
params[i] = original - step
improved = True
energy = qaoa_energy(params, cost_terms, p, n_nodes)
cut_value = -(energy + const_offset)
if energy < best_energy:
best_energy = energy
best_params = params.copy()
if iteration % 20 == 0:
print(f" Iter {iteration:3d}: cut ≈ {cut_value:.3f}")
if not improved:
step *= 0.5
if step < 1e-6:
break
energy = qaoa_energy(best_params, cost_terms, p, n_nodes)
cut_value = -(energy + const_offset)
print(f"\n Best cut value: {cut_value:.3f} / {len(edges)}")
print(f" Approximation ratio: {cut_value / len(edges):.3f}")
return cut_value, best_params
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="QAOA MaxCut example")
parser.add_argument("-p", type=int, default=2, help="QAOA layers")
parser.add_argument("--maxiter", type=int, default=80, help="Max iterations")
args = parser.parse_args()
run_qaoa(p=args.p, maxiter=args.maxiter)
Stdout
QAOA for MaxCut
Graph: 3 edges, 3 nodes
Layers (p): 2
Max possible cut: 3
Iter 0: cut ≈ 1.608
Iter 20: cut ≈ 2.000
Best cut value: 2.000 / 3
Approximation ratio: 0.667
QAOA for MaxCut using TorchQuantum backend with native PyTorch autograd.¶
Source: examples/2_advanced/algorithms/qaoa_pytorch.py
Status: skip — missing requirements: torchquantum (torch + torchquantum installed)
Demonstrates Quantum Approximate Optimization Algorithm on a triangle graph with Adam optimizer, using TorchQuantum’s differentiable simulation.
Source code
"""QAOA for MaxCut using TorchQuantum backend with native PyTorch autograd.
Demonstrates Quantum Approximate Optimization Algorithm on a triangle graph
with Adam optimizer, using TorchQuantum's differentiable simulation.
[doc-require: torchquantum]
"""
try:
import torch
from uniqc import QAOASolver
except ImportError as e:
print(f"Required dependencies not available: {e}")
print("Install with: pip install unified-quantum[pytorch]")
print('Then install TorchQuantum manually: pip install "torchquantum @ git+https://github.com/Agony5757/torchquantum.git@fix/optional-qiskit-deps"')
raise SystemExit(1)
def main():
print("=" * 60)
print("QAOA for MaxCut — TorchQuantum Backend")
print("=" * 60)
# Triangle graph: 3 nodes, 3 edges
edges = [(0, 1), (1, 2), (0, 2)]
n_qubits = 3
p = 2 # QAOA depth
print(f"\nGraph: Triangle (3 nodes, 3 edges)")
print(f"Edges: {edges}")
print(f"QAOA depth p={p}")
print(f"Max cut value (exact): 2.0\n")
# Create solver
solver = QAOASolver(
edges=edges,
n_qubits=n_qubits,
p=p,
lr=0.05,
)
# Optimize
cut_value, optimal_params = solver.solve(n_iters=100, verbose=True)
print(f"\nFinal cut value: {cut_value:.6f}")
print(f"Optimal gammas: {optimal_params[:p].tolist()}")
print(f"Optimal betas: {optimal_params[p:].tolist()}")
print(f"Approximation ratio: {cut_value / 2.0:.4f}")
if __name__ == "__main__":
main()
Note
Example skipped during pre-doc-execution: missing requirements: torchquantum (torch + torchquantum installed)
QAOA Variants – XY Mixer, Warm-Start, and MA-QAOA.¶
Source: examples/2_advanced/algorithms/qaoa_variants.py
Status: pass
Demonstrates:
Standard QAOA with XY mixer for constrained optimization
Warm-start QAOA with custom initial state
MA-QAOA with per-term angles
Usage: python qaoa_variants.py [–p LAYERS] [–n-nodes N]
References: Hadfield, S. et al. (2019). “From the Quantum Approximate Optimization Algorithm to a Quantum Alternating Operator Ansatz.” arXiv:1709.03489.
Egger, D.J. et al. (2021). "Warm-starting quantum optimization."
arXiv:2009.10095.
Hadir, M. et al. (2023). "Multi-Angle Quantum Approximate Optimization
Algorithm." arXiv:2305.04881.
Source code
#!/usr/bin/env python
"""QAOA Variants -- XY Mixer, Warm-Start, and MA-QAOA.
Demonstrates:
* Standard QAOA with XY mixer for constrained optimization
* Warm-start QAOA with custom initial state
* MA-QAOA with per-term angles
Usage:
python qaoa_variants.py [--p LAYERS] [--n-nodes N]
References:
Hadfield, S. et al. (2019). "From the Quantum Approximate Optimization
Algorithm to a Quantum Alternating Operator Ansatz." arXiv:1709.03489.
Egger, D.J. et al. (2021). "Warm-starting quantum optimization."
arXiv:2009.10095.
Hadir, M. et al. (2023). "Multi-Angle Quantum Approximate Optimization
Algorithm." arXiv:2305.04881.
[doc-require: ]
"""
import argparse
import sys
import numpy as np
sys.path.insert(0, str(__file__).rsplit("/", 2)[0])
from uniqc import Circuit
from uniqc.simulator import Simulator
from uniqc.algorithms.core.ansatz import qaoa_ansatz
def _build_maxcut_hamiltonian(edges):
"""Build MaxCut cost Hamiltonian for a graph.
H_C = -1/2 sum_{(i,j) in E} (1 - Z_i Z_j)
"""
hamiltonian = []
for i, j in edges:
# -1/2 * (1 - Z_i Z_j) = -1/2*I + 1/2*Z_i Z_j
hamiltonian.append((f"Z{i}Z{j}", 0.5))
return hamiltonian
def _print_circuit_info(circuit, label):
"""Print circuit statistics."""
print(f"\n {label}:")
print(f" Qubits used: {circuit.max_qubit + 1}")
print(f" Gate count: {len(circuit.opcode_list)}")
def demo_xy_mixer(p=2, n_nodes=4):
"""Demonstrate QAOA with XY mixer for constrained optimization."""
print("=" * 60)
print("Demo 1: XY Mixer")
print("=" * 60)
# Ring graph (constrained to exactly n_nodes/2 cuts in optimal solution)
edges = [(i, (i + 1) % n_nodes) for i in range(n_nodes)]
H = _build_maxcut_hamiltonian(edges)
print(f"\n Graph: Ring with {n_nodes} nodes")
print(f" Edges: {edges}")
# Standard QAOA
c_standard = qaoa_ansatz(H, p=p)
print(f"\n Standard QAOA (X mixer):")
print(f" Parameters: betas={p}, gammas={p} (total: {2*p})")
_print_circuit_info(c_standard, "Standard circuit")
# XY mixer QAOA
c_xy = qaoa_ansatz(H, p=p, mixer="xy")
print(f"\n XY Mixer QAOA:")
print(f" Parameters: betas={p}, gammas={p} (total: {2*p})")
print(f" Note: XY mixer preserves excitation number")
_print_circuit_info(c_xy, "XY mixer circuit")
# Verify both produce valid statevectors
sim = Simulator(backend_type="statevector")
sv_std = sim.simulate_statevector(c_standard.originir)
sv_xy = sim.simulate_statevector(c_xy.originir)
print(f"\n Standard norm: {np.linalg.norm(sv_std):.10f}")
print(f" XY mixer norm: {np.linalg.norm(sv_xy):.10f}")
def demo_warm_start(p=2, n_nodes=4):
"""Demonstrate warm-start QAOA with custom initial state."""
print("\n" + "=" * 60)
print("Demo 2: Warm-Start QAOA")
print("=" * 60)
# Simple line graph
edges = [(i, i + 1) for i in range(n_nodes - 1)]
H = _build_maxcut_hamiltonian(edges)
print(f"\n Graph: Line with {n_nodes} nodes")
print(f" Edges: {edges}")
# Standard QAOA (starts from uniform superposition)
c_standard = qaoa_ansatz(H, p=p)
print(f"\n Standard QAOA (uniform superposition):")
print(f" Initial: Hadamards on all qubits")
_print_circuit_info(c_standard, "Standard circuit")
# Warm-start: custom initial state
# Example: start from a greedy solution (alternate 0/1 pattern)
warm_state = Circuit()
for i in range(n_nodes):
if i % 2 == 0:
warm_state.x(i)
c_warm = qaoa_ansatz(H, p=p, initial_state=warm_state)
print(f"\n Warm-Start QAOA (greedy solution):")
print(f" Initial: |0101...> (alternating pattern)")
_print_circuit_info(c_warm, "Warm-start circuit")
# Compare circuit depths
print(f"\n Comparison:")
print(f" Standard: {len(c_standard.opcode_list)} gates")
print(f" Warm-start: {len(c_warm.opcode_list)} gates")
# Verify both produce valid statevectors
sim = Simulator(backend_type="statevector")
sv_std = sim.simulate_statevector(c_standard.originir)
sv_warm = sim.simulate_statevector(c_warm.originir)
print(f"\n Standard norm: {np.linalg.norm(sv_std):.10f}")
print(f" Warm-start norm: {np.linalg.norm(sv_warm):.10f}")
def demo_ma_qaoa(p=2, n_nodes=4):
"""Demonstrate MA-QAOA with per-term angles."""
print("\n" + "=" * 60)
print("Demo 3: MA-QAOA (Multi-Angle)")
print("=" * 60)
# Triangle graph
edges = [(0, 1), (1, 2), (0, 2)]
H = _build_maxcut_hamiltonian(edges)
n_terms = len(edges)
print(f"\n Graph: Triangle")
print(f" Edges (Hamiltonian terms): {n_terms}")
# Standard QAOA
c_standard = qaoa_ansatz(H, p=p)
std_params = 2 * p # p betas + p gammas
print(f"\n Standard QAOA:")
print(f" Parameters: {std_params} (p betas + p gammas)")
# MA-QAOA: each term gets its own gamma, each qubit gets its own beta
c_ma = qaoa_ansatz(H, p=p, multi_angle=True)
ma_betas = n_nodes * p
ma_gammas = n_terms * p
ma_params = ma_betas + ma_gammas
print(f"\n MA-QAOA:")
print(f" Parameters: {ma_params} ({n_terms} terms x {p} layers gammas + {n_nodes} qubits x {p} layers betas)")
print(f" Improvement: {ma_params - std_params} extra parameters")
_print_circuit_info(c_standard, "Standard circuit")
_print_circuit_info(c_ma, "MA-QAOA circuit")
# Verify both produce valid statevectors
sim = Simulator(backend_type="statevector")
sv_std = sim.simulate_statevector(c_standard.originir)
sv_ma = sim.simulate_statevector(c_ma.originir)
print(f"\n Standard norm: {np.linalg.norm(sv_std):.10f}")
print(f" MA-QAOA norm: {np.linalg.norm(sv_ma):.10f}")
def run_demo(p, n_nodes):
"""Run all QAOA variant demos."""
print(f"\n{'=' * 60}")
print(f"QAOA Variants Demo (p={p}, nodes={n_nodes})")
print(f"{'=' * 60}")
demo_xy_mixer(p, n_nodes)
demo_warm_start(p, n_nodes)
demo_ma_qaoa(p, n_nodes)
print("\n" + "=" * 60)
print("Demo Complete")
print("=" * 60)
def main():
parser = argparse.ArgumentParser(description="QAOA Variants Demo")
parser.add_argument(
"-p", "--p-layers", type=int, default=2,
help="Number of QAOA layers (default: 2)"
)
parser.add_argument(
"-n", "--n-nodes", type=int, default=4,
help="Number of graph nodes (default: 4)"
)
args = parser.parse_args()
if args.p_layers < 1:
parser.error("-p must be at least 1")
if args.n_nodes < 3:
parser.error("-n must be at least 3")
run_demo(args.p_layers, args.n_nodes)
if __name__ == "__main__":
main()
Stdout
============================================================
QAOA Variants Demo (p=2, nodes=4)
============================================================
============================================================
Demo 1: XY Mixer
============================================================
Graph: Ring with 4 nodes
Edges: [(0, 1), (1, 2), (2, 3), (3, 0)]
Standard QAOA (X mixer):
Parameters: betas=2, gammas=2 (total: 4)
Standard circuit:
Qubits used: 4
Gate count: 52
XY Mixer QAOA:
Parameters: betas=2, gammas=2 (total: 4)
Note: XY mixer preserves excitation number
XY mixer circuit:
Qubits used: 4
Gate count: 40
Standard norm: 1.0000000000
XY mixer norm: 1.0000000000
============================================================
Demo 2: Warm-Start QAOA
============================================================
Graph: Line with 4 nodes
Edges: [(0, 1), (1, 2), (2, 3)]
Standard QAOA (uniform superposition):
Initial: Hadamards on all qubits
Standard circuit:
Qubits used: 4
Gate count: 46
Warm-Start QAOA (greedy solution):
Initial: |0101...> (alternating pattern)
Warm-start circuit:
Qubits used: 4
Gate count: 44
Comparison:
Standard: 46 gates
Warm-start: 44 gates
Standard norm: 1.0000000000
Warm-start norm: 1.0000000000
============================================================
Demo 3: MA-QAOA (Multi-Angle)
============================================================
Graph: Triangle
Edges (Hamiltonian terms): 3
Standard QAOA:
Parameters: 4 (p betas + p gammas)
MA-QAOA:
Parameters: 14 (3 terms x 2 layers gammas + 4 qubits x 2 layers betas)
Improvement: 10 extra parameters
Standard circuit:
Qubits used: 3
Gate count: 39
MA-QAOA circuit:
Qubits used: 3
Gate count: 39
Standard norm: 1.0000000000
MA-QAOA norm: 1.0000000000
============================================================
Demo Complete
============================================================
ADAPT-VQE Example.¶
Source: examples/2_advanced/algorithms/adapt_vqe.py
Status: pass
This script demonstrates how to implement ADAPT-VQE (Adaptively Parametrised Variational Quantum Eigensolver) using the existing algorithm components in uniqc.
ADAPT-VQE iteratively builds an ansatz by selecting operators from a pool based on gradient magnitude, rather than using a fixed ansatz structure.
This is an EXAMPLE script, not a reusable algorithm module. Users can copy and adapt this code for their specific use cases.
References:
Grimsley et al., “Adaptively parametric ansatz” (2019)
Source code
#!/usr/bin/env python3
"""ADAPT-VQE Example.
This script demonstrates how to implement ADAPT-VQE (Adaptively Parametrised
Variational Quantum Eigensolver) using the existing algorithm components in uniqc.
ADAPT-VQE iteratively builds an ansatz by selecting operators from a pool
based on gradient magnitude, rather than using a fixed ansatz structure.
This is an EXAMPLE script, not a reusable algorithm module. Users can copy and
adapt this code for their specific use cases.
References:
- Grimsley et al., "Adaptively parametric ansatz" (2019)
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Callable, List, Optional, Tuple
import numpy as np
from uniqc.algorithms.core.ansatz._operator_pool import OperatorPool, compute_operator_gradient
from uniqc.algorithms.core.measurement.pauli_expectation import pauli_expectation
from uniqc.algorithms.core.ansatz._pauli_unitary import _apply_cost_unitary
from uniqc.circuit_builder import Circuit
from uniqc.simulator import Simulator
@dataclass
class ADAPTVQEResult:
"""Result of ADAPT-VQE optimization."""
energy: float
params: np.ndarray
selected_operators: List[Tuple[str, float, float]] # (op, coeff, gradient)
history: List[float] = field(default_factory=list)
n_iterations: int = 0
converged: bool = False
def _build_adapt_circuit(
operators: List[Tuple[str, float]],
params: np.ndarray,
n_qubits: int = 1,
) -> Circuit:
"""Build the ADAPT ansatz circuit from selected operators and parameters."""
circuit = Circuit(n_qubits)
for i, (pauli_str, coeff) in enumerate(operators):
theta = float(params[i]) if i < len(params) else 0.0
_apply_cost_unitary(circuit, [(pauli_str, coeff)], theta)
return circuit
def _energy(
circuit: Circuit,
hamiltonian: List[Tuple[str, float]],
shots: Optional[int] = None,
) -> float:
"""Compute expectation value of Hamiltonian."""
total = 0.0
for pauli, coeff in hamiltonian:
total += float(coeff) * pauli_expectation(circuit, pauli, shots=shots)
return total
def _optimize_circuit(
circuit: Circuit,
hamiltonian: List[Tuple[str, float]],
n_params: int,
shots: Optional[int] = None,
) -> Tuple[float, np.ndarray]:
"""Optimize parameters using COBYLA (simple derivative-free method)."""
try:
from scipy.optimize import minimize
except ImportError:
raise ImportError("scipy is required for ADAPT-VQE optimization")
history: List[float] = []
def objective(params: np.ndarray) -> float:
# Rebuild circuit with current parameters
circ = _build_adapt_circuit(
[(f"I{''.join(['I'] * (n_params - 1))}", 0.0)], # placeholder
params,
)
# Actually rebuild with the operators from the closure
circ = _build_adapt_circuit(operators_from_closure, params)
e = _energy(circ, hamiltonian, shots)
history.append(e)
return e
# Store operators for the closure
global operators_from_closure
operators_from_closure = []
# Use simple random initialization
init_params = np.random.uniform(-np.pi / 4, np.pi / 4, size=n_params)
# Simple optimization
result = minimize(objective, init_params, method="COBYLA", options={"maxiter": 50})
return float(result.fun), np.asarray(result.x)
# Global for closure
operators_from_closure = []
def adapt_vqe(
hamiltonian: List[Tuple[str, float]],
operator_pool: OperatorPool,
*,
n_qubits: Optional[int] = None,
max_iterations: int = 20,
convergence_threshold: float = 1e-4,
shots: Optional[int] = None,
verbose: bool = True,
) -> ADAPTVQEResult:
"""Run ADAPT-VQE optimization.
Args:
hamiltonian: List of (pauli_string, coefficient) tuples.
operator_pool: Pool of operators to choose from.
n_qubits: Number of qubits. Auto-detected if not provided.
max_iterations: Maximum ADAPT iterations.
convergence_threshold: Stop when gradient norm falls below this.
shots: Shots for expectation value. None = statevector.
verbose: Print progress.
Returns:
ADAPTVQEResult with optimal energy and parameters.
Example:
>>> from uniqc.examples.algorithms.adapt_vqe import adapt_vqe, OperatorPool
>>> H = [("ZZ", -1.0), ("ZI", 0.5), ("IZ", 0.5)]
>>> pool = OperatorPool.minimal_pool(n_qubits=2)
>>> result = adapt_vqe(H, pool, n_qubits=2)
>>> print(f"Energy: {result.energy:.6f}")
"""
global operators_from_closure
# Auto-detect n_qubits
if n_qubits is None:
max_idx = 0
for pauli, _ in hamiltonian:
for ch in pauli:
if ch.isdigit():
max_idx = max(max_idx, int(ch))
n_qubits = max_idx + 1
selected_operators: List[Tuple[str, float]] = []
params: np.ndarray = np.array([])
history: List[float] = []
if verbose:
print(f"ADAPT-VQE starting with {len(operator_pool)} operators in pool")
print(f"Hamiltonian: {hamiltonian}")
print("-" * 60)
for iteration in range(max_iterations):
# Build current ansatz circuit
if selected_operators:
current_circuit = _build_adapt_circuit(selected_operators, params, n_qubits)
current_energy = _energy(current_circuit, hamiltonian, shots)
history.append(current_energy)
else:
current_energy = 0.0
current_circuit = Circuit(n_qubits)
if verbose:
print(f"Iter {iteration + 1}: E = {current_energy:.8f}, "
f"n_operators = {len(selected_operators)}")
# Compute gradients for all pool operators
gradients: List[Tuple[int, str, float, float]] = []
for i, (pauli_str, coeff) in enumerate(operator_pool.operators()):
try:
gradient = compute_operator_gradient(
current_circuit,
(pauli_str, coeff),
hamiltonian,
shots,
n_qubits=n_qubits,
)
gradients.append((i, pauli_str, coeff, gradient))
except Exception:
# Skip operators that fail gradient computation
continue
if not gradients:
if verbose:
print(" No valid gradients, stopping.")
break
# Find operator with largest gradient
gradients.sort(key=lambda x: x[3], reverse=True)
best_idx, best_pauli, best_coeff, best_grad = gradients[0]
if verbose:
print(f" Best operator: {best_pauli}, gradient = {best_grad:.8f}")
# Check convergence
if best_grad < convergence_threshold:
if verbose:
print(f" Converged (gradient {best_grad:.2e} < {convergence_threshold:.2e})")
break
# Add operator to ansatz
selected_operators.append((best_pauli, best_coeff))
# Remove from pool (optional - for faster subsequent iterations)
# pool.remove_operator(best_idx)
# Re-optimize all parameters
new_params = np.random.uniform(-np.pi / 4, np.pi / 4, size=len(selected_operators))
# Simple optimization loop
operators_from_closure = selected_operators
for _ in range(30): # Simple SGD-like optimization
for j in range(len(selected_operators)):
# Finite difference gradient
eps = np.pi / 8
test_params_plus = new_params.copy()
test_params_plus[j] += eps
test_params_minus = new_params.copy()
test_params_minus[j] -= eps
circ_plus = _build_adapt_circuit(selected_operators, test_params_plus, n_qubits)
circ_minus = _build_adapt_circuit(selected_operators, test_params_minus, n_qubits)
e_plus = _energy(circ_plus, hamiltonian, shots)
e_minus = _energy(circ_minus, hamiltonian, shots)
grad_j = (e_plus - e_minus) / (2 * eps)
# Simple gradient descent step
lr = 0.1
new_params[j] -= lr * grad_j
params = new_params
# Final energy
if selected_operators:
final_circuit = _build_adapt_circuit(selected_operators, params, n_qubits)
final_energy = _energy(final_circuit, hamiltonian, shots)
else:
final_energy = 0.0
if verbose:
print("-" * 60)
print(f"Final energy: {final_energy:.8f}")
print(f"Selected {len(selected_operators)} operators")
return ADAPTVQEResult(
energy=final_energy,
params=params,
selected_operators=[
(op, coeff, 0.0) for op, coeff in selected_operators
], # gradient not stored
history=history,
n_iterations=iteration + 1,
converged=best_grad < convergence_threshold if gradients else False,
)
def main():
"""Run ADAPT-VQE on a simple 2-qubit Hamiltonian."""
print("=" * 60)
print("ADAPT-VQE Example: 2-Qubit Hamiltonian")
print("=" * 60)
# Simple Hamiltonian: H = -Z0Z1 + 0.5*I
# Ground state energy should be -1.0 (for the ZZ term)
hamiltonian = [
("ZZ", -1.0),
("II", 0.5),
]
# Use minimal operator pool
pool = OperatorPool.minimal_pool(n_qubits=2)
print(f"Using {len(pool)} operators from minimal pool")
print()
result = adapt_vqe(
hamiltonian,
pool,
n_qubits=2,
max_iterations=10,
convergence_threshold=1e-4,
verbose=True,
)
print()
print("=" * 60)
print("ADAPT-VQE Results:")
print(f" Energy: {result.energy:.8f}")
print(f" Iterations: {result.n_iterations}")
print(f" Converged: {result.converged}")
print(f" Selected operators: {len(result.selected_operators)}")
for op, coeff, grad in result.selected_operators:
print(f" {op} (coeff={coeff:.2f})")
# Compare with VQE using full HEA
print()
print("Comparison with standard VQE (HEA):")
from uniqc.algorithms.workflows.vqe_workflow import run_vqe_workflow
vqe_result = run_vqe_workflow(hamiltonian, n_qubits=2, depth=2)
print(f" VQE Energy: {vqe_result.energy:.8f}")
print(f" VQE Success: {vqe_result.success}")
if __name__ == "__main__":
main()
Stdout
============================================================
ADAPT-VQE Example: 2-Qubit Hamiltonian
============================================================
Using 5 operators from minimal pool
ADAPT-VQE starting with 5 operators in pool
Hamiltonian: [('ZZ', -1.0), ('II', 0.5)]
------------------------------------------------------------
Iter 1: E = 0.00000000, n_operators = 0
Best operator: XI, gradient = 0.00000000
Converged (gradient 0.00e+00 < 1.00e-04)
------------------------------------------------------------
Final energy: 0.00000000
Selected 0 operators
============================================================
ADAPT-VQE Results:
Energy: 0.00000000
Iterations: 1
Converged: True
Selected operators: 0
Comparison with standard VQE (HEA):
VQE Energy: -0.50000000
VQE Success: True
Ansatz 配置¶
Hardware-Efficient Ansatz (HEA) – Configuration Options.¶
Source: examples/2_advanced/algorithms/hea_options.py
Status: pass
Demonstrates:
Configurable rotation gates (RX, RY, RZ)
Configurable entangling gates (CNOT, CZ, CRX, XX)
Entanglement topologies (LINEAR, RING, FULL, BRICKWORK)
Parameter counting with hea_param_count()
Usage: python hea_options.py [–n-qubits N] [–depth L]
References: Kandala, A. et al. (2017). “Hardware-efficient variational quantum eigensolver for small molecules and quantum magnets.” Nature 549, 242-246. https://doi.org/10.1038/nature23879
Source code
#!/usr/bin/env python
"""Hardware-Efficient Ansatz (HEA) -- Configuration Options.
Demonstrates:
* Configurable rotation gates (RX, RY, RZ)
* Configurable entangling gates (CNOT, CZ, CRX, XX)
* Entanglement topologies (LINEAR, RING, FULL, BRICKWORK)
* Parameter counting with hea_param_count()
Usage:
python hea_options.py [--n-qubits N] [--depth L]
References:
Kandala, A. et al. (2017). "Hardware-efficient variational quantum
eigensolver for small molecules and quantum magnets."
Nature 549, 242-246. https://doi.org/10.1038/nature23879
[doc-require: ]
"""
import argparse
import sys
import numpy as np
sys.path.insert(0, str(__file__).rsplit("/", 2)[0])
from uniqc import Circuit
from uniqc.simulator import Simulator
from uniqc.algorithms.core.ansatz import (
hea,
hea_param_count,
RotationGate,
EntanglingGate,
EntanglementTopology,
)
def _print_circuit_info(circuit, label):
"""Print circuit statistics."""
print(f"\n {label}:")
print(f" Qubits used: {circuit.max_qubit + 1}")
print(f" Gate count: {len(circuit.opcode_list)}")
def demo_rotation_gates(n_qubits=4, depth=2):
"""Demonstrate different rotation gate configurations."""
print("=" * 60)
print("Demo 1: Rotation Gate Configurations")
print("=" * 60)
# Default: RZ + RY (backward compatible)
n_default = hea_param_count(n_qubits, depth, rotation_gates=["rz", "ry"])
c_default = hea(n_qubits, depth, rotation_gates=["rz", "ry"])
print(f"\n Default (RZ + RY):")
print(f" Parameters: {n_default}")
_print_circuit_info(c_default, "RZ+RY circuit")
# RX only
n_rx = hea_param_count(n_qubits, depth, rotation_gates=["rx"])
c_rx = hea(n_qubits, depth, rotation_gates=["rx"])
print(f"\n RX only:")
print(f" Parameters: {n_rx}")
_print_circuit_info(c_rx, "RX circuit")
# RX + RY + RZ (full rotation)
n_full = hea_param_count(n_qubits, depth, rotation_gates=["rx", "ry", "rz"])
c_full = hea(n_qubits, depth, rotation_gates=["rx", "ry", "rz"])
print(f"\n RX + RY + RZ (full):")
print(f" Parameters: {n_full}")
_print_circuit_info(c_full, "Full rotation circuit")
# Verify all circuits produce valid statevectors
sim = Simulator(backend_type="statevector")
for label, c in [("RZ+RY", c_default), ("RX", c_rx), ("RX+RY+RZ", c_full)]:
sv = sim.simulate_statevector(c.originir)
norm = np.linalg.norm(sv)
print(f"\n {label} statevector norm: {norm:.10f}")
def demo_entangling_gates(n_qubits=4, depth=1):
"""Demonstrate different entangling gate configurations."""
print("\n" + "=" * 60)
print("Demo 2: Entangling Gate Configurations")
print("=" * 60)
# Non-parametric gates (0 extra params per edge)
print("\n Non-parametric gates:")
for gate in ["cnot", "cz", "iswap"]:
n = hea_param_count(n_qubits, depth, entangling_gate=gate, topology="ring")
c = hea(n_qubits, depth, entangling_gate=gate, topology="ring")
print(f" {gate.upper():8s}: {n:3d} params")
_print_circuit_info(c, f"{gate.upper()} circuit")
# Parametric gates (extra params per edge)
print("\n Parametric gates (extra params per edge):")
for gate in ["crx", "xx", "yy", "zz"]:
n = hea_param_count(n_qubits, depth, entangling_gate=gate, topology="ring")
c = hea(n_qubits, depth, entangling_gate=gate, topology="ring")
print(f" {gate.upper():8s}: {n:3d} params")
_print_circuit_info(c, f"{gate.upper()} circuit")
# Verify all circuits produce valid statevectors
sim = Simulator(backend_type="statevector")
for gate in ["cnot", "cz", "crx", "xx"]:
c = hea(n_qubits, depth, entangling_gate=gate, topology="ring")
sv = sim.simulate_statevector(c.originir)
print(f"\n {gate.upper()} statevector norm: {np.linalg.norm(sv):.10f}")
def demo_topologies(n_qubits=4, depth=1):
"""Demonstrate different entanglement topologies."""
print("\n" + "=" * 60)
print("Demo 3: Entanglement Topologies")
print("=" * 60)
for topo in ["linear", "ring", "full", "star", "brickwork"]:
n = hea_param_count(n_qubits, depth, topology=topo)
c = hea(n_qubits, depth, topology=topo)
print(f"\n {topo.upper():10s}:")
print(f" Parameters: {n}")
_print_circuit_info(c, f"{topo.upper()} circuit")
# Custom topology
print("\n CUSTOM (user-defined edges):")
custom_edges = [(0, 1), (0, 2), (0, 3)] # Star from qubit 0
n = hea_param_count(n_qubits, depth, topology="custom", custom_edges=custom_edges)
c = hea(n_qubits, depth, topology="custom", custom_edges=custom_edges)
print(f" Edges: {custom_edges}")
print(f" Parameters: {n}")
_print_circuit_info(c, "Custom circuit")
def run_demo(n_qubits, depth):
"""Run all HEA configuration demos."""
print(f"\n{'=' * 60}")
print(f"HEA Configuration Demo (n_qubits={n_qubits}, depth={depth})")
print(f"{'=' * 60}")
demo_rotation_gates(n_qubits, depth)
demo_entangling_gates(n_qubits, depth)
demo_topologies(n_qubits, depth)
print("\n" + "=" * 60)
print("Demo Complete")
print("=" * 60)
def main():
parser = argparse.ArgumentParser(description="HEA Configuration Options Demo")
parser.add_argument(
"--n-qubits", type=int, default=4,
help="Number of qubits (default: 4)"
)
parser.add_argument(
"--depth", type=int, default=2,
help="Ansatz depth/layers (default: 2)"
)
args = parser.parse_args()
if args.n_qubits < 2:
parser.error("--n-qubits must be at least 2")
if args.depth < 0:
parser.error("--depth must be non-negative")
run_demo(args.n_qubits, args.depth)
if __name__ == "__main__":
main()
Stdout
============================================================
HEA Configuration Demo (n_qubits=4, depth=2)
============================================================
============================================================
Demo 1: Rotation Gate Configurations
============================================================
Default (RZ + RY):
Parameters: 16
RZ+RY circuit:
Qubits used: 4
Gate count: 24
RX only:
Parameters: 8
RX circuit:
Qubits used: 4
Gate count: 16
RX + RY + RZ (full):
Parameters: 24
Full rotation circuit:
Qubits used: 4
Gate count: 32
RZ+RY statevector norm: 1.0000000000
RX statevector norm: 1.0000000000
RX+RY+RZ statevector norm: 1.0000000000
============================================================
Demo 2: Entangling Gate Configurations
============================================================
Non-parametric gates:
CNOT : 16 params
CNOT circuit:
Qubits used: 4
Gate count: 24
CZ : 16 params
CZ circuit:
Qubits used: 4
Gate count: 24
ISWAP : 16 params
ISWAP circuit:
Qubits used: 4
Gate count: 24
Parametric gates (extra params per edge):
CRX : 24 params
CRX circuit:
Qubits used: 4
Gate count: 24
XX : 24 params
XX circuit:
Qubits used: 4
Gate count: 24
YY : 24 params
YY circuit:
Qubits used: 4
Gate count: 24
ZZ : 24 params
ZZ circuit:
Qubits used: 4
Gate count: 24
CNOT statevector norm: 1.0000000000
CZ statevector norm: 1.0000000000
CRX statevector norm: 1.0000000000
XX statevector norm: 1.0000000000
============================================================
Demo 3: Entanglement Topologies
============================================================
LINEAR :
Parameters: 16
LINEAR circuit:
Qubits used: 4
Gate count: 22
RING :
Parameters: 16
RING circuit:
Qubits used: 4
Gate count: 24
FULL :
Parameters: 16
FULL circuit:
Qubits used: 4
Gate count: 28
STAR :
Parameters: 16
STAR circuit:
Qubits used: 4
Gate count: 22
BRICKWORK :
Parameters: 16
BRICKWORK circuit:
Qubits used: 4
Gate count: 19
CUSTOM (user-defined edges):
Edges: [(0, 1), (0, 2), (0, 3)]
Parameters: 16
Custom circuit:
Qubits used: 4
Gate count: 22
============================================================
Demo Complete
============================================================
Hamiltonian Variational Ansatz (HVA) – complete example.¶
Source: examples/2_advanced/algorithms/hva_example.py
Status: pass
Demonstrates:
Building HVA with commuting Hamiltonian groups
Hartree-Fock initial state preparation
Energy evaluation and optimization
Usage: python hva_example.py [–p-layers L] [–n-iterations N]
References: Wecker, D. et al. (2015). “Hackett, and A. Aspuru-Guzik, Progress toward practical quantum variational algorithms.” Phys. Rev. A 92, 060303.
Kivlichan, I.D. et al. (2018). "Quantum Simulation of Electronic
Structure with Linear Depth and Connectivity."
Phys. Rev. Lett. 120, 110501.
Source code
#!/usr/bin/env python
"""Hamiltonian Variational Ansatz (HVA) -- complete example.
Demonstrates:
* Building HVA with commuting Hamiltonian groups
* Hartree-Fock initial state preparation
* Energy evaluation and optimization
Usage:
python hva_example.py [--p-layers L] [--n-iterations N]
References:
Wecker, D. et al. (2015). "Hackett, and A. Aspuru-Guzik,
Progress toward practical quantum variational algorithms."
Phys. Rev. A 92, 060303.
Kivlichan, I.D. et al. (2018). "Quantum Simulation of Electronic
Structure with Linear Depth and Connectivity."
Phys. Rev. Lett. 120, 110501.
[doc-require: ]
"""
import argparse
import sys
import numpy as np
sys.path.insert(0, str(__file__).rsplit("/", 2)[0])
from uniqc import Circuit
from uniqc.simulator import Simulator
from uniqc.algorithms.core.ansatz import hva
from uniqc.algorithms.core.measurement import pauli_expectation
def _print_circuit_info(circuit, label):
"""Print circuit statistics."""
print(f"\n {label}:")
print(f" Qubits used: {circuit.max_qubit + 1}")
print(f" Gate count: {len(circuit.opcode_list)}")
def demo_hva_basic(p=2):
"""Demonstrate basic HVA construction."""
print("=" * 60)
print("Demo 1: Basic HVA Construction")
print("=" * 60)
# Define commuting Hamiltonian groups (simplified Hubbard model)
# Group 1: Hopping terms (X and Y on same pairs)
hopping = [
("X0X1", 1.0),
("Y0Y1", 1.0),
]
# Group 2: Interaction term (Z on same pair)
interaction = [
("Z0Z1", 0.5),
]
groups = [hopping, interaction]
print(f"\n Hamiltonian groups:")
print(f" Group 1 (hopping): {hopping}")
print(f" Group 2 (interaction): {interaction}")
n_params = len(groups) * p
print(f"\n HVA configuration:")
print(f" Layers (p): {p}")
print(f" Groups: {len(groups)}")
print(f" Parameters: {n_params} ({len(groups)} groups x {p} layers)")
# Build the HVA circuit
c = hva(groups, p=p)
_print_circuit_info(c, "HVA circuit")
# Verify statevector validity
sim = Simulator(backend_type="statevector")
sv = sim.simulate_statevector(c.originir)
print(f"\n Statevector norm: {np.linalg.norm(sv):.10f}")
def demo_hva_hf_state(p=2):
"""Demonstrate HVA with Hartree-Fock initial state."""
print("\n" + "=" * 60)
print("Demo 2: HVA with Hartree-Fock Initial State")
print("=" * 60)
# Define groups for a 4-qubit system
groups = [
[("X0X1", 1.0), ("X1X2", 1.0)],
[("Y0Y1", 1.0), ("Y1Y2", 1.0)],
[("Z0Z1", 0.5), ("Z1Z2", 0.5)],
]
print(f"\n Hamiltonian groups: 3 groups")
print(f" Group 1: X hopping")
print(f" Group 2: Y hopping")
print(f" Group 3: ZZ interactions")
# Without Hartree-Fock (all qubits start in |0>)
c_no_hf = hva(groups, p=p)
print(f"\n Without Hartree-Fock:")
print(f" Initial state: |0000>")
_print_circuit_info(c_no_hf, "Circuit")
# With Hartree-Fock (first 2 qubits in |1>)
c_hf = hva(groups, p=p, hf_state=[0, 1])
print(f"\n With Hartree-Fock:")
print(f" Initial state: |1100>")
_print_circuit_info(c_hf, "Circuit")
# Verify both produce valid statevectors
sim = Simulator(backend_type="statevector")
sv_no_hf = sim.simulate_statevector(c_no_hf.originir)
sv_hf = sim.simulate_statevector(c_hf.originir)
print(f"\n Without HF norm: {np.linalg.norm(sv_no_hf):.10f}")
print(f" With HF norm: {np.linalg.norm(sv_hf):.10f}")
def demo_hva_energy(p=3, max_iter=20):
"""Demonstrate HVA energy optimization."""
print("\n" + "=" * 60)
print("Demo 3: HVA Energy Optimization")
print("=" * 60)
# Simple 2-qubit Hamiltonian: H = -Z0Z1 + 0.5*I
# Ground state: |00> or |11> with E = -1.0 + 0.5 = -0.5
H = [
("Z0Z1", -1.0),
("II", 0.5),
]
groups = [[("Z0Z1", -1.0)]] # Single group containing ZZ
print(f"\n Hamiltonian: H = -Z0Z1 + 0.5*I")
print(f" Expected ground state: |00> or |11>")
print(f" Expected energy: -0.5")
def energy(params):
"""Compute expectation value of H."""
c = hva(groups, p=p, params=params)
e = 0.0
for pauli, coeff in H:
e += coeff * pauli_expectation(c, pauli)
return e
# Random initial parameters
n_params = len(groups) * p
params = np.random.uniform(-np.pi/4, np.pi/4, size=n_params)
print(f"\n Optimization (coordinate descent):")
best_energy = float("inf")
best_params = params.copy()
step = 0.1
for iteration in range(max_iter):
improved = False
for i in range(n_params):
# Try positive step
params[i] += step
e_plus = energy(params)
# Try negative step
params[i] -= 2 * step
e_minus = energy(params)
# Reset
params[i] += step
e_curr = energy(params)
if e_plus < e_curr and e_plus < e_minus:
params[i] += step
improved = True
elif e_minus < e_curr:
params[i] -= step
improved = True
e = energy(params)
if e < best_energy:
best_energy = e
best_params = params.copy()
if iteration % 5 == 0:
print(f" Iter {iteration:2d}: E = {e:.6f}")
if not improved:
step *= 0.5
if step < 1e-6:
break
print(f"\n Final energy: {best_energy:.6f}")
print(f" Expected: -0.500000")
print(f" Accuracy: {abs(best_energy - (-0.5)):.6f}")
def run_demo(p, max_iter):
"""Run all HVA demos."""
print(f"\n{'=' * 60}")
print(f"HVA Example (p={p}, iterations={max_iter})")
print(f"{'=' * 60}")
demo_hva_basic(p)
demo_hva_hf_state(p)
demo_hva_energy(p, max_iter)
print("\n" + "=" * 60)
print("Demo Complete")
print("=" * 60)
def main():
parser = argparse.ArgumentParser(description="HVA Example")
parser.add_argument(
"-p", "--p-layers", type=int, default=2,
help="Number of HVA layers (default: 2)"
)
parser.add_argument(
"-n", "--n-iterations", type=int, default=20,
help="Max optimization iterations (default: 20)"
)
args = parser.parse_args()
if args.p_layers < 1:
parser.error("-p must be at least 1")
if args.n_iterations < 1:
parser.error("-n must be at least 1")
run_demo(args.p_layers, args.n_iterations)
if __name__ == "__main__":
main()
Stdout
============================================================
HVA Example (p=2, iterations=20)
============================================================
============================================================
Demo 1: Basic HVA Construction
============================================================
Hamiltonian groups:
Group 1 (hopping): [('X0X1', 1.0), ('Y0Y1', 1.0)]
Group 2 (interaction): [('Z0Z1', 0.5)]
HVA configuration:
Layers (p): 2
Groups: 2
Parameters: 4 (2 groups x 2 layers)
HVA circuit:
Qubits used: 2
Gate count: 42
Statevector norm: 1.0000000000
============================================================
Demo 2: HVA with Hartree-Fock Initial State
============================================================
Hamiltonian groups: 3 groups
Group 1: X hopping
Group 2: Y hopping
Group 3: ZZ interactions
Without Hartree-Fock:
Initial state: |0000>
Circuit:
Qubits used: 3
Gate count: 84
With Hartree-Fock:
Initial state: |1100>
Circuit:
Qubits used: 3
Gate count: 86
Without HF norm: 1.0000000000
With HF norm: 1.0000000000
============================================================
Demo 3: HVA Energy Optimization
============================================================
Hamiltonian: H = -Z0Z1 + 0.5*I
Expected ground state: |00> or |11>
Expected energy: -0.5
Optimization (coordinate descent):
Iter 0: E = -0.500000
Iter 5: E = -0.500000
Iter 10: E = -0.500000
Iter 15: E = -0.500000
Final energy: -0.500000
Expected: -0.500000
Accuracy: 0.000000
============================================================
Demo Complete
============================================================
Symbolic Parameters for Variational Circuits.¶
Source: examples/2_advanced/algorithms/parameters_demo.py
Status: pass
Demonstrates:
Auto-generation of Parameters when params=None
Manual binding workflow with Parameters objects
Pre-computing parameter counts with hea_param_count()
Symbolic arithmetic with Parameter objects
Usage: python parameters_demo.py
Source code
#!/usr/bin/env python
"""Symbolic Parameters for Variational Circuits.
Demonstrates:
* Auto-generation of Parameters when params=None
* Manual binding workflow with Parameters objects
* Pre-computing parameter counts with hea_param_count()
* Symbolic arithmetic with Parameter objects
Usage:
python parameters_demo.py
[doc-require: ]
"""
import sys
import numpy as np
sys.path.insert(0, str(__file__).rsplit("/", 2)[0])
from uniqc import Circuit
from uniqc.simulator import Simulator
from uniqc.algorithms.core.ansatz import hea, hea_param_count, qaoa_ansatz, hva
from uniqc.circuit_builder.parameter import Parameter, Parameters
def demo_auto_generation():
"""Demonstrate auto-generation of Parameters when params=None."""
print("=" * 60)
print("Demo 1: Auto-Generated Parameters")
print("=" * 60)
# HEA without params - auto-generates Parameters
c = hea(n_qubits=4, depth=2)
print(f"\n Calling hea(n_qubits=4, depth=2) without params:")
print(f"\n Circuit._params type: {type(c._params).__name__}")
print(f" Parameters name: {c._params.name}")
print(f" Parameters count: {len(c._params)}")
print(f" Parameter names: {c._params.names}")
# Show first few values
values = [c._params[i].evaluate() for i in range(min(4, len(c._params)))]
print(f" First 4 values: {[f'{v:.4f}' for v in values]}")
# QAOA auto-generates separate betas and gammas
H = [("Z0Z1", 1.0), ("Z1Z2", 0.5)]
c_qaoa = qaoa_ansatz(H, p=2)
print(f"\n QAOA auto-generated parameters:")
print(f" betas: {c_qaoa._params['betas'].name}, len={len(c_qaoa._params['betas'])}")
print(f" gammas: {c_qaoa._params['gammas'].name}, len={len(c_qaoa._params['gammas'])}")
def demo_hea_param_count():
"""Demonstrate pre-computing parameter counts."""
print("\n" + "=" * 60)
print("Demo 2: Pre-computing Parameter Counts")
print("=" * 60)
n_qubits = 4
depth = 2
print(f"\n For n_qubits={n_qubits}, depth={depth}:")
print(f"\n {'Configuration':<30} {'Parameters':<12}")
print(f" {'-'*42}")
configs = [
("RZ+RY (default)", dict(rotation_gates=["rz", "ry"])),
("RX only", dict(rotation_gates=["rx"])),
("RX+RY+RZ", dict(rotation_gates=["rx", "ry", "rz"])),
("CNOT (default)", dict(entangling_gate="cnot")),
("CZ", dict(entangling_gate="cz")),
("XX (parametric)", dict(entangling_gate="xx", topology="ring")),
("Linear topology", dict(topology="linear")),
("Full topology", dict(topology="full")),
]
for label, kwargs in configs:
count = hea_param_count(n_qubits, depth, **kwargs)
print(f" {label:<30} {count:<12}")
print(f"\n Use hea_param_count() to determine array size before building:")
print(f"\n n_params = hea_param_count(4, 2, rotation_gates=['rx', 'ry'])")
n_params = hea_param_count(4, 2, rotation_gates=["rx", "ry"])
print(f" # n_params = {n_params}")
print(f" params = np.zeros(n_params)")
def demo_manual_binding():
"""Demonstrate manual Parameters binding workflow."""
print("\n" + "=" * 60)
print("Demo 3: Manual Parameters Binding")
print("=" * 60)
# Step 1: Determine parameter count
n_qubits = 4
depth = 2
n_params = hea_param_count(n_qubits, depth)
print(f"\n Step 1: Determine parameter count")
print(f" n_params = hea_param_count({n_qubits}, {depth}) = {n_params}")
# Step 2: Create Parameters object
print(f"\n Step 2: Create Parameters object")
params = Parameters("my_ansatz_params", size=n_params)
print(f" params = Parameters('my_ansatz_params', size={n_params})")
print(f" names: {params.names}")
# Step 3: Bind values
print(f"\n Step 3: Bind values")
values = [0.1 * (i + 1) for i in range(n_params)]
params.bind(values)
print(f" params.bind([0.1, 0.2, ..., {values[-1]:.1f}]) # {n_params} values")
print(f" Bound: {params[0].is_bound}")
# Step 4: Use in circuit
print(f"\n Step 4: Build circuit")
c = hea(n_qubits, depth, params=params)
print(f" circuit = hea({n_qubits}, {depth}, params=params)")
print(f" circuit._params is params: {c._params is params}")
# Step 5: Verify statevector
sim = Simulator(backend_type="statevector")
sv = sim.simulate_statevector(c.originir)
print(f"\n Step 5: Verify circuit")
print(f" Statevector norm: {np.linalg.norm(sv):.10f}")
# Step 6: Rebind for new optimization run
print(f"\n Step 6: Rebind for new optimization")
new_values = [0.5] * n_params
params.bind(new_values)
c2 = hea(n_qubits, depth, params=params)
print(f" params.bind([0.5, 0.5, ..., 0.5]) # {n_params} values")
print(f" Rebuilt circuit successfully")
def demo_symbolic_arithmetic():
"""Demonstrate symbolic arithmetic with Parameter objects."""
print("\n" + "=" * 60)
print("Demo 4: Symbolic Arithmetic")
print("=" * 60)
# Create parameters
theta = Parameter("theta")
phi = Parameter("phi")
print(f"\n Creating parameters:")
print(f" theta = Parameter('theta')")
print(f" phi = Parameter('phi')")
# Arithmetic operations create sympy expressions
expr1 = theta + phi / 2
expr2 = theta * 2 - phi
expr3 = -theta
print(f"\n Arithmetic expressions (sympy):")
print(f" theta + phi/2 = {expr1}")
print(f" theta*2 - phi = {expr2}")
print(f" -theta = {expr3}")
# Evaluate with specific values using substitution
theta.bind(1.0)
phi.bind(2.0)
print(f"\n After binding theta=1.0, phi=2.0:")
# Use subs to substitute the symbol values
subs_dict = {theta.symbol: 1.0, phi.symbol: 2.0}
print(f" theta + phi/2 = {float(expr1.subs(subs_dict).evalf())}")
print(f" theta*2 - phi = {float(expr2.subs(subs_dict).evalf())}")
# Show that Parameters array works similarly
print(f"\n Parameters array:")
params = Parameters("alpha", size=3)
params.bind([0.5, 1.0, 1.5])
for i in range(3):
print(f" params[{i}].name = '{params[i].name}', value = {params[i].evaluate()}")
def run_demo():
"""Run all parameter demos."""
print("\n" + "=" * 60)
print("Symbolic Parameters Demo")
print("=" * 60)
demo_auto_generation()
demo_hea_param_count()
demo_manual_binding()
demo_symbolic_arithmetic()
print("\n" + "=" * 60)
print("Demo Complete")
print("=" * 60)
def main():
run_demo()
if __name__ == "__main__":
main()
Stdout
============================================================
Symbolic Parameters Demo
============================================================
============================================================
Demo 1: Auto-Generated Parameters
============================================================
Calling hea(n_qubits=4, depth=2) without params:
Circuit._params type: Parameters
Parameters name: theta_hea
Parameters count: 16
Parameter names: ['theta_hea_0', 'theta_hea_1', 'theta_hea_2', 'theta_hea_3', 'theta_hea_4', 'theta_hea_5', 'theta_hea_6', 'theta_hea_7', 'theta_hea_8', 'theta_hea_9', 'theta_hea_10', 'theta_hea_11', 'theta_hea_12', 'theta_hea_13', 'theta_hea_14', 'theta_hea_15']
First 4 values: ['4.0021', '1.6951', '0.2574', '0.1038']
QAOA auto-generated parameters:
betas: betas_qaoa, len=2
gammas: gammas_qaoa, len=2
============================================================
Demo 2: Pre-computing Parameter Counts
============================================================
For n_qubits=4, depth=2:
Configuration Parameters
------------------------------------------
RZ+RY (default) 16
RX only 8
RX+RY+RZ 24
CNOT (default) 16
CZ 16
XX (parametric) 24
Linear topology 16
Full topology 16
Use hea_param_count() to determine array size before building:
n_params = hea_param_count(4, 2, rotation_gates=['rx', 'ry'])
# n_params = 16
params = np.zeros(n_params)
============================================================
Demo 3: Manual Parameters Binding
============================================================
Step 1: Determine parameter count
n_params = hea_param_count(4, 2) = 16
Step 2: Create Parameters object
params = Parameters('my_ansatz_params', size=16)
names: ['my_ansatz_params_0', 'my_ansatz_params_1', 'my_ansatz_params_2', 'my_ansatz_params_3', 'my_ansatz_params_4', 'my_ansatz_params_5', 'my_ansatz_params_6', 'my_ansatz_params_7', 'my_ansatz_params_8', 'my_ansatz_params_9', 'my_ansatz_params_10', 'my_ansatz_params_11', 'my_ansatz_params_12', 'my_ansatz_params_13', 'my_ansatz_params_14', 'my_ansatz_params_15']
Step 3: Bind values
params.bind([0.1, 0.2, ..., 1.6]) # 16 values
Bound: True
Step 4: Build circuit
circuit = hea(4, 2, params=params)
circuit._params is params: True
Step 5: Verify circuit
Statevector norm: 1.0000000000
Step 6: Rebind for new optimization
params.bind([0.5, 0.5, ..., 0.5]) # 16 values
Rebuilt circuit successfully
============================================================
Demo 4: Symbolic Arithmetic
============================================================
Creating parameters:
theta = Parameter('theta')
phi = Parameter('phi')
Arithmetic expressions (sympy):
theta + phi/2 = phi/2 + theta
theta*2 - phi = -phi + 2*theta
-theta = -theta
After binding theta=1.0, phi=2.0:
theta + phi/2 = 2.0
theta*2 - phi = 0.0
Parameters array:
params[0].name = 'alpha_0', value = 0.5
params[1].name = 'alpha_1', value = 1.0
params[2].name = 'alpha_2', value = 1.5
============================================================
Demo Complete
============================================================
量子‑经典混合模型¶
Hybrid Classical-Quantum Model using TorchQuantum backend.¶
Source: examples/2_advanced/algorithms/hybrid_model.py
Status: skip — missing requirements: torchquantum (torch + torchquantum installed)
Demonstrates a hybrid architecture: Classical encoder → Quantum circuit → Classical decoder, for 2D binary classification.
Source code
"""Hybrid Classical-Quantum Model using TorchQuantum backend.
Demonstrates a hybrid architecture: Classical encoder → Quantum circuit
→ Classical decoder, for 2D binary classification.
[doc-require: torchquantum]
"""
try:
import torch
import torch.nn as nn
from sklearn.datasets import make_moons
from sklearn.preprocessing import StandardScaler
from uniqc import HybridQCLModel
except ImportError as e:
print(f"Required dependencies not available: {e}")
print("Install with: pip install unified-quantum[pytorch] scikit-learn")
print('Then install TorchQuantum manually: pip install "torchquantum @ git+https://github.com/Agony5757/torchquantum.git@fix/optional-qiskit-deps"')
raise SystemExit(1)
def main():
print("=" * 60)
print("Hybrid Classical-Quantum Model — TorchQuantum Backend")
print("=" * 60)
# Generate dataset
X_np, y_np = make_moons(n_samples=100, noise=0.15, random_state=42)
scaler = StandardScaler()
X_np = scaler.fit_transform(X_np)
X = torch.tensor(X_np, dtype=torch.float32)
y = torch.tensor(y_np, dtype=torch.float32).unsqueeze(1)
# Model
model = HybridQCLModel(
n_features=2,
n_qubits=4,
quantum_depth=2,
classical_hidden=16,
)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
loss_fn = nn.BCELoss()
n_params = sum(p.numel() for p in model.parameters())
print(f"\nDataset: make_moons (100 samples)")
print(f"Model: HybridQCLModel (classical → quantum → classical)")
print(f"Total parameters: {n_params}")
print(f" Encoder: {sum(p.numel() for p in model.encoder.parameters())}")
print(f" Quantum: {model.quantum_params.numel()}")
print(f" Decoder: {sum(p.numel() for p in model.decoder.parameters())}\n")
# Training
for epoch in range(50):
optimizer.zero_grad()
y_pred = model(X)
loss = loss_fn(y_pred, y)
loss.backward()
optimizer.step()
if (epoch + 1) % 10 == 0:
acc = ((y_pred > 0.5).float() == y).float().mean()
print(f" Epoch {epoch + 1:3d} | Loss: {loss.item():.4f} | Acc: {acc:.4f}")
# Final accuracy
with torch.no_grad():
y_pred = model(X)
acc = ((y_pred > 0.5).float() == y).float().mean()
print(f"\nFinal accuracy: {acc:.4f}")
if __name__ == "__main__":
main()
Note
Example skipped during pre-doc-execution: missing requirements: torchquantum (torch + torchquantum installed)
QNN Binary Classifier using TorchQuantum backend.¶
Source: examples/2_advanced/algorithms/qnn_classifier.py
Status: skip — missing requirements: torchquantum (torch + torchquantum installed)
Demonstrates a Quantum Neural Network for binary classification on a synthetic moons dataset with native PyTorch autograd.
Source code
"""QNN Binary Classifier using TorchQuantum backend.
Demonstrates a Quantum Neural Network for binary classification on
a synthetic moons dataset with native PyTorch autograd.
[doc-require: torchquantum]
"""
try:
import torch
import torch.nn as nn
from sklearn.datasets import make_moons
from sklearn.preprocessing import StandardScaler
from uniqc import QNNClassifier
except ImportError as e:
print(f"Required dependencies not available: {e}")
print("Install with: pip install unified-quantum[pytorch] scikit-learn")
print('Then install TorchQuantum manually: pip install "torchquantum @ git+https://github.com/Agony5757/torchquantum.git@fix/optional-qiskit-deps"')
raise SystemExit(1)
def main():
print("=" * 60)
print("QNN Binary Classifier — TorchQuantum Backend")
print("=" * 60)
# Generate moons dataset
X_np, y_np = make_moons(n_samples=100, noise=0.15, random_state=42)
scaler = StandardScaler()
X_np = scaler.fit_transform(X_np)
X = torch.tensor(X_np, dtype=torch.float32)
y = torch.tensor(y_np, dtype=torch.float32)
# Model
n_qubits = 4
model = QNNClassifier(n_qubits=n_qubits, n_features=2, depth=2)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
loss_fn = nn.BCELoss()
print(f"\nDataset: make_moons (100 samples, 2 features)")
print(f"Model: QNN (n_qubits={n_qubits}, depth=2)")
print(f"Parameters: {sum(p.numel() for p in model.parameters())}\n")
# Training
for epoch in range(50):
optimizer.zero_grad()
y_pred = model(X)
loss = loss_fn(y_pred, y)
loss.backward()
optimizer.step()
if (epoch + 1) % 10 == 0:
acc = ((y_pred > 0.5).float() == y).float().mean()
print(f" Epoch {epoch + 1:3d} | Loss: {loss.item():.4f} | Acc: {acc:.4f}")
# Final accuracy
with torch.no_grad():
y_pred = model(X)
acc = ((y_pred > 0.5).float() == y).float().mean()
print(f"\nFinal accuracy: {acc:.4f}")
if __name__ == "__main__":
main()
Note
Example skipped during pre-doc-execution: missing requirements: torchquantum (torch + torchquantum installed)
QCNN Quantum State Classifier using TorchQuantum backend.¶
Source: examples/2_advanced/algorithms/qcnn_classifier.py
Status: skip — missing requirements: torchquantum (torch + torchquantum installed)
Demonstrates a Quantum Convolutional Neural Network for classifying quantum states (GHZ vs product states) with native PyTorch autograd.
Source code
"""QCNN Quantum State Classifier using TorchQuantum backend.
Demonstrates a Quantum Convolutional Neural Network for classifying
quantum states (GHZ vs product states) with native PyTorch autograd.
[doc-require: torchquantum]
"""
try:
import torch
import torch.nn as nn
from uniqc import QCNNClassifier
except ImportError as e:
print(f"Required dependencies not available: {e}")
print("Install with: pip install unified-quantum[pytorch]")
print('Then install TorchQuantum manually: pip install "torchquantum @ git+https://github.com/Agony5757/torchquantum.git@fix/optional-qiskit-deps"')
raise SystemExit(1)
def generate_state_dataset(n_qubits: int, n_samples: int):
"""Generate dataset of GHZ (label=1) and |0...0> (label=0) states."""
from uniqc import Circuit
from uniqc.simulator import Simulator
sim = Simulator(backend_type="statevector")
states = []
labels = []
for i in range(n_samples):
if i < n_samples // 2:
# |0...0> state
c = Circuit(n_qubits)
sv = sim.simulate_statevector(c.originir)
labels.append(0.0)
else:
# GHZ state: (|0...0> + |1...1>) / sqrt(2)
c = Circuit(n_qubits)
c.h(0)
for q in range(1, n_qubits):
c.cx(0, q)
sv = sim.simulate_statevector(c.originir)
labels.append(1.0)
states.append(torch.tensor(sv, dtype=torch.float32))
return states, labels
def main():
print("=" * 60)
print("QCNN State Classifier — TorchQuantum Backend")
print("=" * 60)
n_qubits = 4
n_samples = 20
print(f"\nTask: Classify GHZ vs |0...0> states")
print(f"Qubits: {n_qubits}")
print(f"Model: QCNN\n")
# QCNN model
model = QCNNClassifier(n_qubits=n_qubits, n_classes=2)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
print(f"Parameters: {sum(p.numel() for p in model.parameters())}\n")
# Simple training loop with synthetic labels
# Since QCNN operates on the quantum state directly, we train
# the conv/pool parameters to distinguish states
labels = torch.tensor(
[0.0] * (n_samples // 2) + [1.0] * (n_samples // 2), dtype=torch.float32
)
for epoch in range(50):
total_loss = 0.0
correct = 0
for i in range(n_samples):
optimizer.zero_grad()
output = model()
target = labels[i]
loss = ((output - target) ** 2).mean()
loss.backward()
optimizer.step()
total_loss += loss.item()
correct += int((output.item() > 0.5) == target.item())
if (epoch + 1) % 10 == 0:
avg_loss = total_loss / n_samples
acc = correct / n_samples
print(f" Epoch {epoch + 1:3d} | Loss: {avg_loss:.4f} | Acc: {acc:.4f}")
print("\nTraining complete.")
if __name__ == "__main__":
main()
Note
Example skipped during pre-doc-execution: missing requirements: torchquantum (torch + torchquantum installed)