> ## Documentation Index
> Fetch the complete documentation index at: https://docs.haiqu.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# AI Context: Transverse-Field Ising (TFIM) 127-Qubit Demo — Full Notebook Code Flow

> Hidden AI-assistant context distilled from ising.ipynb, including the complete executable code flow (imports → config → circuit/observable → scenario runners → plots → cost).

<Info>
  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).
</Info>

## 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:

1. Magnetization vs transverse field strength: (M(h))
2. 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{1}{N}\sum\_{i=0}^{N-1} 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:

```json theme={null}
{
  "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": "..." }
}
```

<Warning>
  **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.
</Warning>

***

## Full notebook code flow (verbatim, ordered)

The following sections reproduce the notebook’s executable flow in the same order as the `.ipynb`.

<Steps>
  <Step title="1) Import, load config, init Haiqu, read device connectivity">
    ```python theme={null}
    ## 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")
    ```
  </Step>

  <Step title="2) Define the parameter sweep and build the parameterized evolution circuit">
    ```python theme={null}
    # 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
    )
    ```
  </Step>

  <Step title="3) Build the magnetization observable as SparsePauliOp">
    ```python theme={null}
    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)]
    ```
  </Step>

  <Step title="4) Define the four scenario runners (Ideal / Noisy / IBM / Haiqu)">
    ```python theme={null}
    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"],
        }
    ```
  </Step>

  <Step title="5) Run all scenarios and tabulate results">
    ```python theme={null}
    # 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
    ```
  </Step>

  <Step title="6) Plot magnetization curves and log to Haiqu experiment">
    ```python theme={null}
    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")
    ```
  </Step>

  <Step title="7) Compute bill breakdown, plot log-scale costs, and log to Haiqu">
    ```python theme={null}
    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")
    ```
  </Step>
</Steps>

***

## 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`.
