This page is intended as retrieval context for an AI assistant. It preserves the notebook’s full code flow while making implicit assumptions explicit (config keys, file paths, expected return shapes).
What the notebook does (high level)
It benchmarks a utility-scale quantum dynamics simulation of the 2D transverse-field Ising model (TFIM) on 127 qubits across four scenarios:
- Ideal (noise-free, loaded from cache)
- Noisy (raw hardware run; or cached)
- IBM (mitigated results and runtime taken from IBM Nature paper; loaded from cache)
- Haiqu (hardware run with Haiqu mitigation; or cached)
Primary outputs:
- Magnetization vs transverse field strength: (M(h))
- Quantum cloud bill comparison from per-minute pricing
Model and observable (assistant-ready)
- Transverse-field sweep:
transverse_field_params is a list of 1-parameter bindings (shape: [(1,), (1,), ...]).
- Observable: average magnetization
[
M = \frac\sum_^ Z_i
]
implemented as a
SparsePauliOp containing N single-Z Pauli strings with coefficient 1/N.
Files and config contract
The notebook expects:
utils/config.json — credentials + experiment + processor pricing
utils/utils.py — helpers:
load_json_data(path)
make_ising_evolution_circuit(num_qubits, num_steps, backend_coupling_map)
show_device_connectivity(coupling_map)
Cached results:
{cached_folder}/ideal.json
{cached_folder}/noisy.json
{cached_folder}/ibm.json
{cached_folder}/haiqu.json
Minimum config structure used directly:
{
"haiqu": { "api_key": "...", "org_id": "..." },
"experiment": { "name": "ising-demo", "cached_folder": "cache/..." },
"processor": { "name": "ibm_backend_name", "usd_per_minute": 48 },
"device_credentials": { "ibm_token": "...", "ibm_instance": "..." }
}
Hidden page note: keep this file out of docs.json navigation to remain hidden.
If you want hidden pages to still be indexed for search/AI, set docs.json -> seo.indexing = "all" and avoid noindex: true in this frontmatter.
Full notebook code flow (verbatim, ordered)
The following sections reproduce the notebook’s executable flow in the same order as the .ipynb.
1) Import, load config, init Haiqu, read device connectivity
## Import and Setup
import matplotlib.pyplot as plt
import numpy as np
from qiskit.quantum_info import SparsePauliOp
from haiqu.sdk import haiqu
from utils.utils import make_ising_evolution_circuit, load_json_data, show_device_connectivity
# Load configurations
config = load_json_data("utils/config.json")
device_execution = any(config["device_credentials"].values())
# Initialize Haiqu
haiqu.login(**{k: v for k, v in config["haiqu"].items() if v})
haiqu.init(config["experiment"]["name"])
# Show the device connectivity
device = haiqu.get_device(config["processor"]["name"])
coupling_map = device.coupling_map
show_device_connectivity(coupling_map=coupling_map)
if device_execution:
print(f"Executing circuits on {device.id}")
else:
print("Loading existing results from cache")
2) Define the parameter sweep and build the parameterized evolution circuit
# Define the circuit
config["circuit"] = {
"num_qubits": 127,
"shots": 10_000,
"num_steps": 5,
"transverse_field_params": [[0.0], [0.1], [0.2], [0.3], [0.5], [0.7], [0.8], [1.0], [1.2], [1.3], [1.4], [1.5], [1.5708]]
}
parameterized_circuit = make_ising_evolution_circuit(
num_qubits=config["circuit"]["num_qubits"],
num_steps=config["circuit"]["num_steps"],
backend_coupling_map=coupling_map
)
3) Build the magnetization observable as SparsePauliOp
paulis = []
for i in range(config["circuit"]["num_qubits"]):
pauli_string = ["I"] * config["circuit"]["num_qubits"]
pauli_string[i] = "Z"
paulis.append("".join(pauli_string))
coeffs = [1 / config["circuit"]["num_qubits"]] * len(paulis)
list_observables = [SparsePauliOp(paulis, coeffs)]
4) Define the four scenario runners (Ideal / Noisy / IBM / Haiqu)
def run_scenario_ideal(config, circuit, observables):
"""
Scenario 1: Ideal - Results with no noise.
"""
results = load_json_data(
f"{config['experiment']['cached_folder']}/ideal.json"
)
return {
"scenario_name": "Ideal",
"scenario_description": "Results with no noise",
"magnetization": results[0][0],
"runtime_minutes": None,
"runtime_bill": None,
}
def run_scenario_noisy(config, circuit, observables):
"""
Scenario 2: Noisy - Raw results from quantum processor without error mitigation.
"""
if device_execution:
results = haiqu.run(
circuit,
parameters=config["circuit"]["transverse_field_params"],
observables=observables,
shots=config["circuit"]["shots"],
device_id=config["processor"]["name"],
options=config["device_credentials"],
).result()
else:
results = load_json_data(f"{config['experiment']['cached_folder']}/noisy.json")["results"]
runtime_minutes = 38 / 60 # For cached job.
return {
"scenario_name": "Noisy",
"scenario_description": "Raw results from quantum processor without error mitigation",
"magnetization": results[0][0],
"runtime_minutes": runtime_minutes,
"runtime_bill": runtime_minutes * config["processor"]["usd_per_minute"],
}
def run_scenario_mitigated_ibm(config, circuit, observables):
"""
Scenario 3: Error Mitigated IBM - Results using IBM's error mitigation methods.
"""
# Results and runtime from IBM Nature Article: https://www.nature.com/articles/s41586-023-06096-3
results = load_json_data(f"{config['experiment']['cached_folder']}/ibm.json")
runtime_minutes = 4 * 60 # See IBM paper.
return {
"scenario_name": "IBM",
"scenario_description": (
"Results using IBM's error mitigation methods "
"(Sparse Pauli-Lindblad Noise Learning + ZNE)"
),
"magnetization": results[0][0],
"runtime_minutes": runtime_minutes,
"runtime_bill": runtime_minutes * config["processor"]["usd_per_minute"],
}
def run_scenario_mitigated_haiqu(config, circuit, observables):
"""
Scenario 4: Error Mitigated Haiqu - Results using Haiqu's error mitigation.
"""
if device_execution:
results = haiqu.run(
circuit,
parameters=config["circuit"]["transverse_field_params"],
observables=observables,
shots=config["circuit"]["shots"],
device_id=config["processor"]["name"],
options=config["device_credentials"],
use_mitigation=True,
).result()
else:
results = load_json_data(f"{config['experiment']['cached_folder']}/haiqu.json")["results"]
runtime_minutes = 41 / 60 # For cached job.
return {
"scenario_name": "Haiqu",
"scenario_description": "Results using Haiqu's error mitigation",
"magnetization": results[0][0],
"runtime_minutes": runtime_minutes,
"runtime_bill": runtime_minutes * config["processor"]["usd_per_minute"],
}
5) Run all scenarios and tabulate results
# Run all scenarios and collect results
scenario_ideal = run_scenario_ideal(config, parameterized_circuit, list_observables)
scenario_noisy = run_scenario_noisy(config, parameterized_circuit, list_observables)
scenario_ibm = run_scenario_mitigated_ibm(config, parameterized_circuit, list_observables)
scenario_error_mitigated = run_scenario_mitigated_haiqu(config, parameterized_circuit, list_observables)
# Create summary array with all scenario results
scenarios_summary = [
scenario_ideal,
scenario_noisy,
scenario_ibm,
scenario_error_mitigated
]
# Display the summary
import pandas as pd
summary_df = pd.DataFrame(scenarios_summary)
summary_df
6) Plot magnetization curves and log to Haiqu experiment
markers = ["x", "o", "s", "d"]
linestyles = [":", "--", "--", "-"]
plt.figure()
for i, scenario in enumerate(scenarios_summary):
try:
x = np.asarray(config["circuit"]["transverse_field_params"]).ravel()
y = np.asarray(scenario["magnetization"]).ravel()
plt.plot(x, y, label=scenario["scenario_name"], marker=markers[i % len(markers)], linestyle=linestyles[i % len(linestyles)] )
except Exception:
print(f"Error plotting {scenario['scenario_name']}")
plt.xlabel("Transverse Field Strength $h$")
plt.ylabel("Magnetization")
plt.legend()
plt.tight_layout()
haiqu.log(plt, name="result")
7) Compute bill breakdown, plot log-scale costs, and log to Haiqu
labels = []
costs = []
for scenario in scenarios_summary:
if scenario["runtime_bill"] is not None:
labels.append(scenario["scenario_name"])
costs.append(scenario["runtime_bill"])
plt.figure()
plt.bar(labels, costs)
plt.yscale("log")
plt.ylabel("Quantum Cloud Bill ($)")
plt.tight_layout()
haiqu.log(plt, name="performance")
AI retrieval notes (non-obvious but important)
device_execution = any(config["device_credentials"].values()) controls whether the notebook runs live jobs or loads cached JSON.
- Cached file format differs:
ideal.json and ibm.json are loaded as results = load_json_data(...); magnetization = results[0][0]
noisy.json and haiqu.json are loaded as load_json_data(...)["results"] (then results[0][0])
haiqu.run(...).result() is assumed to return an indexable structure where results[0][0] yields the magnetization vector aligned with transverse_field_params.