{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "title",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Execute cargas de trabalho quânticas com o QRMI\"\n",
        "description: \"Utilize a Interface de Gerenciamento de Recursos Quânticos para gerenciar cargas de trabalho d IBM Quantum e e executar um fluxo de trabalho de química quântica a partir de um ambiente de HPC.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore QRMI SPANK GRES Slurm LUCJ CCSD pvdz hcore Pellegrini rustup cregs CUDA SBATCH dotenv */}\n",
        "\n",
        "<span id=\"run-quantum-workloads-with-qrmi\" />\n",
        "\n",
        "# Execute cargas de trabalho quânticas com o QRMI\n",
        "\n",
        "Estimativa de tempo de *execução: menos de um minuto em um hardware d IBM Quantum® e para a seção SQD. Essa estimativa não inclui o tempo de fila nem o processamento clássico; o tempo de execução pode variar.*\n",
        "\n",
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Resultados do aprendizado\n",
        "\n",
        "1. O papel que o QRMI desempenha como middleware entre os agendadores de HPC e o hardwar IBM Quantum\n",
        "2. Como utilizar o ciclo de vida básico do QRMI (`acquire` → `task_start` → `task_status` → `task_result` → `release`) em um backend real do IBM®\n",
        "3. Como usar o Qiskit de nível superior e `SamplerV2` os wrappers `QRMIService` sobre o QRMI\n",
        "4. Como os agendadores de HPC (Slurm) injetam recursos quânticos por meio de variáveis de ambiente e como as aplicações os consomem\n",
        "5. Como executar um fluxo de trabalho completo de química SQD (Diagonalização Quântica Baseada em Amostras) em N $_2$, utilizando o hardware d IBM, por meio do QRMI\n",
        "\n",
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Pré-requisitos\n",
        "\n",
        "* [Qiskit primitives (Amostrador e Estimador)](/docs/guides/primitives)\n",
        "* [IBM Quantum sessões](/docs/guides/run-jobs-session)\n",
        "* [IBM Quantum transpilacão](/docs/guides/transpile)\n",
        "* [Diagonalização quântica baseada em amostras (SQD)](/docs/tutorials/sample-based-quantum-diagonalization)\n",
        "* Conhecimento básico dos ambientes virtuais “ Python ” e de química quântica\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "background",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Segundo plano\n",
        "\n",
        "<span id=\"the-quantum-hpc-integration-challenge\" />\n",
        "\n",
        "### O desafio da integração entre computação quântica e HPC\n",
        "\n",
        "Os fluxos de trabalho de computação de alto desempenho (HPC) geralmente exigem uma coordenação perfeita entre clusters de computação clássicos e unidades de processamento quântico (QPUs). Diferentes back-ends e serviços de hardware quântico oferecem mecanismos de autenticação, formatos de transmissão e APIs de ciclo de vida de tarefas distintos. A integração de sistemas de computação em nuvem ( IBM Quantum ) a gerenciadores de cargas de trabalho de HPC (como o Slurm) requer uma interface simples e padronizada para aquisição de recursos, execução de tarefas e gerenciamento de sessões.\n",
        "\n",
        "<span id=\"what-qrmi-is\" />\n",
        "\n",
        "### O que é o QRMI\n",
        "\n",
        "A **Quantum Resource Management Interface (QRMI)** é uma biblioteca de middleware escrita em Rust que padroniza o acesso ao hardware quântico a partir de agendadores de HPC e aplicativos clássicos. Ele expõe uma única API unificada para o ciclo de vida:\n",
        "\n",
        "```\n",
        "┌─────────────────────────────────────────────────────────────────┐\n",
        "│                     HPC Application Layer                       │\n",
        "│          (Slurm job script / Python workflow / CUDA-Q)          │\n",
        "└───────────────────────────┬─────────────────────────────────────┘\n",
        "                            │  QRMI API\n",
        "                            │  acquire() / task_start() / task_result() / release()\n",
        "┌───────────────────────────▼─────────────────────────────────────┐\n",
        "│                        QRMI Core (Rust)                         │\n",
        "│            Python bindings · C bindings · Lua bindings          │\n",
        "└───────────────────────────┬─────────────────────────────────────┘\n",
        "                            │\n",
        "               IBM Quantum Compute Service / IBM Quantum System\n",
        "```\n",
        "\n",
        "O QRMI é publicado como um projeto de código aberto em [github.com/qiskit-community/qrmi](https://github.com/qiskit-community/qrmi) e é descrito no artigo de visão geral [arXiv:2506.10052](https://arxiv.org/abs/2506.10052).\n",
        "\n",
        "<span id=\"key-design-choices\" />\n",
        "\n",
        "### Principais escolhas de design\n",
        "\n",
        "**Ciclo de vida do recurso, e não compilação do circuito.** O QRMI lida com o ciclo de vida de aquisição/envio/consulta/liberação e nada mais. A compilação, a otimização e a transpilagem de circuitos continuam ocorrendo na camada de aplicação (por exemplo, no Qiskit). Isso mantém a interface minimalista e modular.\n",
        "\n",
        "**Modelo de portabilidade de fornecedores.** Embora o QRMI forneça chamadas comuns de gerenciamento de tarefas (`acquire`, `task_start`, `task_status`, `task_result`, `release`) em todos os back-ends de hardware compatíveis, a troca de fornecedores também exige diferentes etapas de compilação, construção de carga útil específica para cada fornecedor e decodificação dos resultados na camada de aplicação.\n",
        "\n",
        "**Formato de carga útil nativo do IBM.** Para back-ends do tipo “ IBM Quantum ”, o QRMI utiliza cargas de dados JSON do tipo “ OpenQASM 3” (`QiskitPrimitive`) em conformidade com o esquema Qiskit Runtime.\n",
        "\n",
        "**Configuração por meio de variáveis de ambiente.** As credenciais e os URLs dos endpoints são lidos a partir de variáveis de ambiente durante a execução. Em um cluster de HPC, o plug-in Slurm QRMI SPANK define esses parâmetros automaticamente quando uma tarefa é enviada. Em um notebook ou em uma sessão interativa, você os carrega a partir de um arquivo `.env` . O código do aplicativo nunca contém credenciais ou URLs de endpoints codificadas de forma estática.\n",
        "\n",
        "**Integração do agendador de HPC por meio do GRES.** Quando um trabalho do Slurm solicita recursos do Quantum por meio da interface do plug-in QRMI SPANK (`#SBATCH --gres=qpu:1` e `#SBATCH --qpu=ibm_kingston`), o plug-in insere `QRMI_JOB_QPU_RESOURCES` e `QRMI_JOB_QPU_TYPES` no ambiente do trabalho. Os aplicativos fazem chamadas para `get_job_qpu_resources_and_types()` descobrir quais recursos foram alocados — não é necessário usar nomes de back-end fixos. `QRMIService` encapsula esse padrão para usuários do Qiskit.\n",
        "\n",
        "<span id=\"the-core-api-calls\" />\n",
        "\n",
        "### As chamadas à API principal\n",
        "\n",
        "| Chamada                    | Propósito                                                                                     |\n",
        "| -------------------------- | --------------------------------------------------------------------------------------------- |\n",
        "| `qrmi.acquire()`           | Obtém acesso ao recurso (por exemplo, abre uma sessão dedicada); retorna um token de bloqueio |\n",
        "| `qrmi.target()`            | Recuperar as capacidades do backend (qubits, portas, mapa de acoplamento) como JSON           |\n",
        "| `qrmi.task_start(payload)` | Enviar um trabalho quântico; retorna um ID do trabalho                                        |\n",
        "| `qrmi.task_status(job_id)` | Verificar o status da tarefa (`Queued`, `Running`, `Completed`, `Failed`)                     |\n",
        "| `qrmi.task_result(job_id)` | Recuperar os resultados de tarefas concluídas como uma string JSON não formatada              |\n",
        "| `qrmi.task_stop(job_id)`   | Cancelar ou limpar uma tarefa                                                                 |\n",
        "| `qrmi.release(lock)`       | Liberar o bloqueio do recurso (por exemplo, encerrar a sessão)                                |\n",
        "\n",
        "<span id=\"what-this-tutorial-covers\" />\n",
        "\n",
        "### O que este tutorial aborda\n",
        "\n",
        "Este tutorial está dividido em duas partes:\n",
        "\n",
        "**Etapas 1–3 (exemplos em pequena escala):** Apresentar a API do QRMI por meio de uma demonstração simples de um circuito no estado de Bell no hardwar IBM Quantum, abordando tanto o uso direto de primitivas de baixo nível quanto a programação de alto nível `QRMIService` e a integração `SamplerV2` .\n",
        "\n",
        "**Exemplo de hardware em grande escala:** um fluxo de trabalho SQD completo para a molécula N $_2$, com distância entre ligações de 1.0 $\\AA$ (espaço ativo com base em cc-pVDZ, 26 orbitais espaciais / 52 qubits), executado em hardware IBM Quantum por meio do QRMI. O SQD combina a amostragem quântica de um ansatz LUCJ construído com e `ffsim` a recuperação de configuração autoconsistente com `qiskit-addon-sqd`.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "requirements",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisitos\n",
        "\n",
        "Antes de começar este tutorial, certifique-se de que os seguintes itens estejam instalados.\n",
        "\n",
        "<span id=\"python-environment-setup\" />\n",
        "\n",
        "### Python configuração do ambiente\n",
        "\n",
        "Estão disponíveis pacotes binários pré-compilados para o Linux no site PyPI,, de modo que a versão padrão `pip install` funciona diretamente em sistemas Linux /HPC.\n",
        "\n",
        "```bash\n",
        "python3 -m venv ~/.venvs/qrmi-ibm\n",
        "source ~/.venvs/qrmi-ibm/bin/activate\n",
        "python -m pip install \"qrmi[ibm]\" python-dotenv pyscf ffsim qiskit-addon-sqd matplotlib ipykernel\n",
        "python -m ipykernel install --user --name qrmi-ibm --display-name \"QRMI IBM\"\n",
        "```\n",
        "\n",
        "<Admonition type=\"note\" title=\"Plataformas sem rodas pré-montadas\">\n",
        "  Se você compilar `pip` o QRMI a partir do código-fonte, certifique-se de ter uma cadeia de ferramentas Rust atualizada (Rust ≥ 1.91.1, instalada por meio `rustup` do link [rustup.rs](https://rustup.rs) ).\n",
        "</Admonition>\n",
        "\n",
        "Selecione o kernel **“ IBM ” do QRMI** no Jupyter, reinicie-o e execute as células do notebook em ordem. Os resultados salvos são provenientes da execução no hardware do colaborador; os comandos de instalação não especificam as versões exatas utilizadas nessa execução.\n",
        "\n",
        "<span id=\"credentials-required\" />\n",
        "\n",
        "### Credenciais Necessárias\n",
        "\n",
        "* IBM Quantum : chave da API do IAM e endereço do serviço CRN, disponível em [IBM Quantum Platform]()\n",
        "\n",
        "Para execução autônoma, crie um arquivo `.env` ao lado deste notebook com os seguintes valores, substituindo os espaços reservados para as credenciais. Mantenha este arquivo em sigilo. Se você selecionar um backend diferente, atualize tanto o nome dele quanto os prefixos das variáveis de ambiente.\n",
        "\n",
        "```dotenv\n",
        "ibm_kingston_QRMI_IBM_QCS_ENDPOINT=https://quantum.cloud.ibm.com/api/v1\n",
        "ibm_kingston_QRMI_IBM_QCS_IAM_ENDPOINT=https://iam.cloud.ibm.com\n",
        "ibm_kingston_QRMI_IBM_QCS_IAM_APIKEY=<your-iam-api-key>\n",
        "ibm_kingston_QRMI_IBM_QCS_SERVICE_CRN=<your-crn-starting-with-crn:v1:>\n",
        "ibm_kingston_QRMI_IBM_QCS_SESSION_MODE=dedicated\n",
        "ibm_kingston_QRMI_IBM_QCS_SESSION_MAX_TTL=28800\n",
        "QRMI_JOB_QPU_RESOURCES=ibm_kingston\n",
        "QRMI_JOB_QPU_TYPES=ibm-quantum-compute-service\n",
        "```\n",
        "\n",
        "Para uma alocação do Slurm, utilize as configurações de recursos e as credenciais fornecidas pelo cluster. O caderno mantém os valores ambientais existentes.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "setup-header",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Instalação\n",
        "\n",
        "Importe as dependências e carregue a configuração dos recursos.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "setup-imports",
      "metadata": {
        "tags": [
          "environment-ibm"
        ]
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Backend: ibm_kingston\n",
            "Environment ready.\n"
          ]
        }
      ],
      "source": [
        "import os\n",
        "import time\n",
        "import json\n",
        "import numpy as np\n",
        "from dotenv import load_dotenv\n",
        "\n",
        "from qrmi import (\n",
        "    QuantumResource,\n",
        "    ResourceType,\n",
        "    Payload,\n",
        "    TaskStatus,\n",
        "    get_job_qpu_resources_and_types,\n",
        ")\n",
        "from qrmi.primitives import QRMIService\n",
        "from qrmi.primitives.ibm import SamplerV2, get_target\n",
        "\n",
        "from qiskit import QuantumCircuit, qasm3\n",
        "from qiskit.circuit.library import efficient_su2\n",
        "from qiskit.primitives.containers.sampler_pub import SamplerPub\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "\n",
        "# Load credentials from .env without overriding already-set scheduler environment variables\n",
        "load_dotenv(override=False)\n",
        "\n",
        "# Preserve resources if injected by Slurm SPANK plugin; fallback to default for interactive run\n",
        "BACKEND_NAME = os.environ.get(\"QRMI_JOB_QPU_RESOURCES\", \"ibm_kingston\")\n",
        "os.environ.setdefault(\"QRMI_JOB_QPU_RESOURCES\", BACKEND_NAME)\n",
        "os.environ.setdefault(\"QRMI_JOB_QPU_TYPES\", \"ibm-quantum-compute-service\")\n",
        "\n",
        "print(f\"Backend: {BACKEND_NAME}\")\n",
        "print(\"Environment ready.\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "small-scale-header",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-examples\" />\n",
        "\n",
        "## Exemplos em pequena escala\n",
        "\n",
        "As etapas 1 a 3 apresentam a API do QRMI por meio de circuitos simples. Cada etapa corresponde a uma fase principal do ciclo de vida do QRMI em relação ao hardware d IBM Quantum.\n",
        "\n",
        "A carga útil para essas etapas iniciais é um pequeno circuito no estado de Bell, escolhido por ser rápido e de baixo custo para ser executado.\n",
        "\n",
        "Esses exemplos utilizam hardware porque demonstram a alocação remota de recursos e o gerenciamento de tarefas. Um simulador de circuito local não valida a integração do serviço QRMI com o agendador. A execução deste notebook envia tarefas d IBM Quantum e requer acesso ao backend configurado.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step1-header",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-the-classical-problem-to-a-quantum-resource\" />\n",
        "\n",
        "### Etapa 1: Mapeie o problema clássico para um recurso quântico\n",
        "\n",
        "O primeiro passo em qualquer fluxo de trabalho do QRMI é criar um objeto `QuantumResource` e verificar se ele está acessível.\n",
        "\n",
        "`get_target()` recupera a descrição de hardware do backend (número de qubits, portas de base, mapa de acoplamento) e a organiza como um `Target` objeto Qiskit, que o transpiler utiliza na Etapa 2.\n",
        "\n",
        "<span id=\"step-2-optimize-the-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "### Etapa 2: Otimizar o problema para execução em hardware quântico\n",
        "\n",
        "Antes do envio, use o Qiskit para transpilar o circuito para a arquitetura do conjunto de instruções (ISA) do backend, utilizando o objeto `Target` recuperado na Etapa 1.\n",
        "\n",
        "O exemplo, então, cria um `Payload.QiskitPrimitive`, que encapsula a sequência de circuitos OpenQASM 3 e os metadados da tarefa no esquema primitivo IBM.\n",
        "\n",
        "<span id=\"step-3-execute-using-qrmi-primitives\" />\n",
        "\n",
        "### Etapa 3: Executar usando primitivas QRMI\n",
        "\n",
        "Com a carga útil criada, o exemplo envia o trabalho e verifica se ele foi concluído. `task_start()` retorna um ID de tarefa imediatamente; `task_status()` é consultado até que o status não seja mais `Queued`/`Running`. Os resultados são recuperados como uma string JSON bruta e analisados para extrair amostras de medição.\n",
        "\n",
        "A célula a seguir agrupa a aquisição, a execução e a limpeza, de modo que, mesmo em caso de falhas após a aquisição, a sessão pertencente ao notebook ainda seja liberada.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "step1-ibm",
      "metadata": {
        "tags": [
          "environment-ibm"
        ]
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Resource id:   ibm_kingston\n",
            "Resource type: ResourceType.IBMQuantumComputeService\n",
            "Accessible:    True\n",
            "Lock token:    2ff43011-aed1-4436-a4df-40f37ec588b7\n",
            "\n",
            "Backend: ibm_kingston\n",
            "Qubits:  156\n",
            "Gates:   ['cz', 'id', 'rx', 'rz', 'rzz', 'sx', 'x', 'xslow']\n",
            "        ┌───┐      ░ ┌─┐   \n",
            "   q_0: ┤ H ├──■───░─┤M├───\n",
            "        └───┘┌─┴─┐ ░ └╥┘┌─┐\n",
            "   q_1: ─────┤ X ├─░──╫─┤M├\n",
            "             └───┘ ░  ║ └╥┘\n",
            "meas: 2/══════════════╩══╩═\n",
            "                      0  1 \n",
            "\n",
            "Transpiled gate counts: OrderedDict([('rz', 6), ('sx', 3), ('measure', 2), ('cz', 1), ('barrier', 1)])\n",
            "Payload ready\n",
            "Job submitted: dai43g8mhr3c73e7a7o0\n",
            "  Status: TaskStatus.Queued\n",
            "  Status: TaskStatus.Running\n",
            "  Status: TaskStatus.Completed\n",
            "\n",
            "Final status: TaskStatus.Completed\n",
            "\n",
            "Measurement counts: {'11': 487, '00': 254, '01': 177, '10': 106}\n",
            "\n",
            "Session released.\n"
          ]
        }
      ],
      "source": [
        "# ── IBM Quantum ───────────────────────────────────────────────────────\n",
        "qrmi = QuantumResource(BACKEND_NAME, ResourceType.IBMQuantumComputeService)\n",
        "# ResourceType.IBMQuantumSystem is the alternative for directly provisioned systems\n",
        "\n",
        "print(f\"Resource id:   {qrmi.resource_id()}\")\n",
        "print(f\"Resource type: {qrmi.resource_type()}\")\n",
        "print(f\"Accessible:    {qrmi.is_accessible()}\")\n",
        "\n",
        "# Acquire exclusive access — open try/finally immediately so every\n",
        "# subsequent failure (target retrieval, transpilation, submission) is covered.\n",
        "# Release is skipped when running under Slurm: the SPANK plugin owns the\n",
        "# session lifecycle and will release it when the job finishes.\n",
        "lock = qrmi.acquire()\n",
        "print(f\"Lock token:    {lock}\")\n",
        "try:\n",
        "    # Retrieve backend capabilities\n",
        "    transpiler_target = get_target(\n",
        "        qrmi\n",
        "    )  # calls qrmi.target() and parses the JSON\n",
        "    target_json = json.loads(qrmi.target().value)\n",
        "    config = target_json.get(\"configuration\", {})\n",
        "    print(f\"\\nBackend: {config.get('backend_name', 'unknown')}\")\n",
        "    print(f\"Qubits:  {config.get('n_qubits', 'unknown')}\")\n",
        "    print(f\"Gates:   {config.get('basis_gates', [])}\")\n",
        "\n",
        "    # ── IBM Quantum ───────────────────────────────────────────────────\n",
        "\n",
        "    # Build a Bell state circuit\n",
        "    qc = QuantumCircuit(2)\n",
        "    qc.h(0)\n",
        "    qc.cx(0, 1)\n",
        "    qc.measure_all()\n",
        "    print(qc.draw(\"text\"))\n",
        "\n",
        "    # Transpile to ISA using the target retrieved in Step 1\n",
        "    pm = generate_preset_pass_manager(\n",
        "        optimization_level=1, target=transpiler_target\n",
        "    )\n",
        "    isa_circuit = pm.run(qc)\n",
        "    print(f\"\\nTranspiled gate counts: {isa_circuit.count_ops()}\")\n",
        "\n",
        "    # Build the QRMI payload\n",
        "    # Payload.QiskitPrimitive wraps the IBM SamplerV2 input schema:\n",
        "    #   pubs: list of [qasm3_string, parameter_values]  (shots goes at top level)\n",
        "    #   program_id: \"sampler\" or \"estimator\"\n",
        "    shots = 1024\n",
        "    pub = SamplerPub.coerce((isa_circuit,), shots)\n",
        "    qasm3_str = qasm3.dumps(\n",
        "        pub.circuit,\n",
        "        disable_constants=True,\n",
        "        allow_aliasing=True,\n",
        "        experimental=qasm3.ExperimentalFeatures.SWITCH_CASE_V1,\n",
        "    )\n",
        "    # Parameter values as a flat list (empty for non-parametric circuits)\n",
        "    param_array = pub.parameter_values.as_array(\n",
        "        pub.circuit.parameters\n",
        "    ).tolist()\n",
        "\n",
        "    input_json = {\n",
        "        \"pubs\": [\n",
        "            [qasm3_str, param_array]\n",
        "        ],  # list-of-lists; shots at top level\n",
        "        \"version\": 2,\n",
        "        \"support_qiskit\": False,  # True returns binary-encoded Qiskit result\n",
        "        \"shots\": shots,\n",
        "    }\n",
        "    payload = Payload.QiskitPrimitive(\n",
        "        input=json.dumps(input_json), program_id=\"sampler\"\n",
        "    )\n",
        "    print(\"Payload ready\")\n",
        "\n",
        "    # ── IBM Quantum ───────────────────────────────────────────────────\n",
        "\n",
        "    # Submit the job\n",
        "    job_id = qrmi.task_start(payload)\n",
        "    print(f\"Job submitted: {job_id}\")\n",
        "\n",
        "    # Poll until complete\n",
        "    while True:\n",
        "        status = qrmi.task_status(job_id)\n",
        "        print(f\"  Status: {status}\")\n",
        "        if status not in [TaskStatus.Running, TaskStatus.Queued]:\n",
        "            break\n",
        "        time.sleep(5)\n",
        "\n",
        "    print(f\"\\nFinal status: {status}\")\n",
        "\n",
        "    # Retrieve results\n",
        "    # support_qiskit=False → plain JSON; parse directly without ResultDecoder\n",
        "    if status == TaskStatus.Completed:\n",
        "        raw = qrmi.task_result(job_id).value\n",
        "        result = json.loads(raw)\n",
        "        # IBM QCS plain-JSON result shape: {\"results\": [{\"data\": {\"meas\": {\"samples\": [...]}}}]}\n",
        "        # samples is a list of hex-encoded integers; decode to zero-padded bitstrings\n",
        "        samples = result[\"results\"][0][\"data\"][\"meas\"][\"samples\"]\n",
        "        num_bits = sum(reg.size for reg in isa_circuit.cregs)\n",
        "        from collections import Counter\n",
        "\n",
        "        counts = Counter(format(int(s, 16), f\"0{num_bits}b\") for s in samples)\n",
        "        print(f\"\\nMeasurement counts: {dict(counts.most_common(8))}\")\n",
        "        qrmi.task_stop(job_id)\n",
        "    else:\n",
        "        print(f\"Job did not complete. Logs:\\n{qrmi.task_logs(job_id)}\")\n",
        "\n",
        "finally:\n",
        "    # Release only in interactive sessions; under Slurm the SPANK plugin\n",
        "    # manages the session lifecycle and calling release() here would\n",
        "    # prematurely close a session it does not own.\n",
        "    if not os.environ.get(\"SLURM_JOB_ID\"):\n",
        "        qrmi.release(lock)\n",
        "        print(\"\\nSession released.\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "higher-level-header",
      "metadata": {},
      "source": [
        "<span id=\"higher-level-qiskit-interface-qrmiservice-and-samplerv2\" />\n",
        "\n",
        "### Interface de nível superior do Qiskit: QRMIService e SamplerV2\n",
        "\n",
        "O ciclo de vida “raw” acima oferece controle explícito sobre cada chamada. Para fluxos de trabalho padrão do Qiskit, o QRMI fornece uma primitiva `SamplerV2` que implementa `BaseSamplerV2`.\n",
        "\n",
        "<Admonition type=\"note\" title=\"Gerenciamento de ciclo de vida\">\n",
        "  `SamplerV2` cuida da serialização da carga útil, do envio (`task_start`), da consulta e da decodificação dos resultados. Em um ambiente de processamento em lote de HPC (por exemplo, com o Slurm), a alocação e a liberação são gerenciadas pelo agendador e pelo plug-in SPANK. Em uma sessão interativa do Python que utiliza objetos diretos da API de baixo nível, `acquire()` e `release()` podem ser usados para gerenciar explicitamente sessões dedicadas.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "higher-level-sampler",
      "metadata": {
        "tags": [
          "environment-ibm"
        ]
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Using: ibm_kingston (ResourceType.IBMQuantumComputeService)\n",
            "Job ID: dai43jj9k43c73afhrhg | Status: JobStatus.QUEUED\n",
            "Counts (first 5): {'00010': 66, '00100': 28, '11000': 71, '00110': 23, '10100': 14}\n"
          ]
        }
      ],
      "source": [
        "# QRMIService reads QRMI_JOB_QPU_RESOURCES / QRMI_JOB_QPU_TYPES set in Setup or Slurm\n",
        "service = QRMIService()\n",
        "qrmi_svc = service.resources()[0]\n",
        "print(f\"Using: {qrmi_svc.resource_id()} ({qrmi_svc.resource_type()})\")\n",
        "\n",
        "# Build an EfficientSU2 circuit\n",
        "circuit = efficient_su2(5, entanglement=\"linear\")\n",
        "circuit.measure_all()\n",
        "param_values = np.random.rand(circuit.num_parameters)\n",
        "\n",
        "pm = generate_preset_pass_manager(\n",
        "    optimization_level=1, target=get_target(qrmi_svc)\n",
        ")\n",
        "isa_circuit = pm.run(circuit)\n",
        "\n",
        "# SamplerV2 executes jobs against the QRMI resource and decodes results into primitive containers\n",
        "sampler = SamplerV2(qrmi_svc, options={\"default_shots\": 1024})\n",
        "job = sampler.run([(isa_circuit, param_values)])\n",
        "print(f\"Job ID: {job.job_id()} | Status: {job.status()}\")\n",
        "\n",
        "# Poll with retry — re-raise immediately on permanent failures;\n",
        "# only retry on transient network/timeout errors (connection resets, 503s).\n",
        "_TRANSIENT = (\n",
        "    \"503\",\n",
        "    \"Service Unavailable\",\n",
        "    \"ConnectionError\",\n",
        "    \"TimeoutError\",\n",
        "    \"timed out\",\n",
        "    \"Connection reset\",\n",
        ")\n",
        "result = None\n",
        "for attempt in range(60):\n",
        "    try:\n",
        "        result = job.result()  # blocks until complete\n",
        "        break\n",
        "    except Exception as e:\n",
        "        if not any(tok in str(e) for tok in _TRANSIENT):\n",
        "            raise\n",
        "        print(f\"  Transient error on attempt {attempt + 1}: {e}\")\n",
        "        time.sleep(10)\n",
        "\n",
        "if result is not None:\n",
        "    counts = result[0].data.meas.get_counts()\n",
        "    print(f\"Counts (first 5): {dict(list(counts.items())[:5])}\")\n",
        "else:\n",
        "    print(\"Job did not complete after retries.\")\n",
        "\n",
        "if job.errored():\n",
        "    print(f\"Logs:\\n{job.logs()}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "hpc-header",
      "metadata": {},
      "source": [
        "<span id=\"hpc-context-slurm-resource-injection\" />\n",
        "\n",
        "### Contexto de HPC: injeção de recursos no Slurm\n",
        "\n",
        "Em um cluster de HPC, os usuários solicitam recursos quânticos utilizando a sintaxe do Slurm GRES, juntamente com as opções do plug-in QRMI SPANK. O plug-in lida com a injeção de credenciais e recursos automaticamente:\n",
        "\n",
        "```bash\n",
        "#SBATCH --gres=qpu:1\n",
        "#SBATCH --qpu=ibm_kingston\n",
        "python my_workflow.py   # QRMI_JOB_QPU_RESOURCES and QRMI_JOB_QPU_TYPES are already set\n",
        "```\n",
        "\n",
        "O código do aplicativo descobre os recursos alocados a ele em tempo de execução — sem nomes de back-ends codificados:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "hpc-discovery",
      "metadata": {
        "tags": [
          "environment-ibm"
        ]
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Resources allocated by scheduler:\n",
            "  ibm_kingston  (ibm-quantum-compute-service)\n",
            "\n",
            "QRMIService found: ibm_kingston  accessible=True\n"
          ]
        }
      ],
      "source": [
        "# get_job_qpu_resources_and_types() reads QRMI_JOB_QPU_RESOURCES / QRMI_JOB_QPU_TYPES\n",
        "# set by the Slurm SPANK plugin (or manually above in Setup)\n",
        "qpus, qpu_types = get_job_qpu_resources_and_types()\n",
        "print(\"Resources allocated by scheduler:\")\n",
        "for qpu, qpu_type in zip(qpus, qpu_types):\n",
        "    print(f\"  {qpu}  ({qpu_type})\")\n",
        "\n",
        "# QRMIService wraps this into a list of ready QuantumResource objects\n",
        "for r in QRMIService().resources():\n",
        "    print(\n",
        "        f\"\\nQRMIService found: {r.resource_id()}  accessible={r.is_accessible()}\"\n",
        "    )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "large-scale-header",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example-sqd-on-n$_2$\" />\n",
        "\n",
        "## Exemplo de hardware em grande escala: SQD no N $_2$\n",
        "\n",
        "Aqui, reunimos todos os componentes em um fluxo de trabalho completo de química quântica em maior escala, executado em hardware real do IBM Quantum por meio do QRMI.\n",
        "\n",
        "**O SQD** combina os seguintes elementos:\n",
        "\n",
        "1. Amostragem quântica de um modelo Jastrow de aglomerado unitário local (LUCJ) construído utilizando `ffsim` e inicializado a partir de amplitudes CCSD\n",
        "2. Transpilagem orientada para o hardware que se adapta à topologia de rede “heavy-hex” por meio de `generate_lucj_pass_manager`\n",
        "3. Execução de amostragem em hardware d IBM Quantum, gerenciado por meio de e `QRMIService` QRMI `SamplerV2`\n",
        "4. Pós-processamento clássico: recuperação de configuração autoconsistente e diagonalização iterativa de subespaços utilizando `qiskit-addon-sqd`\n",
        "\n",
        "Aplicamos o SQD ao N $_2$, com uma distância de ligação de 1.0 $\\AA$, utilizando um espaço ativo derivado do conjunto de bases `cc-pVDZ` (26 orbitais espaciais, correspondentes a 52 orbitais de spin/qubits).\n",
        "\n",
        "**Energia de referência para o espaço ativo de N $_2$ /cc-pVDZ (distância de ligação 1.0 $\\AA$ ):**\n",
        "\n",
        "* Energia de referência (cálculo SCI separado): **− 109.22802922 Ha**\n",
        "\n",
        "<Admonition type=\"note\" title=\"Precisão da execução salva\">\n",
        "  A execução do SQD abaixo demonstra a execução bem-sucedida do QRMI de ponta a ponta em um hardware d IBM Quantum. Com uma única repetição do LUCJ e 100.000 simulações, o resultado fica aproximadamente 23.7 kcal/mol acima da energia de referência e não atinge a precisão química (≤ 1 kcal/mol). Alterar a `n_reps` contagem de disparos ou o número de iterações do SQD pode melhorar a precisão, mas requer mais testes.\n",
        "</Admonition>\n",
        "\n",
        "Na execução salva, o gerenciador de passagens `ffsim` removeu as interações de spin oposto e `(24, 24)` `(20, 20)` porque o backend não conseguia acomodá-las. Os resultados apresentados utilizam esse circuito ajustado.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "large-scale-all",
      "metadata": {
        "tags": [
          "environment-ibm"
        ]
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "\n",
            "WARN: Unable to to identify input symmetry using original axes.\n",
            "Different symmetry axes will be used.\n",
            "\n",
            "converged SCF energy = -108.929838385609\n",
            "N₂/cc-pVDZ active space: 26 orbitals (52 qubits), (5, 5) electrons\n",
            "SCF energy:       -108.92983839 Ha\n",
            "Reference energy: -109.22802922 Ha\n",
            "E(CCSD) = -109.2177884185545  E_corr = -0.2879500329450047\n",
            "CCSD energy:      -109.21778842 Ha\n",
            "Using QRMI resource: ibm_kingston\n"
          ]
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "LUCJ circuit: 52 qubits, depth 3\n",
            "Transpiled gate counts: OrderedDict([('sx', 7041), ('rz', 6969), ('cz', 1858), ('measure', 52), ('x', 47), ('barrier', 1)])\n",
            "Job submitted via QRMI: dai43o0mhr3c73e7a81g | Status: JobStatus.QUEUED\n",
            "Waiting for results from hardware...\n",
            "Total shots collected: 100000\n",
            "Fraction of valid configurations sampled: 0.00319\n",
            "Expected fraction from uniform random:     9.6079e-07\n",
            "\n",
            "Running SQD post-processing...\n",
            "Iteration 1\n",
            "  Subsample 0: Energy = -109.09341960 Ha | Subspace dim = 208849\n",
            "  Subsample 1: Energy = -109.11738590 Ha | Subspace dim = 204304\n",
            "  Subsample 2: Energy = -109.09947704 Ha | Subspace dim = 212521\n",
            "Iteration 2\n",
            "  Subsample 0: Energy = -109.16015998 Ha | Subspace dim = 332929\n",
            "  Subsample 1: Energy = -109.16823702 Ha | Subspace dim = 319225\n",
            "  Subsample 2: Energy = -109.16189785 Ha | Subspace dim = 336400\n",
            "Iteration 3\n",
            "  Subsample 0: Energy = -109.17759299 Ha | Subspace dim = 471969\n",
            "  Subsample 1: Energy = -109.17937442 Ha | Subspace dim = 512656\n",
            "  Subsample 2: Energy = -109.17970409 Ha | Subspace dim = 504100\n",
            "Iteration 4\n",
            "  Subsample 0: Energy = -109.18410905 Ha | Subspace dim = 608400\n",
            "  Subsample 1: Energy = -109.18265405 Ha | Subspace dim = 636804\n",
            "  Subsample 2: Energy = -109.18608430 Ha | Subspace dim = 657721\n",
            "Iteration 5\n",
            "  Subsample 0: Energy = -109.18870837 Ha | Subspace dim = 846400\n",
            "  Subsample 1: Energy = -109.18890818 Ha | Subspace dim = 848241\n",
            "  Subsample 2: Energy = -109.19022232 Ha | Subspace dim = 804609\n",
            "\n",
            "=== Energy Summary (N₂/cc-pVDZ active space) ===\n",
            "SCF energy:       -108.92983839 Ha\n",
            "Reference energy: -109.22802922 Ha\n",
            "Final SQD energy: -109.19022232 Ha\n",
            "Energy error:     0.03780690 Ha (23.7238 kcal/mol)\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/run-quantum-workloads-with-qrmi/extracted-outputs/large-scale-all-3.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "from qrmi.primitives.ibm import get_backend\n",
        "import math\n",
        "import os\n",
        "import time\n",
        "from functools import partial\n",
        "from dotenv import load_dotenv\n",
        "import numpy as np\n",
        "import matplotlib.pyplot as plt\n",
        "\n",
        "import pyscf\n",
        "import pyscf.gto\n",
        "import pyscf.scf\n",
        "import pyscf.cc\n",
        "import pyscf.mcscf\n",
        "import pyscf.ao2mo\n",
        "\n",
        "import ffsim\n",
        "import ffsim.qiskit\n",
        "from qiskit import QuantumCircuit, QuantumRegister\n",
        "from qiskit_addon_sqd.fermion import (\n",
        "    SCIResult,\n",
        "    diagonalize_fermionic_hamiltonian,\n",
        "    solve_sci_batch,\n",
        ")\n",
        "from qrmi.primitives import QRMIService\n",
        "from qrmi.primitives.ibm import SamplerV2, get_target\n",
        "\n",
        "load_dotenv(override=False)\n",
        "os.environ.setdefault(\"QRMI_JOB_QPU_RESOURCES\", \"ibm_kingston\")\n",
        "os.environ.setdefault(\"QRMI_JOB_QPU_TYPES\", \"ibm-quantum-compute-service\")\n",
        "\n",
        "# ── Step 1: Map classical inputs to a quantum problem ─────────────────\n",
        "\n",
        "# Build N2 molecule at 1.0 Å bond distance\n",
        "mol = pyscf.gto.Mole()\n",
        "mol.build(\n",
        "    atom=[[\"N\", (0, 0, 0)], [\"N\", (1.0, 0, 0)]],\n",
        "    basis=\"cc-pvdz\",\n",
        "    symmetry=\"Dooh\",\n",
        ")\n",
        "\n",
        "# Define active space: freeze 2 core orbitals\n",
        "n_frozen = 2\n",
        "active_space = range(n_frozen, mol.nao_nr())\n",
        "\n",
        "# Get molecular integrals\n",
        "scf = pyscf.scf.RHF(mol).run()\n",
        "norb = len(active_space)\n",
        "n_electrons = int(sum(scf.mo_occ[active_space]))\n",
        "n_alpha = (n_electrons + mol.spin) // 2\n",
        "n_beta = (n_electrons - mol.spin) // 2\n",
        "nelec = (n_alpha, n_beta)\n",
        "\n",
        "cas = pyscf.mcscf.CASCI(scf, norb, nelec)\n",
        "mo = cas.sort_mo(active_space, base=0)\n",
        "hcore, nuclear_repulsion_energy = cas.get_h1cas(mo)\n",
        "eri = pyscf.ao2mo.restore(1, cas.get_h2cas(mo), norb)\n",
        "\n",
        "# Reference energy from external SCI calculation\n",
        "reference_energy = -109.22802921665716\n",
        "\n",
        "print(\n",
        "    f\"N₂/cc-pVDZ active space: {norb} orbitals ({2 * norb} qubits), {nelec} electrons\"\n",
        ")\n",
        "print(f\"SCF energy:       {scf.e_tot:.8f} Ha\")\n",
        "print(f\"Reference energy: {reference_energy:.8f} Ha\")\n",
        "\n",
        "# Get CCSD amplitudes for initializing the LUCJ ansatz\n",
        "ccsd = pyscf.cc.CCSD(\n",
        "    scf, frozen=[i for i in range(mol.nao_nr()) if i not in active_space]\n",
        ").run()\n",
        "t1 = ccsd.t1\n",
        "t2 = ccsd.t2\n",
        "print(f\"CCSD energy:      {ccsd.e_tot:.8f} Ha\")\n",
        "\n",
        "# Discover backend via QRMIService (QRMI_JOB_QPU_RESOURCES set in Setup)\n",
        "service = QRMIService()\n",
        "qrmi_sqd = service.resources()[0]\n",
        "print(f\"Using QRMI resource: {qrmi_sqd.resource_id()}\")\n",
        "\n",
        "# get_backend() wraps the QRMI resource as a Qiskit backend for layout synthesis\n",
        "\n",
        "backend = get_backend(qrmi_sqd)\n",
        "\n",
        "# Set ansatz properties\n",
        "n_reps = 1\n",
        "pairs_aa = [(p, p + 1) for p in range(norb - 1)]\n",
        "pairs_ab = None\n",
        "\n",
        "# Create pass manager adapted to hardware heavy-hex topology\n",
        "pass_manager, pairs_ab = ffsim.qiskit.generate_lucj_pass_manager(\n",
        "    backend=backend,\n",
        "    norb=norb,\n",
        "    connectivity=\"heavy-hex\",\n",
        "    interaction_pairs=(pairs_aa, pairs_ab),\n",
        "    optimization_level=3,\n",
        ")\n",
        "\n",
        "# Create the compressed LUCJ ansatz operator\n",
        "ucj_op = ffsim.UCJOpSpinBalanced.from_t_amplitudes(\n",
        "    t2=t2,\n",
        "    t1=t1,\n",
        "    n_reps=n_reps,\n",
        "    interaction_pairs=(pairs_aa, pairs_ab),\n",
        "    optimize=True,\n",
        "    options=dict(maxiter=1000),\n",
        ")\n",
        "\n",
        "# Assemble the circuit\n",
        "qubits = QuantumRegister(2 * norb, name=\"q\")\n",
        "circuit = QuantumCircuit(qubits)\n",
        "circuit.append(ffsim.qiskit.PrepareHartreeFockJW(norb, nelec), qubits)\n",
        "circuit.append(ffsim.qiskit.UCJOpSpinBalancedJW(ucj_op), qubits)\n",
        "circuit.measure_all()\n",
        "print(f\"LUCJ circuit: {circuit.num_qubits} qubits, depth {circuit.depth()}\")\n",
        "\n",
        "# ── Step 2: Optimize for quantum hardware execution ───────────────────\n",
        "\n",
        "isa_circuit = pass_manager.run(circuit)\n",
        "print(f\"Transpiled gate counts: {isa_circuit.count_ops()}\")\n",
        "\n",
        "# ── Step 3: Execute using Qiskit primitives (QRMI SamplerV2) ─────────\n",
        "\n",
        "sampler = SamplerV2(qrmi_sqd, options={\"default_shots\": 100_000})\n",
        "# sampler.options.environment.job_tags = [\"TUT_SQD\"]\n",
        "job = sampler.run([(isa_circuit,)])\n",
        "print(f\"Job submitted via QRMI: {job.job_id()} | Status: {job.status()}\")\n",
        "print(\"Waiting for results from hardware...\")\n",
        "\n",
        "_TRANSIENT = (\n",
        "    \"503\",\n",
        "    \"Service Unavailable\",\n",
        "    \"ConnectionError\",\n",
        "    \"TimeoutError\",\n",
        "    \"timed out\",\n",
        "    \"Connection reset\",\n",
        ")\n",
        "primitive_result = None\n",
        "for attempt in range(120):\n",
        "    try:\n",
        "        primitive_result = job.result()\n",
        "        break\n",
        "    except Exception as e:\n",
        "        if not any(tok in str(e) for tok in _TRANSIENT):\n",
        "            raise\n",
        "        print(f\"  Transient error on attempt {attempt + 1}: {e}\")\n",
        "        time.sleep(10)\n",
        "\n",
        "if primitive_result is None:\n",
        "    raise RuntimeError(\"Job did not complete after retries\")\n",
        "\n",
        "pub_result = primitive_result[0]\n",
        "bit_array = pub_result.data.meas\n",
        "print(f\"Total shots collected: {bit_array.num_shots}\")\n",
        "\n",
        "# ── Step 4: Post-process and return result in classical format ────────\n",
        "\n",
        "\n",
        "def is_valid_bitstring(\n",
        "    bitstring: str, norb: int, nelec: tuple[int, int]\n",
        ") -> bool:\n",
        "    n_a, n_b = nelec\n",
        "    return (\n",
        "        len(bitstring) == 2 * norb\n",
        "        and bitstring[norb:].count(\"1\") == n_a\n",
        "        and bitstring[:norb].count(\"1\") == n_b\n",
        "    )\n",
        "\n",
        "\n",
        "num_valid = sum(\n",
        "    is_valid_bitstring(b, norb, nelec) for b in bit_array.get_bitstrings()\n",
        ")\n",
        "valid_fraction = num_valid / bit_array.num_shots\n",
        "expected_random = (\n",
        "    math.comb(norb, n_alpha) * math.comb(norb, n_beta) / (2 ** (2 * norb))\n",
        ")\n",
        "print(f\"Fraction of valid configurations sampled: {valid_fraction:.5f}\")\n",
        "print(f\"Expected fraction from uniform random:     {expected_random:.4e}\")\n",
        "\n",
        "# Configure SQD eigensolver\n",
        "energy_tol = 1e-3\n",
        "occupancies_tol = 1e-3\n",
        "max_iterations = 5\n",
        "num_batches = 3\n",
        "samples_per_batch = 300\n",
        "symmetrize_spin = True\n",
        "carryover_threshold = 1e-4\n",
        "max_cycle = 200\n",
        "\n",
        "# Hartree-Fock initial occupancy guess\n",
        "initial_occupancies = (\n",
        "    np.array([1] * n_alpha + [0] * (norb - n_alpha)),\n",
        "    np.array([1] * n_beta + [0] * (norb - n_beta)),\n",
        ")\n",
        "\n",
        "sci_solver = partial(solve_sci_batch, spin_sq=0.0, max_cycle=max_cycle)\n",
        "result_history = []\n",
        "\n",
        "\n",
        "def callback(results: list[SCIResult]):\n",
        "    result_history.append(results)\n",
        "    iteration = len(result_history)\n",
        "    print(f\"Iteration {iteration}\")\n",
        "    for i, res in enumerate(results):\n",
        "        subspace_dim = np.prod(res.sci_state.amplitudes.shape)\n",
        "        print(\n",
        "            f\"  Subsample {i}: Energy = {res.energy + nuclear_repulsion_energy:.8f} Ha | Subspace dim = {subspace_dim}\"\n",
        "        )\n",
        "\n",
        "\n",
        "print(\"\\nRunning SQD post-processing...\")\n",
        "rng = np.random.default_rng(42)\n",
        "sqd_result = diagonalize_fermionic_hamiltonian(\n",
        "    hcore,\n",
        "    eri,\n",
        "    bit_array,\n",
        "    samples_per_batch=samples_per_batch,\n",
        "    norb=norb,\n",
        "    nelec=nelec,\n",
        "    num_batches=num_batches,\n",
        "    energy_tol=energy_tol,\n",
        "    occupancies_tol=occupancies_tol,\n",
        "    max_iterations=max_iterations,\n",
        "    sci_solver=sci_solver,\n",
        "    symmetrize_spin=symmetrize_spin,\n",
        "    initial_occupancies=initial_occupancies,\n",
        "    carryover_threshold=carryover_threshold,\n",
        "    callback=callback,\n",
        "    seed=rng,\n",
        ")\n",
        "\n",
        "final_energy = sqd_result.energy + nuclear_repulsion_energy\n",
        "energy_error = final_energy - reference_energy\n",
        "\n",
        "print(\"\\n=== Energy Summary (N₂/cc-pVDZ active space) ===\")\n",
        "print(f\"SCF energy:       {scf.e_tot:.8f} Ha\")\n",
        "print(f\"Reference energy: {reference_energy:.8f} Ha\")\n",
        "print(f\"Final SQD energy: {final_energy:.8f} Ha\")\n",
        "print(\n",
        "    f\"Energy error:     {energy_error:.8f} Ha ({abs(energy_error) * 627.5:.4f} kcal/mol)\"\n",
        ")\n",
        "\n",
        "# ── Visualization ─────────────────────────────────────────────────────\n",
        "\n",
        "x1 = range(len(result_history))\n",
        "min_e = [\n",
        "    min(res, key=lambda r: r.energy).energy + nuclear_repulsion_energy\n",
        "    for res in result_history\n",
        "]\n",
        "e_diff = [abs(e - reference_energy) for e in min_e]\n",
        "chem_accuracy = 0.001  # ~1 mHa / ~0.6 kcal/mol\n",
        "\n",
        "y2 = np.sum(sqd_result.orbital_occupancies, axis=0)\n",
        "x2 = range(len(y2))\n",
        "\n",
        "fig, axs = plt.subplots(1, 2, figsize=(12, 5))\n",
        "\n",
        "# Energies convergence plot\n",
        "axs[0].plot(x1, e_diff, label=\"Energy error\", marker=\"o\")\n",
        "axs[0].set_xticks(list(x1))\n",
        "axs[0].set_xticklabels(list(x1))\n",
        "axs[0].set_yscale(\"log\")\n",
        "axs[0].axhline(\n",
        "    y=chem_accuracy,\n",
        "    color=\"#BF5700\",\n",
        "    linestyle=\"--\",\n",
        "    label=\"Chemical accuracy (1 mHa)\",\n",
        ")\n",
        "axs[0].set_title(\"SQD Energy Error vs Iteration\")\n",
        "axs[0].set_xlabel(\"Iteration\")\n",
        "axs[0].set_ylabel(\"Energy Error (Ha)\")\n",
        "axs[0].legend()\n",
        "\n",
        "# Spatial orbital occupancy plot\n",
        "axs[1].bar(x2, y2, width=0.8)\n",
        "axs[1].set_xticks(list(x2)[::2])\n",
        "axs[1].set_xticklabels(list(x2)[::2])\n",
        "axs[1].set_title(\"Avg Occupancy per Spatial Orbital\")\n",
        "axs[1].set_xlabel(\"Spatial Orbital Index\")\n",
        "axs[1].set_ylabel(\"Avg Occupancy\")\n",
        "\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "next-steps",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Próximas etapas\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Recomendações\">\n",
        "  Se você achou este trabalho interessante, talvez se interesse pelo material a seguir:\n",
        "\n",
        "  * [Tutorial sobre diagonalização quântica baseada em amostras](/docs/tutorials/sample-based-quantum-diagonalization) — o fluxo de trabalho completo de química SQD no IBM Quantum Platform, incluindo moléculas maiores e conjuntos de bases\n",
        "  * [Diagonalização quântica de Krylov baseada em amostras](/docs/tutorials/sample-based-krylov-quantum-diagonalization) — um método relacionado que utiliza circuitos de evolução temporal para modelos de rede fermiónica\n",
        "  * [`qiskit-addon-sqd` documentação](/docs/addons/qiskit-addon-sqd) — referência completa da API e tutoriais adicionais para a biblioteca de pós-processamento SQD\n",
        "  * [Repositório QRMI GitHub](https://github.com/qiskit-community/qrmi) — código-fonte, exemplos adicionais de back-end (CUDA-Q, C, Lua)\n",
        "  * [Documento de visão geral do QRMI](https://arxiv.org/abs/2506.10052) — descrição técnica da arquitetura do QRMI e da integração com HPC\n",
        "  * [IBM Quantum Compute Guia de sessões de serviço](/docs/guides/run-jobs-session) — como as sessões se relacionam com o QRMI `acquire`/`release` ciclo de vida dos backends d IBM\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "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"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}