qedclib Guide
qedclib is a quantum program execution engine with built-in performance monitoring. It provides backend abstraction, automatic metrics collection, batched execution, and multi-GPU support for quantum computing applications.
You can use qedclib independently of the QED-C benchmarks — it is available as a standalone package on PyPI:
pip install qedclib
Or install from the full repository (includes qedcbench benchmarks):
git clone https://github.com/SRI-International/QC-App-Oriented-Benchmarks.git
cd QC-App-Oriented-Benchmarks
pip install -e .
Initialization
Before using qedclib, initialize it with the quantum computing API you want to use. This loads the appropriate execution backend module.
import qedclib
# Initialize with the API (loads the execute module)
qedclib.initialize("qiskit")
# Access execute and metrics directly from qedclib
ex = qedclib.execute
ex.set_execution_target(backend_id="qasm_simulator")
# Metrics is always available
qedclib.metrics.verbose = True
After initialize() or get_kernel(), the execute module is available as qedclib.execute. Use ex = qedclib.execute for a shorter reference. The from qedclib import metrics shorthand also works: import qedclib.metrics as metrics.
Execution Paths
qedclib provides two execution paths depending on whether you want automatic metrics collection.
Path 1: Direct Execution
Use execute_circuits() for raw execution. You get back results directly and handle timing yourself.
from qiskit import QuantumCircuit
# Build circuits
circuits = []
for n in [3, 5, 8]:
qc = QuantumCircuit(n, n)
qc.h(0)
for i in range(n - 1):
qc.cx(i, i + 1)
qc.measure(range(n), range(n))
circuits.append(qc)
# Execute and get results
job_id, result = ex.execute_circuits(circuits, num_shots=1000)
# Process results
for i, counts in enumerate(result.get_counts()):
print(f"Circuit {i}: {counts}")
Parameterized Execution
To execute a single circuit template with multiple parameter sets, pass the params argument. The execute module handles parameter binding internally for both Qiskit and CUDA-Q backends.
from qiskit.circuit import QuantumCircuit, Parameter
# Build a parameterized circuit
theta = Parameter('theta')
qc = QuantumCircuit(2)
qc.ry(theta, 0)
qc.cx(0, 1)
qc.measure_all()
# Execute with 5 different parameter values
params = (["theta"], [[0.0], [0.5], [1.0], [1.5], [3.14]])
job_id, result = ex.execute_circuits([qc], num_shots=1000, params=params)
for i, counts in enumerate(result.get_counts()):
print(f"theta={params[1][i][0]:.2f}: {counts}")
The params argument accepts two formats:
- List of dicts:
[{"theta": 0.5, "phi": 1.0}, {"theta": 0.7, "phi": 0.9}, ...] - Tuple of (names, values):
(["theta", "phi"], [[0.5, 1.0], [0.7, 0.9], ...])-- more efficient when executing many parameter sets with the same parameter names.
Parameter names are strings that match the names defined in the circuit. The execute module maps them to the appropriate framework objects (Qiskit Parameter objects or CUDA-Q kernel arguments).
Path 2: Metrics-Integrated Execution
Use submit_circuits() for automatic metrics collection. Circuits are organized as a nested dict keyed by group (typically qubit width) and circuit ID. Timing, job IDs, and result processing are handled automatically.
from qedclib import metrics
# Build circuits as a nested dict {group: {circuit_id: qc}}
circuits = {}
for n_qubits in [4, 6, 8]:
group = str(n_qubits)
circuits[group] = {}
for cid in range(3):
qc = QuantumCircuit(n_qubits, n_qubits)
qc.h(0)
for i in range(n_qubits - 1):
qc.cx(i, i + 1)
qc.measure(range(n_qubits), range(n_qubits))
circuits[group][str(cid)] = qc
# Submit — auto-initializes metrics if needed
ex.submit_circuits(circuits, num_shots=1000)
# Finalize and retrieve metrics
metrics.end_metrics()
metrics.finalize_all_groups()
# Per-circuit metrics: {group: {circuit_id: {metric: value}}}
cm = metrics.get_circuit_metrics()
# Group-level averages with standard deviations
gm = metrics.get_group_metrics()
for i, group in enumerate(gm["groups"]):
print(f"Group {group}: avg_exec={gm['avg_exec_times'][i]:.4f}s "
f"+/- {gm['std_exec_times'][i]:.4f}s")
Result Handlers
A result handler is a callback that processes each circuit's results as they arrive. Use init_execution() to register one before calling submit_circuits().
def my_handler(qc, result, group, circuit_id, num_shots):
"""Called for each circuit after execution."""
counts = result.get_counts()
# Compute and store your own metrics (fidelity, expectation values, etc.)
fidelity = compute_fidelity(counts, expected)
metrics.store_metric(group, circuit_id, "fidelity", fidelity)
ex.init_execution(my_handler)
ex.submit_circuits(circuits, num_shots=1000)
Metrics Flow
The full metrics-integrated workflow:
qedclib.initialize("qiskit")
ex = qedclib.execute
ex.set_execution_target → ex.init_execution(handler)
→ ex.submit_circuits (one or more calls)
→ metrics.end_metrics()
→ metrics.finalize_all_groups()
→ metrics.save_app_metrics(benchmark_name, method=method)
→ metrics.get_group_metrics() / get_circuit_metrics()
→ metrics.plot_metrics("Title")
Note: save_app_metrics writes benchmark results to __data/DATA-{backend_id}.json. It is called explicitly in each benchmark's run_circuits() and is separate from plotting. The plot_metrics function is purely for visualization.
submit_circuits auto-calls metrics.init_metrics() if not yet initialized. You can call submit_circuits multiple times before finalizing — all results accumulate in the metrics module.
Backend Configuration
Local Simulators (Qiskit)
ex.set_execution_target(backend_id="qasm_simulator")
IBM Hardware
ex.set_execution_target(
backend_id="ibm_sherbrooke",
project="your-crn-or-instance",
exec_options={"use_ibm_quantum_platform": False}
)
IonQ
from qiskit_ionq import IonQProvider
provider = IonQProvider()
backend = provider.get_backend("ionq_simulator")
ex.set_execution_target(
backend_id="ionq_simulator",
provider_backend=backend
)
CUDA-Q (GPU Simulator)
qedclib.initialize("cudaq")
import execute as ex
ex.set_execution_target(backend_id="nvidia")
job_id, result = ex.execute_circuits(circuits, num_shots=1000)
Parallel Execution
Set ex.parallel_execution = True to distribute circuits across multiple execution targets for faster completion. For group-level parallel execution with per-group shot counts, use execute_circuit_groups().
For full details on parallel and distributed statevector execution modes, see Parallel Execution.
API Reference
Top-Level Functions (qedclib)
| Function | Description |
|---|---|
initialize(api) |
Initialize qedclib: set API and load execution backend |
get_api() |
Get current quantum SDK name |
set_api(api) |
Set default quantum SDK (called automatically by initialize) |
get_kernel(name, api, benchmark) |
Load and return a benchmark kernel module |
is_leader() |
True if MPI rank 0 or MPI not active |
Execution Functions (execute as ex)
| Function | Description |
|---|---|
set_execution_target(backend_id, ...) |
Configure the backend for execution |
init_execution(handler) |
Register a result handler callback |
execute_circuits(circuits, num_shots, params=None) |
Execute circuits, return (job_id, result). With params, execute a single template with multiple parameter sets. |
execute_circuit_groups(groups, num_shots_list) |
Execute groups of circuits with per-group shot counts |
submit_circuits(circuits, num_shots, max_batch_size, batch_by_group) |
Execute a circuit dict with automatic metrics collection |
process_circuit_results(circuits_info, results, ...) |
Map batch results back to individual circuits and store metrics |
compute_all_circuit_metrics(circuits) |
Compute depth, gate count, and transpiled metrics for a circuit dict |
Metrics Functions (from qedclib import metrics)
| Function | Description |
|---|---|
init_metrics() |
Initialize/reset metrics tracking |
end_metrics() |
Record end time for the execution run |
store_metric(group, circuit, name, value) |
Store a custom metric value |
get_metric(group, circuit, name) |
Retrieve a stored metric value |
get_circuit_metrics() |
Return the full per-circuit metrics dict |
get_group_metrics() |
Return the aggregated group-level metrics dict |
finalize_all_groups() |
Aggregate per-circuit metrics into group averages |
aggregate_metrics() |
Compute group averages (called by finalize_all_groups) |
report_metrics() |
Print a summary of all group metrics |
save_app_metrics(name, method) |
Save metrics to __data/DATA-{backend_id}.json |
plot_metrics(title) |
Generate volumetric benchmarking plots (no side effects) |
Group Metrics Keys
After calling finalize_all_groups(), get_group_metrics() returns a dict with parallel arrays indexed by group. Key fields:
| Key | Description |
|---|---|
groups |
Group names (typically qubit widths) |
avg_elapsed_times / std_elapsed_times |
Wall-clock time per circuit (mean / std) |
avg_exec_times / std_exec_times |
Backend execution time per circuit (mean / std) |
avg_create_times / std_create_times |
Circuit creation time (mean / std) |
avg_depths |
Average algorithmic circuit depth |
avg_tr_depths / avg_tr_xis / avg_tr_n2qs |
Transpiled depth, xi, and 2-qubit gate counts |
avg_fidelities / std_fidelities |
Normalized fidelity (mean / std) |
avg_hf_fidelities / std_hf_fidelities |
Hellinger fidelity (mean / std) |
Examples
See the qedclib-examples repository for standalone usage examples.
Test scripts demonstrating various patterns are also included in qedclib/_tests/:
| Script | What it demonstrates |
|---|---|
01_basic_api.py |
Initialize, execute circuits, get counts |
02_parameter_sweep.py |
Parameterized circuits, batch execution |
03_backend_switching.py |
Configure different backends (simulator, IBM, IonQ, IQM) |
04_batch_scaling.py |
Batch size scaling, serial vs batch performance |
05_submit_with_metrics.py |
submit_circuits with automatic metrics collection |
© 2025 Quantum Economic Development Consortium (QED-C). All Rights Reserved.