{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "frontmatter",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Simulación de la dispersión de neutrones mediante un flujo de trabajo sin servidor basado en AQC y dinámica de Trotter\"\n",
        "description: \"Calcula el factor de estructura dinámico S(q, w) del imán cuántico KCuF3 ejecutando un flujo de trabajo de Trotter con compresión AQC como plantilla de función implementada.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore Trotter Trotterization spinon spinons KCuF quimb DMRG magnon antiferromagnetic Suzuki fidelities isa COBYQA Gjjc qpoints viridis fontsize vmax vmin */}\n",
        "\n",
        "<span id=\"simulate-neutron-scattering-with-an-aqc-+-trotter-dynamics-serverless-workflow\" />\n",
        "\n",
        "# Simulación de la dispersión de neutrones mediante un flujo de trabajo sin servidor basado en AQC y dinámica de Trotter\n",
        "\n",
        "Estimación *de tiempo de ejecución: 18 minutos en un procesador Heron r3 (NOTA: Se trata únicamente de una estimación. (El tiempo de ejecución puede variar.)*\n",
        "\n",
        "<Admonition type=\"note\" title=\"¿Qué tutorial debería seguir?\">\n",
        "  Utiliza este tutorial para llevar a cabo el experimento de dispersión de neutrones mediante una función implementada de « Qiskit Serverless » que agrupa la construcción, la compresión y la ejecución del circuito en una sola llamada. La compresión utiliza los recursos de procesamiento y memoria del trabajador sin servidor y puede continuar después de cerrar el cuaderno; la preparación del estado fundamental y el posprocesamiento siguen ejecutándose localmente. Primero debes [implementar la plantilla de función](/docs/guides/function-template-aqc-trotter). Para aprender a implementarlo paso a paso, consulta el [tutorial original](/docs/tutorials/simulate-neutron-scattering).\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "learning-outcomes",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Resultados del aprendizaje\n",
        "\n",
        "* Cómo se corresponde un espectro de dispersión inelástica de neutrones con el factor de estructura dinámico $S(q, \\omega)$ de un imán cuántico de tipo « 1D ».\n",
        "* Cómo preparar el estado fundamental KCuF$_3$ (Heisenberg isotrópico) mediante el grupo de renormalización de la matriz de densidad (DMRG) y la maximización de la fidelidad del estado de producto matricial (MPS).\n",
        "* Cómo ejecutar la evolución temporal de Trotter, la compresión de circuitos mediante compilación cuántica aproximada (AQC) y la ejecución mitigada mediante una única llamada a función.\n",
        "* Cómo procesar posteriormente las series temporales de « $\\langle \\sigma_z \\rangle(t)$ » por sitio para convertirlas en « $S(q, \\omega)$ » e identificar el continuo de dos spinones.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "prerequisites",
      "metadata": {},
      "source": [
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Requisitos previos\n",
        "\n",
        "* Conocimiento de [los patrones de Qiskit](/docs/guides/intro-to-patterns) y [`SparsePauliOp`](/docs/api/qiskit/qiskit.quantum_info.SparsePauliOp)de [la evolución temporal de Trotter](/learning/courses/utility-scale-quantum-computing/quantum-simulation).\n",
        "* Es útil tener conocimientos básicos sobre los métodos de redes tensoriales (DMRG y MPS), aunque no es imprescindible, al igual que la familiaridad con la biblioteca [`qiskit-addon-aqc-tensor`](https://github.com/Qiskit/qiskit-addon-aqc-tensor) que utiliza la función para comprimir los circuitos de Trotter.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "background",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## En segundo plano\n",
        "\n",
        "La dispersión inelástica de neutrones mide el factor de estructura dinámico $S(q, \\omega)$, que es la transformada de Fourier en el espacio y el tiempo de la función de correlación espín-espín; por lo tanto, reproducir $S(q, \\omega)$ a partir de un modelo de espín microscópico constituye una prueba directa y falsable de una simulación cuántica. Este tutorial analiza KCuF$_3$, una cadena de Heisenberg antiferromagnética de espín- $\\frac{1}{2}$, cuyas excitaciones no son cambios de espín individuales, sino pares de espinones fraccionados: en lugar de una dispersión nítida de magnones, $S(q, \\omega)$ muestra un amplio *continuo de dos espinones*, limitado por debajo por $\\tfrac{\\pi}{2}|\\sin q|$ y por encima por $\\pi|\\sin(q/2)|$. Esas son las curvas discontinuas que aparecen en los gráficos siguientes. La física al completo, así como la comparación con los datos de neutrones medidos, se tratan en el [tutorial original](/docs/tutorials/simulate-neutron-scattering) y en Lee et al., [arXiv:2603.15608](https://arxiv.org/abs/2603.15608).\n",
        "\n",
        "El flujo de trabajo cuántico reproduce el experimento de dispersión:\n",
        "\n",
        "1. Prepara el estado fundamental de la cadena $|\\psi_0\\rangle$.\n",
        "2. Empieza con una perturbación local en el punto central, una rotación del tipo $\\pi/2$ $Z$, que imita la transferencia de momento y energía del neutrón.\n",
        "3. Evoluciona en el tiempo bajo el hamiltoniano de Heisenberg, $e^{-iHt}$, utilizando la fórmula del producto de Trotter.\n",
        "4. Mide la magnetización por sitio $\\langle \\sigma_z^j \\rangle(t)$. En función del sitio $j$ y del tiempo $t$, esto corresponde exactamente a la función de Green retardada $G^R(j, j_c, t)$, por lo que no es necesaria ninguna conversión antes de la transformada de Fourier del paso 5.\n",
        "5. La transformada de Fourier de $G^R$ es $S(q, \\omega)$.\n",
        "\n",
        "Pueden surgir problemas en el paso 3, cuando los circuitos exactos de Trotter para evoluciones largas resultan demasiado profundos para el hardware. El método AQC con redes tensoriales resuelve este problema comprimiendo un bloque de pasos de Trotter en un ansatz parametrizado, fijo y poco profundo, cuya fidelidad de estado respecto a la evolución exacta se maximiza de forma clásica mediante un simulador MPS ( [arXiv:2301.08609](https://arxiv.org/abs/2301.08609) ). [La plantilla AQC Dynamics](/docs/guides/function-template-aqc-trotter) agrupa todo este núcleo cuántico (síntesis de Trotter, compresión AQC y ejecución mitigada) en una sola llamada:\n",
        "\n",
        "| PRE (este cuaderno)                                                                                                                       | FUNCIÓN (`aqc-dynamics-function`)                                                                                                                    | PUBLICAR (este cuaderno)                          |\n",
        "| ----------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |\n",
        "| Estado fundamental obtenido mediante DMRG más maximización de la fidelidad MPS, con el «kick» de neutrones integrado en el mismo circuito | Síntesis de Trotter → Compresión AQC → ejecución en `statevector`, `fake`, o `runtime`, devolviendo un « $\\langle \\sigma_z^j \\rangle(t)$ » por sitio | $S(q, \\omega)$ : el factor de estructura dinámico |\n",
        "\n",
        "El trabajo específico del experimento se queda aquí, en el cuaderno: la preparación del estado fundamental (PRE) y el posprocesamiento de la técnica « $S(q, \\omega)$ » (POST). Los dos pasos que requieren un mayor uso de la computación cuántica, la compresión y la ejecución, se ejecutan dentro de la función.\n",
        "\n",
        "Este tutorial es un complemento del [artículo](/docs/tutorials/simulate-neutron-scattering) «Simulación de la dispersión de neutrones en materiales cuánticos con circuitos cuánticos», en el que se desarrolla el mismo experimento paso a paso: el mismo modelo KCuF$_3$, la preparación del estado fundamental, el «kick» de neutrones y el posprocesamiento, con la síntesis de Trotter, la compresión AQC y la ejecución mitigada explicadas paso a paso. Lee ese tutorial para saber cómo funciona la compresión AQC. Lee este artículo para realizar el mismo experimento mediante una plantilla de función implementada: el núcleo cuántico se convierte en una única llamada a una función, y la compresión AQC, que dura varias horas, se ejecuta dentro del trabajador sin servidor en lugar de en tu equipo, por lo que no necesitas un sistema HPC ni un kernel abierto mientras se ejecuta. Esa misma llamada también impulsa otros experimentos sobre la dinámica de la « 1D ».\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "requirements",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisitos\n",
        "\n",
        "Antes de empezar este tutorial, asegúrate de que dispones de lo siguiente:\n",
        "\n",
        "* La función se ha implementado en tu cuenta de Qiskit Serverless. Ejecuta primero la plantilla de función complementaria: [implementa y ejecuta la plantilla de función «AQC + Trotter dynamics](/docs/guides/function-template-aqc-trotter) ». En esta guía se explica paso a paso cómo obtener los archivos fuente y cómo subir la función a tu cuenta. Este tutorial solo llama a la función implementada.\n",
        "\n",
        "* IBM Quantum® credenciales guardadas para `QiskitServerless` (véase la plantilla de función). Los dos ejemplos de este tutorial llaman a la función implementada, por lo que ambos la necesitan.\n",
        "\n",
        "* Qiskit SDK v2.0 o posterior (`pip install qiskit`).\n",
        "\n",
        "* El cliente del catálogo de Qiskit IBM (`pip install qiskit-ibm-catalog`).\n",
        "\n",
        "* NumPy, SciPy, y Matplotlib (`pip install numpy scipy matplotlib`). Se necesita SciPy 1.14 o una versión posterior para el optimizador COBYQA utilizado en la preparación del estado fundamental.\n",
        "\n",
        "* La pila de redes tensoriales AQC, ya que la preparación del estado fundamental del paso 1 se ejecuta localmente en este cuaderno: `pip install 'qiskit-addon-aqc-tensor[quimb-jax]==0.3.1'`.\n",
        "\n",
        "La primera llamada a una función recién implementada queda en espera mientras el trabajador «serverless» instala sus dependencias, por lo que cabe esperar una latencia adicional en esa ejecución.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "setup-md",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Configuración\n",
        "\n",
        "Importa las bibliotecas y define los auxiliares específicos del experimento que se utilizarán más adelante: `build_gs_ansatz` (el enfoque variacional hamiltoniano, o HVA, para la preparación del estado fundamental), `prepare_ground_state` (DMRG más maximización de la fidelidad MPS), y `get_spectrum`, `plot_green`, y `plot_spectrum` (el posprocesamiento « $S(q, \\omega)$ »). Estos se han adaptado a partir del [tutorial original sobre dispersión de neutrones](/docs/tutorials/simulate-neutron-scattering).\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "setup-imports",
      "metadata": {},
      "outputs": [],
      "source": [
        "from functools import partial\n",
        "\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "import scipy.optimize\n",
        "\n",
        "import quimb.tensor as qtn\n",
        "from qiskit import QuantumCircuit\n",
        "from qiskit.quantum_info import SparsePauliOp\n",
        "from qiskit_addon_aqc_tensor.simulation import tensornetwork_from_circuit\n",
        "from qiskit_addon_aqc_tensor.simulation.quimb import QuimbSimulator\n",
        "from qiskit_ibm_catalog import QiskitServerless"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "setup-helpers",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Setup complete - helpers defined.\n"
          ]
        }
      ],
      "source": [
        "#  Dynamical structure factor via discrete Fourier transform\n",
        "\n",
        "\n",
        "def get_spectrum(n, Gjjc, dt, time_steps, q_steps, w_steps):\n",
        "    \"\"\"Compute the dynamical structure factor from the retarded Green's function.\n",
        "\n",
        "    Uses the center-site approximation and a discrete Fourier transform.\n",
        "    \"\"\"\n",
        "    green = Gjjc / 4  # sigma -> S=1/2\n",
        "    omega_max = np.pi / dt\n",
        "    qpoints = np.arange(0, 2 * np.pi, 2 * np.pi / q_steps)\n",
        "    omegas = np.arange(0, omega_max, omega_max / w_steps)\n",
        "    green_map = np.zeros((omegas.shape[0], qpoints.shape[0]))\n",
        "    center = n // 2 - 1\n",
        "    for iw, w in enumerate(omegas):\n",
        "        exponent = np.exp(1j * w * dt * np.arange(1, time_steps + 1))\n",
        "        S_w = np.dot(green.T, exponent) * dt\n",
        "        for iq, q in enumerate(qpoints):\n",
        "            q_matrix = np.exp(-1j * q * np.arange(-center, center + 2, 1))\n",
        "            green_map[iw, iq] = np.imag(np.dot(S_w, q_matrix))\n",
        "    return green_map\n",
        "\n",
        "\n",
        "#  Plotting helpers\n",
        "\n",
        "\n",
        "def plot_spectrum(\n",
        "    dsf,\n",
        "    dt,\n",
        "    q_steps,\n",
        "    w_steps,\n",
        "    lower_bound=False,\n",
        "    upper_bound=False,\n",
        "    title=None,\n",
        "):\n",
        "    \"\"\"Heat-map of the dynamical structure factor.\"\"\"\n",
        "    omega_max = np.pi / dt\n",
        "    qpoints = np.arange(0, 2 * np.pi, 2 * np.pi / q_steps)\n",
        "    omegas = np.arange(0, omega_max, omega_max / w_steps)\n",
        "    x, y = np.meshgrid(qpoints, omegas)\n",
        "    fig, ax = plt.subplots(figsize=(8, 5))\n",
        "    c = ax.pcolormesh(x, y, dsf / np.max(dsf), cmap=\"viridis\", shading=\"auto\")\n",
        "    fig.colorbar(c, ax=ax, label=\"Normalized intensity\")\n",
        "    if lower_bound:\n",
        "        ax.plot(\n",
        "            qpoints,\n",
        "            np.pi * np.abs(np.sin(qpoints)) / 2,\n",
        "            \"--\",\n",
        "            color=\"white\",\n",
        "            lw=1.5,\n",
        "            label=\"Lower bound\",\n",
        "        )\n",
        "    if upper_bound:\n",
        "        ax.plot(\n",
        "            qpoints,\n",
        "            np.pi * np.abs(np.sin(qpoints / 2)),\n",
        "            \"--\",\n",
        "            color=\"red\",\n",
        "            lw=1.5,\n",
        "            label=\"Upper bound\",\n",
        "        )\n",
        "    ax.set_ylim(0, 3.6)\n",
        "    ax.set_xlim(0, 2 * np.pi - 2 * np.pi / q_steps)\n",
        "    ax.set_xlabel(r\"$q$\", fontsize=16)\n",
        "    ax.set_ylabel(r\"$\\tilde{\\omega} = \\omega / J$\", fontsize=16)\n",
        "    ax.set_xticks([0, np.pi / 2, np.pi, 3 * np.pi / 2, 2 * np.pi])\n",
        "    ax.set_xticklabels([\"0\", r\"$\\pi/2$\", r\"$\\pi$\", r\"$3\\pi/2$\", r\"$2\\pi$\"])\n",
        "    if lower_bound or upper_bound:\n",
        "        ax.legend(loc=\"upper right\", fontsize=11)\n",
        "    if title:\n",
        "        ax.set_title(title, fontsize=14)\n",
        "    plt.tight_layout()\n",
        "    plt.show()\n",
        "\n",
        "\n",
        "def plot_green(n, Gjjc, time_steps, dt, title=None):\n",
        "    \"\"\"Heat-map of the retarded Green's function in real space and time.\"\"\"\n",
        "    fig, ax = plt.subplots(figsize=(8, 6))\n",
        "    t_axis = np.arange(1, time_steps + 1) * dt\n",
        "    site_axis = np.arange(n)\n",
        "    x, y = np.meshgrid(t_axis, site_axis)\n",
        "    c = ax.pcolormesh(\n",
        "        x,\n",
        "        y,\n",
        "        np.real(Gjjc).T,\n",
        "        cmap=\"RdBu\",\n",
        "        vmax=0.5,\n",
        "        vmin=-0.5,\n",
        "        shading=\"auto\",\n",
        "    )\n",
        "    fig.colorbar(c, ax=ax, label=r\"Re $G^R(j, j_c, t)$\")\n",
        "    ax.set_xlabel(r\"Time  ($t / J^{-1}$)\", fontsize=16)\n",
        "    ax.set_ylabel(\"Site index $j$\", fontsize=16)\n",
        "    if title:\n",
        "        ax.set_title(title, fontsize=14)\n",
        "    plt.tight_layout()\n",
        "    plt.show()\n",
        "\n",
        "\n",
        "#  Variational ground-state ansatz (HVA)\n",
        "\n",
        "\n",
        "def _apply_xxz_pair_gate(qc, q0, q1, theta):\n",
        "    \"\"\"Apply the parameterized XXZ-type two-qubit gate used in the HVA.\"\"\"\n",
        "    qc.cx(q0, q1)\n",
        "    qc.rz(theta, q1)\n",
        "    qc.h(q0)\n",
        "    qc.rz(theta + np.pi / 2, q0)\n",
        "    qc.cx(q0, q1)\n",
        "    qc.rz(-theta, q1)\n",
        "    qc.h(q1)\n",
        "    qc.cx(q1, q0)\n",
        "    qc.rz(np.pi / 2, q1)\n",
        "    qc.rz(-np.pi / 2, q0)\n",
        "    qc.h(q1)\n",
        "    qc.h(q0)\n",
        "\n",
        "\n",
        "def build_gs_ansatz(n, params, layers):\n",
        "    \"\"\"Build the Hamiltonian variational ansatz (HVA) circuit for\n",
        "    ground-state preparation of the 1D Heisenberg model.\n",
        "\n",
        "    Starts from a product of singlet pairs and applies alternating\n",
        "    odd/even layers of parameterized XXZ gates. For layer r,\n",
        "    params[2 * r] is the odd-layer (inter-pair) angle and\n",
        "    params[2 * r + 1] is the even-layer (intra-pair) angle.\n",
        "    \"\"\"\n",
        "    qc = QuantumCircuit(n)\n",
        "    # Initial singlet product state\n",
        "    for i in range(n // 2):\n",
        "        qc.x(2 * i)\n",
        "        qc.x(2 * i + 1)\n",
        "        qc.h(2 * i + 1)\n",
        "        qc.cx(2 * i + 1, 2 * i)\n",
        "    # Variational layers\n",
        "    for r in range(layers):\n",
        "        for i in range(1, (n + 1) // 2):  # odd layer\n",
        "            _apply_xxz_pair_gate(qc, 2 * i - 1, 2 * i, params[2 * r])\n",
        "        for i in range(n // 2):  # even layer\n",
        "            _apply_xxz_pair_gate(qc, 2 * i, 2 * i + 1, params[2 * r + 1])\n",
        "    return qc\n",
        "\n",
        "\n",
        "def prepare_ground_state(n, gs_layers=5, max_bond=128, cutoff=1e-8):\n",
        "    \"\"\"Prepare the KCuF3 (isotropic Heisenberg) ground state as a QuantumCircuit.\n",
        "\n",
        "    Runs DMRG (quimb MPO + DMRG2) to get the chain's ground state, then optimizes\n",
        "    the HVA angles to maximize the MPS overlap |<psi_ansatz|psi_DMRG>|^2. No exact\n",
        "    diagonalization, so it scales to larger n.\n",
        "    \"\"\"\n",
        "    J = Jz = 1.0\n",
        "    builder = qtn.SpinHam1D(S=1 / 2)\n",
        "    builder += J * 0.5, \"+\", \"-\"\n",
        "    builder += J * 0.5, \"-\", \"+\"\n",
        "    builder += Jz, \"Z\", \"Z\"\n",
        "    H_mpo = builder.build_mpo(L=n)\n",
        "    dmrg = qtn.DMRG2(H_mpo)\n",
        "    dmrg.solve(tol=1e-8, verbosity=0)\n",
        "\n",
        "    gs_sim = QuimbSimulator(\n",
        "        quimb_circuit_factory=partial(\n",
        "            qtn.CircuitMPS, gate_opts=dict(cutoff=cutoff, max_bond=max_bond)\n",
        "        ),\n",
        "        autodiff_backend=\"jax\",\n",
        "    )\n",
        "\n",
        "    def gs_infidelity(params):\n",
        "        psi = tensornetwork_from_circuit(\n",
        "            build_gs_ansatz(n, params, gs_layers), gs_sim\n",
        "        ).psi\n",
        "        return 1 - abs(psi.H @ dmrg.state) ** 2\n",
        "\n",
        "    # Seed and optimizer match the original tutorial. Each layer starts at\n",
        "    # [0, pi/2]: an odd-layer angle of 0 makes the inter-pair gate the identity,\n",
        "    # and an even-layer angle of pi/2 makes the intra-pair gate a SWAP (since\n",
        "    # 0.5 * (XX + YY + ZZ) = SWAP - I/2). That puts the seed at the singlet-pair\n",
        "    # product limit, which is already a decent approximation to the Heisenberg\n",
        "    # ground state, so the optimizer only has to refine it. The small jitter\n",
        "    # (fixed RNG seed, so runs are reproducible) breaks the exact symmetry\n",
        "    # between layers; COBYQA then runs for up to 100 iterations.\n",
        "    rng = np.random.default_rng(12345)\n",
        "    x0 = np.tile([0.0, np.pi / 2], gs_layers) + rng.normal(\n",
        "        scale=0.1, size=2 * gs_layers\n",
        "    )\n",
        "    result_gs = scipy.optimize.minimize(\n",
        "        gs_infidelity, x0, method=\"COBYQA\", options={\"maxiter\": 100}\n",
        "    )\n",
        "    print(f\"DMRG ground-state energy: {dmrg.energy:.6f}\")\n",
        "    print(f\"GS fidelity: {1 - result_gs.fun:.4f}\")\n",
        "    return build_gs_ansatz(n, result_gs.x, gs_layers)\n",
        "\n",
        "\n",
        "print(\"Setup complete - helpers defined.\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "setup-load-md",
      "metadata": {},
      "source": [
        "<span id=\"load-the-function-template\" />\n",
        "\n",
        "### Cargar la plantilla de función\n",
        "\n",
        "Conéctate a Qiskit Serverless y carga el archivo `aqc-dynamics-function`. Los dos ejemplos de este tutorial utilizan el mismo `fn` identificador, por lo que la función se carga una sola vez, aquí.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "setup-connect",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Credentials are read from the account saved once via QiskitServerless.save_account(...)\n",
        "serverless = QiskitServerless()\n",
        "fn = serverless.load(\"aqc-dynamics-function\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "small-md",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-simulator-example\" />\n",
        "\n",
        "## Ejemplo de simulador a pequeña escala\n",
        "\n",
        "En primer lugar, ejecutamos el flujo de trabajo completo en una pequeña cadena de 10 establecimientos utilizando el mismo `statevector` backend. Esto permite validar el proceso PRE → FUNCTION → POST antes de dedicar tiempo a la QPU.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "small-s1-md",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "### Paso 1: Asignar entradas clásicas a un problema cuántico\n",
        "\n",
        "Construimos el hamiltoniano KCuF$_3$ como un `SparsePauliOp` hamiltoniano de Heisenberg isotrópico: $XX + YY + ZZ$ con un acoplamiento de $\\tfrac14$ en cada enlace entre vecinos más cercanos; las cadenas son operadores de Pauli, por lo que $\\tfrac14$ da el acoplamiento de espín $\\frac{1}{2}$ ). Prepara el estado fundamental con DMRG más maximización de la fidelidad MPS y, a continuación, incorpora el «neutron kick»: una rotación de tipo « $\\pi/2$ » $Z$ en el sitio central. El circuito preparado es lo que pasamos a la función como `initial_state`. Lo dejamos en `observables` su valor por defecto ( $Z$ por sitio), que es exactamente el valor de $\\langle \\sigma_z^j \\rangle(t)$ que necesita el flujo de trabajo de neutrones.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "small-s1-code",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "DMRG ground-state energy: -4.258035\n",
            "GS fidelity: 0.9841\n",
            "Prepared 10-qubit ground state with the neutron kick at site 4.\n"
          ]
        }
      ],
      "source": [
        "n = 10\n",
        "dt = 0.6  # physical time per Trotter step (also the omega-axis unit in POST)\n",
        "time_steps = 10\n",
        "center = n // 2 - 1\n",
        "\n",
        "# MPS-simulator settings, shared by the ground-state prep here and the AQC\n",
        "# compression inside the function (matches the original tutorial).\n",
        "mps_max_bond = 32\n",
        "mps_cutoff = 1e-8\n",
        "\n",
        "# 1D isotropic Heisenberg (KCuF3) Hamiltonian on n qubits\n",
        "H = SparsePauliOp.from_sparse_list(\n",
        "    [(p, [i, i + 1], 0.25) for i in range(n - 1) for p in (\"XX\", \"YY\", \"ZZ\")],\n",
        "    num_qubits=n,\n",
        ")\n",
        "\n",
        "# Ground state (DMRG + fidelity max) + neutron kick baked into the same circuit\n",
        "gs_circuit = prepare_ground_state(\n",
        "    n, gs_layers=3, max_bond=mps_max_bond, cutoff=mps_cutoff\n",
        ")\n",
        "gs_circuit.rz(\n",
        "    np.pi / 2, center\n",
        ")  # exp(-i (pi/2)/2 Z_center): the neutron perturbation\n",
        "print(\n",
        "    f\"Prepared {n}-qubit ground state with the neutron kick at site {center}.\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "small-s3-md",
      "metadata": {},
      "source": [
        "<span id=\"steps-2-and-3-compress-and-execute-with-the-function-template\" />\n",
        "\n",
        "### Pasos 2 y 3: Comprimir y ejecutar con la plantilla de función\n",
        "\n",
        "En un flujo de trabajo realizado a mano, se trata de dos fases distintas: optimizar los circuitos para el hardware (paso 2) y ejecutarlos (paso 3). La plantilla de función agrupa ambas en una sola llamada. Realiza la síntesis de Trotter, la compresión AQC y la transpilación de hardware, y a continuación ejecuta los circuitos (en este caso, en el simulador exacto; posteriormente, con mitigación de errores integrada en el hardware). Los dos parámetros de ajuste son `aqc_segments` (el plan de compresión) y `aqc_options` (la configuración del MPS y del optimizador). Cada segmento `{\"n_steps\": k, \"ansatz_steps\": m}` comprime los pasos consecutivos `k` de Trotter en un ansatz construido a partir de un objetivo de Trotter de `m`-pasos, y cualquier paso adicional se `sum(n_steps)` ejecuta como un método de Trotter estándar. Los primeros pasos, con bajo entrelazamiento, se comprimen bien en un ansatz superficial (`ansatz_steps=1`), por lo que aquí comprimimos los tres primeros pasos en un ansatz de una sola capa y los dos siguientes en un ansatz más profundo de dos capas; los cinco pasos restantes de los 10 pasos de Trotter se ejecutan como Trotter simple. Para ello `aqc_options` , seguimos el tutorial original: dimensión de enlace MPS `max_bond=32`, `cutoff=1e-8`, y un optimizador L-BFGS-B con un límite de 100 iteraciones.\n",
        "\n",
        "Llama a la función cargada en «Setup». `backend=\"statevector\"` ejecuta la ruta de referencia exacta: sin tiempo de QPU, con los circuitos ejecutándose en un simulador de vectores de estado exactos dentro del worker sin servidor (se sigue necesitando una cuenta de Qiskit Serverless guardada para llamarlo). El incluye `initial_state` el estado fundamental preparado (incluido el «kick»); `observables` se omite para que la función mida la « $Z$ » predeterminada por sitio.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "small-s3-run",
      "metadata": {},
      "outputs": [],
      "source": [
        "job = fn.run(\n",
        "    t_steps=time_steps,\n",
        "    aqc_segments=[\n",
        "        {\n",
        "            \"n_steps\": 3,\n",
        "            \"ansatz_steps\": 1,\n",
        "        },  # early steps -> shallow 1-layer ansatz\n",
        "        {\n",
        "            \"n_steps\": 2,\n",
        "            \"ansatz_steps\": 2,\n",
        "        },  # later steps -> deeper 2-layer ansatz\n",
        "    ],\n",
        "    aqc_options={\n",
        "        \"max_bond\": mps_max_bond,  # MPS bond dimension for AQC compression\n",
        "        \"cutoff\": mps_cutoff,\n",
        "        \"optimizer_settings\": {\n",
        "            \"method\": \"L-BFGS-B\",\n",
        "            \"jac\": True,\n",
        "            \"options\": {\"maxiter\": 100},\n",
        "        },\n",
        "    },\n",
        "    dt=dt,\n",
        "    hamiltonian=H,\n",
        "    initial_state=gs_circuit,  # prepared ground state including the neutron kick\n",
        "    # observables omitted -> default per-site Z (the neutron sigma_z readout)\n",
        "    backend=\"statevector\",\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 20,
      "id": "small-status",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "DONE\n"
          ]
        }
      ],
      "source": [
        "print(job.status())  # rerun this cell until status says DONE"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 21,
      "id": "small-s3-result",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "AQC fidelities: {'1': 1.0, '2': 0.9999, '3': 0.9992, '4': 0.9998, '5': 0.9995}\n",
            "Green's function shape: (10, 10)\n"
          ]
        }
      ],
      "source": [
        "# The per-site <sigma_z>(t) the function returns is the retarded Green's function\n",
        "# G(j, j_c, t). The workflow samples t = 1..time_steps, so drop the t = 0 row (the\n",
        "# prepared+kicked state before any evolution) before post-processing.\n",
        "result = job.result()\n",
        "print(\n",
        "    \"AQC fidelities:\",\n",
        "    {k: round(v, 4) for k, v in result[\"metadata\"][\"aqc_fidelities\"].items()},\n",
        ")\n",
        "\n",
        "ev = np.array(result[\"expectation_values\"])\n",
        "Gjjc = ev[1:]  # shape (time_steps, n)\n",
        "print(\"Green's function shape:\", Gjjc.shape)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "small-s4-md",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-desired-classical-format\" />\n",
        "\n",
        "### Paso 4: Realizar el posprocesamiento y obtener el resultado en el formato clásico deseado\n",
        "\n",
        "Aplicar la transformada de Fourier a la función de Green para obtener un $S(q, \\omega)$ a, aplicar la simetría especular y recortar los valores negativos: el posprocesamiento estándar de los neutrones. El reflejo es exacto porque, en este modelo, « $S(q, \\omega) = S(-q, \\omega)$ », y los valores negativos que persisten son artefactos derivados de la transformación de Fourier de una serie temporal finita y muestreada de forma discreta, por lo que se recortan a cero. En esta pequeña simulación exacta, el continuo de dos espínones solo se resuelve de forma aproximada, pero el mecanismo es idéntico al de la simulación con hardware que viene a continuación.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 22,
      "id": "small-s4-code",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/simulate-neutron-scattering-with-a-serverless-workflow/extracted-outputs/small-s4-code-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/simulate-neutron-scattering-with-a-serverless-workflow/extracted-outputs/small-s4-code-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "q_res, w_res = 100, 100\n",
        "spectrum = get_spectrum(n, Gjjc, dt, time_steps, q_res, w_res)\n",
        "spectrum = -(spectrum + spectrum[:, ::-1]) / 2  # mirror symmetry\n",
        "spectrum = np.clip(spectrum, a_min=0, a_max=None)  # clip negatives\n",
        "\n",
        "plot_green(\n",
        "    n,\n",
        "    Gjjc,\n",
        "    time_steps,\n",
        "    dt,\n",
        "    title=f\"Retarded Green's function - {n} qubits (AQC, statevector)\",\n",
        ")\n",
        "plot_spectrum(\n",
        "    spectrum,\n",
        "    dt,\n",
        "    q_res,\n",
        "    w_res,\n",
        "    lower_bound=True,\n",
        "    upper_bound=True,\n",
        "    title=f\"Dynamical structure factor - {n} qubits (AQC, statevector)\",\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "large-md",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## Ejemplo de hardware a gran escala\n",
        "\n",
        "El mismo flujo de trabajo se amplía sin modificar el código científico: una cadena de 30 sitios, el doble de profundidad de Trotter (20 pasos), un plan de compresión que varía la profundidad del ansatz (un ansatz más profundo para los pasos posteriores, más entrelazados) y la ejecución en un procesador IBM Quantum con la mitigación de errores integrada en la función (desacoplamiento dinámico, giro de Pauli y extinción de errores de lectura girada (TREX)). Seguimos los mismos cuatro pasos que en el ejemplo del simulador, reutilizando el `fn` referencia de «Setup».\n",
        "\n",
        "|                                              | PEQUEÑA ESCALA | GRAN ESCALA                       |\n",
        "| -------------------------------------------- | -------------- | --------------------------------- |\n",
        "| Qubits                                       | 10             | 30                                |\n",
        "| Pasos de trote                               | 10             | 20                                |\n",
        "| Pasos comprimidos con AQC (1 capa + 2 capas) | 3 + 2 = 5      | 6 + 4 = 10                        |\n",
        "| Capas de ansatz del estado fundamental       | 3              | 5                                 |\n",
        "| Dimensión máxima de la unión MPS             | 32             | 128                               |\n",
        "| Programa de fondo                            | `statevector`  | QPU con DD, giros de Pauli y TREX |\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "large-s1-md",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "### Paso 1: Asignar entradas clásicas a un problema cuántico\n",
        "\n",
        "Construye el mismo KCuF$_3$ Heisenberg y `SparsePauliOp` prepara el estado fundamental, ahora con un `gs_layers=5` ansatz más profundo para la cadena más larga; a continuación, aplica el impulso de neutrones $\\pi/2$ $Z$ en el sitio central. Es idéntico a la cartografía a pequeña escala, pero en $n = 30$.\n",
        "\n",
        "Cabe esperar una fidelidad del estado fundamental inferior a la del cálculo de 10 sitios: en este caso, alrededor de 0.82 frente a 0.98 para la cadena más pequeña, ya que cinco capas de HVA no pueden capturar por completo un estado fundamental de 30 sitios. Eso es lo habitual, en lugar de un error, y el tutorial original admite aproximadamente 0.65 en 50 sitios web por la misma razón. Aumentar el límite de iteraciones de COBYQA mejora el resultado, aunque `gs_layers` con un coste adicional en términos clásicos.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 34,
      "id": "large-code",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "DMRG ground-state energy: -13.111355\n",
            "GS fidelity: 0.8201\n",
            "Prepared 30-qubit ground state with the neutron kick at site 14.\n"
          ]
        }
      ],
      "source": [
        "n = 30\n",
        "dt = 0.6\n",
        "time_steps = 20\n",
        "center = n // 2 - 1\n",
        "\n",
        "# Same MPS settings as the original large-scale run: a larger bond for the\n",
        "# longer, more-entangled chain (shared by GS prep and AQC compression).\n",
        "mps_max_bond = 128\n",
        "mps_cutoff = 1e-8\n",
        "\n",
        "# Same KCuF3 Hamiltonian and ground-state prep, on a larger chain\n",
        "H = SparsePauliOp.from_sparse_list(\n",
        "    [(p, [i, i + 1], 0.25) for i in range(n - 1) for p in (\"XX\", \"YY\", \"ZZ\")],\n",
        "    num_qubits=n,\n",
        ")\n",
        "gs_circuit = prepare_ground_state(\n",
        "    n, gs_layers=5, max_bond=mps_max_bond, cutoff=mps_cutoff\n",
        ")\n",
        "gs_circuit.rz(np.pi / 2, center)  # neutron kick at the center site\n",
        "print(\n",
        "    f\"Prepared {n}-qubit ground state with the neutron kick at site {center}.\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "large-s3-md",
      "metadata": {},
      "source": [
        "<span id=\"steps-2-and-3-compress-and-execute-with-the-function-template\" />\n",
        "\n",
        "### Pasos 2 y 3: Comprimir y ejecutar con la plantilla de función\n",
        "\n",
        "La misma llamada que en el ejemplo del simulador, pero ahora `backend_name` apuntando a un procesador « IBM Quantum », de modo que la función se compila y se ejecuta allí. El plan de compresión varía la profundidad del ansatz: los seis primeros pasos de Trotter (de bajo entrelazamiento) se comprimen en un ansatz superficial de una sola capa; los cuatro siguientes, en un ansatz más profundo de dos capas; y los 10 pasos restantes de los 20 se ejecutan como Trotter simple. `aqc_options` aumenta la dimensión del enlace MPS a `max_bond=128` para la cadena más larga y más entrelazada (igual que en el original), manteniendo el mismo optimizador L-BFGS-B con un límite de 100 iteraciones. `estimator_options` Activa las medidas de mitigación de errores integradas: desacoplamiento dinámico ( XY4 ), «gate twirling» y mitigación de mediciones TREX. Los valores por defecto de la función ya coinciden con los del tutorial original para todos estos casos, excepto en el presupuesto de aprendizaje de TREX (`measure_noise_learning`). El bloque completo sigue apareciendo porque un valor proporcionado por el usuario sustituye `estimator_options` por completo los valores por defecto de la función, en lugar de fusionarse con ellos, por lo que, si se omite una clave, se recurriría al valor por defecto de IBM Quantum Compute en lugar del de la función.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "large-s3-run",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "job ID (save this to reconnect later): 43ed8d07-6d7d-4f33-b70a-7f31b765b310\n"
          ]
        }
      ],
      "source": [
        "# Steps 2 + 3: the function compresses (varied ansatz) and executes on hardware.\n",
        "job = fn.run(\n",
        "    t_steps=time_steps,\n",
        "    aqc_segments=[\n",
        "        {\n",
        "            \"n_steps\": 6,\n",
        "            \"ansatz_steps\": 1,\n",
        "        },  # early steps -> shallow 1-layer ansatz\n",
        "        {\n",
        "            \"n_steps\": 4,\n",
        "            \"ansatz_steps\": 2,\n",
        "        },  # later steps -> deeper 2-layer ansatz\n",
        "    ],\n",
        "    aqc_options={\n",
        "        \"max_bond\": mps_max_bond,  # 128 for the longer chain\n",
        "        \"cutoff\": mps_cutoff,\n",
        "        \"optimizer_settings\": {\n",
        "            \"method\": \"L-BFGS-B\",\n",
        "            \"jac\": True,\n",
        "            \"options\": {\"maxiter\": 100},\n",
        "        },\n",
        "    },\n",
        "    dt=dt,\n",
        "    hamiltonian=H,\n",
        "    initial_state=gs_circuit,\n",
        "    backend_name=\"ibm_pittsburgh\",\n",
        "    # Mitigation settings from the original tutorial. Only the two\n",
        "    # measure_noise_learning values differ from the function's defaults; the rest\n",
        "    # restates them, because a caller-supplied estimator_options dict replaces the\n",
        "    # function's defaults wholesale rather than merging into them.\n",
        "    estimator_options={\n",
        "        \"environment\": {\"job_tags\": [\"TUT-SNS\"]},\n",
        "        \"dynamical_decoupling\": {\"enable\": True, \"sequence_type\": \"XY4\"},\n",
        "        \"twirling\": {\n",
        "            \"enable_gates\": True,\n",
        "            \"num_randomizations\": 1000,\n",
        "            \"shots_per_randomization\": 128,\n",
        "        },\n",
        "        \"resilience\": {\n",
        "            \"measure_mitigation\": True,\n",
        "            \"measure_noise_learning\": {\n",
        "                \"num_randomizations\": 32,\n",
        "                \"shots_per_randomization\": 100,\n",
        "            },\n",
        "        },\n",
        "    },\n",
        ")\n",
        "print(\"job ID (save this to reconnect later):\", job.job_id)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "large-reconnect-md",
      "metadata": {},
      "source": [
        "<Admonition type=\"note\" title=\"Volver a conectarse a un trabajo de larga duración\">\n",
        "  La ejecución a gran escala no es rápida, y la mayor parte del tiempo se realiza de forma clásica, en lugar de en la QPU. La compresión AQC se ejecuta dentro de la función antes de que nada llegue a la QPU: en 30 sitios, lo `max_bond=128` que nos llevó casi cuatro horas en nuestra ejecución, frente a los aproximadamente 18 minutos de tiempo de QPU indicados en la *estimación de uso* que aparece al principio de este tutorial. A esto hay que sumarle el tiempo de espera en la cola. No es necesario mantener abierto este cuaderno ni el kernel mientras se está ejecutando.\n",
        "\n",
        "  Copia el número de trabajo que aparece en la celda anterior y anótalo. Las tres casillas siguientes te permiten retomar la partida más tarde:\n",
        "\n",
        "  1. Vuelve a conectarte; solo es necesario en una nueva sesión del kernel: vuelve a ejecutar las celdas [de configuración](#setup) para volver a crearlas y `serverless`, a continuación, vuelve a generar el `job` identificador a partir del ID que hayas guardado. Omite esta celda si aún te encuentras en la sesión en la que realizaste el envío, ya que el identificador ya está activo.\n",
        "  2. Comprobar el estado: volver a ejecutar hasta que se muestre el mensaje `DONE`.\n",
        "  3. Obtén el resultado: ejecútalo solo cuando el estado sea `DONE`.\n",
        "\n",
        "  La siguiente celda de reconexión contiene un marcador de posición. Sustitúyelo por el tuyo propio `job_id`:\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "large-reconnect",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Reconnect to a previously submitted job by its ID. Only needed in a NEW kernel\n",
        "# session; if you are still in the session where you submitted, the `job` handle\n",
        "# from the preceding cell is already live, so skip this cell. Replace the ID that follows with your own.\n",
        "job = serverless.get_job_by_id(\"<your job ID>\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 27,
      "id": "large-status",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "DONE\n"
          ]
        }
      ],
      "source": [
        "# Check where the job is. Re-run this until it reports DONE before fetching the\n",
        "# result in the following cell: QUEUED -> INITIALIZING -> RUNNING: OPTIMIZING_FOR_HARDWARE ->\n",
        "# RUNNING: WAITING_FOR_QPU -> RUNNING: EXECUTING_QPU -> RUNNING: POST_PROCESSING\n",
        "# -> DONE.\n",
        "print(job.status())"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "large-s3-result",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "AQC fidelities: {'1': 1.0, '2': 0.9994, '3': 0.9944, '4': 0.9853, '5': 0.9747, '6': 0.959, '7': 0.9495, '8': 0.9542, '9': 0.9533, '10': 0.9451}\n"
          ]
        }
      ],
      "source": [
        "# Run this only once the preceding status cell reports DONE. result() blocks until\n",
        "# the job finishes, so calling it earlier just waits (possibly for hours).\n",
        "result = job.result()\n",
        "print(\n",
        "    \"AQC fidelities:\",\n",
        "    {k: round(v, 4) for k, v in result[\"metadata\"][\"aqc_fidelities\"].items()},\n",
        ")\n",
        "\n",
        "ev = np.array(result[\"expectation_values\"])\n",
        "Gjjc = ev[1:]  # drop the t = 0 row -> shape (time_steps, n)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "large-s4-md",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-desired-classical-format\" />\n",
        "\n",
        "### Paso 4: Realizar el posprocesamiento y obtener el resultado en el formato clásico deseado\n",
        "\n",
        "El mismo procesamiento posterior que en la ejecución del simulador: aplicar la transformada de Fourier a la función de Green para obtener un $S(q, \\omega)$ a, aplicar la simetría especular y recortar los valores negativos. Gracias a la cadena más larga y a la evolución, el continuo de dos espinones se resuelve mucho mejor. Debería ocupar la franja comprendida entre los límites marcados con líneas discontinuas, siendo más brillante cerca de $q = \\pi$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 33,
      "id": "large-result",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/simulate-neutron-scattering-with-a-serverless-workflow/extracted-outputs/large-result-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/simulate-neutron-scattering-with-a-serverless-workflow/extracted-outputs/large-result-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "n = result[\"metadata\"][\"n\"]\n",
        "q_res, w_res = 100, 100\n",
        "spectrum = get_spectrum(n, Gjjc, dt, time_steps, q_res, w_res)\n",
        "spectrum = -(spectrum + spectrum[:, ::-1]) / 2  # mirror symmetry\n",
        "spectrum = np.clip(spectrum, a_min=0, a_max=None)  # clip negatives\n",
        "\n",
        "plot_green(\n",
        "    n,\n",
        "    Gjjc,\n",
        "    time_steps,\n",
        "    dt,\n",
        "    title=f\"Retarded Green's function - {n} qubits (AQC, hardware)\",\n",
        ")\n",
        "plot_spectrum(\n",
        "    spectrum,\n",
        "    dt,\n",
        "    q_res,\n",
        "    w_res,\n",
        "    lower_bound=True,\n",
        "    upper_bound=True,\n",
        "    title=f\"Dynamical structure factor - {n} qubits (AQC, hardware)\",\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "appendix-md",
      "metadata": {},
      "source": [
        "<span id=\"appendix\" />\n",
        "\n",
        "## Apéndice\n",
        "\n",
        "El ejemplo de hardware anterior ejecuta una sola longitud de cadena. Los tres espectros que se muestran a continuación proceden de ejecuciones anteriores de este mismo flujo de trabajo `ibm_pittsburgh` en 10, 20 y 30 sitios, manteniendo fijas todas las demás variables de entrada: 20 pasos de Trotter en `dt = 0.6`, el plan de compresión de seis pasos comprimidos con AQC de una capa más cuatro de dos capas, y `max_bond = 128`. Se trata de resultados registrados, no de los valores de las celdas anteriores.\n",
        "\n",
        "En los tres tamaños se utilizan los mismos parámetros, por lo que los espectros son directamente comparables. Ajustarlos en función de la longitud de la cadena, por ejemplo, añadiendo más capas de ansatz del estado fundamental o aumentando el valor de `max_bond`, puede dar mejores resultados que cualquiera de los que se muestran aquí.\n",
        "\n",
        "![Factor de estructura dinámica en 10 sitios: un único pico brillante y bien definido en q = π, cerca del límite](https://quantum.cloud.ibm.com/docs/images/tutorials/simulate-neutron-scattering-with-a-serverless-workflow/appendix-dsf-10.avif \" inferior de 10 qubits.\")\n",
        "\n",
        "![Factor de estructura dinámica en 20 sitios, con un peso espectral que ocupa la banda entre los dos límites](https://quantum.cloud.ibm.com/docs/images/tutorials/simulate-neutron-scattering-with-a-serverless-workflow/appendix-dsf-20.avif \" de dos espinones marcados con líneas discontinuas: 20 qubits\")\n",
        "\n",
        "![Factor de estructura dinámica en 30 sitios; el continuo se resolvió con mayor precisión, con un contraste más tenue y cierta ponderación fuera de los ](https://quantum.cloud.ibm.com/docs/images/tutorials/simulate-neutron-scattering-with-a-serverless-workflow/appendix-dsf-30.avif \"límites de los 30 qubits.\")\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "nextsteps",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Próximos pasos\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Recomendaciones\">\n",
        "  * Adapta este flujo de trabajo a tu propio sistema: la función admite cualquier «vecino más cercano» ( 1D ) `SparsePauliOp`, por lo que una cadena hamiltoniana, un estado inicial o un conjunto de observables diferentes siguen el mismo proceso PRE → FUNCIÓN → POST. Consulta el contrato completo de entrada y salida en la [plantilla de AQC Dynamics en GitHub](https://github.com/qiskit-community/qiskit-function-templates/tree/main/physics/aqc_trotter).\n",
        "  * Lee el artículo del que procede esta prueba comparativa: Lee et al., [*Comparación de la simulación cuántica con experimentos de dispersión de neutrones*](https://arxiv.org/abs/2603.15608) ( arXiv:2603.15608 ).\n",
        "  * En comparación con el [tutorial original «Simulación de la dispersión de neutrones»](/docs/tutorials/simulate-neutron-scattering), este flujo de trabajo integrado se adapta a una plantilla de función implementada.\n",
        "  * Profundiza en las [técnicas de mitigación y supresión de errores](/docs/guides/error-mitigation-and-suppression-techniques) aplicadas en la ejecución en hardware: desacoplamiento dinámico, «Pauli twirling» y TREX.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "hours": 4,
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3"
    },
    "qpuSeconds": 1080
  },
  "nbformat": 4,
  "nbformat_minor": 5
}