{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "f502c3c3-d0be-4be4-afab-2d569f171f98",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Hamiltoniani\"\n",
        "description: \"Viene esaminata la costruzione e la mappatura degli hamiltoniani per applicazioni di chimica quantistica. Utilizza un mappatore Jordan-Wigner personalizzato e sfrutta l' 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",
        "# Hamiltoniani per la chimica quantistica\n",
        "\n",
        "Cominciamo con una breve panoramica del ruolo degli hamiltoniani nella VQE.\n",
        "\n",
        "<span id=\"the-hamiltonian-in-vqe-overview\" />\n",
        "\n",
        "## L'Hamiltoniano in VQE Panoramica\n",
        "\n",
        "La dottoressa Victoria Lipinska ci illustra gli hamiltoniani e come mapparli per utilizzarli nell'informatica quantistica.\n",
        "\n",
        "<IBMVideo id=\"132414924\" title=\"Victoria Lipinska ci ricorda cos'è un'hamiltoniana e come si usa nella VQE. In particolare, descrive la necessità di mappare un'hamiltoniana fermionica che governa fermioni indistinguibili in un'hamiltoniana di qubit.\" />\n",
        "\n",
        "<span id=\"references\" />\n",
        "\n",
        "### Riferimenti\n",
        "\n",
        "I seguenti articoli sono citati nel video di cui sopra.\n",
        "\n",
        "* [Algoritmi quantistici per simulazioni fermioniche, Ortiz, et al.](https://arxiv.org/pdf/cond-mat/0012334.pdf)\n",
        "* [Simulazione della chimica con i computer quantistici, Kassal et al.](https://arxiv.org/pdf/1007.2648.pdf)\n",
        "* [Confronto tra le trasformazioni di Bravyi-Kitaev e Jordan-Wigner per la simulazione della chimica quantistica, 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",
        "* [Chimica quantistica computazionale, McArdle, et al.](https://arxiv.org/pdf/1808.10402.pdf)\n",
        "* [La trasformazione di Bravyi-Kitaev per il calcolo quantistico della struttura elettronica, Seeley et al.](https://arxiv.org/pdf/1208.5986.pdf)\n",
        "\n",
        "<span id=\"preparing-hamiltonians-for-quantum-chemistry\" />\n",
        "\n",
        "## Preparare gli hamiltoniani per la chimica quantistica\n",
        "\n",
        "Un buon primo passo per applicare l'informatica quantistica a un problema di chimica è la definizione di un'hamiltoniana per il sistema di interesse. In questa sede, limiteremo la discussione agli hamiltoniani della chimica quantistica, poiché questi ultimi richiedono una mappatura specifica per i sistemi di fermioni identici.\n",
        "\n",
        "Se lavorate nel campo della chimica quantistica, probabilmente avete già il vostro software preferito per la modellazione delle molecole, in grado di generare un'hamiltoniana che descrive il vostro sistema di interesse. In questa sede, utilizzeremo codice costruito esclusivamente su PySCF, numpy e Qiskit. Ma il processo di preparazione hamiltoniana si trasferisce anche alle soluzioni preconfezionate. L'unica differenza tra questo approccio e altri software sarà costituita da piccole differenze di sintassi; alcune di queste sono trattate nella sottosezione \"Software di terze parti\" per facilitare l'integrazione dei flussi di lavoro esistenti.\n",
        "\n",
        "La generazione di un hamiltoniano di chimica quantistica da utilizzare sulle QPU IBM Quantum® comporta le seguenti fasi:\n",
        "\n",
        "1. Definire la molecola (geometria, spin, spazio attivo e così via)\n",
        "2. Generare l'hamiltoniana fermionica (operatori di creazione e annichilazione)\n",
        "3. Mappa dall'hamiltoniano fermionico a un operatore bosonico (in questo contesto, utilizzando gli operatori di Pauli)\n",
        "4. Se si utilizza un software di terze parti: gestire eventuali discrepanze di sintassi tra il software di generazione e Qiskit\n",
        "\n",
        "L'hamiltoniana fermionica è scritta in termini di operatori fermionici e, in particolare, tiene conto del fatto che gli elettroni sono fermioni indistinguibili. Ciò significa che obbediscono a statistiche completamente diverse da quelle dei qubit bosonici distinguibili. Da qui il processo di mappatura.\n",
        "\n",
        "Chi ha già familiarità con questi processi può saltare questa sezione.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "28e3a254-be8c-4bca-a467-3a92d0ddb1ae",
      "metadata": {},
      "source": [
        "L’obiettivo:\n",
        "\n",
        "L'obiettivo finale è ottenere un'hamiltoniana della 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": [
        "Oppure\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": [
        "Inizieremo importando alcuni pacchetti:\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. Definire la propria molecola\n",
        "\n",
        "Qui si specificano gli attributi della molecola di interesse. In questo esempio, abbiamo scelto l'idrogeno biatomico (perché gli hamiltoniani risultanti sono abbastanza brevi da poter essere visualizzati).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2d55595c-45f2-4e90-a3d1-22b6ee51ccc6",
      "metadata": {},
      "source": [
        "Il framework Python - Simulations of Chemistry ( [PySCF](https://pyscf.org/) ) dispone di un'ampia collezione di moduli di struttura elettronica che possono essere utilizzati, tra l'altro, per generare hamiltoniani molecolari adatti al calcolo quantistico. La guida [rapida di PySCF](https://pyscf.org/quickstart.html) è una risorsa eccellente per una descrizione completa di tutte le variabili e le funzionalità. Ne daremo solo una panoramica sommaria, poiché molti di voi ne saranno già a conoscenza. Per capirli meglio, visitate il sito [PySCF.](https://pyscf.org/)\n",
        "Brevemente:\n",
        "\n",
        "**la distanza** può essere utilizzata per le molecole biatomiche oppure è sufficiente specificare le coordinate cartesiane per ciascun atomo. Le distanze sono in unità di Angstrom.\n",
        "\n",
        "**gto** genera orbitali di tipo gaussiano.\n",
        "\n",
        "il termine **base** si riferisce alle funzioni utilizzate per modellare gli orbitali molecolari. Qui ' sto-6g ' è una base minima comune, chiamata così per adattare gli orbitali di tipo Slater utilizzando 6 orbitali gaussiani primitivi.\n",
        "\n",
        "**spin** un valore intero che indica il numero di elettroni spaiati (uguale a $2S$ ). Si noti che alcuni software utilizzano invece la molteplicità ( $2S+1$ ).\n",
        "\n",
        "**carica** la carica della molecola.\n",
        "\n",
        "**simmetria** - il gruppo di simmetria puntiforme della molecola, specificato con una stringa o rilevato automaticamente impostando \"simmetria = Vero\". Qui \"Dooh\" è il gruppo di simmetria appropriato per le molecole biatomiche con due atomi della stessa specie.\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": [
        "Si tenga presente che si può descrivere l'energia totale (che comprende l'energia di repulsione nucleare e quella elettronica), l'energia totale degli orbitali elettronici o l'energia di un sottoinsieme di orbitali elettronici (con il sottoinsieme complementare congelato). Nel caso specifico di $\\text{H}_2$, si notino le diverse energie di seguito riportate e si noti che l'energia totale meno l'energia di repulsione nucleare produce di fatto l'energia elettronica:\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. Generare l'hamiltoniana fermionica\n",
        "\n",
        "**scf** si riferisce a un'ampia gamma di metodi di campo autoconsistenti.\n",
        "\n",
        "**rhf** come in **mf** = scf.RHF (mol) in mf è un solutore che utilizza il calcolo Restricted Hartree Fock. Il nocciolo di questo (E, sotto) è l'energia totale, che include la repulsione nucleare e gli orbitali molecolari.\n",
        "\n",
        "**mcscf** è un pacchetto di campi autoconsistenti multi-configurazione.\n",
        "\n",
        "**ao2mo** è una trasformazione da orbitali atomici a orbitali molecolari.\n",
        "\n",
        "Utilizziamo anche le seguenti variabili:\n",
        "\n",
        "**ncas** : numero di orbitali nello spazio attivo completo\n",
        "\n",
        "**nelecas** : numero di elettroni nello spazio attivo 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": [
        "Vogliamo un'hamiltoniana, che viene spesso separata in energia di un nucleo elettronico (ecore, non coinvolto nella minimizzazione), operatori a singolo elettrone ( h1e ) ed energie a due elettroni ( h2e ). Questi sono esplicitamente estratti nelle ultime due righe.\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": [
        "Questi hamiltoniani sono attualmente operatori fermionici (di creazione e annichilazione), applicabili a sistemi di fermioni (indistinguibili) e, di conseguenza, soggetti ad antisimmetria per scambio. Ciò comporta una statica diversa da quella che si applicherebbe a un sistema distinguibile o bosonico. Per eseguire i calcoli sulle QPU di IBM Quantum, è necessario un operatore bosonico che descriva l'energia. Il risultato di una tale mappatura viene convenzionalmente scritto in termini di operatori di Pauli, poiché sono entrambi ermitiani e unitari. Si possono utilizzare diverse mappature. Una delle più semplici è la trasformazione di Jordan Wigner.\n",
        "\n",
        "3. Mappatura dell'hamiltoniano\n",
        "\n",
        "Va notato che sono disponibili molti strumenti per mappare un'hamiltoniana chimica in una adatta all'esecuzione su un computer quantistico. Qui implementiamo la mappatura di Jordan Wigner direttamente utilizzando solo PySCF, numpy e Qiskit. Commentiamo di seguito le considerazioni sulla sintassi per altre soluzioni.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f90c2756-8155-4417-89e9-f248ae76895c",
      "metadata": {},
      "source": [
        "La funzione Cholesky ci aiuta a ottenere una decomposizione a basso rango dei termini a due elettroni dell'hamiltoniana.\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": [
        "Le funzioni `identity` e `creators_destructors` sostituiscono gli operatori di creazione e annichilazione nell'Hamiltoniano fermionico con operatori di Pauli; `creators_destructors` utilizza la mappatura di 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": [
        "Infine, `build_hamiltonian` utilizza le funzioni `cholesky`, `identity` e `creators_destructors` per creare l'hamiltoniana finale adatta all'esecuzione su un computer quantistico.\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": [
        "Infine, utilizziamo `build_hamiltonian` per costruire l'hamiltoniana del qubit a partire dagli operatori di Pauli, utilizzando la trasformazione di Jordan-Wigner. Questo ci dà anche la precisione della decomposizione di Cholesky che abbiamo utilizzato.\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": [
        "Questo [quaderno di molecole di esempio](https://ibm.ent.box.com/public/static/59c2kjqefvjy5wy5lct099thr4y0ry8c.zip) mostra l'impostazione e gli hamiltoniani di diverse molecole di varia complessità; con qualche piccola modifica, questo dovrebbe consentire di esaminare la maggior parte delle piccole molecole.\n",
        "\n",
        "Vediamo brevemente due punti importanti da considerare quando si costruiscono gli operatori fermionici per una molecola. Al variare del tipo di molecola, cambierà la simmetria. Allo stesso modo, cambierà il numero di orbitali con diverse simmetrie, come quello a simmetria cilindrica \" A1 \". Questi cambiamenti sono evidenti anche con la semplice estensione a LiH,, come si vede qui:\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": [
        "Vale anche la pena di notare che si può perdere rapidamente l'intuizione per l'hamiltoniana finale risultante. L'hamiltoniana di LiH (utilizzando il mappatore di Jordan-Wigner) è già composta da 276 termini.\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": [
        "In caso di dubbi sulle simmetrie, si possono anche generare alcune informazioni sulla simmetria della molecola impostando `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": [
        "Tra le altre informazioni utili, restituisce sia `point group symmetry = Coov` sia il numero di orbitali in ogni rappresentazione irriducibile.\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",
        "Questo non indica necessariamente quanti orbitali si vogliono includere nello spazio attivo, ma aiuta a vedere quali orbitali sono presenti e le loro simmetrie.\n",
        "\n",
        "Spesso è utile specificare la simmetria e gli orbitali, ma è anche possibile specificare il numero di orbitali che si desidera includere. Consideriamo il caso dell'etene, qui di seguito. Utilizzando `verbose = 4`, possiamo stampare le simmetrie dei vari orbitali:\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": [
        "Otteniamo:\n",
        "\n",
        "numero di orbitali dell'irrep Ag = 4\n",
        "\n",
        "num. orbitali dell'irrep B2g = 2\n",
        "\n",
        "num. orbitali dell'irrep B3g = 1\n",
        "\n",
        "num. orbitali dell'irrep B1u = 4\n",
        "\n",
        "num. orbitali dell'irrep B2u = 1\n",
        "\n",
        "num. orbitali dell'irrep B3u = 2\n",
        "\n",
        "Ma piuttosto che specificare tutti gli orbitali per simmetria, possiamo semplicemente scrivere:\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": [
        "In questo approccio, prendiamo diversi orbitali vicini al livello di riempimento (valenza e non occupati). Qui sono stati selezionati 5 orbitali da includere nello spazio attivo (dal 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 di terze parti\n",
        "\n",
        "Esistono diversi pacchetti software sviluppati per la chimica quantistica, alcuni dei quali offrono più mappatori e strumenti per limitare gli spazi attivi. I passi descritti sopra sono generali e si applicano anche al software di terze parti. Ma questo altro software potrebbe restituire gli hamiltoniani in un formato non accettato da Qiskit. Ad esempio, alcuni software restituiscono hamiltoniani della 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": [
        "Si noti in particolare che le porte sono numerate e che gli operatori di identità non sono mostrati. Questo è in contrasto con gli hamiltoniani utilizzati in Qiskit, che scrivono il termine `[Z2 Z3]` come `ZZII` (i qubit 0 e 1 sono agiti dall'operatore identità, i qubit 2 e 3 dall'operatore Z, ordinati con il qubit 0 più a destra).\n",
        "\n",
        "Per adattarsi a qualsiasi flusso di lavoro esistente, il blocco di codice sottostante converte da una sintassi all'altra. La funzione `convert_openfermion_to_qiskit` prende come argomenti un'hamiltoniana generata in OpenFermion o Tangelo (e già mappata sugli operatori di Pauli usando qualsiasi mappatore disponibile) e il numero di qubit necessari per la molecola.\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": [
        "Inoltre, [questo notebook Python](https://ibm.ent.box.com/s/fh3xele1e7k0nrgd1imivvq52hy3wz9c) contiene un codice di esempio completo per la migrazione di hamiltoniani da altri flussi di lavoro software a Qiskit, compresa la conversione di cui sopra.\n",
        "\n",
        "Ora dovreste disporre di un arsenale di strumenti per ottenere l'hamiltoniana necessaria per eseguire calcoli di chimica quantistica sui computer quantistici 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
}