{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "f502c3c3-d0be-4be4-afab-2d569f171f98",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Hamiltonianos\"\n",
        "description: \"A construção e o mapeamento de hamiltonianos para aplicações químicas quânticas são revisados. Use um mapeador Jordan-Wigner personalizado e aproveite o 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 Química Quântica\n",
        "\n",
        "Vamos começar com uma breve visão geral da função que os hamiltonianos desempenham na VQE.\n",
        "\n",
        "<span id=\"the-hamiltonian-in-vqe-overview\" />\n",
        "\n",
        "## O Hamiltoniano em VQE Visão geral\n",
        "\n",
        "A Dra. Victoria Lipinska nos apresenta os Hamiltonianos e como mapeá-los para uso em computação quântica.\n",
        "\n",
        "<IBMVideo id=\"132414924\" title=\"Victoria Lipinska nos lembra o que é um Hamiltoniano e como ele é usado no VQE. Em particular, ela descreve a necessidade de mapear um Hamiltoniano fermiônico que governa férmions indistinguíveis para um Hamiltoniano de qubit.\" />\n",
        "\n",
        "<span id=\"references\" />\n",
        "\n",
        "### Referências\n",
        "\n",
        "Os artigos a seguir são mencionados no vídeo acima.\n",
        "\n",
        "* [Quantum Algorithms for Fermionic Simulations (Algoritmos Quânticos para Simulações Fermiônicas), Ortiz, et al.](https://arxiv.org/pdf/cond-mat/0012334.pdf)\n",
        "* [Simulating Chemistry using Quantum Computers (Simulação de química usando computadores quâ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 (Uma comparação das transformações de Bravyi-Kitaev e Jordan-Wigner para a simulação quântica da química quântica), Tranter, et al.](https://pubs.acs.org/doi/full/10.1021/acs.jctc.8b00450)\n",
        "* [Quantum Chemistry in the Age of Quantum Computing (Química Quântica na Era da Computação Quântica), Cao, et al.](https://arxiv.org/pdf/1812.09976.pdf)\n",
        "* [Química quântica computacional, McArdle, et al.](https://arxiv.org/pdf/1808.10402.pdf)\n",
        "* [A transformação de Bravyi-Kitaev para computação quântica da estrutura eletrô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 os hamiltonianos para a química quântica\n",
        "\n",
        "Uma boa primeira etapa para aplicar a computação quântica a um problema químico é definir um Hamiltoniano para o sistema de interesse. Aqui, restringiremos a discussão aos Hamiltonianos da química quântica, pois esses Hamiltonianos exigem algum mapeamento específico para sistemas de férmions idênticos.\n",
        "\n",
        "Como alguém que trabalha com química quântica, você provavelmente já tem seu software favorito para modelagem de moléculas, que pode gerar um Hamiltoniano que descreve seu sistema de interesse. Aqui, usaremos o código criado exclusivamente com o PySCF, numpy e o Qiskit. Mas o processo de preparação hamiltoniana também é transferido para soluções pré-embaladas. A única diferença entre essa abordagem e outros softwares serão pequenas diferenças de sintaxe; algumas delas são abordadas na subseção \"Software de terceiros\" para facilitar a integração de fluxos de trabalho existentes.\n",
        "\n",
        "A geração de um Hamiltoniano de química quântica para uso em IBM Quantum® QPUs envolve as seguintes etapas:\n",
        "\n",
        "1. Defina sua molécula (geometria, spin, espaço ativo, etc.)\n",
        "2. Gerar o Hamiltoniano fermiônico (operadores de criação e aniquilação)\n",
        "3. Mapa do Hamiltoniano fermiônico para um operador bosônico (nesse contexto, usando operadores de Pauli)\n",
        "4. Se estiver usando um software de terceiros: trate de quaisquer incompatibilidades de sintaxe entre o software gerador e o Qiskit\n",
        "\n",
        "O Hamiltoniano fermiônico é escrito em termos de operadores fermiônicos e, em particular, leva em conta que os elétrons são férmions indistinguíveis. Isso significa que eles obedecem a estatísticas completamente diferentes dos qubits distinguíveis e bosônicos. Daí o processo de mapeamento.\n",
        "\n",
        "Aqueles que já estão familiarizados com esses processos provavelmente podem pular esta seção.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "28e3a254-be8c-4bca-a467-3a92d0ddb1ae",
      "metadata": {},
      "source": [
        "Meta:\n",
        "\n",
        "O objetivo final é obter um Hamiltoniano da 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": [
        "Ou\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": [
        "Começaremos importando alguns pacotes:\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 sua molécula\n",
        "\n",
        "Aqui, especificaremos os atributos da molécula de interesse. Neste exemplo, escolhemos o hidrogênio diatômico (porque os Hamiltonianos resultantes são curtos o suficiente para serem exibidos).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2d55595c-45f2-4e90-a3d1-22b6ee51ccc6",
      "metadata": {},
      "source": [
        "O Python -based Simulations of Chemistry Framework ( [PySCF](https://pyscf.org/) ) tem uma ampla coleção de módulos de estrutura eletrônica que podem ser usados para, entre outras coisas, gerar Hamiltonianos moleculares adequados para a computação quântica. O guia de [início rápido do site PySCF](https://pyscf.org/quickstart.html) é um excelente recurso para uma descrição completa de todas as variáveis e funcionalidades. Daremos apenas uma visão geral superficial, pois isso já é familiar para muitos de vocês. Para entendê-los melhor, visite [PySCF.](https://pyscf.org/)\n",
        "Resumidamente:\n",
        "\n",
        "**a distância** pode ser usada para moléculas diatômicas, ou simplesmente especificar coordenadas cartesianas para cada átomo. As distâncias estão em unidades de Angstrom.\n",
        "\n",
        "**gpara** gerar orbitais do tipo gaussiano.\n",
        "\n",
        "**base** refere-se às funções usadas para modelar orbitais moleculares. Aqui, ' sto-6g ' é uma base mínima comum, nomeada para ajustar os orbitais do tipo Slater usando 6 orbitais Gaussianos primitivos.\n",
        "\n",
        "**spin** um valor inteiro que indica o número de elétrons desemparelhados (igual a $2S$ ). Observe que alguns softwares usam a multiplicidade em vez disso ( $2S+1$ ).\n",
        "\n",
        "**carga** a carga da molécula.\n",
        "\n",
        "**symmetry (simetria** ) - o grupo de simetria de ponto da molécula, especificado com uma cadeia de caracteres ou detectado automaticamente pela configuração \"symmetry = True\". Aqui, \"Dooh\" é o grupo de simetria apropriado para moléculas diatômicas com duas espécies do mesmo átomo.\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": [
        "Lembre-se de que é possível descrever a energia total (que inclui a energia de repulsão nuclear e a eletrônica), a energia orbital eletrônica total ou a energia de algum subconjunto de orbitais eletrônicos (com o subconjunto complementar congelado). No caso específico de $\\text{H}_2$, observe as diferentes energias abaixo e note que a energia total menos a energia de repulsão nuclear produz, de fato, a energia eletrô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. Gerar Hamiltoniano fermiônico\n",
        "\n",
        "**scf** refere-se a uma ampla gama de métodos de campo autoconsistentes.\n",
        "\n",
        "**rhf** como em **mf** = scf.RHF (mol) em mf é um solucionador que usa o cálculo de Hartree Fock restrito. O núcleo disso (E, abaixo) é a energia total, incluindo a repulsão nuclear e os orbitais moleculares.\n",
        "\n",
        "**o mcscf** é um pacote de campos autoconsistentes de várias configurações.\n",
        "\n",
        "**ao2mo** é uma transformação de orbitais atômicos em orbitais moleculares.\n",
        "\n",
        "Também usamos as seguintes variáveis:\n",
        "\n",
        "**ncas** : número de orbitais no espaço ativo completo\n",
        "\n",
        "**nelecas** : número de elétrons no espaço ativo 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 um Hamiltoniano, e isso geralmente é separado em energia de um núcleo eletrônico (ecore, não envolvido na minimização), operadores de elétron único ( h1e ) e energias de dois elétrons ( h2e ). Elas são explicitamente extraídas abaixo nas duas últimas linhas.\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": [
        "Atualmente, esses hamiltonianos são operadores fermiônicos (criação e aniquilação), aplicáveis a sistemas de férmions (indistinguíveis) e, consequentemente, sujeitos à antissimetria sob troca. Isso resulta em uma estática diferente da que se aplicaria a um sistema distinguível ou bosônico. Para executar cálculos em IBM Quantum QPUs, precisamos de um operador bosônico que descreva a energia. O resultado desse mapeamento é convencionalmente escrito em termos de operadores Pauli, já que eles são hermitianos e unitários. Há vários mapeamentos que podem ser usados. Uma das mais simples é a transformação de Jordan Wigner.\n",
        "\n",
        "3. Mapeamento do Hamiltoniano\n",
        "\n",
        "Deve-se observar que há muitas ferramentas disponíveis para mapear um Hamiltoniano químico para um adequado para execução em um computador quântico. Aqui, implementamos o mapeamento de Jordan Wigner diretamente usando apenas PySCF, numpy e Qiskit. Comentaremos abaixo as considerações sobre a sintaxe de outras soluções.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f90c2756-8155-4417-89e9-f248ae76895c",
      "metadata": {},
      "source": [
        "A função Cholesky nos ajuda a obter uma decomposição de baixa classificação dos termos de dois elétrons no 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": [
        "As funções `identity` e `creators_destructors` substituem os operadores de criação e aniquilação no Hamiltoniano fermiônico por operadores Pauli; `creators_destructors` usa o mapeamento 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 fim, o site `build_hamiltonian` usa as funções `cholesky`, `identity` e `creators_destructors` para criar o Hamiltoniano final adequado para execução em um computador quâ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 fim, usamos o site `build_hamiltonian` para construir nosso qubit Hamiltoniano a partir de operadores Pauli usando a transformação Jordan-Wigner. Isso também nos dá a precisão da decomposição de Cholesky que usamos.\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": [
        "Esse [notebook de molécula de exemplo](https://ibm.ent.box.com/public/static/59c2kjqefvjy5wy5lct099thr4y0ry8c.zip) mostra a configuração e os Hamiltonianos de várias moléculas de complexidade variável; com algumas modificações, isso deve permitir que você examine a maioria das moléculas pequenas.\n",
        "\n",
        "Vamos observar brevemente dois pontos importantes a serem considerados ao construir os operadores fermiônicos para uma molécula. Conforme o tipo de molécula muda, a simetria muda. Da mesma forma, o número de orbitais com várias simetrias, como a simetria cilíndrica \" A1 \", será alterado. Essas alterações são evidentes mesmo com a simples extensão para LiH,, como visto aqui:\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": [
        "Também vale a pena observar que é possível perder rapidamente a intuição do Hamiltoniano final resultante. O Hamiltoniano para LiH (usando o mapeador Jordan-Wigner) já consiste em 276 termos.\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": [
        "Em caso de dúvida com relação às simetrias, também é possível gerar algumas informações de simetria para a molécula definindo `symmetry = True` e `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 outras informações úteis, isso retorna `point group symmetry = Coov` e também o número de orbitais em cada representação irredutível.\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",
        "Isso não necessariamente informa quantos orbitais você deseja incluir no espaço ativo, mas ajuda a ver quais orbitais estão presentes e suas simetrias.\n",
        "\n",
        "Especificar a simetria e os orbitais geralmente é útil, mas você também pode especificar o número de orbitais que deseja incluir. Considere o caso do eteno, abaixo. Usando o site `verbose = 4`, podemos imprimir as simetrias dos vários orbitais:\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": [
        "Obtemos:\n",
        "\n",
        "número de orbitais do irrep Ag = 4\n",
        "\n",
        "número de orbitais do irrep B2g = 2\n",
        "\n",
        "número de orbitais do irrep B3g = 1\n",
        "\n",
        "número de orbitais do irrep B1u = 4\n",
        "\n",
        "número de orbitais do irrep B2u = 1\n",
        "\n",
        "número de orbitais do irrep B3u = 2\n",
        "\n",
        "Mas, em vez de especificar todos os orbitais por simetria, podemos simplesmente escrever:\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": [
        "Nessa abordagem, usamos vários orbitais próximos ao nível de preenchimento (valência e desocupado). Aqui, 5 orbitais foram selecionados para inclusão no espaço ativo (do 6º ao 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 terceiros\n",
        "\n",
        "Há vários pacotes de software desenvolvidos para a química quântica, alguns oferecendo vários mapeadores e ferramentas para restringir os espaços ativos. As etapas descritas acima são gerais e também se aplicam a softwares de terceiros. Mas esse outro software pode retornar Hamiltonianos em um formato que não é aceito pelo Qiskit. Por exemplo, alguns softwares retornam Hamiltonianos no formato:\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": [
        "Observe, em particular, que as portas são numeradas e os operadores de identidade não são mostrados. Isso contrasta com os hamiltonianos usados no Qiskit, que escreveriam o termo `[Z2 Z3]` como `ZZII` (os qubits 0 e 1 agindo pelo operador de identidade, os qubits 2 e 3 agindo pelo operador Z, ordenados com o qubit 0 mais à direita).\n",
        "\n",
        "Para acomodar qualquer fluxo de trabalho existente, o bloco de código abaixo converte de uma sintaxe para a outra. A função `convert_openfermion_to_qiskit` recebe como argumentos um Hamiltoniano gerado em OpenFermion ou Tangelo (e já mapeado em operadores Pauli usando qualquer mapeador disponível) e o número de qubits necessários para a 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": [
        "Além disso, [este notebook Python](https://ibm.ent.box.com/s/fh3xele1e7k0nrgd1imivvq52hy3wz9c) contém um código de amostra completo para migrar Hamiltonians de outros fluxos de trabalho de software para o Qiskit, incluindo a conversão acima.\n",
        "\n",
        "Agora você deve ter um arsenal de ferramentas para obter o Hamiltoniano necessário para realizar cálculos de química quântica em computadores quânticos IBM®.\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
}