{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "f502c3c3-d0be-4be4-afab-2d569f171f98",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"hamiltonianos\"\n",
        "description: \"Se revisa la construcción y el mapeo de hamiltonianos para aplicaciones químicas cuánticas. Utilice un mapeador Jordan-Wigner personalizado y aproveche PySCF.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore pxxr prqs nelecas Lipinska mcscf chmax Dmax vmax ecore ncas Excp disp antisymmetry Coov irrep IIZZ ZZII workstreams openfermion webkitallowfullscreen allowfullscreen frameborder referrerpolicy Kassal Tranter Ardle Seeley */}\n",
        "\n",
        "<span id=\"hamiltonians-for-quantum-chemistry\" />\n",
        "\n",
        "# Hamiltonianos para la química cuántica\n",
        "\n",
        "Comencemos con un breve repaso del papel que desempeñan los hamiltonianos en la VQE.\n",
        "\n",
        "<span id=\"the-hamiltonian-in-vqe-overview\" />\n",
        "\n",
        "## El hamiltoniano en VQE Descripción general\n",
        "\n",
        "La Dra. Victoria Lipinska nos habla de los hamiltonianos y de cómo mapearlos para su uso en computación cuántica.\n",
        "\n",
        "<IBMVideo id=\"132414924\" title=\"Victoria Lipinska nos recuerda qué es un hamiltoniano y cómo se utiliza en VQE. En particular, describe la necesidad de mapear un Hamiltoniano fermiónico que gobierna fermiones indistinguibles a un Hamiltoniano qubit.\" />\n",
        "\n",
        "<span id=\"references\" />\n",
        "\n",
        "### Referencias\n",
        "\n",
        "En el vídeo se hace referencia a los siguientes artículos.\n",
        "\n",
        "* [Algoritmos cuánticos para simulaciones fermiónicas, Ortiz, et al.](https://arxiv.org/pdf/cond-mat/0012334.pdf)\n",
        "* [Simulación de la química mediante ordenadores cuánticos, Kassal et al.](https://arxiv.org/pdf/1007.2648.pdf)\n",
        "* [A Comparison of the Bravyi-Kitaev and Jordan-Wigner Transformations for the Quantum Simulation of Quantum Chemistry, Tranter, et al.](https://pubs.acs.org/doi/full/10.1021/acs.jctc.8b00450)\n",
        "* [Quantum Chemistry in the Age of Quantum Computing, Cao, et al.](https://arxiv.org/pdf/1812.09976.pdf)\n",
        "* [Química cuántica computacional, McArdle, et al.](https://arxiv.org/pdf/1808.10402.pdf)\n",
        "* [La transformación Bravyi-Kitaev para el cálculo cuántico de la estructura electrónica, Seeley, et al., McArdle, et al.](https://arxiv.org/pdf/1812.09976.pdf)\n",
        "\n",
        "<span id=\"preparing-hamiltonians-for-quantum-chemistry\" />\n",
        "\n",
        "## Preparando a los habitantes de Hamilton para la química cuántica\n",
        "\n",
        "Un buen primer paso para aplicar la computación cuántica a un problema químico es definir un Hamiltoniano para el sistema de interés. Aquí, restringiremos la discusión a los Hamiltonianos de la química cuántica, ya que esos Hamiltonianos requieren algún mapeo específico para sistemas de fermiones idénticos.\n",
        "\n",
        "Como alguien que trabaja en química cuántica, probablemente ya tiene su software favorito para modelar moléculas, que puede generar un Hamiltoniano que describa su sistema de interés. Aquí, utilizaremos código construido únicamente en PySCF, numpy, y Qiskit. Pero el proceso de preparación hamiltoniana se traslada también a las soluciones preenvasadas. La única diferencia entre este enfoque y otro software serán pequeñas diferencias de sintaxis; algunas de ellas se abordan en la subsección \"Software de terceros\" para facilitar la integración de los flujos de trabajo existentes.\n",
        "\n",
        "La generación de un Hamiltoniano de química cuántica para su uso en IBM Quantum® QPUs implica los siguientes pasos:\n",
        "\n",
        "1. Defina su molécula (geometría, espín, espacio activo, etc.)\n",
        "2. Generar el Hamiltoniano fermiónico (operadores de creación y aniquilación)\n",
        "3. Mapa del Hamiltoniano fermiónico a un operador bosónico (en este contexto, utilizando operadores de Pauli)\n",
        "4. Si utiliza software de terceros: Solucione cualquier desajuste de sintaxis entre el software generador y Qiskit\n",
        "\n",
        "El hamiltoniano fermiónico se escribe en términos de operadores fermiónicos y, en particular, tiene en cuenta que los electrones son fermiones indistinguibles. Eso significa que obedecen a estadísticas completamente diferentes de las de los qubits bosónicos distinguibles. De ahí el proceso de mapeo.\n",
        "\n",
        "Quienes ya estén familiarizados con estos procesos pueden saltarse esta sección.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "28e3a254-be8c-4bca-a467-3a92d0ddb1ae",
      "metadata": {},
      "source": [
        "Objetivo:\n",
        "\n",
        "El objetivo final es obtener un Hamiltoniano de la forma:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "b802fd0d-40d5-45f1-9cdc-4ae4c5914d82",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[(1, 'XX'), (1, 'YY'), (1, 'ZZ')]\n"
          ]
        }
      ],
      "source": [
        "H = [(1, \"XX\"), (1, \"YY\"), (1, \"ZZ\")]\n",
        "print(H)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "dac88e28-2554-4370-90c8-06c4e1042255",
      "metadata": {},
      "source": [
        "O\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "3375bd43-b53d-479e-93f5-7ec37d9e6a34",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "SparsePauliOp(['XX', 'YY', 'ZZ'],\n",
            "              coeffs=[1.+0.j, 1.+0.j, 1.+0.j])\n"
          ]
        }
      ],
      "source": [
        "from qiskit.quantum_info import SparsePauliOp\n",
        "\n",
        "H = SparsePauliOp([\"XX\", \"YY\", \"ZZ\"], coeffs=[1.0 + 0.0j, 1.0 + 0.0j, 1.0 + 0.0j])\n",
        "print(H)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f92e93d1-270d-4c0c-a3bb-353b790222a7",
      "metadata": {},
      "source": [
        "Empezaremos importando algunos paquetes:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "f8833e31-77e6-446b-9d3c-1e394fbe1661",
      "metadata": {},
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "from pyscf import ao2mo, gto, mcscf, scf"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "88603887-e5bc-4905-b8e6-ee78b8473d7d",
      "metadata": {},
      "source": [
        "1. Defina su molécula\n",
        "\n",
        "Aquí especificaremos atributos de la molécula de interés. En este ejemplo, hemos elegido hidrógeno diatómico (porque los hamiltonianos resultantes son lo suficientemente cortos como para mostrarlos).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2d55595c-45f2-4e90-a3d1-22b6ee51ccc6",
      "metadata": {},
      "source": [
        "El marco Python -based Simulations of Chemistry Framework ( [PySCF](https://pyscf.org/) ) dispone de una amplia colección de módulos de estructura electrónica que pueden utilizarse, entre otras cosas, para generar hamiltonianos moleculares adecuados para la computación cuántica. La guía de [inicio rápido PySCF](https://pyscf.org/quickstart.html) es un recurso excelente para obtener una descripción completa de todas las variables y funcionalidades. Sólo daremos una visión muy somera, puesto que a muchos de ustedes ya les resultará familiar. Para entenderlos mejor, visite [PySCF.](https://pyscf.org/)\n",
        "Brevemente:\n",
        "\n",
        "**distancia** puede utilizarse para moléculas diatómicas, o simplemente especificar coordenadas cartesianas para cada átomo. Las distancias están en unidades de Angstrom.\n",
        "\n",
        "**gto** genera orbitales de tipo gaussiano.\n",
        "\n",
        "**base** se refiere a las funciones utilizadas para modelar los orbitales moleculares. Aquí ' sto-6g ' es una base mínima común, llamada así para ajustar orbitales tipo Slater usando 6 orbitales Gaussianos primitivos.\n",
        "\n",
        "**spin** un valor entero que indica el número de electrones no apareados (igual a $2S$ ). Tenga en cuenta que algunos programas utilizan la multiplicidad en su lugar ( $2S+1$ ).\n",
        "\n",
        "**carga** la carga de la molécula.\n",
        "\n",
        "**simetría** - el grupo de simetría puntual de la molécula, especificado con una cadena o detectado automáticamente estableciendo \"simetría = True\". Aquí \"Dooh\" es el grupo de simetría apropiado para moléculas diatómicas con dos de las mismas especies de átomos.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "dc4d672b-e73a-4822-b158-b17736d9a197",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<pyscf.gto.mole.Mole at 0x7fc718f07610>"
            ]
          },
          "execution_count": 4,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "distance = 0.735\n",
        "a = distance / 2\n",
        "mol = gto.Mole()\n",
        "mol.build(\n",
        "    verbose=0,\n",
        "    atom=[\n",
        "        [\"H\", (0, 0, -a)],\n",
        "        [\"H\", (0, 0, a)],\n",
        "    ],\n",
        "    basis=\"sto-6g\",\n",
        "    spin=0,\n",
        "    charge=0,\n",
        "    symmetry=\"Dooh\",\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2da25c9c-f517-497d-8668-70629e817e3a",
      "metadata": {},
      "source": [
        "Hay que tener en cuenta que se puede describir la energía total (que incluye la energía de repulsión nuclear además de la electrónica), la energía total de los orbitales electrónicos o la energía de algún subconjunto de orbitales electrónicos (con el subconjunto complementario congelado). En el caso concreto de $\\text{H}_2$, obsérvense las diferentes energías a continuación, y nótese que la energía total menos la energía de repulsión nuclear da, de hecho, la energía electrónica:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "a22e88c6-ef7d-4089-9461-28ed70ebae13",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "0.7199689944489797 -1.8455976628764188 -1.125628668427439 -1.8455976628764188\n"
          ]
        }
      ],
      "source": [
        "mf = scf.RHF(mol)\n",
        "mf.scf()\n",
        "\n",
        "print(\n",
        "    mf.energy_nuc(),\n",
        "    mf.energy_elec()[0],\n",
        "    mf.energy_tot(),\n",
        "    mf.energy_tot() - mol.energy_nuc(),\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "7e1a23ba-d2ac-4961-8c29-8be7e1163c9e",
      "metadata": {},
      "outputs": [],
      "source": [
        "active_space = range(mol.nelectron // 2 - 1, mol.nelectron // 2 + 1)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5ee2e242-6508-4a0d-87a7-b7faa897f36d",
      "metadata": {},
      "source": [
        "2. Generar el Hamiltoniano fermiónico\n",
        "\n",
        "**scf** hace referencia a una amplia gama de métodos de campo autoconsistente.\n",
        "\n",
        "**rhf** como en **mf** = scf.RHF (mol) en mf es un solver que utiliza el cálculo Restricted Hartree Fock. El núcleo de esto (E, abajo) es la energía total, incluyendo la repulsión nuclear y los orbitales moleculares.\n",
        "\n",
        "**mcscf** es un paquete de campos autoconsistentes multiconfiguración.\n",
        "\n",
        "**ao2mo** es una transformación de orbitales atómicos a orbitales moleculares.\n",
        "\n",
        "También utilizamos las siguientes variables:\n",
        "\n",
        "**ncas** : número de orbitales en el espacio activo completo\n",
        "\n",
        "**nelecas** : número de electrones en el espacio activo completo\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "e58fe57a-8135-429d-bd53-2ad4f0a93806",
      "metadata": {},
      "outputs": [],
      "source": [
        "E1 = mf.kernel()\n",
        "mx = mcscf.CASCI(mf, ncas=2, nelecas=(1, 1))\n",
        "mo = mx.sort_mo(active_space, base=0)\n",
        "E2 = mx.kernel(mo)[:2]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d77b6abe-a0c3-467e-9551-14bdad6b0fd0",
      "metadata": {},
      "source": [
        "Queremos un Hamiltoniano, y esto a menudo se separa en energía de un núcleo electrónico (ecore, no involucrado en la minimización), operadores de un solo electrón ( h1e ), y energías de dos electrones ( h2e ). A continuación se extraen explícitamente en las dos últimas líneas.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "1025a53a-7414-47f3-9b78-10acb03615d4",
      "metadata": {},
      "outputs": [],
      "source": [
        "h1e, ecore = mx.get_h1eff()\n",
        "h2e = ao2mo.restore(1, mx.get_h2eff(), mx.ncas)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "928d72cf-212f-4f85-9d81-a7ffb33d6db8",
      "metadata": {},
      "source": [
        "Estos hamiltonianos son actualmente operadores fermiónicos (creación y aniquilación), aplicables a sistemas de fermiones (indistinguibles), y correspondientemente sujetos a antisimetría bajo intercambio. Esto da lugar a una estática diferente de la que se aplicaría a un sistema distinguible o bosónico. Para realizar cálculos en IBM Quantum QPUs, necesitamos un operador bosónico que describa la energía. El resultado de dicho mapeo se escribe convencionalmente en términos de operadores de Pauli, ya que ambos son hermitianos y unitarios. Se pueden utilizar varias correspondencias. Una de las más sencillas es la transformación de Jordan Wigner.\n",
        "\n",
        "3. Trazado del Hamiltoniano\n",
        "\n",
        "Hay que tener en cuenta que existen muchas herramientas para convertir un Hamiltoniano químico en uno adecuado para ser ejecutado en un ordenador cuántico. Aquí, implementamos el mapeo Jordan Wigner directamente usando sólo PySCF, numpy, y Qiskit. A continuación comentamos las consideraciones de sintaxis para otras soluciones.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f90c2756-8155-4417-89e9-f248ae76895c",
      "metadata": {},
      "source": [
        "La función Cholesky nos ayuda a obtener una descomposición de bajo rango de los términos de dos electrones en el Hamiltoniano.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "af2deda9-75e3-464f-a4e3-489e00631e92",
      "metadata": {},
      "outputs": [],
      "source": [
        "def cholesky(V, eps):\n",
        "    # see https://arxiv.org/pdf/1711.02242.pdf section B2\n",
        "    # see https://arxiv.org/abs/1808.02625\n",
        "    # see https://arxiv.org/abs/2104.08957\n",
        "    no = V.shape[0]\n",
        "    chmax, ng = 20 * no, 0\n",
        "    W = V.reshape(no**2, no**2)\n",
        "    L = np.zeros((no**2, chmax))\n",
        "    Dmax = np.diagonal(W).copy()\n",
        "    nu_max = np.argmax(Dmax)\n",
        "    vmax = Dmax[nu_max]\n",
        "    while vmax > eps:\n",
        "        L[:, ng] = W[:, nu_max]\n",
        "        if ng > 0:\n",
        "            L[:, ng] -= np.dot(L[:, 0:ng], (L.T)[0:ng, nu_max])\n",
        "        L[:, ng] /= np.sqrt(vmax)\n",
        "        Dmax[: no**2] -= L[: no**2, ng] ** 2\n",
        "        ng += 1\n",
        "        nu_max = np.argmax(Dmax)\n",
        "        vmax = Dmax[nu_max]\n",
        "    L = L[:, :ng].reshape((no, no, ng))\n",
        "    print(\n",
        "        \"accuracy of Cholesky decomposition \",\n",
        "        np.abs(np.einsum(\"prg,qsg->prqs\", L, L) - V).max(),\n",
        "    )\n",
        "    return L, ng"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a6fae700-aac9-445d-97f5-4274ede58428",
      "metadata": {},
      "source": [
        "Las funciones `identity` y `creators_destructors` sustituyen los operadores de creación y aniquilación en el Hamiltoniano fermiónico por operadores de Pauli; `creators_destructors` utiliza el mapeo de Jordan-Wigner.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "b6c0a6a7-94e8-448d-b911-858bc79e6242",
      "metadata": {},
      "outputs": [],
      "source": [
        "def identity(n):\n",
        "    return SparsePauliOp.from_list([(\"I\" * n, 1)])\n",
        "\n",
        "\n",
        "def creators_destructors(n, mapping=\"jordan_wigner\"):\n",
        "    c_list = []\n",
        "    if mapping == \"jordan_wigner\":\n",
        "        for p in range(n):\n",
        "            if p == 0:\n",
        "                ell, r = \"I\" * (n - 1), \"\"\n",
        "            elif p == n - 1:\n",
        "                ell, r = \"\", \"Z\" * (n - 1)\n",
        "            else:\n",
        "                ell, r = \"I\" * (n - p - 1), \"Z\" * p\n",
        "            cp = SparsePauliOp.from_list([(ell + \"X\" + r, 0.5), (ell + \"Y\" + r, -0.5j)])\n",
        "            c_list.append(cp)\n",
        "    else:\n",
        "        raise ValueError(\"Unsupported mapping.\")\n",
        "    d_list = [cp.adjoint() for cp in c_list]\n",
        "    return c_list, d_list"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "04d51259-2188-4c62-9171-c78f74873dd8",
      "metadata": {},
      "source": [
        "Por último, `build_hamiltonian` utiliza las funciones `cholesky`, `identity`, y `creators_destructors` para crear el Hamiltoniano final adecuado para su ejecución en un ordenador cuántico.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "6eeb704d-202c-4c54-8f1a-a2d98f99342e",
      "metadata": {},
      "outputs": [],
      "source": [
        "def build_hamiltonian(ecore: float, h1e: np.ndarray, h2e: np.ndarray) -> SparsePauliOp:\n",
        "    ncas, _ = h1e.shape\n",
        "\n",
        "    C, D = creators_destructors(2 * ncas, mapping=\"jordan_wigner\")\n",
        "    Exc = []\n",
        "    for p in range(ncas):\n",
        "        Excp = [C[p] @ D[p] + C[ncas + p] @ D[ncas + p]]\n",
        "        for r in range(p + 1, ncas):\n",
        "            Excp.append(\n",
        "                C[p] @ D[r]\n",
        "                + C[ncas + p] @ D[ncas + r]\n",
        "                + C[r] @ D[p]\n",
        "                + C[ncas + r] @ D[ncas + p]\n",
        "            )\n",
        "        Exc.append(Excp)\n",
        "\n",
        "    # low-rank decomposition of the Hamiltonian\n",
        "    Lop, ng = cholesky(h2e, 1e-6)\n",
        "    t1e = h1e - 0.5 * np.einsum(\"pxxr->pr\", h2e)\n",
        "\n",
        "    H = ecore * identity(2 * ncas)\n",
        "    # one-body term\n",
        "    for p in range(ncas):\n",
        "        for r in range(p, ncas):\n",
        "            H += t1e[p, r] * Exc[p][r - p]\n",
        "    # two-body term\n",
        "    for g in range(ng):\n",
        "        Lg = 0 * identity(2 * ncas)\n",
        "        for p in range(ncas):\n",
        "            for r in range(p, ncas):\n",
        "                Lg += Lop[p, r, g] * Exc[p][r - p]\n",
        "        H += 0.5 * Lg @ Lg\n",
        "\n",
        "    return H.chop().simplify()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1912f38d-73ee-4d9f-a89b-a3f08b7f65a0",
      "metadata": {},
      "source": [
        "Por último, utilizamos `build_hamiltonian` para construir nuestro Hamiltoniano qubit a partir de operadores Pauli utilizando la transformación Jordan-Wigner. Esto también nos da la precisión de la descomposición Cholesky que hemos utilizado.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "4e951f66-6632-4f07-973e-6c505a1b7a87",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "accuracy of Cholesky decomposition  2.220446049250313e-16\n",
            "SparsePauliOp(['IIII', 'IIIZ', 'IZII', 'IIZI', 'ZIII', 'IZIZ', 'IIZZ', 'ZIIZ', 'IZZI', 'ZZII', 'ZIZI', 'YYYY', 'XXYY', 'YYXX', 'XXXX'],\n",
            "              coeffs=[-0.09820182+0.j, -0.1740751 +0.j, -0.1740751 +0.j,  0.2242933 +0.j,\n",
            "  0.2242933 +0.j,  0.16891402+0.j,  0.1210099 +0.j,  0.16631441+0.j,\n",
            "  0.16631441+0.j,  0.1210099 +0.j,  0.17504456+0.j,  0.04530451+0.j,\n",
            "  0.04530451+0.j,  0.04530451+0.j,  0.04530451+0.j])\n"
          ]
        }
      ],
      "source": [
        "H = build_hamiltonian(ecore, h1e, h2e)\n",
        "print(H)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b211c7f6-7ef4-444d-84f9-bfe4b80dda5a",
      "metadata": {},
      "source": [
        "Este [cuaderno de moléculas de ejemplo](https://ibm.ent.box.com/public/static/59c2kjqefvjy5wy5lct099thr4y0ry8c.zip) muestra la configuración y los Hamiltonianos para varias moléculas de complejidad variable; con una pequeña modificación, esto debería permitirle examinar la mayoría de las moléculas pequeñas.\n",
        "\n",
        "Señalemos brevemente dos puntos importantes a tener en cuenta al construir los operadores fermiónicos de una molécula. Al cambiar el tipo de molécula, cambiará la simetría. Del mismo modo, cambiará el número de orbitales con distintas simetrías, como el de simetría cilíndrica \" A1 \". Estos cambios son evidentes incluso con la simple ampliación a LiH,, como se ve aquí:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "400658f1-ce74-460d-8bd6-f78c715982e1",
      "metadata": {},
      "outputs": [],
      "source": [
        "distance = 1.56\n",
        "mol = gto.Mole()\n",
        "mol.build(\n",
        "    verbose=0,\n",
        "    atom=[[\"Li\", (0, 0, 0)], [\"H\", (0, 0, distance)]],\n",
        "    basis=\"sto-6g\",\n",
        "    spin=0,\n",
        "    charge=0,\n",
        "    symmetry=\"Coov\",\n",
        ")\n",
        "mf = scf.RHF(mol)\n",
        "E1 = mf.kernel()\n",
        "\n",
        "# %% ----------------------------------------------------------------------------------------------\n",
        "\n",
        "mx = mcscf.CASCI(mf, ncas=5, nelecas=(1, 1))\n",
        "cas_space_symmetry = {\"A1\": 3, \"E1x\": 1, \"E1y\": 1}\n",
        "mo = mcscf.sort_mo_by_irrep(mx, mf.mo_coeff, cas_space_symmetry)\n",
        "E2 = mx.kernel(mo)[:2]\n",
        "h1e, ecore = mx.get_h1eff()\n",
        "h2e = ao2mo.restore(1, mx.get_h2eff(), mx.ncas)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a7c91a40-c0ee-47ab-a33f-9adfd9f2bd18",
      "metadata": {},
      "source": [
        "También vale la pena señalar que uno puede perder rápidamente la intuición para el Hamiltoniano final resultante. El Hamiltoniano para LiH (utilizando el mapeador Jordan-Wigner) ya consta de 276 términos.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "d3269648-538e-41a5-b343-f75e3ffadb28",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "accuracy of Cholesky decomposition  1.1102230246251565e-16\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "276"
            ]
          },
          "execution_count": 14,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "len(build_hamiltonian(ecore, h1e, h2e))"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "aee74cff-d3b4-46db-b511-e4f9686b56de",
      "metadata": {},
      "source": [
        "En caso de duda con respecto a las simetrías, también se puede generar alguna información de simetría para la molécula estableciendo `symmetry = True` y `verbose = 4`:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "29fa8e0b-9481-4ab6-841d-e2ba0a076986",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "System: uname_result(system='Linux', node='IBM-R912JTRT', release='5.10.102.1-microsoft-standard-WSL2', version='#1 SMP Wed Mar 2 00:30:59 UTC 2022', machine='x86_64')  Threads 16\n",
            "Python 3.11.12 (main, May 16 2025, 02:33:32) [GCC 11.4.0]\n",
            "numpy 2.3.1  scipy 1.16.0  h5py 3.14.0\n",
            "Date: Mon Jun 30 12:56:55 2025\n",
            "PySCF version 2.9.0\n",
            "PySCF path  /home/porter284/.pyenv/versions/3.11.12/lib/python3.11/site-packages/pyscf\n",
            "\n",
            "[CONFIG] conf_file None\n",
            "[INPUT] verbose = 4\n",
            "[INPUT] num. atoms = 2\n",
            "[INPUT] num. electrons = 4\n",
            "[INPUT] charge = 0\n",
            "[INPUT] spin (= nelec alpha-beta = 2S) = 0\n",
            "[INPUT] symmetry True subgroup None\n",
            "[INPUT] Mole.unit = angstrom\n",
            "[INPUT] Symbol           X                Y                Z      unit          X                Y                Z       unit  Magmom\n",
            "[INPUT]  1 Li     0.000000000000   0.000000000000   0.000000000000 AA    0.000000000000   0.000000000000   0.000000000000 Bohr   0.0\n",
            "[INPUT]  2 H      0.000000000000   0.000000000000   1.560000000000 AA    0.000000000000   0.000000000000   2.947972754321 Bohr   0.0\n",
            "\n",
            "nuclear repulsion = 1.01764848253846\n",
            "point group symmetry = Coov\n",
            "symmetry origin: [0.         0.         0.73699319]\n",
            "symmetry axis x: [1. 0. 0.]\n",
            "symmetry axis y: [0. 1. 0.]\n",
            "symmetry axis z: [0. 0. 1.]\n",
            "num. orbitals of irrep A1 = 4\n",
            "num. orbitals of irrep E1x = 1\n",
            "num. orbitals of irrep E1y = 1\n",
            "number of shells = 4\n",
            "number of NR pGTOs = 36\n",
            "number of NR cGTOs = 6\n",
            "basis = sto-6g\n",
            "ecp = {}\n",
            "CPU time:         9.85\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<pyscf.gto.mole.Mole at 0x7fc719f94850>"
            ]
          },
          "execution_count": 15,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "distance = 1.56\n",
        "mol = gto.Mole()\n",
        "mol.build(\n",
        "    verbose=4,\n",
        "    atom=[[\"Li\", (0, 0, 0)], [\"H\", (0, 0, distance)]],\n",
        "    basis=\"sto-6g\",\n",
        "    spin=0,\n",
        "    charge=0,\n",
        "    symmetry=True,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "18c6b81d-cb14-479f-888c-3d598473d7da",
      "metadata": {},
      "source": [
        "Entre otra información útil, devuelve `point group symmetry = Coov` y también el número de orbitales en cada representación irreducible.\n",
        "\n",
        "```\n",
        "point group symmetry = Coov\n",
        "num. orbitals of irrep A1 = 4\n",
        "num. orbitals of irrep E1x = 1\n",
        "num. orbitals of irrep E1y = 1\n",
        "number of shells = 4\n",
        "```\n",
        "\n",
        "Esto no le indica necesariamente cuántos orbitales quiere incluir en su espacio activo, pero le ayuda a ver qué orbitales están presentes y sus simetrías.\n",
        "\n",
        "Especificar la simetría y los orbitales suele ser útil, pero también puede especificar el número de orbitales que desea incluir. Consideremos el caso del eteno, a continuación. Utilizando `verbose = 4`, podemos imprimir las simetrías de los distintos orbitales:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "555442e7-e883-4465-9278-5312cd8d1fd0",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "System: uname_result(system='Linux', node='IBM-R912JTRT', release='5.10.102.1-microsoft-standard-WSL2', version='#1 SMP Wed Mar 2 00:30:59 UTC 2022', machine='x86_64')  Threads 16\n",
            "Python 3.11.12 (main, May 16 2025, 02:33:32) [GCC 11.4.0]\n",
            "numpy 2.3.1  scipy 1.16.0  h5py 3.14.0\n",
            "Date: Mon Jun 30 12:57:07 2025\n",
            "PySCF version 2.9.0\n",
            "PySCF path  /home/porter284/.pyenv/versions/3.11.12/lib/python3.11/site-packages/pyscf\n",
            "\n",
            "[CONFIG] conf_file None\n",
            "[INPUT] verbose = 4\n",
            "[INPUT] num. atoms = 6\n",
            "[INPUT] num. electrons = 16\n",
            "[INPUT] charge = 0\n",
            "[INPUT] spin (= nelec alpha-beta = 2S) = 0\n",
            "[INPUT] symmetry True subgroup None\n",
            "[INPUT] Mole.unit = angstrom\n",
            "[INPUT] Symbol           X                Y                Z      unit          X                Y                Z       unit  Magmom\n",
            "[INPUT]  1 C      0.000000000000   0.000000000000   1.000000000000 AA    0.000000000000   0.000000000000   1.889726124565 Bohr   0.0\n",
            "[INPUT]  2 C      0.000000000000   0.000000000000  -1.000000000000 AA    0.000000000000   0.000000000000  -1.889726124565 Bohr   0.0\n",
            "[INPUT]  3 H      0.000000000000   1.000000000000   1.000000000000 AA    0.000000000000   1.889726124565   1.889726124565 Bohr   0.0\n",
            "[INPUT]  4 H      0.000000000000  -1.000000000000   1.000000000000 AA    0.000000000000  -1.889726124565   1.889726124565 Bohr   0.0\n",
            "[INPUT]  5 H      0.000000000000   1.000000000000  -1.000000000000 AA    0.000000000000   1.889726124565  -1.889726124565 Bohr   0.0\n",
            "[INPUT]  6 H      0.000000000000  -1.000000000000  -1.000000000000 AA    0.000000000000  -1.889726124565  -1.889726124565 Bohr   0.0\n",
            "\n",
            "nuclear repulsion = 29.3377079104231\n",
            "point group symmetry = D2h\n",
            "symmetry origin: [0. 0. 0.]\n",
            "symmetry axis x: [0. 1. 0.]\n",
            "symmetry axis y: [1. 0. 0.]\n",
            "symmetry axis z: [-0. -0. -1.]\n",
            "num. orbitals of irrep Ag = 4\n",
            "num. orbitals of irrep B2g = 2\n",
            "num. orbitals of irrep B3g = 1\n",
            "num. orbitals of irrep B1u = 4\n",
            "num. orbitals of irrep B2u = 1\n",
            "num. orbitals of irrep B3u = 2\n",
            "number of shells = 10\n",
            "number of NR pGTOs = 84\n",
            "number of NR cGTOs = 14\n",
            "basis = sto-6g\n",
            "ecp = {}\n",
            "CPU time:         9.92\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<pyscf.gto.mole.Mole at 0x7fc719fa9290>"
            ]
          },
          "execution_count": 16,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Replace these variables with correct distances:\n",
        "a = 1\n",
        "b = 1\n",
        "c = 1\n",
        "\n",
        "# Build\n",
        "mol = gto.Mole()\n",
        "mol.build(\n",
        "    verbose=4,\n",
        "    atom=[\n",
        "        [\"C\", (0, 0, a)],\n",
        "        [\"C\", (0, 0, -a)],\n",
        "        [\"H\", (0, c, b)],\n",
        "        [\"H\", (0, -c, b)],\n",
        "        [\"H\", (0, c, -b)],\n",
        "        [\"H\", (0, -c, -b)],\n",
        "    ],\n",
        "    basis=\"sto-6g\",\n",
        "    spin=0,\n",
        "    charge=0,\n",
        "    symmetry=True,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7bd49628-dab3-4a1d-800b-32bc107cf242",
      "metadata": {},
      "source": [
        "Obtenemos:\n",
        "\n",
        "número de orbitales de irrep Ag = 4\n",
        "\n",
        "número de orbitales de irrep B2g = 2\n",
        "\n",
        "número de orbitales de irrep B3g = 1\n",
        "\n",
        "número de orbitales de irrep B1u = 4\n",
        "\n",
        "número de orbitales de irrep B2u = 1\n",
        "\n",
        "número de orbitales de irrep B3u = 2\n",
        "\n",
        "Pero en lugar de especificar todos los orbitales por simetría, podemos simplemente escribir:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "55448849-e0a0-489b-9edb-86bad0dad75b",
      "metadata": {},
      "outputs": [],
      "source": [
        "active_space = range(mol.nelectron // 2 - 2, mol.nelectron // 2 + 2)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "160aa2e7-2d7e-4de9-8069-7acdd9f824e2",
      "metadata": {},
      "source": [
        "En este enfoque, tomamos varios orbitales cercanos al nivel de llenado (valencia y desocupados). Aquí se han seleccionado 5 orbitales para incluirlos en el espacio activo (del 6º al 10º).\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "e8b42f17-9d8a-4564-8c6c-b1d0130690fb",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "6 10\n"
          ]
        }
      ],
      "source": [
        "print(\n",
        "    mol.nelectron // 2 - 2,\n",
        "    mol.nelectron // 2 + 2,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0b5b7cc0-0520-4626-8ffb-684f9154cb1c",
      "metadata": {},
      "source": [
        "4. software de terceros\n",
        "\n",
        "Existen varios paquetes de software desarrollados para la química cuántica, algunos de los cuales ofrecen múltiples mapeadores y herramientas para restringir los espacios activos. Los pasos descritos anteriormente son generales y se aplican también al software de terceros. Pero este otro software puede devolver Hamiltonianos en un formato que no es aceptado por Qiskit. Por ejemplo, algunos programas informáticos devuelven Hamiltonianos de la forma:\n",
        "\n",
        "`H = -0.042 [] + -0.045 [X0 X1 Y2 Y3] + ... + 0.178 [Z0] + ... + 0.176 [Z2 Z3] + -0.243 [Z3]`\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8fa9546f-1cb2-4e4d-816f-8ee630b7e133",
      "metadata": {},
      "source": [
        "Obsérvese, en particular, que las puertas están numeradas y que no se muestran los operadores de identidad. Esto contrasta con los Hamiltonianos utilizados en Qiskit, que escribirían el término `[Z2 Z3]` como `ZZII` (los qubits 0 y 1 actuando sobre ellos el operador identidad, los qubits 2 y 3 actuando sobre ellos el operador Z, ordenados con el qubit 0 más a la derecha).\n",
        "\n",
        "Para acomodar cualquier flujo de trabajo existente, el bloque de código siguiente convierte de una sintaxis a la otra. La función `convert_openfermion_to_qiskit` toma como argumentos un Hamiltoniano generado en OpenFermion o Tangelo (y ya mapeado en operadores Pauli utilizando cualquier mapeador disponible), y el número de qubits necesarios para la molécula.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "id": "435e8271-b48e-4e89-9166-91c9cc6f4563",
      "metadata": {},
      "outputs": [],
      "source": [
        "from openfermion import QubitOperator\n",
        "from qiskit.quantum_info import SparsePauliOp\n",
        "\n",
        "\n",
        "def convert_openfermion_to_qiskit(\n",
        "    openfermion_operator: QubitOperator, num_qubits: int\n",
        ") -> SparsePauliOp:\n",
        "    terms = openfermion_operator.terms\n",
        "\n",
        "    labels = []\n",
        "    coefficients = []\n",
        "\n",
        "    for term, constant in terms.items():\n",
        "        # Default set to identity\n",
        "        operator = list(\"I\" * num_qubits)\n",
        "\n",
        "        # Iterate through PauliSum and replace I with Pauli\n",
        "        for index, pauli in term:\n",
        "            operator[index] = pauli\n",
        "        label = \"\".join(operator)\n",
        "        labels.append(label)\n",
        "        coefficients.append(constant)\n",
        "\n",
        "    return SparsePauliOp(labels, coefficients)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0fe58207-451d-4bfa-8f2d-8969b1a48cfa",
      "metadata": {},
      "source": [
        "Además, [este cuaderno Python](https://ibm.ent.box.com/s/fh3xele1e7k0nrgd1imivvq52hy3wz9c) contiene código de ejemplo completo para migrar hamiltonianos de otros flujos de trabajo de software a Qiskit, incluida la conversión anterior.\n",
        "\n",
        "Ahora debería tener un arsenal de herramientas para obtener el Hamiltoniano que necesita para realizar cálculos de química cuántica en IBM® quantum computers.\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": 2
}