A PyTorch-native quantum statevector plumbing library for quantum machine learning
Project description
Quantum Conduit
The world's first PyTorch-native quantum statevector plumbing library for quantum machine learning
Features • Installation • Quick Start • Documentation • Examples
Quantum Conduit is a minimal, PyTorch-native quantum statevector library designed specifically for quantum machine learning applications. Unlike high-level quantum frameworks, Quantum Conduit provides clean, low-level abstractions that integrate seamlessly with PyTorch's computational graph, enabling native autograd support and batch processing for quantum operations.
Table of Contents
- Features
- Installation
- Quick Start
- Architecture
- Examples
- API Reference
- Use Cases
- Performance Considerations
- Comparison with Alternatives
- Contributing
- License
Features
Quantum Conduit provides a comprehensive set of quantum computing primitives optimized for machine learning:
Core Capabilities
- 🔬 Statevector Backend: Pure quantum state operations with full batch support
- 🌊 Density Matrix Backend: Mixed quantum states for noise modeling (optimized for small systems)
- ⚛️ Standard Gate Library: Complete set of single- and two-qubit gates (I, X, Y, Z, H, S, T, CNOT, RX, RY, RZ)
- 🖥️ Device Abstraction: Seamless CPU and CUDA support with automatic device management
- 🧩 QuantumModule: PyTorch-native module system compatible with
torch.nn.Module - 🔌 Circuit IR: Structured circuit representation with simulation and visualization
- 🔍 Diagnostics: State validation, fidelity computation, and debugging tools
Quantum Machine Learning
- 📊 Parametric Ansätze: Hardware-efficient and custom ansätze for variational algorithms
- 🔍 VQE Algorithm: Built-in Variational Quantum Eigensolver for ground-state energy estimation
- 🎯 QAOA Algorithm: Quantum Approximate Optimization Algorithm for MaxCut/Ising problems
- 🌡️ Adiabatic Evolution: Adiabatic quantum computing with configurable schedules and circuit building
- 🎛️ Variational Scaffolding: High-level APIs for running VQE and QAOA with result objects
- 🤖 Hybrid Quantum-Classical: Seamless integration with PyTorch neural networks
- 📈 Parameter-Shift Gradients: Quantum-aware gradient computation via parameter-shift rule
- 🔄 Full Autograd Support: Native PyTorch differentiation throughout the stack
- 🏋️ Training Infrastructure: Complete VQE training loop with callbacks and history tracking
Advanced Features
- 🎯 Pauli Operators: Complete support for Pauli-term and Pauli-sum Hamiltonians
- 🌪️ Noise Models: Standard quantum channels, enhanced Kraus channels, and circuit-level noise simulation
- 📦 Batch Processing: Efficient batch operations for training quantum models
- 🎨 Extensible Design: Clean abstractions for custom gates, ansätze, and algorithms
- 🐛 Debug Mode: Built-in debugging with normalization checks and validation
- 🎲 Sampling Utilities: Bitstring sampling and probability distribution analysis
- ⏱️ Time Evolution: Trotterization and exact Hamiltonian time evolution (dual APIs)
- ⚙️ Optimizer Factory: Convenient optimizer creation utilities
- 🔬 Experimental Tools: Parameter sweep utilities for algorithm exploration
- 🔬 Exact Solvers: Exact diagonalization for benchmarking and validation (small systems)
- 🏗️ Pre-built Models: Standard quantum many-body models (spin chains, chemistry models)
- 🧬 Fermion-to-Qubit Mappings: Jordan-Wigner and Bravyi-Kitaev transforms for quantum chemistry
- 🔬 Quantum State Tomography: Density matrix reconstruction from Pauli measurements
- ⚙️ Circuit Transpilation: Gate decomposition and basis set conversion for hardware compatibility
- 🔌 Quantum Channels API: Textbook quantum noise channels with Kraus operators
- 📥 I/O Support: OpenQASM 2.0 and JSON IR import/export for circuit interchange
- 🔗 PyTorch Integration: Native
nn.Moduleintegration with parameter-shift gradients - 📊 Visualization: Circuit drawing, Bloch sphere visualization, and circuit analysis
Installation
Requirements
- Python 3.10 or higher
- PyTorch 2.1 or higher
Install from PyPI (Recommended)
pip install qconduit
Install from Source
For the latest development version:
git clone https://github.com/seansimms/Quantum_Conduit.git
cd Quantum_Conduit
pip install -e .
Development Installation
For development with testing and linting tools:
pip install -e ".[dev]"
CUDA Support
CUDA support is automatically available if PyTorch was installed with CUDA support. No additional configuration is required.
Quick Start
Example 1: Basic Quantum Operations
import torch
import qconduit as qc
# Create a 1-qubit zero state
state = qc.zero_state(n_qubits=1)
# Apply Hadamard gate
h_gate = qc.H()
state = qc.apply_gate(state, h_gate, qubit=0, n_qubits=1)
# Compute probabilities
probs = qc.measure_probs(state, n_qubits=1)
print(f"Probabilities: {probs}") # [0.5, 0.5]
# Measure Z expectation
z_exp = qc.measure_expectation_z(state, qubit=0, n_qubits=1)
print(f"<Z>: {z_exp}") # ~0.0
Example 2: Variational Quantum Eigensolver (VQE)
import torch
import qconduit as qc
from qconduit.algorithms import VQE
from qconduit.layers import HardwareEfficientAnsatz
# Define a 2-qubit Hamiltonian (diagonal)
hamiltonian = torch.tensor([0.0, 0.5, 0.5, 1.0], dtype=torch.float32)
# Create ansatz
ansatz = HardwareEfficientAnsatz(n_qubits=2, depth=2)
# Create VQE instance (hamiltonian can be tensor or PauliSum)
vqe = VQE(ansatz=ansatz, hamiltonian=hamiltonian)
# Initialize parameters
params = torch.nn.Parameter(0.1 * torch.randn(ansatz.num_parameters))
# Optimize
optimizer = torch.optim.Adam([params], lr=0.1)
for step in range(50):
optimizer.zero_grad()
energy = vqe.energy(params)
energy.backward()
optimizer.step()
if (step + 1) % 10 == 0:
print(f"Step {step + 1}: energy = {energy.item():.6f}")
Example 3: Hybrid Quantum-Classical Model
import torch
import torch.nn as nn
from qconduit.layers import QuantumBlock
class HybridClassifier(nn.Module):
def __init__(self):
super().__init__()
# Quantum block: 2 qubits, depth 1, 2 input features
self.quantum = QuantumBlock(n_qubits=2, depth=1, in_features=2)
# Classical head
self.head = nn.Linear(2, 2) # 2 classes
def forward(self, x):
q_features = self.quantum(x) # Quantum expectations
return self.head(q_features) # Classical classification
# Use like any PyTorch model
model = HybridClassifier()
x = torch.randn(32, 2) # Batch of 32 samples
logits = model(x)
Example 4: Noise Modeling
import torch
import qconduit as qc
from qconduit.noise import DepolarizingChannel
from qconduit.layers import HardwareEfficientAnsatz
# Create a noisy quantum circuit
ansatz = HardwareEfficientAnsatz(n_qubits=2, depth=2)
params = torch.randn(ansatz.num_parameters)
# Build state
state = ansatz(params)
# Apply noise model
noise = DepolarizingChannel(p=0.1) # 10% depolarizing noise
rho = noise.apply_statevector(state, n_qubits=2)
# Compute noisy expectation values
import qconduit as qc
probs = qc.measure_probs_dm(rho)
print(f"Noisy probabilities: {probs}")
Example 5: Circuit IR
from qconduit.circuit import QuantumCircuit
# Create and simulate a Bell state circuit
circuit = QuantumCircuit(n_qubits=2)
circuit.add_gate("H", [0])
circuit.add_gate("CNOT", [1, 0])
# Simulate the circuit
state = circuit.simulate_state()
# Visualize the circuit
print(circuit.to_text_diagram())
# Output:
# q0: ─H──⊕─
# q1: ────●─
# Analyze circuit properties
print(f"Depth: {circuit.depth()}") # 2
print(f"Gate counts: {circuit.gate_counts()}") # {'H': 1, 'CNOT': 1}
print(f"Number of gates: {circuit.num_gates()}") # 2
# Parametric gates
circuit2 = QuantumCircuit(n_qubits=1)
circuit2.add_gate("RX", [0], params=[0.5]) # Rotation gate with angle
state2 = circuit2.simulate_state()
Example 6: Diagnostics and Debug Mode
import qconduit as qc
from qconduit.diagnostics import state_norm, fidelity, bloch_vector, assert_normalized
# Check state normalization
state = qc.zero_state(n_qubits=1)
norm = state_norm(state)
print(f"State norm: {norm}") # 1.0
assert_normalized(state) # Validates norm ≈ 1
# Compute fidelity between states
state1 = qc.zero_state(n_qubits=1)
h_gate = qc.H()
state2 = qc.apply_gate(state1, h_gate, qubit=0, n_qubits=1)
f = fidelity(state1, state2)
print(f"Fidelity: {f}") # 0.5 (states are orthogonal)
# Compute Bloch vector for single-qubit state
bloch = bloch_vector(state2)
print(f"Bloch vector (x, y, z): {bloch}") # [1.0, 0.0, 0.0] for |+⟩
# Enable debug mode for validation
qc.set_debug_enabled(True)
# Operations now include automatic normalization checks
# This helps catch bugs during development
# Use context manager for temporary debug mode
with qc.debug_context(True):
# Debug checks enabled here
state = qc.apply_gate(state, h_gate, qubit=0, n_qubits=1)
# Debug mode restored to previous state
Example 7: QAOA for MaxCut
from qconduit.algorithms import QAOAAnsatz, ising_maxcut_hamiltonian, Edge, VQE
import torch
# Define a graph (triangle: 3 nodes, 3 edges)
edges = [Edge(0, 1), Edge(1, 2), Edge(2, 0)]
hamiltonian = ising_maxcut_hamiltonian(num_nodes=3, edges=edges)
# Create QAOA ansatz (p is the number of QAOA layers)
qaoa = QAOAAnsatz(n_qubits=3, problem_hamiltonian=hamiltonian, p=2)
# Optimize with VQE
vqe = VQE(ansatz=qaoa, hamiltonian=hamiltonian)
params = torch.nn.Parameter(0.1 * torch.randn(qaoa.num_parameters))
optimizer = torch.optim.Adam([params], lr=0.1)
for step in range(50):
optimizer.zero_grad()
energy = vqe.energy(params)
energy.backward()
optimizer.step()
if (step + 1) % 10 == 0:
print(f"Step {step + 1}: energy = {energy.item():.6f}")
Example 8: VQE Training with Callbacks
from qconduit.training import VQETrainer, TrainingCallback, EarlyStoppingConfig
from qconduit.algorithms import VQE
import torch
# Define callback for logging
class LoggingCallback(TrainingCallback):
def __call__(self, info):
if info.step % 10 == 0:
print(f"Step {info.step}: energy = {info.energy:.6f}")
# Configure early stopping
early_stop = EarlyStoppingConfig(patience=10, min_delta=1e-6)
# Create VQE and optimizer
vqe = VQE(ansatz=ansatz, hamiltonian=hamiltonian)
params = torch.nn.Parameter(0.1 * torch.randn(ansatz.num_parameters))
optimizer = torch.optim.Adam([params], lr=0.1)
# Train VQE with callbacks and early stopping
trainer = VQETrainer(vqe, optimizer=optimizer)
history = trainer.train(
params,
max_steps=100,
callbacks=[LoggingCallback()],
early_stopping=early_stop,
)
print(f"Best energy: {history.best_energy():.6f}")
print(f"Final energy: {history.final_energy():.6f}")
Example 9: Sampling and Analysis
from qconduit.sampling import sample_bitstrings_state, bitstring_counts, kl_divergence
import qconduit as qc
# Create a quantum state
state = qc.zero_state(n_qubits=3)
state = qc.apply_gate(state, qc.H(), qubit=0, n_qubits=3)
state = qc.apply_gate(state, qc.H(), qubit=1, n_qubits=3)
# Sample bitstrings from the state
samples = sample_bitstrings_state(state, n_qubits=3, n_shots=1000)
# Count occurrences
counts = bitstring_counts(samples)
print(f"Sample counts: {counts}")
# Compare probability distributions using KL divergence
probs1 = qc.measure_probs(state, n_qubits=3)
probs2 = qc.measure_probs(qc.zero_state(n_qubits=3), n_qubits=3)
# Convert probability tensors to dictionaries for kl_divergence
probs1_dict = {format(i, f'0{3}b'): float(probs1[i].item()) for i in range(len(probs1))}
probs2_dict = {format(i, f'0{3}b'): float(probs2[i].item()) for i in range(len(probs2))}
kl = kl_divergence(probs1_dict, probs2_dict)
print(f"KL divergence: {kl:.6f}")
Example 10: Time Evolution
from qconduit.time_evolution import time_evolve_state, build_trotter_circuit
from qconduit.operators import PauliTerm, PauliSum
import qconduit as qc
# Create a simple Hamiltonian (transverse field Ising model)
hamiltonian = PauliSum.from_terms([
PauliTerm(1.0, ("Z", "Z")), # Interaction
PauliTerm(0.5, ("X", "I")), # Transverse field
PauliTerm(0.5, ("I", "X")),
])
# Evolve state under the Hamiltonian
state = qc.zero_state(n_qubits=2)
evolved_state = time_evolve_state(
state, hamiltonian, t=0.5, n_steps=10, n_qubits=2
)
# Build Trotter circuit for the same evolution
circuit = build_trotter_circuit(
hamiltonian, t=0.5, n_steps=10, n_qubits=2, order=1 # First-order Trotter
)
state_from_circuit = circuit.simulate_state()
print("Time evolution complete")
Example 11: Exact Diagonalization
from qconduit.exact import exact_eigensystem, exact_ground_state, paulisum_to_dense
from qconduit.operators import PauliTerm, PauliSum
import torch
# Create a simple Hamiltonian
hamiltonian = PauliSum.from_terms([
PauliTerm(1.0, ("Z", "Z")),
PauliTerm(0.5, ("X", "I")),
PauliTerm(0.5, ("I", "X")),
])
# Convert to dense matrix
dense_matrix = paulisum_to_dense(hamiltonian, num_qubits=2)
print(f"Dense matrix shape: {dense_matrix.shape}") # (4, 4)
# Compute full eigensystem
eigenvalues, eigenvectors = exact_eigensystem(hamiltonian, num_qubits=2)
print(f"Eigenvalues: {eigenvalues}")
# Get just the ground state
ground_energy, ground_state = exact_ground_state(hamiltonian, num_qubits=2)
print(f"Ground state energy: {ground_energy.item():.6f}")
Example 12: Pre-built Models
from qconduit.models import (
transverse_field_ising_chain,
heisenberg_xxz_chain,
ising_zz_chain,
two_qubit_generic_chemistry_like,
diagonal_z_field,
)
# Transverse field Ising model (TFIM)
tfim = transverse_field_ising_chain(
num_sites=4,
j_coupling=1.0,
h_field=0.5,
periodic=True # Periodic boundary conditions
)
# Heisenberg XXZ chain
heisenberg = heisenberg_xxz_chain(
num_sites=3,
j_coupling=1.0,
delta=0.5, # Anisotropy parameter
periodic=False
)
# Ising ZZ chain (no transverse field)
ising = ising_zz_chain(
num_sites=4,
j_coupling=1.0,
periodic=True
)
# Two-qubit chemistry-like model
chemistry_ham = two_qubit_generic_chemistry_like(
c_i=0.0, # Identity coefficient
c_z0=0.5, # Z⊗I coefficient
c_z1=0.3, # I⊗Z coefficient
c_z0z1=0.1, # Z⊗Z coefficient
c_xx=0.0, # X⊗X coefficient
c_yy=0.0 # Y⊗Y coefficient
)
# Diagonal Z field
z_field = diagonal_z_field(num_qubits=3, local_fields=[0.5, 0.5, 0.5])
# Use with VQE or exact diagonalization
from qconduit.exact import exact_ground_state
energy, state = exact_ground_state(tfim, num_qubits=4)
print(f"TFIM ground energy: {energy.item():.6f}")
Example 13: Adiabatic Evolution
from qconduit.adiabatic import (
AdiabaticConfig,
linear_schedule,
polynomial_schedule,
adiabatic_evolve_state,
build_adiabatic_circuit,
build_x_mixer_hamiltonian,
interpolate_paulisum,
)
from qconduit.operators import PauliSum, PauliTerm
import qconduit as qc
import torch
# Define initial (mixer) and final (problem) Hamiltonians
h_mixer = build_x_mixer_hamiltonian(num_qubits=3) # -sum_i X_i
h_problem = PauliSum.from_terms([
PauliTerm(1.0, ("Z", "Z", "I")),
PauliTerm(1.0, ("I", "Z", "Z")),
])
# Create schedule (linear interpolation)
num_steps = 20
schedule = linear_schedule(num_steps)
# Configure adiabatic evolution
config = AdiabaticConfig(
total_time=1.0,
num_steps=num_steps,
schedule=schedule,
trotter_steps_per_interval=5
)
# Evolve state adiabatically
initial_state = qc.zero_state(n_qubits=3)
# Prepare |+⟩^⊗n state
for i in range(3):
initial_state = qc.apply_gate(initial_state, qc.H(), qubit=i, n_qubits=3)
final_state = adiabatic_evolve_state(
initial_state,
h_mixer,
h_problem,
config
)
# Build adiabatic circuit for visualization
circuit = build_adiabatic_circuit(
n_qubits=3, h_mixer=h_mixer, h_problem=h_problem, config=config
)
print(circuit.to_text_diagram())
Example 14: Fermion-to-Qubit Mappings
from qconduit.fermion import (
FermionOperator,
FermionTerm,
FermionOpSymbol,
jordan_wigner,
bravyi_kitaev,
)
# Create a fermionic operator (e.g., a^†_0 a_1 + a^†_1 a_0)
term1 = FermionTerm(
coeff=1.0,
operators=((0, "+"), (1, "-")) # a^†_0 a_1
)
term2 = FermionTerm(
coeff=1.0,
operators=((1, "+"), (0, "-")) # a^†_1 a_0
)
fermion_op = FermionOperator([term1, term2])
# Map to qubits using Jordan-Wigner transform
jw_hamiltonian = jordan_wigner(fermion_op, n_spin_orbitals=2)
print(f"Jordan-Wigner: {len(jw_hamiltonian.terms)} Pauli terms")
# Map to qubits using Bravyi-Kitaev transform
bk_hamiltonian = bravyi_kitaev(fermion_op, n_spin_orbitals=2)
print(f"Bravyi-Kitaev: {len(bk_hamiltonian.terms)} Pauli terms")
# Use the mapped Hamiltonian with VQE or exact diagonalization
from qconduit.exact import exact_ground_state
energy, state = exact_ground_state(jw_hamiltonian, num_qubits=2)
print(f"Ground energy: {energy.item():.6f}")
Example 15: Noisy Circuit Simulation
from qconduit.noise import NoiseConfig, simulate_noisy_circuit_dm, sample_noisy_circuit_dm
from qconduit.circuit import QuantumCircuit
from qconduit.noise import DepolarizingChannel
import qconduit as qc
# Create a quantum circuit
circuit = QuantumCircuit(n_qubits=2)
circuit.add_gate("H", [0])
circuit.add_gate("CNOT", [0, 1])
circuit.add_gate("RX", [1], params=[0.5])
# Configure noise: depolarizing noise on qubit 0, amplitude damping on qubit 1
from qconduit.noise import AmplitudeDampingChannel
noise_config = NoiseConfig(
per_qubit_channels={
0: DepolarizingChannel(p=0.01), # 1% depolarizing on qubit 0
1: AmplitudeDampingChannel(gamma=0.05), # Amplitude damping on qubit 1
}
)
# Simulate noisy circuit (returns density matrix)
rho = simulate_noisy_circuit_dm(circuit, noise=noise_config)
print(f"Density matrix shape: {rho.shape}") # (4, 4)
# Sample bitstrings from noisy circuit
samples = sample_noisy_circuit_dm(
circuit,
noise=noise_config,
n_shots=1000
)
# Analyze results
from qconduit.sampling import bitstring_counts
counts = bitstring_counts(samples)
print(f"Sample distribution: {counts}")
Example 16: Quantum State Tomography
from qconduit.measurement import (
single_qubit_pauli_expectations_from_statevector,
reconstruct_single_qubit_density_from_pauli,
two_qubit_pauli_expectations_from_statevector,
reconstruct_two_qubit_density_from_pauli,
pauli_expectation_from_statevector,
)
import qconduit as qc
# Create a quantum state
state = qc.zero_state(n_qubits=1)
state = qc.apply_gate(state, qc.H(), qubit=0, n_qubits=1)
state = qc.apply_gate(state, qc.RY(0.5), qubit=0, n_qubits=1)
# Measure Pauli expectations
ex_x, ex_y, ex_z = single_qubit_pauli_expectations_from_statevector(state)
print(f"Pauli expectations: X={ex_x:.4f}, Y={ex_y:.4f}, Z={ex_z:.4f}")
# Reconstruct density matrix from measurements
rho_reconstructed = reconstruct_single_qubit_density_from_pauli(ex_x, ex_y, ex_z)
# Compare with actual density matrix
rho_actual = qc.dm_from_statevector(state)
fidelity = qc.fidelity(rho_actual, rho_reconstructed)
print(f"Reconstruction fidelity: {fidelity.item():.6f}")
# Two-qubit tomography
state_2q = qc.zero_state(n_qubits=2)
state_2q = qc.apply_gate(state_2q, qc.H(), qubit=0, n_qubits=2)
state_2q = qc.apply_two_qubit_gate(state_2q, qc.CNOT(), qubit1=0, qubit2=1, n_qubits=2)
# Get all two-qubit Pauli expectations
pauli_expectations = two_qubit_pauli_expectations_from_statevector(state_2q)
rho_2q = reconstruct_two_qubit_density_from_pauli(pauli_expectations)
Example 17: Variational Algorithm Scaffolding
from qconduit.variational import (
VariationalAnsatz,
HardwareEfficientAnsatz,
LayeredEntanglerAnsatz,
run_vqe,
run_qaoa,
VQEResult,
QAOAResult,
)
from qconduit.operators import PauliSum, PauliTerm
import torch
# Create a Hamiltonian
hamiltonian = PauliSum.from_terms([
PauliTerm(1.0, ("Z", "Z")),
PauliTerm(0.5, ("X", "I")),
PauliTerm(0.5, ("I", "X")),
])
# High-level VQE API
ansatz = HardwareEfficientAnsatz(num_qubits=2, num_layers=2)
initial_params = torch.randn(ansatz.num_parameters)
result = run_vqe(
hamiltonian=hamiltonian,
ansatz=ansatz,
initial_params=initial_params,
max_iterations=100,
learning_rate=0.1,
)
print(f"Ground state energy: {result.optimal_value:.6f}")
print(f"Optimal parameters: {result.optimal_params}")
print(f"Converged: {result.converged}")
# High-level QAOA API
qaoa_result = run_qaoa(
cost_hamiltonian=hamiltonian,
num_qubits=2,
depth=2,
max_iterations=100,
learning_rate=0.05,
)
print(f"QAOA optimal energy: {qaoa_result.optimal_value:.6f}")
Example 18: Circuit Transpilation
from qconduit.transpile import (
decompose_h_to_rz_rx_rz,
transpile_to_rx_rz_cx_basis,
transpile_to_clifford_t,
summarize_gate_counts,
estimate_circuit_depth,
GateCountSummary,
)
from qconduit.circuit import QuantumCircuit
import qconduit as qc
# Create a circuit with various gates
circuit = QuantumCircuit(n_qubits=3)
circuit.add_gate("H", [0])
circuit.add_gate("CNOT", [0, 1])
circuit.add_gate("T", [1])
circuit.add_gate("S", [2])
circuit.add_gate("RX", [0], params=[0.5])
# Transpile to RX, RZ, CNOT basis (common hardware basis)
transpiled = transpile_to_rx_rz_cx_basis(circuit)
print("Transpiled circuit:")
print(transpiled.to_text_diagram())
# Transpile to Clifford+T basis
clifford_t = transpile_to_clifford_t(circuit)
print("\nClifford+T circuit:")
print(clifford_t.to_text_diagram())
# Analyze gate counts
summary = summarize_gate_counts(clifford_t)
print(f"\nGate counts: {summary.counts}")
print(f"T-count: {summary.t_count}")
print(f"Clifford count: {summary.clifford_count}")
# Estimate circuit depth
depth = estimate_circuit_depth(transpiled)
print(f"Circuit depth: {depth}")
Example 19: Enhanced Kraus Channels
from qconduit.noise import (
KrausChannel,
bit_flip_channel,
phase_flip_channel,
bit_phase_flip_channel,
generalized_amplitude_damping_channel,
two_qubit_depolarizing_channel,
compose_kraus_channels,
apply_kraus_channel_to_statevector,
apply_kraus_channel_to_density_matrix,
)
import qconduit as qc
import torch
# Create various noise channels
bit_flip = bit_flip_channel(p=0.01) # 1% bit flip probability
phase_flip = phase_flip_channel(p=0.02) # 2% phase flip probability
bit_phase_flip = bit_phase_flip_channel(p=0.005) # 0.5% bit-phase flip
# Generalized amplitude damping (with temperature)
amp_damp = generalized_amplitude_damping_channel(
gamma=0.1, # Damping rate
n_th=0.1, # Thermal population
)
# Two-qubit depolarizing channel
two_qubit_depol = two_qubit_depolarizing_channel(p=0.01)
# Compose channels (apply sequentially)
combined = compose_kraus_channels(bit_flip, phase_flip)
# Apply to statevector
state = qc.zero_state(n_qubits=1)
state = qc.apply_gate(state, qc.H(), qubit=0, n_qubits=1)
noisy_state = apply_kraus_channel_to_statevector(
state, bit_flip, qubit=0, n_qubits=1
)
# Apply to density matrix
rho = qc.dm_from_statevector(state)
noisy_rho = apply_kraus_channel_to_density_matrix(
rho, phase_flip, qubit=0, n_qubits=1
)
# Check channel properties
print(f"Bit flip channel is trace-preserving: {bit_flip.is_trace_preserving()}")
print(f"Kraus operators: {len(bit_flip.kraus_ops)}")
Example 20: Exact Time Evolution
from qconduit.evolution import (
exact_time_evolution_statevector,
TrotterOrder,
TrotterSchedule,
evolve_state_trotter,
)
from qconduit.operators import PauliSum, PauliTerm
import qconduit as qc
# Create a Hamiltonian
hamiltonian = PauliSum.from_terms([
PauliTerm(1.0, ("Z", "Z")),
PauliTerm(0.5, ("X", "I")),
PauliTerm(0.5, ("I", "X")),
])
# Exact time evolution (for small systems)
state = qc.zero_state(n_qubits=2)
state = qc.apply_gate(state, qc.H(), qubit=0, n_qubits=2)
evolved_exact = exact_time_evolution_statevector(
state, hamiltonian, time=0.5
)
# Trotterized evolution (for larger systems)
from qconduit.evolution import TrotterOrder, TrotterSchedule
schedule = TrotterSchedule(
num_steps=10, # Number of steps
total_time=0.5, # Total evolution time
order=1, # TrotterOrder.FIRST (1) or TrotterOrder.SECOND (2)
)
evolved_trotter = evolve_state_trotter(
state,
hamiltonian,
schedule,
)
# Compare results
fidelity = qc.fidelity(
qc.dm_from_statevector(evolved_exact),
qc.dm_from_statevector(evolved_trotter)
)
print(f"Fidelity between exact and Trotter: {fidelity.item():.6f}")
Example 21: Quantum Channels
import torch
from qconduit.channels import (
KrausChannel,
DepolarizingChannel,
BitFlipChannel,
AmplitudeDampingChannel,
apply_circuit_with_noise,
)
from qconduit.circuit import QuantumCircuit
# Create standard noise channels
depol = DepolarizingChannel(p=0.01) # 1% depolarizing noise
bit_flip = BitFlipChannel(p=0.05) # 5% bit-flip probability
amp_damp = AmplitudeDampingChannel(gamma=0.1) # Amplitude damping
# Apply channel to a density matrix
from qconduit.backend import zero_dm_state, dm_from_statevector
from qconduit.backend.statevector import zero_state, apply_gate
import qconduit as qc
rho = zero_dm_state(n_qubits=1)
# Apply depolarizing channel
rho_noisy = depol.apply_to_density(rho)
# Apply channel to a statevector (returns density matrix)
psi = zero_state(n_qubits=1)
psi = apply_gate(psi, qc.H(), qubit=0, n_qubits=1)
rho_mixed = depol.apply_to_statevector(psi)
# Simulate noisy circuit
circuit = QuantumCircuit(n_qubits=2)
circuit.add_gate("H", [0])
circuit.add_gate("CNOT", [0, 1])
# Apply noise after each gate
channel_locations = [
(0, DepolarizingChannel(p=0.01)), # Noise after H gate
(1, DepolarizingChannel(p=0.01)), # Noise after CNOT gate
]
rho_final = apply_circuit_with_noise(circuit, channel_locations)
print(f"Final density matrix shape: {rho_final.shape}") # (4, 4)
Example 22: Batched Operations
import torch
from qconduit.batched import (
BatchedState,
apply_circuit_to_batched_states,
evaluate_expectations_for_params_batched,
)
from qconduit.variational import HardwareEfficientAnsatz
from qconduit.operators import PauliSum
from qconduit.circuit import QuantumCircuit
# Create a batch of states
states = torch.randn(10, 4, dtype=torch.complex128) # 10 states, 2 qubits each
states = states / torch.linalg.norm(states, dim=1, keepdim=True)
batched = BatchedState(states, n_qubits=2)
# Apply circuit to all states at once
circuit = QuantumCircuit(n_qubits=2)
circuit.add_gate("H", [0])
circuit.add_gate("CNOT", [0, 1])
result = apply_circuit_to_batched_states(circuit, batched)
print(f"Result shape: {result.states.shape}") # (10, 4)
# Batch evaluate many parameter sets
ansatz = HardwareEfficientAnsatz(num_qubits=2, num_layers=2)
params_batch = torch.randn(100, ansatz.num_parameters) # 100 parameter vectors
H = PauliSum.from_label("ZZ")
energies = evaluate_expectations_for_params_batched(ansatz, params_batch, H)
print(f"Energies shape: {energies.shape}") # (100,)
Example 23: Circuit I/O
from qconduit.io import (
parse_qasm_string,
parse_qasm_file,
export_circuit_to_qasm,
circuit_to_json,
json_to_circuit,
dump_json_circuit,
load_json_circuit,
)
from qconduit.circuit import QuantumCircuit
# Parse OpenQASM 2.0
qasm = """OPENQASM 2.0;
qreg q[2];
h q[0];
cx q[0],q[1];
"""
circuit = parse_qasm_string(qasm)
# Export to QASM
qasm_output = export_circuit_to_qasm(circuit)
print(qasm_output)
# JSON IR roundtrip
json_data = circuit_to_json(circuit)
circuit_restored = json_to_circuit(json_data)
# File I/O
dump_json_circuit(circuit, "circuit.json")
circuit_loaded = load_json_circuit("circuit.json")
# Parse from file
circuit_from_file = parse_qasm_file("circuit.qasm")
Example 24: PyTorch nn.Module Integration
import torch
import torch.nn as nn
from qconduit.torch import QuantumModule
from qconduit.variational import HardwareEfficientAnsatz
from qconduit.operators import PauliSum, PauliTerm
# Create a quantum module
ansatz = HardwareEfficientAnsatz(num_qubits=2, num_layers=2)
H = PauliSum.from_terms([PauliTerm(1.0, ("Z", "Z"))])
module = QuantumModule(
ansatz=ansatz,
hamiltonian=H,
gradient_method="parameter_shift" # or "autograd"
)
# Use in PyTorch training loop
optimizer = torch.optim.Adam(module.parameters(), lr=0.1)
for epoch in range(100):
optimizer.zero_grad()
energy = module() # Forward pass
energy.backward() # Backward pass (uses parameter-shift rule)
optimizer.step()
print(f"Epoch {epoch}: Energy = {energy.item():.6f}")
# Integrate with classical neural networks
class HybridModel(nn.Module):
def __init__(self):
super().__init__()
self.classical = nn.Linear(10, 2)
self.quantum = QuantumModule(ansatz, H)
self.head = nn.Linear(1, 1)
def forward(self, x):
classical_out = self.classical(x)
quantum_energy = self.quantum()
return self.head(torch.cat([classical_out, quantum_energy.unsqueeze(0)]))
Example 25: Visualization
from qconduit.viz import (
print_circuit,
to_text,
bloch_coords_from_statevector,
plot_bloch_vector,
circuit_summary,
print_circuit_summary,
)
from qconduit.circuit import QuantumCircuit
import qconduit as qc
# Text circuit drawing
circuit = QuantumCircuit(n_qubits=3)
circuit.add_gate("H", [0])
circuit.add_gate("CNOT", [0, 1])
circuit.add_gate("RX", [2], [0.5])
print_circuit(circuit)
# Output:
# q0: [H]─●───────
# q1: ────⊕───────
# q2: ───────[RX(0.5)]
# Bloch sphere coordinates
state = qc.zero_state(n_qubits=2)
state = qc.apply_gate(state, qc.H(), qubit=0, n_qubits=2)
coords = bloch_coords_from_statevector(state, qubit_index=0, n_qubits=2)
print(f"Bloch coordinates (x, y, z): {coords}") # (1.0, 0.0, 0.0) for |+⟩
# Plot Bloch vector (requires matplotlib)
try:
plot_bloch_vector(coords)
except RuntimeError:
print("matplotlib not available")
# Circuit summary
summary = circuit_summary(circuit)
print_circuit_summary(circuit)
# Output:
# Circuit Summary
# ==================================================
# Qubits: 3
# Total Gates: 3
# Estimated Depth: 2
# T-count: 0
# Clifford-count: 2
# Parameters: 1
# Uses Parametric Gates: True
Architecture
Design Principles
Quantum Conduit is built on three core principles:
-
PyTorch-Native: All operations integrate seamlessly with PyTorch's autograd system, enabling end-to-end differentiation of quantum-classical hybrid models.
-
Minimal Abstractions: The library provides "plumbing" rather than high-level abstractions, giving you direct control over quantum states and operations.
-
Batch-First: All operations support batched inputs, enabling efficient training of quantum models on classical data.
Library Structure
qconduit/
├── core/ # Core abstractions (Device, QuantumModule)
├── backend/ # Statevector and density matrix backends
├── gates/ # Standard quantum gates
├── circuit/ # Circuit IR (GateOp, QuantumCircuit)
├── layers/ # Parametric ansätze and hybrid blocks
├── algorithms/ # Quantum algorithms (VQE, QAOA)
├── operators/ # Pauli operators and expectations
├── grad/ # Gradient computation (parameter-shift)
├── noise/ # Noise models and quantum channels
├── channels/ # Quantum channels API with Kraus operators
├── batched/ # Batched operations for efficient training
├── io/ # OpenQASM 2.0 and JSON IR import/export
├── torch/ # PyTorch nn.Module integration
├── viz/ # Visualization and circuit analysis
├── diagnostics/ # State validation and debugging tools
├── training/ # Training loops and utilities
├── sampling/ # Bitstring sampling and analysis
├── time_evolution/ # Trotterization and time evolution
├── evolution/ # Alternative evolution API (exact + enhanced Trotter)
├── optim/ # Optimizer factory utilities
├── experiments/ # Parameter sweep utilities
├── exact/ # Exact diagonalization for small systems
├── models/ # Pre-built quantum many-body models
├── adiabatic/ # Adiabatic quantum computing
├── fermion/ # Fermion-to-qubit mappings
├── measurement/ # Measurement and quantum state tomography
├── variational/ # Variational algorithm scaffolding
└── transpile/ # Gate decomposition and circuit transpilation
Key Components
- Device Abstraction: Unified interface for CPU and CUDA operations
- Statevector Backend: Efficient pure-state simulation with O(2^n) memory
- Density Matrix Backend: Mixed-state simulation for noise (O(4^n) memory, small systems)
- Gate Library: Standard gates with gradient-preserving implementations
- Module System:
QuantumModulebase class compatible with PyTorch's module system - Circuit IR: Structured circuit representation with simulation and visualization
- Diagnostics: State validation, fidelity computation, and debug mode integration
- Training Infrastructure: Complete training loops with callbacks and history tracking
- Sampling: Bitstring sampling and probability distribution analysis
- Time Evolution: Trotterization for Hamiltonian simulation
- Evolution: Alternative evolution API with exact and enhanced Trotter methods
- Optimizers: Factory utilities for optimizer creation
- Experiments: Parameter sweep utilities for algorithm exploration
- Exact Solvers: Exact diagonalization for benchmarking and validation
- Pre-built Models: Standard quantum many-body models (spin chains, chemistry)
- Adiabatic Evolution: Adiabatic quantum computing with configurable schedules
- Fermion-to-Qubit Mappings: Jordan-Wigner and Bravyi-Kitaev transforms
- Measurement/Tomography: Quantum state tomography and Pauli expectation measurements
- Variational Scaffolding: High-level APIs for VQE and QAOA algorithms
- Transpilation: Gate decomposition and basis set conversion for hardware
- Quantum Channels: Textbook quantum noise channels with Kraus operators, channel composition, and circuit integration
- Batched Operations: Efficient batch processing for multiple circuits, parameter sets, and states
- I/O Support: OpenQASM 2.0 parser/exporter and JSON IR for circuit interchange
- PyTorch Integration: Native
nn.Modulewrapper with parameter-shift gradients - Visualization: Circuit text drawing, Bloch sphere coordinates, and circuit summary utilities
Examples
The examples/ directory contains complete, runnable examples:
vqe_h2.py: Variational Quantum Eigensolver for finding ground-state energyhybrid_classifier.py: Hybrid quantum-classical neural network for classification
Run examples directly:
python examples/vqe_h2.py
python examples/hybrid_classifier.py
API Reference
Core Abstractions
import qconduit as qc
# Device management
device = qc.device("sv_cpu") # or "sv_cuda"
default = qc.default_device()
# Quantum module base class
class MyQuantumLayer(qc.QuantumModule):
def forward(self, x):
# Your quantum operations
pass
Backend Operations
# Statevector operations
state = qc.zero_state(n_qubits=2, batch_shape=(10,)) # Batched states
# Gate application
gate = qc.H()
state = qc.apply_gate(state, gate, qubit=0, n_qubits=2)
state = qc.apply_two_qubit_gate(state, qc.CNOT(), qubit1=0, qubit2=1, n_qubits=2)
# Measurements
probs = qc.measure_probs(state, n_qubits=2)
z_exp = qc.measure_expectation_z(state, qubit=0, n_qubits=2)
# Density matrix operations (for noise modeling)
rho = qc.zero_dm_state(n_qubits=2) # Create |00><00|
rho = qc.dm_from_statevector(state) # Convert statevector to density matrix
# Apply Kraus operators (for noise channels)
kraus_ops = (E0, E1, E2) # Tuple of 2x2 matrices
rho = qc.apply_kraus_single_qubit(rho, kraus_ops, qubit=0, n_qubits=2)
# Density matrix measurements
probs_dm = qc.measure_probs_dm(rho)
z_exp_dm = qc.measure_expectation_z_dm(rho, qubit=0, n_qubits=2)
Gates
# Single-qubit gates
I_gate = qc.I()
X_gate = qc.X()
Y_gate = qc.Y()
Z_gate = qc.Z()
H_gate = qc.H()
S_gate = qc.S()
T_gate = qc.T()
# Parametric gates (preserve gradients)
theta = torch.tensor(0.5, requires_grad=True)
RX_gate = qc.RX(theta)
RY_gate = qc.RY(theta)
RZ_gate = qc.RZ(theta)
# Two-qubit gates
cnot = qc.CNOT(control_first=True)
# Utility
is_unitary = qc.is_unitary(gate_matrix)
Layers and Ansätze
from qconduit.layers import HardwareEfficientAnsatz, QuantumBlock, ParametricAnsatz
# Hardware-efficient ansatz
ansatz = HardwareEfficientAnsatz(n_qubits=4, depth=3)
params = torch.randn(ansatz.num_parameters)
state = ansatz(params) # Forward pass
# Hybrid quantum-classical block
quantum_block = QuantumBlock(n_qubits=2, depth=2, in_features=10)
classical_features = torch.randn(32, 10) # Batch of 32
quantum_features = quantum_block(classical_features) # Shape: (32, 2)
# Custom ansatz
class MyAnsatz(ParametricAnsatz):
def forward(self, params):
state = qc.zero_state(n_qubits=self.n_qubits)
# Your custom circuit
return state
Algorithms
from qconduit.algorithms import VQE
from qconduit.operators import PauliTerm, PauliSum
# VQE with diagonal Hamiltonian
hamiltonian_diag = torch.tensor([0.0, 0.5, 0.5, 1.0])
vqe = VQE(ansatz=ansatz, hamiltonian=hamiltonian_diag)
# VQE with Pauli-sum Hamiltonian
pauli_ham = PauliSum.from_terms([
PauliTerm(1.0, ("Z", "I")),
PauliTerm(0.5, ("X", "X")),
])
vqe = VQE(ansatz=ansatz, hamiltonian=pauli_ham)
# Compute energy
energy = vqe.energy(params) # Differentiable
Operators
from qconduit.operators import PauliTerm, PauliSum, expectation_pauli_term, expectation_pauli_sum
# Create Pauli terms
term1 = PauliTerm(1.0, ("Z", "I"))
term2 = PauliTerm(0.5, ("X", "Y"))
# Create Pauli-sum Hamiltonian
hamiltonian = PauliSum.from_terms([term1, term2])
hamiltonian = hamiltonian.simplify() # Combine like terms
# Compute expectations
exp_val = expectation_pauli_term(state, term1)
total_exp = expectation_pauli_sum(state, hamiltonian)
# Convert to matrix (small systems only)
matrix = hamiltonian.to_matrix() # (2^n, 2^n) complex tensor
Gradients
from qconduit.grad import param_shift_energy
# Parameter-shift gradients (alternative to autograd)
params = torch.tensor([0.1, 0.2], requires_grad=True)
energy = param_shift_energy(ansatz, hamiltonian, params)
energy.backward() # Gradients computed via parameter-shift rule
Circuit IR
from qconduit.circuit import QuantumCircuit, GateOp
# Create a circuit
circuit = QuantumCircuit(n_qubits=3)
# Add gates
circuit.add_gate("H", [0]) # Hadamard on qubit 0
circuit.add_gate("CNOT", [0, 1]) # CNOT with control=0, target=1
circuit.add_gate("RX", [2], params=[0.5]) # Parametric rotation
# Circuit properties
n_gates = circuit.num_gates() # Number of gates
gate_counts = circuit.gate_counts() # Dict: {"H": 1, "CNOT": 1, "RX": 1}
depth = circuit.depth() # Circuit depth (parallel gate scheduling)
# Simulate circuit
state = circuit.simulate_state() # Returns statevector
# Visualize circuit
diagram = circuit.to_text_diagram()
print(diagram)
# q0: ─H──●───────
# q1: ────⊕───────
# q2: ────────R───
# Copy circuit
circuit_copy = circuit.copy()
# Access operations
for op in circuit.ops:
print(f"{op.name} on qubits {op.qubits} with params {op.params}")
Diagnostics
import qconduit as qc
from qconduit.diagnostics import (
state_norm,
assert_normalized,
is_hermitian,
assert_hermitian,
fidelity,
bloch_vector,
is_debug_enabled,
set_debug_enabled,
debug_context,
)
# State validation
state = qc.zero_state(n_qubits=2)
norm = state_norm(state) # Compute L2 norm
assert_normalized(state, atol=1e-5) # Assert norm ≈ 1
# Matrix validation
matrix = qc.H() # Get a gate matrix
is_herm = is_hermitian(matrix) # Check if Hermitian
assert_hermitian(matrix, atol=1e-6) # Assert Hermitian
# Fidelity computation
state1 = qc.zero_state(n_qubits=1)
state2 = qc.apply_gate(state1, qc.H(), qubit=0, n_qubits=1)
f = fidelity(state1, state2) # |<state1|state2>|²
# Bloch vector (single-qubit only)
bloch = bloch_vector(state2) # Returns (x, y, z) components
# Debug mode management
is_enabled = is_debug_enabled() # Check current status
set_debug_enabled(True) # Enable globally
# Context manager for temporary debug mode
with debug_context(True):
# Debug checks enabled here
state = qc.apply_gate(state, qc.H(), qubit=0, n_qubits=1)
# Debug mode restored to previous state
# Environment variable: QCONDUIT_DEBUG=1 enables debug mode at startup
QAOA
from qconduit.algorithms import QAOAAnsatz, ising_maxcut_hamiltonian, Edge
# Define graph edges
edges = [Edge(0, 1, weight=1.0), Edge(1, 2, weight=0.5)]
# Or use tuples: edges = [(0, 1), (1, 2)]
# Build MaxCut Ising Hamiltonian
hamiltonian = ising_maxcut_hamiltonian(
num_nodes=3,
edges=edges,
include_constant=True # Include constant term in Hamiltonian
)
# Create QAOA ansatz (p is the number of QAOA layers)
qaoa = QAOAAnsatz(n_qubits=3, problem_hamiltonian=hamiltonian, p=2)
params = torch.randn(qaoa.num_parameters)
state = qaoa(params) # Forward pass
# Use with VQE for optimization
from qconduit.algorithms import VQE
vqe = VQE(ansatz=qaoa, hamiltonian=hamiltonian)
energy = vqe.energy(params)
Training
from qconduit.training import (
VQETrainer,
TrainingHistory,
TrainingCallback,
TrainingStepInfo,
EarlyStoppingConfig,
)
# Create trainer
trainer = VQETrainer(vqe, optimizer=optimizer)
# Define custom callback
class MyCallback(TrainingCallback):
def __call__(self, info: TrainingStepInfo):
# Access step, epoch, energy, loss, grad_norm, param_norm
if info.step % 10 == 0:
print(f"Step {info.step}: energy={info.energy:.6f}")
# Configure early stopping
early_stop = EarlyStoppingConfig(
patience=10, # Stop if no improvement for 10 steps
min_delta=1e-6, # Minimum change to count as improvement
)
# Train with callbacks and early stopping
history = trainer.train(
params,
max_steps=100,
callbacks=[MyCallback()],
early_stopping=early_stop,
)
# Access training history
best_energy = history.best_energy()
final_energy = history.final_energy()
num_steps = history.num_steps()
# Access individual steps
for step_info in history.steps:
print(f"Step {step_info.step}: energy={step_info.energy}")
Sampling
from qconduit.sampling import (
sample_bitstrings_state,
sample_bitstrings_dm,
sample_bitstrings_circuit,
sample_from_probs,
bitstring_counts,
counts_to_probs,
kl_divergence,
marginalize_probs,
)
# Sample from statevector
samples = sample_bitstrings_state(
state, n_qubits=3, n_shots=1000, qubits=None # None = all qubits
)
# Sample from density matrix
samples_dm = sample_bitstrings_dm(rho, n_qubits=3, n_shots=1000)
# Sample from circuit
from qconduit.circuit import QuantumCircuit
circuit = QuantumCircuit(n_qubits=2)
circuit.add_gate("H", [0])
circuit.add_gate("CNOT", [0, 1])
samples_circuit = sample_bitstrings_circuit(circuit, n_shots=1000)
# Sample from probability distribution
probs = qc.measure_probs(state, n_qubits=3)
samples = sample_from_probs(probs, n_qubits=3, n_shots=1000)
# Count bitstring occurrences
counts = bitstring_counts(samples)
# Returns dict: {"000": 250, "001": 250, ...}
# Convert counts to probabilities
probs_from_counts = counts_to_probs(counts)
# Compute KL divergence between distributions
# Convert probability tensors to dictionaries
probs1_dict = {format(i, f'0{3}b'): float(probs1[i].item()) for i in range(len(probs1))}
probs2_dict = {format(i, f'0{3}b'): float(probs2[i].item()) for i in range(len(probs2))}
kl = kl_divergence(probs1_dict, probs2_dict)
# Marginalize probabilities (sum over some qubits)
marginal = marginalize_probs(probs, n_qubits=3, qubits_to_keep=[0, 1]) # Keep qubits 0,1
Time Evolution
from qconduit.time_evolution import (
time_evolve_state,
trotter_step_pauli_sum,
build_trotter_step_circuit,
build_trotter_circuit,
OrderLiteral,
)
# Evolve state under Hamiltonian
evolved_state = time_evolve_state(
state,
hamiltonian, # PauliSum
t=0.5, # Total evolution time
n_steps=10, # Number of Trotter steps
n_qubits=2, # Number of qubits
)
# Single Trotter step
state_after_step = trotter_step_pauli_sum(
state, hamiltonian, dt=0.05, n_qubits=2, order=1 # order: 1 or 2
)
# Build Trotter circuit (for visualization or reuse)
circuit = build_trotter_circuit(
hamiltonian,
t=0.5,
n_steps=10,
n_qubits=2,
order=1, # or OrderLiteral.FIRST (1 or 2)
)
# Build single Trotter step circuit
step_circuit = build_trotter_step_circuit(
hamiltonian, dt=0.05, n_qubits=2, order=1
)
Optimizers
from qconduit.optim import OptimConfig, create_optimizer
# Create optimizer configuration
config = OptimConfig(
name="adam", # Optimizer name: "adam", "sgd", "rmsprop", etc.
lr=0.01, # Learning rate
weight_decay=0.0, # Weight decay (L2 regularization)
# Additional optimizer-specific kwargs
betas=(0.9, 0.999), # For Adam
)
# Create optimizer from parameters
params = [torch.nn.Parameter(torch.randn(5))]
optimizer = create_optimizer(config, params)
# Use with training
for step in range(100):
optimizer.zero_grad()
loss = compute_loss(params)
loss.backward()
optimizer.step()
Experiments
from qconduit.experiments import (
run_1d_sweep,
run_2d_sweep,
sweep_vqe_1d,
sweep_vqe_2d,
SweepResult1D,
SweepResult2D,
)
# Generic 1D parameter sweep
def objective(params):
return params[0] ** 2
result_1d = run_1d_sweep(
objective,
points=torch.linspace(0, 1, 50),
base_params=torch.tensor([0.0]), # Base parameter tensor
index=0, # Index of parameter to sweep
metadata={"param_name": "x", "x_label": "Parameter"},
)
# Access results
print(f"Best value: {result_1d.values.min()}")
print(f"Best point: {result_1d.points[result_1d.values.argmin()]}")
# VQE-specific 1D sweep
vqe_result = sweep_vqe_1d(
vqe,
points=torch.linspace(0, 2 * torch.pi, 50),
base_params=params_template, # Template parameter tensor
index=0, # Index of parameter to sweep
)
# 2D parameter sweep
def objective_2d(params):
return params[0] ** 2 + params[1] ** 2
result_2d = run_2d_sweep(
objective_2d,
x_points=torch.linspace(0, 1, 20),
y_points=torch.linspace(0, 1, 20),
metadata={"x_label": "X", "y_label": "Y"},
)
# Access 2D results
print(f"Values shape: {result_2d.values.shape}") # (20, 20)
print(f"Best value: {result_2d.values.min()}")
Exact Solvers
from qconduit.exact import (
paulisum_to_dense,
exact_eigensystem,
exact_ground_state,
)
# Convert PauliSum to dense matrix
dense_matrix = paulisum_to_dense(
hamiltonian, # PauliSum
num_qubits=3,
device=None, # Optional, defaults to default_device()
dtype=torch.complex128, # Complex dtype
)
# Compute full eigensystem
eigenvalues, eigenvectors = exact_eigensystem(
hamiltonian,
num_qubits=3,
k=None, # Reserved for future use (subset of eigenpairs)
device=None,
dtype=torch.complex128,
)
# eigenvalues: shape (2**n_qubits,)
# eigenvectors: shape (2**n_qubits, 2**n_qubits), columns are eigenvectors
# Get just the ground state
ground_energy, ground_state = exact_ground_state(
hamiltonian,
num_qubits=3,
device=None,
dtype=torch.complex128,
)
# ground_energy: scalar tensor
# ground_state: shape (2**n_qubits,)
Pre-built Models
from qconduit.models import (
transverse_field_ising_chain,
heisenberg_xxz_chain,
ising_zz_chain,
two_qubit_generic_chemistry_like,
diagonal_z_field,
)
# Transverse field Ising model (TFIM)
# H = -J * sum_{<i,j>} Z_i Z_j - h * sum_i X_i
tfim = transverse_field_ising_chain(
num_sites=4, # Number of spins
j_coupling=1.0, # ZZ coupling strength
h_field=0.5, # Transverse field strength
periodic=True, # Periodic boundary conditions
)
# Heisenberg XXZ chain
# H = J * sum_{<i,j>} (X_i X_j + Y_i Y_j + Δ Z_i Z_j)
heisenberg = heisenberg_xxz_chain(
num_sites=3,
j_coupling=1.0, # Overall coupling
delta=0.5, # Anisotropy parameter
periodic=False, # Open chain
)
# Ising ZZ chain (no transverse field)
# H = -J * sum_{<i,j>} Z_i Z_j
ising = ising_zz_chain(
num_sites=4,
j_coupling=1.0,
periodic=True,
)
# Two-qubit chemistry-like model
# Generic two-qubit Hamiltonian for chemistry applications
chemistry_ham = two_qubit_generic_chemistry_like(
c_i=0.0, # Identity coefficient
c_z0=0.5, # Z⊗I coefficient
c_z1=0.3, # I⊗Z coefficient
c_z0z1=0.1, # Z⊗Z coefficient
c_xx=0.0, # X⊗X coefficient
c_yy=0.0 # Y⊗Y coefficient
)
# Diagonal Z field
# H = sum_i h_i Z_i where h_i are the local field strengths
z_field = diagonal_z_field(
num_qubits=3,
local_fields=[0.5, 0.5, 0.5], # Field strength for each qubit
)
Adiabatic Evolution
from qconduit.adiabatic import (
ScheduleFn,
linear_schedule,
polynomial_schedule,
sample_schedule,
AdiabaticConfig,
interpolate_paulisum,
adiabatic_evolve_state,
build_adiabatic_circuit,
build_x_mixer_hamiltonian,
adiabatic_x_mixer_to_problem_state,
)
# Create schedules
schedule_linear = linear_schedule(num_steps=20) # Linear s(t) = t/T
schedule_poly = polynomial_schedule(num_steps=20, power=2) # Polynomial s(t) = (t/T)^p
# Custom schedule function
def custom_schedule(num_steps: int) -> torch.Tensor:
# Return 1D tensor of shape (num_steps,) with values in [0, 1]
return torch.linspace(0, 1, num_steps) ** 0.5
# Build X mixer Hamiltonian: H_mixer = -sum_i X_i
h_mixer = build_x_mixer_hamiltonian(num_qubits=3)
# Interpolate between two Hamiltonians
h_interpolated = interpolate_paulisum(
h_initial, # Initial Hamiltonian
h_final, # Final Hamiltonian
s=0.5, # Interpolation parameter in [0, 1]
)
# Configure adiabatic evolution
config = AdiabaticConfig(
total_time=1.0, # Total evolution time
num_steps=20, # Number of discrete steps
schedule=linear_schedule(20), # Schedule function
trotter_steps_per_interval=5, # Trotter steps per interval
)
# Evolve state adiabatically
final_state = adiabatic_evolve_state(
initial_state, # Initial statevector
h_mixer, # Initial (mixer) Hamiltonian
h_problem, # Final (problem) Hamiltonian
config # AdiabaticConfig
)
# Build adiabatic circuit
circuit = build_adiabatic_circuit(
n_qubits=3,
h_mixer=h_mixer,
h_problem=h_problem,
config=config
)
# Prepare ground state of X mixer (|+⟩^⊗n)
initial_state = qc.zero_state(n_qubits=3)
for i in range(3):
initial_state = qc.apply_gate(initial_state, qc.H(), qubit=i, n_qubits=3)
ground_state = adiabatic_x_mixer_to_problem_state(
initial_state,
h_problem,
config
)
Fermion-to-Qubit Mappings
from qconduit.fermion import (
FermionOpSymbol,
FermionTerm,
FermionOperator,
jordan_wigner,
bravyi_kitaev,
)
# Create fermionic operators
# FermionOpSymbol: (mode_index, op_type) where op_type is "+" (creation) or "-" (annihilation)
term1 = FermionTerm(
coeff=1.0,
operators=((0, "+"), (1, "-")) # a^†_0 a_1
)
term2 = FermionTerm(
coeff=0.5,
operators=((1, "+"), (0, "-"), (2, "+"), (2, "-")) # 0.5 * a^†_1 a_0 a^†_2 a_2
)
# Build FermionOperator (sum of terms)
fermion_op = FermionOperator([term1, term2])
# Map to qubits using Jordan-Wigner transform
jw_hamiltonian = jordan_wigner(
fermion_op,
n_spin_orbitals=3, # Number of fermionic modes (spin-orbitals)
)
# Map to qubits using Bravyi-Kitaev transform
bk_hamiltonian = bravyi_kitaev(
fermion_op,
n_spin_orbitals=3,
)
# Both return PauliSum that can be used with VQE, exact diagonalization, etc.
Evolution Module (Alternative API)
from qconduit.evolution import (
exact_time_evolution_statevector,
TrotterOrder,
TrotterSchedule,
evolve_state_trotter,
build_trotter_step_circuit,
build_trotter_circuit,
)
# Exact time evolution (for small systems, uses dense matrix exponentiation)
evolved = exact_time_evolution_statevector(
state, # Initial statevector
hamiltonian, # PauliSum Hamiltonian
time=0.5, # Evolution time
device=None, # Optional device
)
# Enhanced Trotter evolution with schedule
schedule = TrotterSchedule(
num_steps=10, # Number of Trotter steps
total_time=0.5, # Total evolution time
order=1, # TrotterOrder.FIRST (1) or TrotterOrder.SECOND (2)
)
evolved_trotter = evolve_state_trotter(
state,
hamiltonian,
schedule,
)
# Build Trotter circuits
step_circuit = build_trotter_step_circuit(hamiltonian, schedule.step_time, schedule.order, num_qubits=2)
full_circuit = build_trotter_circuit(hamiltonian, schedule, num_qubits=2)
Measurement and Quantum State Tomography
from qconduit.measurement import (
# Sampling utilities
basis_probabilities_from_statevector,
sample_bitstrings_from_probabilities,
sample_bitstrings_from_statevector,
bitstring_counts,
empirical_probabilities_from_bitstrings,
estimate_pauli_z_expectation_from_samples,
# Pauli expectation values
pauli_matrix_from_label,
pauli_expectation_from_statevector,
single_qubit_pauli_expectations_from_statevector,
two_qubit_pauli_expectations_from_statevector,
# State tomography
reconstruct_single_qubit_density_from_pauli,
reconstruct_two_qubit_density_from_pauli,
)
# Get basis probabilities
probs = basis_probabilities_from_statevector(state)
# Sample bitstrings
samples = sample_bitstrings_from_statevector(state, n_shots=1000)
# Compute Pauli expectation values
ex_x = pauli_expectation_from_statevector(state, "X")
ex_zz = pauli_expectation_from_statevector(state, "ZZ")
# Single-qubit tomography
ex_x, ex_y, ex_z = single_qubit_pauli_expectations_from_statevector(state)
rho = reconstruct_single_qubit_density_from_pauli(ex_x, ex_y, ex_z)
# Two-qubit tomography
pauli_expectations = two_qubit_pauli_expectations_from_statevector(state_2q)
rho_2q = reconstruct_two_qubit_density_from_pauli(pauli_expectations)
# Estimate expectation from samples
z_expectation, std_error = estimate_pauli_z_expectation_from_samples(samples, qubit_index=0)
Variational Algorithm Scaffolding
from qconduit.variational import (
VariationalAnsatz,
HardwareEfficientAnsatz,
LayeredEntanglerAnsatz,
QAOAAnsatz,
run_vqe,
run_qaoa,
VQEResult,
QAOAResult,
evaluate_expectation_value,
)
# High-level VQE API
from qconduit.variational import HardwareEfficientAnsatz
import torch
ansatz = HardwareEfficientAnsatz(num_qubits=3, num_layers=2)
initial_params = torch.randn(ansatz.num_parameters)
result = run_vqe(
hamiltonian=hamiltonian,
ansatz=ansatz,
initial_params=initial_params,
optimizer_name="adam", # or "sgd"
max_iterations=200,
learning_rate=0.1,
tol_rel=1e-6, # Relative tolerance for convergence
device=None,
)
# Access results
print(f"Optimal energy: {result.optimal_value}")
print(f"Optimal parameters: {result.optimal_params}")
print(f"Converged: {result.converged}")
print(f"Number of iterations: {result.num_iterations}")
# High-level QAOA API
qaoa_result = run_qaoa(
cost_hamiltonian=hamiltonian,
num_qubits=3,
depth=2,
initial_params=None,
optimizer_name="adam",
max_iterations=200,
learning_rate=0.05,
tol_rel=1e-6,
device=None,
)
# Evaluate expectation value for custom ansatz
ansatz = HardwareEfficientAnsatz(num_qubits=3, num_layers=2)
params = torch.randn(ansatz.num_parameters)
energy = evaluate_expectation_value(ansatz, params, hamiltonian)
Circuit Transpilation
from qconduit.transpile import (
# Gate decomposition
decompose_h_to_rz_rx_rz,
decompose_x_to_rx,
decompose_y_to_ry,
decompose_z_to_rz,
decompose_rz_to_clifford_t,
decompose_gate_to_basis,
# Basis transpilation
transpile_to_basis,
transpile_to_rx_rz_cx_basis,
transpile_to_clifford_t,
# Circuit analysis
GateCountSummary,
summarize_gate_counts,
estimate_circuit_depth,
)
# Decompose individual gates
circuit = QuantumCircuit(n_qubits=2)
circuit.add_gate("H", [0])
decompose_h_to_rz_rx_rz(circuit, qubit=0) # Modifies circuit in-place
# Transpile to specific basis
rx_rz_cx = transpile_to_rx_rz_cx_basis(circuit) # Returns new circuit
clifford_t = transpile_to_clifford_t(circuit) # Returns new circuit
# Transpile to custom basis
custom_basis = transpile_to_basis(circuit, basis_gates=["RX", "RZ", "CNOT"])
# Analyze circuits
summary = summarize_gate_counts(circuit)
print(f"Gate counts: {summary.counts}")
print(f"T-count: {summary.t_count}")
print(f"Clifford count: {summary.clifford_count}")
print(f"Total gates: {summary.total_gates}")
# Estimate depth
depth = estimate_circuit_depth(circuit)
print(f"Circuit depth: {depth}")
Enhanced Kraus Channels
from qconduit.noise import (
KrausChannel,
bit_flip_channel,
phase_flip_channel,
bit_phase_flip_channel,
generalized_amplitude_damping_channel,
two_qubit_depolarizing_channel,
to_density_matrix,
apply_kraus_channel_to_density_matrix,
apply_kraus_channel_to_statevector,
compose_kraus_channels,
)
# Create standard noise channels
bit_flip = bit_flip_channel(p=0.01) # p in [0, 1]
phase_flip = phase_flip_channel(p=0.02)
bit_phase_flip = bit_phase_flip_channel(p=0.005)
# Generalized amplitude damping (with thermal population)
amp_damp = generalized_amplitude_damping_channel(
gamma=0.1, # Damping rate
n_th=0.1, # Thermal population (0 = zero temperature)
)
# Two-qubit depolarizing channel
two_qubit_depol = two_qubit_depolarizing_channel(p=0.01)
# Create custom Kraus channel
kraus_ops = (K0, K1, K2) # Tuple of Kraus operators
custom_channel = KrausChannel(
name="custom",
kraus_ops=kraus_ops,
num_qubits=1,
)
# Compose multiple channels
combined = compose_kraus_channels([bit_flip, phase_flip])
# Apply to statevector
noisy_state = apply_kraus_channel_to_statevector(
state, channel, qubit=0, n_qubits=2
)
# Apply to density matrix
noisy_rho = apply_kraus_channel_to_density_matrix(
rho, channel, qubit=0, n_qubits=2
)
# Convert statevector to density matrix
rho = to_density_matrix(state)
# Check channel properties
is_tp = channel.is_trace_preserving() # Check trace-preserving property
Enhanced Noise Models
from qconduit.noise import (
NoiseConfig,
simulate_noisy_circuit_dm,
sample_noisy_circuit_dm,
DepolarizingChannel,
AmplitudeDampingChannel,
PhaseDampingChannel,
)
from qconduit.circuit import QuantumCircuit
# Configure per-qubit noise channels
noise_config = NoiseConfig(
per_qubit_channels={
0: DepolarizingChannel(p=0.01), # 1% depolarizing on qubit 0
1: AmplitudeDampingChannel(gamma=0.05), # Amplitude damping on qubit 1
2: PhaseDampingChannel(gamma=0.02), # Phase damping on qubit 2
}
)
# Simulate noisy circuit (returns density matrix)
circuit = QuantumCircuit(n_qubits=3)
circuit.add_gate("H", [0])
circuit.add_gate("CNOT", [0, 1])
rho = simulate_noisy_circuit_dm(
circuit,
noise=noise_config,
)
# Returns density matrix of shape (2**n_qubits, 2**n_qubits)
# Sample bitstrings from noisy circuit
samples = sample_noisy_circuit_dm(
circuit,
noise=noise_config,
n_shots=1000, # Number of samples
)
# Returns tensor of shape (n_samples, n_qubits) with bitstrings
Noise Models
from qconduit.noise import DepolarizingChannel, AmplitudeDampingChannel, PhaseDampingChannel
# Depolarizing noise
noise = DepolarizingChannel(p=0.1) # 10% depolarizing probability
rho = noise.apply_statevector(state, n_qubits=2)
# Amplitude damping
amp_damp = AmplitudeDampingChannel(gamma=0.05) # 5% damping
rho = amp_damp.apply_density_matrix(rho, n_qubits=2)
# Phase damping
phase_damp = PhaseDampingChannel(gamma=0.03) # 3% dephasing
rho = phase_damp.apply_density_matrix(rho, n_qubits=2)
Density Matrix Backend
from qconduit.backend.density_matrix import (
zero_dm_state,
dm_from_statevector,
measure_probs_dm,
measure_expectation_z_dm,
)
# Create density matrix
rho = zero_dm_state(n_qubits=2)
# Or convert from statevector
rho = dm_from_statevector(state)
# Measurements
probs = measure_probs_dm(rho)
z_exp = measure_expectation_z_dm(rho, qubit=0, n_qubits=2)
Use Cases
When to Use Circuit IR vs Direct Gate Application
Use Circuit IR (QuantumCircuit) when:
- You need to visualize circuits with
to_text_diagram() - You want to analyze circuit properties (depth, gate counts)
- You're building circuits dynamically or from external specifications
- You need to copy or modify circuits before simulation
- You're working with circuit optimization or compilation
Use direct gate application when:
- You need maximum performance (no IR overhead)
- You're building circuits statically in code
- You want direct control over state manipulation
- You're working with batched operations (Circuit IR doesn't support batching yet)
Example: Circuit IR for visualization
from qconduit.circuit import QuantumCircuit
circuit = QuantumCircuit(n_qubits=3)
circuit.add_gate("H", [0])
circuit.add_gate("CNOT", [0, 1])
circuit.add_gate("CNOT", [1, 2])
print(circuit.to_text_diagram())
# Great for debugging and documentation!
Example: Direct gates for performance
import qconduit as qc
# More efficient for tight loops
state = qc.zero_state(n_qubits=3, batch_shape=(100,)) # Batched
for i in range(100):
state = qc.apply_gate(state, qc.H(), qubit=0, n_qubits=3)
Debug Mode Best Practices
Enable debug mode during development:
import qconduit as qc
# Global enable for development
qc.set_debug_enabled(True)
# Or use environment variable
# QCONDUIT_DEBUG=1 python your_script.py
Use context managers for specific sections:
# Only enable for critical sections
with qc.debug_context(True):
# Critical quantum operations
state = complex_quantum_operation(state)
# Automatically disabled after context
Debug mode automatically:
- Validates state normalization after gate applications
- Helps catch bugs early in development
- Has minimal overhead when disabled (production-ready)
Diagnostics for Validation and Debugging
State validation:
from qconduit.diagnostics import assert_normalized, state_norm
# Validate states in test suites
def test_my_quantum_function():
state = my_quantum_function()
assert_normalized(state) # Raises if not normalized
assert state_norm(state).item() == pytest.approx(1.0)
Fidelity for algorithm verification:
from qconduit.diagnostics import fidelity
# Compare expected vs actual states
expected = create_expected_state()
actual = run_algorithm()
f = fidelity(expected, actual)
assert f > 0.99 # High fidelity means correct implementation
Bloch vector for single-qubit visualization:
from qconduit.diagnostics import bloch_vector
# Visualize single-qubit states
state = create_single_qubit_state()
bloch = bloch_vector(state) # (x, y, z) coordinates
# Use for plotting or analysis
QAOA for Optimization Problems
MaxCut optimization:
from qconduit.algorithms import QAOAAnsatz, ising_maxcut_hamiltonian, Edge, VQE
# Define your graph
edges = [Edge(0, 1), Edge(1, 2), Edge(2, 3), Edge(3, 0)] # 4-cycle
hamiltonian = ising_maxcut_hamiltonian(num_nodes=4, edges=edges)
# Use QAOA to find maximum cut
qaoa = QAOAAnsatz(n_qubits=4, problem_hamiltonian=hamiltonian, p=3)
vqe = VQE(ansatz=qaoa, hamiltonian=hamiltonian)
# Optimize to find maximum cut value
Weighted graphs:
# Use weighted edges for optimization problems
weighted_edges = [
Edge(0, 1, weight=2.0),
Edge(1, 2, weight=1.5),
Edge(2, 0, weight=1.0),
]
hamiltonian = ising_maxcut_hamiltonian(num_nodes=3, edges=weighted_edges)
Training Workflows
Complete training pipeline:
from qconduit.training import VQETrainer, TrainingCallback, EarlyStoppingConfig
# Set up training with callbacks
class CheckpointCallback(TrainingCallback):
def __call__(self, info):
if info.step % 50 == 0:
# Save checkpoint
torch.save(params, f"checkpoint_step_{info.step}.pt")
trainer = VQETrainer(vqe, optimizer=optimizer)
history = trainer.train(
params,
max_steps=500,
callbacks=[CheckpointCallback()],
early_stopping=EarlyStoppingConfig(patience=20),
)
# Analyze training
print(f"Converged in {history.num_steps()} steps")
print(f"Best energy: {history.best_energy()}")
Quantum Channels API
Textbook quantum noise channels with Kraus operators:
from qconduit.channels import (
KrausChannel,
DepolarizingChannel,
BitFlipChannel,
PhaseFlipChannel,
PhaseDampingChannel,
AmplitudeDampingChannel,
GeneralKraus,
apply_circuit_with_noise,
NoisyCircuit,
)
# Create standard noise channels
depol = DepolarizingChannel(p=0.01) # 1% depolarizing noise
bit_flip = BitFlipChannel(p=0.05) # 5% bit-flip probability
phase_flip = PhaseFlipChannel(p=0.03) # 3% phase-flip probability
phase_damp = PhaseDampingChannel(p=0.1) # Phase damping
amp_damp = AmplitudeDampingChannel(gamma=0.1) # Amplitude damping
# Apply channel to density matrix
rho = qc.zero_dm_state(n_qubits=1)
rho_noisy = depol.apply_to_density(rho)
# Apply channel to statevector (returns density matrix)
psi = qc.zero_state(n_qubits=1)
psi = qc.apply_gate(psi, qc.H(), qubit=0, n_qubits=1)
rho_mixed = depol.apply_to_statevector(psi)
# Compose channels
combined = depol.compose(bit_flip) # Apply depol, then bit_flip
# Create custom channel from Kraus operators
kraus_ops = (K0, K1, K2) # Tuple of 2x2 matrices
custom_channel = GeneralKraus(kraus_ops)
# Simulate noisy circuit
circuit = QuantumCircuit(n_qubits=2)
circuit.add_gate("H", [0])
circuit.add_gate("CNOT", [0, 1])
# Apply noise after specific gates
channel_locations = [
(0, DepolarizingChannel(p=0.01)), # Noise after gate 0 (H)
(1, DepolarizingChannel(p=0.01)), # Noise after gate 1 (CNOT)
]
rho_final = apply_circuit_with_noise(circuit, channel_locations)
# Channel properties
is_cptp = depol.is_cptp() # Check CPTP property
superop = depol.as_superoperator() # Get superoperator representation
Limitations:
tensor_extend()currently only supports single-qubit channels- For multi-qubit channels, construct full-system Kraus operators manually
Batched Operations
Efficient batch processing for multiple circuits and parameter sets:
from qconduit.batched import (
BatchedState,
apply_circuit_to_batched_states,
apply_ansatz_batch_to_state,
evaluate_expectations_batched_via_states,
evaluate_expectations_for_params_batched,
)
# Create batch of states
states = torch.randn(100, 4, dtype=torch.complex128) # 100 states, 2 qubits
states = states / torch.linalg.norm(states, dim=1, keepdim=True)
batched = BatchedState(states, n_qubits=2)
# Apply circuit to all states at once
circuit = QuantumCircuit(n_qubits=2)
circuit.add_gate("H", [0])
circuit.add_gate("CNOT", [0, 1])
result = apply_circuit_to_batched_states(circuit, batched)
print(f"Result shape: {result.states.shape}") # (100, 4)
# Batch evaluate many parameter sets (efficient for VQE)
ansatz = HardwareEfficientAnsatz(num_qubits=2, num_layers=2)
params_batch = torch.randn(1000, ansatz.num_parameters) # 1000 parameter vectors
H = PauliSum.from_label("ZZ")
energies = evaluate_expectations_for_params_batched(ansatz, params_batch, H)
print(f"Energies shape: {energies.shape}") # (1000,)
# Evaluate expectations for batch of states
expectations = evaluate_expectations_batched_via_states(H, batched)
print(f"Expectations shape: {expectations.shape}") # (100,)
# BatchedState utilities
norms = batched.norms() # Get norm of each state
normalized = batched.renormalize() # Normalize all states
individual_states = batched.unstack() # Get tuple of individual states
Performance notes:
- Automatically uses vectorized operations when memory allows (B * dim² ≤ 1e8)
- Falls back to per-row loops for large batches
- All operations are deterministic and reproducible
Circuit I/O
OpenQASM 2.0 and JSON IR import/export:
from qconduit.io import (
parse_qasm_string,
parse_qasm_file,
export_circuit_to_qasm,
circuit_to_json,
json_to_circuit,
dump_json_circuit,
load_json_circuit,
)
# Parse OpenQASM 2.0 string
qasm = """OPENQASM 2.0;
qreg q[2];
h q[0];
cx q[0],q[1];
rx(pi/4) q[0];
"""
circuit = parse_qasm_string(qasm)
# Parse from file
circuit = parse_qasm_file("circuit.qasm")
# Export to QASM
qasm_output = export_circuit_to_qasm(circuit, include_qelib=True)
print(qasm_output)
# JSON IR roundtrip
json_data = circuit_to_json(circuit, metadata={"producer": "qconduit"})
circuit_restored = json_to_circuit(json_data)
# File I/O
dump_json_circuit(circuit, "circuit.json")
circuit_loaded = load_json_circuit("circuit.json")
# Supported QASM features:
# - Standard gates: h, x, y, z, s, t, sdg, tdg
# - Rotation gates: rx, ry, rz with angle parameters
# - Controlled gates: cx (CNOT)
# - U gates: u1(λ), u2(φ,λ), u3(θ,φ,λ) - automatically decomposed
# - Multiple qreg declarations
# - Comments: // and /* */
# - Safe angle parsing: supports pi, pi/2, 3*pi/4, etc.
Supported QASM 2.0 subset:
- Standard gates:
h,x,y,z,s,t,sdg,tdg - Rotation gates:
rx(θ),ry(θ),rz(θ) - Controlled gates:
cx(CNOT) - U gates:
u1(λ),u2(φ,λ),u3(θ,φ,λ)(decomposed to RZ/RY) - Angle expressions:
pi,pi/2,3*pi/4, arithmetic operations - Multiple
qregdeclarations - Comments:
//and/* */
Unsupported features:
- OpenQASM 3.0
- Custom gate definitions
- Classical control flow (
ifstatements) - Parameterized gates (only constant angles)
PyTorch nn.Module Integration
Native PyTorch module wrapper with parameter-shift gradients:
from qconduit.torch import QuantumModule
from qconduit.variational import HardwareEfficientAnsatz
from qconduit.operators import PauliSum, PauliTerm
# Create quantum module
ansatz = HardwareEfficientAnsatz(num_qubits=2, num_layers=2)
H = PauliSum.from_terms([PauliTerm(1.0, ("Z", "Z"))])
module = QuantumModule(
ansatz=ansatz,
hamiltonian=H,
gradient_method="parameter_shift", # or "autograd"
init_params=None, # Optional initial parameters
device=None, # Optional device
)
# Use in PyTorch training loop
optimizer = torch.optim.Adam(module.parameters(), lr=0.1)
for epoch in range(100):
optimizer.zero_grad()
energy = module() # Forward pass
energy.backward() # Backward pass (uses parameter-shift rule)
optimizer.step()
print(f"Epoch {epoch}: Energy = {energy.item():.6f}")
# Integrate with classical neural networks
class HybridModel(nn.Module):
def __init__(self):
super().__init__()
self.classical = nn.Linear(10, 2)
self.quantum = QuantumModule(ansatz, H)
self.head = nn.Linear(3, 1) # 2 (classical) + 1 (quantum)
def forward(self, x):
classical_out = self.classical(x)
quantum_energy = self.quantum()
combined = torch.cat([classical_out, quantum_energy.unsqueeze(0)])
return self.head(combined)
# Parameter management
params = module.get_parameters() # Get current parameters
module.set_parameters(new_params) # Set parameters
# Device handling
module = module.to(device=torch.device("cuda")) # Move to GPU
Gradient methods:
"parameter_shift": Uses deterministic parameter-shift rule (default)"autograd": Uses PyTorch autograd if backend supports it
Visualization
Circuit drawing, Bloch sphere visualization, and circuit analysis:
from qconduit.viz import (
print_circuit,
to_text,
bloch_coords_from_statevector,
bloch_coords_from_density,
plot_bloch_vector,
plot_bloch_projections,
circuit_summary,
print_circuit_summary,
compare_circuits,
)
# Text circuit drawing
circuit = QuantumCircuit(n_qubits=3)
circuit.add_gate("H", [0])
circuit.add_gate("CNOT", [0, 1])
circuit.add_gate("RX", [2], [0.5])
print_circuit(circuit)
# Output:
# q0: [H]─●───────
# q1: ────⊕───────
# q2: ───────[RX(0.5)]
# Get circuit text representation
text_repr = to_text(circuit, max_width=80, use_ascii=False)
# Bloch sphere coordinates
state = qc.zero_state(n_qubits=2)
state = qc.apply_gate(state, qc.H(), qubit=0, n_qubits=2)
coords = bloch_coords_from_statevector(state, qubit_index=0, n_qubits=2)
print(f"Bloch coordinates (x, y, z): {coords}") # (1.0, 0.0, 0.0) for |+⟩
# From density matrix
rho = qc.dm_from_statevector(state)
coords = bloch_coords_from_density(rho[0:2, 0:2]) # Single-qubit reduced density
# Plot Bloch vector (requires matplotlib)
try:
plot_bloch_vector(coords) # 2D projection
plot_bloch_projections(coords) # 3 projections (X-Y, X-Z, Y-Z)
except RuntimeError:
print("matplotlib not available")
# Circuit summary and analysis
summary = circuit_summary(circuit)
print_circuit_summary(circuit)
# Output:
# Circuit Summary
# ==================================================
# Qubits: 3
# Total Gates: 3
# Estimated Depth: 2
# T-count: 0
# Clifford-count: 2
# Parameters: 1
# Uses Parametric Gates: True
#
# Gate Counts:
# CNOT: 1
# H: 1
# RX: 1
# Compare circuits
circuit2 = QuantumCircuit(n_qubits=3)
circuit2.add_gate("H", [0])
circuit2.add_gate("CNOT", [0, 1])
comparison = compare_circuits(circuit, circuit2)
print(f"Same unitary: {comparison['same_unitary']}")
print(f"Depth difference: {comparison['depth_diff']}")
Visualization features:
- ASCII/text circuit diagrams with gate labels
- Bloch sphere coordinate computation
- Circuit summary statistics (depth, gate counts, T-count)
- Circuit comparison utilities
- Optional matplotlib integration for plotting
Sampling for Measurement Simulation
Simulate quantum measurements:
from qconduit.sampling import sample_bitstrings_state, bitstring_counts
# Simulate 1000 measurements
samples = sample_bitstrings_state(state, n_qubits=4, n_shots=1000)
# Analyze measurement statistics
counts = bitstring_counts(samples)
most_common = max(counts.items(), key=lambda x: x[1])
print(f"Most frequent outcome: {most_common[0]} ({most_common[1]} times)")
# Compare with theoretical probabilities
probs = qc.measure_probs(state, n_qubits=4)
# Use KL divergence to measure agreement
Partial measurements:
# Sample only specific qubits
samples = sample_bitstrings_state(
state, n_qubits=4, n_shots=1000, qubits=[0, 1] # Only measure qubits 0,1
)
Time Evolution for Quantum Dynamics
Simulate quantum dynamics:
from qconduit.time_evolution import time_evolve_state
# Evolve state under a Hamiltonian
times = torch.linspace(0, 1.0, 100)
states = []
for t_val in times:
evolved = time_evolve_state(state, hamiltonian, t=t_val.item(), n_steps=20, n_qubits=2)
states.append(evolved)
# Analyze time-dependent properties
expectations = [qc.measure_expectation_z(s, qubit=0, n_qubits=2) for s in states]
Trotter circuit for hardware:
# Build circuit representation for hardware execution
circuit = build_trotter_circuit(
hamiltonian, t=1.0, n_steps=50, n_qubits=2, order=2 # Second-order Trotter
)
print(circuit.to_text_diagram()) # Visualize the circuit
Quantum State Tomography
Reconstruct density matrices from measurements:
from qconduit.measurement import (
single_qubit_pauli_expectations_from_statevector,
reconstruct_single_qubit_density_from_pauli,
two_qubit_pauli_expectations_from_statevector,
reconstruct_two_qubit_density_from_pauli,
)
# Single-qubit tomography
state = qc.zero_state(n_qubits=1)
state = qc.apply_gate(state, qc.H(), qubit=0, n_qubits=1)
# Measure Pauli expectations
ex_x, ex_y, ex_z = single_qubit_pauli_expectations_from_statevector(state)
# Reconstruct density matrix
rho_reconstructed = reconstruct_single_qubit_density_from_pauli(ex_x, ex_y, ex_z)
# Verify reconstruction fidelity
rho_actual = qc.dm_from_statevector(state)
fidelity = qc.fidelity(rho_actual, rho_reconstructed)
print(f"Reconstruction fidelity: {fidelity.item():.6f}")
# Two-qubit tomography
state_2q = qc.zero_state(n_qubits=2)
state_2q = qc.apply_gate(state_2q, qc.H(), qubit=0, n_qubits=2)
state_2q = qc.apply_two_qubit_gate(state_2q, qc.CNOT(), qubit1=0, qubit2=1, n_qubits=2)
# Get all two-qubit Pauli expectations
pauli_expectations = two_qubit_pauli_expectations_from_statevector(state_2q)
rho_2q = reconstruct_two_qubit_density_from_pauli(pauli_expectations)
Estimate expectations from samples:
from qconduit.measurement import estimate_pauli_z_expectation_from_samples
# Sample bitstrings from state
samples = sample_bitstrings_from_statevector(state, n_shots=10000)
# Estimate Pauli-Z expectation from samples
z_expectation, std_error = estimate_pauli_z_expectation_from_samples(samples, qubit_index=0)
High-Level Variational Algorithms
Run VQE with minimal code:
from qconduit.variational import run_vqe
# Simple VQE execution
from qconduit.variational import HardwareEfficientAnsatz
import torch
ansatz = HardwareEfficientAnsatz(num_qubits=4, num_layers=3)
initial_params = torch.randn(ansatz.num_parameters)
result = run_vqe(
hamiltonian=hamiltonian,
ansatz=ansatz,
initial_params=initial_params,
max_iterations=200,
)
print(f"Ground state energy: {result.optimal_value:.6f}")
print(f"Converged: {result.converged}")
Run QAOA for optimization:
from qconduit.variational import run_qaoa
# High-level QAOA API
qaoa_result = run_qaoa(
cost_hamiltonian=maxcut_hamiltonian,
num_qubits=5,
depth=2,
max_iterations=150,
)
print(f"Optimal cost: {qaoa_result.optimal_value:.6f}")
Custom ansätze with result objects:
from qconduit.variational import HardwareEfficientAnsatz, evaluate_expectation_value
ansatz = HardwareEfficientAnsatz(num_qubits=3, num_layers=2)
params = torch.randn(ansatz.num_parameters)
energy = evaluate_expectation_value(ansatz, params, hamiltonian)
Circuit Transpilation for Hardware
Transpile to hardware-native gates:
from qconduit.transpile import transpile_to_rx_rz_cx_basis, transpile_to_clifford_t
# Original circuit with various gates
circuit = QuantumCircuit(n_qubits=3)
circuit.add_gate("H", [0])
circuit.add_gate("T", [1])
circuit.add_gate("S", [2])
circuit.add_gate("CNOT", [0, 1])
# Transpile to RX, RZ, CNOT (common hardware basis)
hardware_circuit = transpile_to_rx_rz_cx_basis(circuit)
# Transpile to Clifford+T (for fault-tolerant quantum computing)
clifford_t_circuit = transpile_to_clifford_t(circuit)
# Analyze gate counts
from qconduit.transpile import summarize_gate_counts
summary = summarize_gate_counts(clifford_t_circuit)
print(f"T-count: {summary.t_count}") # Important for fault-tolerant computing
print(f"Clifford count: {summary.clifford_count}")
Gate decomposition:
from qconduit.transpile import decompose_h_to_rz_rx_rz
# Decompose Hadamard gate
circuit = QuantumCircuit(n_qubits=1)
circuit.add_gate("H", [0])
decompose_h_to_rz_rx_rz(circuit, qubit=0) # Replaces H with RZ-RX-RZ
Advanced Noise Modeling
Enhanced Kraus channels:
from qconduit.noise import (
bit_flip_channel,
phase_flip_channel,
generalized_amplitude_damping_channel,
two_qubit_depolarizing_channel,
compose_kraus_channels,
)
# Create various noise channels
bit_flip = bit_flip_channel(p=0.01)
phase_flip = phase_flip_channel(p=0.02)
# Generalized amplitude damping (with temperature)
amp_damp = generalized_amplitude_damping_channel(gamma=0.1, n_th=0.1)
# Two-qubit correlated noise
two_qubit_depol = two_qubit_depolarizing_channel(p=0.01)
# Compose multiple noise channels (compose two at a time)
combined_noise = compose_kraus_channels(bit_flip, phase_flip)
# Apply to state
from qconduit.noise import apply_kraus_channel_to_statevector
noisy_state = apply_kraus_channel_to_statevector(
state, combined_noise, qubit=0, n_qubits=2
)
Exact vs Approximate Evolution
Compare exact and Trotter evolution:
from qconduit.evolution import (
exact_time_evolution_statevector,
TrotterSchedule,
evolve_state_trotter,
)
# Exact evolution (for small systems)
evolved_exact = exact_time_evolution_statevector(
state, hamiltonian, time=0.5
)
# Trotter evolution (scales to larger systems)
schedule = TrotterSchedule(
num_steps=10,
total_time=0.5,
order=1, # First-order Trotter
)
evolved_trotter = evolve_state_trotter(
state, hamiltonian, schedule
)
# Compare fidelity
fidelity = qc.fidelity(
qc.dm_from_statevector(evolved_exact),
qc.dm_from_statevector(evolved_trotter)
)
print(f"Fidelity: {fidelity.item():.6f}")
# Use higher-order Trotter for better accuracy
schedule_2nd = TrotterSchedule(
num_steps=10,
total_time=0.5,
order=2, # Second-order symmetric Trotter
)
Parameter Sweeps for Algorithm Exploration
Explore parameter landscapes:
from qconduit.experiments import sweep_vqe_1d, sweep_vqe_2d
# 1D sweep: explore single parameter
result = sweep_vqe_1d(
vqe,
points=torch.linspace(0, 2 * torch.pi, 100),
base_params=params_template,
index=0,
)
# Find optimal parameter value
optimal_idx = result.values.argmin()
optimal_param = result.points[optimal_idx]
print(f"Optimal parameter: {optimal_param}")
# 2D sweep: explore parameter interactions
result_2d = sweep_vqe_2d(
vqe,
x_points=torch.linspace(0, 2 * torch.pi, 50),
y_points=torch.linspace(0, 2 * torch.pi, 50),
base_params=params_template,
x_index=0,
y_index=1,
)
# Visualize with matplotlib: plt.contourf(result_2d.values)
Exact Solvers for Benchmarking
Validate VQE results:
from qconduit.exact import exact_ground_state
from qconduit.algorithms import VQE
# Get exact ground state energy
exact_energy, exact_state = exact_ground_state(hamiltonian, num_qubits=4)
# Compare with VQE result
vqe = VQE(ansatz=ansatz, hamiltonian=hamiltonian)
vqe_energy = vqe.energy(optimized_params)
error = abs(vqe_energy - exact_energy)
print(f"VQE error: {error.item():.6f}")
print(f"Relative error: {(error / abs(exact_energy)).item():.2%}")
Analyze full spectrum:
from qconduit.exact import exact_eigensystem
# Get all eigenvalues and eigenvectors
eigenvalues, eigenvectors = exact_eigensystem(hamiltonian, num_qubits=3)
# Analyze energy gap
gap = eigenvalues[1] - eigenvalues[0]
print(f"Ground state energy: {eigenvalues[0].item():.6f}")
print(f"First excited state: {eigenvalues[1].item():.6f}")
print(f"Energy gap: {gap.item():.6f}")
Pre-built Models for Research
Study phase transitions:
from qconduit.models import transverse_field_ising_chain
from qconduit.exact import exact_ground_state
# Study critical point in TFIM
h_values = torch.linspace(0.1, 2.0, 20)
energies = []
for h in h_values:
hamiltonian = transverse_field_ising_chain(
num_sites=8, j_coupling=1.0, h_field=h.item(), periodic=True
)
energy, _ = exact_ground_state(hamiltonian, num_qubits=8)
energies.append(energy.item())
# Plot energy vs field strength to identify phase transition
Compare different models:
from qconduit.models import (
transverse_field_ising_chain,
heisenberg_xxz_chain,
ising_zz_chain,
)
# Compare ground state energies
tfim = transverse_field_ising_chain(4, j_coupling=1.0, h_field=0.5)
heisenberg = heisenberg_xxz_chain(4, j_coupling=1.0, delta=0.5)
ising = ising_zz_chain(4, j_coupling=1.0)
# Use exact diagonalization or VQE to compare
Adiabatic Quantum Computing
Adiabatic optimization:
from qconduit.adiabatic import (
AdiabaticConfig, linear_schedule, adiabatic_evolve_state,
build_x_mixer_hamiltonian
)
# Set up adiabatic evolution for optimization
h_mixer = build_x_mixer_hamiltonian(num_qubits=4)
h_problem = your_problem_hamiltonian
config = AdiabaticConfig(
total_time=2.0,
num_steps=50,
schedule=linear_schedule(50),
trotter_steps_per_interval=10
)
# Prepare initial state (ground state of mixer = |+⟩^⊗n)
initial_state = qc.zero_state(n_qubits=4)
for i in range(4):
initial_state = qc.apply_gate(initial_state, qc.H(), qubit=i, n_qubits=4)
# Evolve adiabatically
final_state = adiabatic_evolve_state(
initial_state, h_mixer, h_problem, config
)
# Measure to get solution
probs = qc.measure_probs(final_state, n_qubits=4)
solution = torch.argmax(probs)
Custom schedules:
# Use polynomial schedule for slower initial evolution
schedule = polynomial_schedule(num_steps=50, power=3.0)
# Or create custom schedule
def custom_schedule(num_steps):
# Spend more time near s=1 (problem Hamiltonian)
t = torch.linspace(0, 1, num_steps)
return t ** 0.3 # Slow start, fast finish
Quantum Chemistry Applications
Map fermionic Hamiltonians:
from qconduit.fermion import FermionOperator, FermionTerm, jordan_wigner
# Create molecular Hamiltonian (simplified example)
# H = sum_{p,q} h_{pq} a^†_p a_q + sum_{p,q,r,s} g_{pqrs} a^†_p a^†_q a_r a_s
terms = []
# One-body terms
for p in range(n_orbitals):
for q in range(n_orbitals):
if h_matrix[p, q] != 0:
terms.append(FermionTerm(
coeff=h_matrix[p, q],
operators=((p, "+"), (q, "-"))
))
# Two-body terms (simplified)
# ... add interaction terms ...
fermion_ham = FermionOperator(terms)
# Map to qubits
qubit_hamiltonian = jordan_wigner(fermion_ham, n_spin_orbitals=n_orbitals)
# Use with VQE or exact diagonalization
from qconduit.algorithms import VQE
vqe = VQE(ansatz=chemistry_ansatz, hamiltonian=qubit_hamiltonian)
Compare mapping methods:
from qconduit.fermion import jordan_wigner, bravyi_kitaev
# Jordan-Wigner typically has more Pauli terms but simpler structure
jw_ham = jordan_wigner(fermion_op, n_spin_orbitals=4)
print(f"JW: {len(jw_ham.terms)} terms")
# Bravyi-Kitaev often has fewer terms but more complex structure
bk_ham = bravyi_kitaev(fermion_op, n_spin_orbitals=4)
print(f"BK: {len(bk_ham.terms)} terms")
# Choose based on your hardware constraints
Noisy Circuit Simulation
Model realistic hardware:
from qconduit.noise import NoiseConfig, simulate_noisy_circuit_dm
from qconduit.noise import DepolarizingChannel, AmplitudeDampingChannel
# Model realistic noise from quantum hardware
noise_config = NoiseConfig(
per_qubit_channels={
0: DepolarizingChannel(p=0.005), # 0.5% gate error
1: DepolarizingChannel(p=0.008), # 0.8% gate error
2: AmplitudeDampingChannel(gamma=0.01), # T1 decay
}
)
# Simulate circuit with noise
rho = simulate_noisy_circuit_dm(circuit, noise=noise_config)
# Compare with ideal simulation
ideal_state = circuit.simulate_state()
ideal_rho = qc.dm_from_statevector(ideal_state)
# Compute fidelity
from qconduit.diagnostics import fidelity
f = fidelity(ideal_rho, rho)
print(f"Fidelity: {f.item():.6f}")
Error mitigation studies:
# Study how noise affects algorithm performance
noise_levels = [0.001, 0.005, 0.01, 0.02]
fidelities = []
for prob in noise_levels:
noise = NoiseConfig(per_qubit_channels={
i: DepolarizingChannel(p=prob) for i in range(n_qubits)
})
rho = simulate_noisy_circuit_dm(circuit, noise=noise)
f = fidelity(ideal_rho, rho)
fidelities.append(f.item())
# Analyze noise threshold
Performance Considerations
Memory Complexity
-
Statevector Backend: O(2^n) memory for n qubits
- 1 qubit: 8 bytes (complex64)
- 10 qubits: ~8 KB
- 20 qubits: ~8 MB
- 30 qubits: ~8 GB
-
Density Matrix Backend: O(4^n) memory for n qubits
- Intended for small systems (typically n ≤ 4)
- 4 qubits: ~512 bytes
- 8 qubits: ~128 MB
Batch Processing
All operations support batched inputs, enabling efficient processing of multiple quantum states simultaneously. Batch dimensions are preserved throughout operations, making it easy to train quantum models on classical datasets.
CUDA Acceleration
CUDA support is available when PyTorch is installed with CUDA. Simply use device("sv_cuda") to enable GPU acceleration. Quantum operations benefit from GPU parallelization, especially for large batch sizes.
Optimization Tips
- Use statevector backend for pure states (most common case)
- Use density matrix backend only when noise modeling is required
- Leverage batch processing for training on datasets
- Use CUDA for large-scale simulations and batch processing
- Consider parameter-shift gradients for specific use cases where autograd may be inefficient
Versioning and API Stability
Quantum Conduit follows semantic versioning (SemVer) principles:
- 0.0.x (Beta): No API stability guarantees. Breaking changes may occur between patch versions. Use for experimentation and development.
- 0.x.0 (Minor releases): New features and enhancements. Backward compatible within the minor version. API may evolve.
- x.0.0 (Major releases): Breaking changes allowed. Major API redesigns or incompatible changes.
Current version: 0.0.4 (Beta)
For production use: We recommend waiting for version 0.1.0 or later for API stability guarantees. The current beta version is suitable for research and development.
Migration guides: Will be provided for breaking changes in future releases.
Comparison with Alternatives
Quantum Conduit vs. Other Frameworks
| Feature | Quantum Conduit | Qiskit | PennyLane | Cirq |
|---|---|---|---|---|
| PyTorch Integration | ✅ Native | ❌ | ✅ Plugin | ❌ |
| Autograd Support | ✅ Full | ❌ | ✅ Plugin | ❌ |
| Batch Processing | ✅ Built-in | ❌ | ⚠️ Limited | ❌ |
| Abstraction Level | Low (plumbing) | High | Medium | Low |
| Noise Models | ✅ Standard | ✅ Advanced | ✅ Plugin | ✅ |
| ML Focus | ✅ Primary | ❌ | ✅ Primary | ❌ |
| Learning Curve | Low (PyTorch users) | Medium | Medium | Medium |
Unique Value Proposition
Quantum Conduit is the only quantum library designed from the ground up as PyTorch-native plumbing. This means:
- Zero friction when integrating quantum layers into PyTorch models
- Native autograd without plugin layers or wrappers
- Batch-first design optimized for ML workloads
- Minimal abstractions giving you direct control
If you're building quantum machine learning models and already know PyTorch, Quantum Conduit provides the most natural integration.
Contributing
We welcome contributions! Here's how to get started:
Development Setup
-
Clone the repository:
git clone https://github.com/seansimms/Quantum_Conduit.git cd Quantum_Conduit
-
Install in development mode:
pip install -e ".[dev]"
-
Run tests:
pytest
- Run linter:
ruff check .
Code Style
- Follow PEP 8 style guidelines
- Use type hints for all function signatures
- Write docstrings for all public functions and classes
- Keep functions focused and modular
Testing
- Add tests for all new features
- Ensure all tests pass before submitting PR
- Aim for high test coverage
Pull Request Process
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Add tests for new functionality
- Ensure all tests pass and code is linted
- Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Citation
If you use Quantum Conduit in your research, please cite it as:
APA:
Simms, S. (2025). Quantum Conduit: A PyTorch-native quantum statevector plumbing library for quantum machine learning (Version 0.0.1) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.17599984
BibTeX:
@software{simms2025quantum,
author = {Simms, Sean},
title = {Quantum Conduit: A PyTorch-native quantum statevector
plumbing library for quantum machine learning},
version = {0.0.1},
month = {11},
year = {2025},
publisher = {Zenodo},
doi = {10.5281/zenodo.17599984},
url = {https://doi.org/10.5281/zenodo.17599984}
}
Citation File Format:
The repository includes a CITATION.cff file that can be used by citation management tools.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Built with ❤️ for the quantum machine learning community
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file qconduit-0.0.5.tar.gz.
File metadata
- Download URL: qconduit-0.0.5.tar.gz
- Upload date:
- Size: 318.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f56d7fbdeb8e3553ed926a633a17f8e5b4080e1a9a5b1f7ff08ca47a0e78f28f
|
|
| MD5 |
312a8ed1bc2e230c4c414cf52e52f9a4
|
|
| BLAKE2b-256 |
af6285546a18a55231a6d681e3d8269c93561cb46673302a274c27a79cc46682
|
File details
Details for the file qconduit-0.0.5-py3-none-any.whl.
File metadata
- Download URL: qconduit-0.0.5-py3-none-any.whl
- Upload date:
- Size: 211.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c36e90af2dde05c224764c2aa3e6c95734cafd91ed2b303c4f1feb996538417
|
|
| MD5 |
532b24fa0b0cb12df50c83c2b839db10
|
|
| BLAKE2b-256 |
bc2e0bcab0f7311a22b5523d43c909c0f6dc3f473af534391c8a37e1ccbc589d
|