{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "454a9dfd",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Fórmulas multiprodutos para reduzir o erro de Trotter\"\n",
        "description: \"Utilize fórmulas multiproduto na estimativa de observáveis para reduzir o erro de Trotter ou implemente a evolução temporal com erro de Trotter fixo em profundidade menor.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore ncol circo Layerwise markersize unbiasedness infty ndash lesssim propto tenpy unfused Néel correlator Neel exponentiating gtrsim */}\n",
        "\n",
        "<span id=\"multi-product-formulas-to-reduce-trotter-error\" />\n",
        "\n",
        "# Fórmulas multiprodutos para reduzir o erro de Trotter\n",
        "\n",
        "*Estimativa de tempo de execução: Quatro minutos em um processador Heron r2 (NOTA: Trata-se apenas de uma estimativa. (O tempo de execução pode variar.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c4d0b2f2",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Resultados do aprendizado\n",
        "\n",
        "* Como as fórmulas multiproduto (MPFs) reduzem o erro de Trotter na simulação hamiltoniana ao combinar valores esperados de vários circuitos rasos\n",
        "* Quando as MPFs são mais vantajosas do que as fórmulas padrão dos produtos e quando não são a ferramenta adequada\n",
        "* Como calcular os coeficientes MPF estáticos e dinâmicos usando o `qiskit_addon_mpf` pacote\n",
        "* Como executar um fluxo de trabalho do MPF de ponta a ponta em um hardwar IBM Quantum®, incluindo transpilação, mitigação de erros e pós-processamento\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f5dfd316",
      "metadata": {},
      "source": [
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Pré-requisitos\n",
        "\n",
        "* [Métodos de compilação para circuitos de simulação hamiltoniana](/docs/tutorials/compilation-methods-for-hamiltonian-simulation-circuits) — apresenta os circuitos de Trotter (fórmula do produto) no Qiskit.\n",
        "* Fórmulas de produtos no Qiskit, em especial as [`SuzukiTrotter`](/docs/api/qiskit/qiskit.synthesis.SuzukiTrotter) classes de síntese e [`LieTrotter`](/docs/api/qiskit/qiskit.synthesis.LieTrotter) .\n",
        "* [Qiskit primitives e a interface do Estimador](/docs/guides/primitives).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "07273b26",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Segundo plano\n",
        "\n",
        "<span id=\"what-are-multi-product-formulas\" />\n",
        "\n",
        "### O que são fórmulas multiprodutos?\n",
        "\n",
        "Ao simular sistemas quânticos em um computador quântico, uma tarefa central consiste em aproximar o operador de evolução temporal $e^{-iHt}$ para um hamiltoniano $H$. A abordagem padrão utiliza *fórmulas de produto* (PFs), também conhecidas como decomposições de Trotter-Suzuki. Essas funções decompõem $H = \\sum_{a=1}^d F_a$ em termos cujas operações unitárias individuais $e^{-iF_a t}$ são eficientes de implementar e, em seguida, aproximam a evolução completa como um produto ordenado dessas operações unitárias mais simples.\n",
        "\n",
        "A fórmula do produto de primeira ordem (Lie-Trotter) é:\n",
        "\n",
        "$$\n",
        "S_1(t) := \\prod_{a=1}^d e^{-i F_a t},\n",
        "$$\n",
        "\n",
        "o que resulta em um erro quadrático: $S_1(t) = e^{-iHt} + \\mathcal{O}(t^2)$. Fórmulas simétricas de ordem superior $S_{2\\chi}(t)$, em que $\\chi$ indica a ordem da fórmula do produto simétrico (ver Ref. [\\[1\\]](#references) ), convergem mais rapidamente, conforme $e^{-iHt} + \\mathcal{O}(t^{2\\chi+1})$, mas à custa de circuitos mais profundos a cada passo.\n",
        "\n",
        "Para reduzir o erro em uma ordem *fixa* $\\chi$, costuma-se dividir o tempo total de evolução $t$ em $k$ passos de Trotter menores. Cada etapa aproxima $e^{-iHt/k}$ por meio de uma fórmula de produto, e as etapas são concatenadas:\n",
        "\n",
        "$$\n",
        "e^{-iHt} \\approx \\left[S_{2\\chi}(t/k)\\right]^k.\n",
        "$$\n",
        "\n",
        "Para uma fórmula simétrica de ordem $2\\chi$, o erro residual de Trotter varia proporcionalmente a $\\mathcal{O}\\!\\left(t^{2\\chi+1} / k^{2\\chi}\\right)$. Assim, aumentar $k$ suprime rapidamente o erro de Trotter — mas também aumenta linearmente a profundidade do circuito, e em hardware sujeito a ruído isso significa mais ruído acumulado nas portas. Essa tensão entre **o erro de Trotter (que favorece valores maiores de $k$ )** e **o ruído do hardware (que favorece valores menores de $k$ )** é exatamente o que as fórmulas multiproduto se propõem a resolver. Observe que as MPFs consistem em combinar resultados de *diferentes escolhas de $k$* em uma ordem fixa $\\chi$ — elas não alteram a ordem da fórmula do produto subjacente.\n",
        "\n",
        "**As fórmulas multiproduto (MPFs)** [\\[1\\]](#references) constroem uma *combinação linear ponderada* dos valores esperados obtidos a partir de vários circuitos de Trotter menos profundos, cada um utilizando um número diferente de etapas de Trotter $k_1, k_2, \\ldots, k_r$ (um conjunto de contagens de etapas $r$ ):\n",
        "\n",
        "$$\n",
        "\\langle A \\rangle_{\\text{MPF}}(t) = \\sum_{j=1}^r x_j \\, \\langle A \\rangle_{k_j}(t),\n",
        "$$\n",
        "\n",
        "onde $\\langle A \\rangle_{k_j}(t)$ é o valor esperado de um observável $A$ no instante $t$, estimado a partir de um circuito de Trotter com $k_j$ passos, e os coeficientes $\\{x_j\\}_{j=1}^r$ são escolhidos de modo que os principais termos de erro de Trotter na combinação se cancelem. Voltaremos a essa expressão na [Etapa 4](#small-scale-step-4), onde a avaliaremos explicitamente para combinar nossos resultados de Trotter. O ponto prático fundamental é que o circuito mais profundo do MPF precisa apenas de $k_{\\max}$ etapas, o que é muito menor do que o único $k$ que seria necessário para atingir diretamente o mesmo erro efetivo de Trotter. Os circuitos menos complexos tornam a abordagem MPF mais adequada para hardware com ruído.\n",
        "\n",
        "<span id=\"how-are-the-coefficients-determined\" />\n",
        "\n",
        "### Como os coeficientes são determinados?\n",
        "\n",
        "Existem duas famílias de coeficientes MPF:\n",
        "\n",
        "**Os coeficientes estáticos** são independentes do hamiltoniano, do estado inicial e do tempo de evolução. Elas são determinadas pela resolução de um sistema linear $Ax = b$ que garante o cancelamento dos principais termos de erro de Trotter. Para um conjunto de passos de Trotter $\\{k_j\\}_{j=1}^r$ utilizado com uma fórmula de produto simétrico de ordem $2\\chi$, a expansão do erro de Trotter em potências inversas de $k_j$ leva a equações de restrição da forma:\n",
        "\n",
        "$$\n",
        "\\sum_{j=1}^r x_j = 1, \\quad \\sum_{j=1}^r \\frac{x_j}{k_j^{\\eta_n}} = 0 \\quad (n = 0, \\ldots, r-2),\n",
        "$$\n",
        "\n",
        "onde os expoentes inteiros $\\{\\eta_n\\}$ são as ordens dos termos sucessivos do erro de Trotter para a fórmula de produto escolhida. Para um PF *simétrico* de ordem $2\\chi$, o erro principal em $\\left[S_{2\\chi}(t/k)\\right]^k$ varia proporcionalmente a $1/k^{2\\chi}$, com correções subsequentes em $1/k^{2\\chi+2}, 1/k^{2\\chi+4}, \\ldots$ — portanto, os expoentes são $\\eta_n = 2\\chi + 2n$. Para PFs não simétricos, tanto as potências ímpares quanto as pares contribuem e $\\eta_n = 2\\chi + n$. Consulte a Ref. [\\[1\\]](#references) para a derivação completa. A primeira equação do sistema acima garante a imparcialidade (o MPF reproduz o valor exato da expectativa no limite $k_j \\to \\infty$ ), e as demais equações $r-1$ cancelam sucessivamente os primeiros termos de erro de Trotter $r-1$. Quando a norma $L_1$ resultante $\\|x\\|_1$ é muito grande (o que amplifica o ruído de amostragem), é possível, em vez disso, resolver um problema de otimização aproximada que limite $\\|x\\|_1$ e, ao mesmo tempo, minimize $\\|Ax - b\\|$.\n",
        "\n",
        "**Os coeficientes dinâmicos** [\\[2\\]](#references), [\\[3\\]](#references) dependem, além disso, do hamiltoniano, do estado inicial e do tempo de evolução $t$. Eles minimizam a distância na norma de Frobenius entre o estado real evoluído no tempo e a aproximação MPF:\n",
        "\n",
        "$$\n",
        "\\|\\rho(t) - \\mu^D(t)\\|_F^2 = 1 + \\sum_{i,j} M_{ij}(t)\\, x_i(t)\\, x_j(t) - 2\\sum_i L_i(t)\\, x_i(t),\n",
        "$$\n",
        "\n",
        "onde $M_{ij}(t) = \\mathrm{Tr}[\\rho_{k_i}(t)\\,\\rho_{k_j}(t)]$ é a matriz de Gram das sobreposições entre os estados evoluídos por Trotter para diferentes contagens de passos $k_i, k_j$, e $L_i(t) = \\mathrm{Tr}[\\rho(t)\\,\\rho_{k_i}(t)]$ mede a sobreposição com o estado exato (aproximado). Neste tutorial, essas grandezas são calculadas de forma eficiente utilizando métodos de redes tensoriais, especificamente os backends TeNPy-based em `qiskit_addon_mpf`.\n",
        "\n",
        "<span id=\"when-to-use-mpfs\" />\n",
        "\n",
        "### Quando usar os MPFs\n",
        "\n",
        "Os MPFs são mais benéficos quando:\n",
        "\n",
        "* **A profundidade do circuito é o gargalo.** Se o ruído do hardware limitar a profundidade em que é possível operar, utilize MPFs para obter maior precisão efetiva do método de Trotter em circuitos menos profundos.\n",
        "* **Você precisa de valores de expectativa precisos, e não da preparação completa do estado.** As MPFs operam no nível dos valores esperados — elas combinam números clássicos, e não estados quânticos. Portanto, são ideais para estimativa observável ao se utilizar a primitiva Estimador.\n",
        "* **Você combina um número modesto de passos de trot.** Normalmente, a combinação de $r = 3$ – $5$, com diferentes contagens de passos $k_j$, é suficiente para eliminar vários termos de erro de Trotter de ordem superior, mantendo $\\|x\\|_1$ em um nível gerenciável.\n",
        "\n",
        "<span id=\"when-mpfs-might-not-help\" />\n",
        "\n",
        "### Quando os MPFs podem não ser úteis\n",
        "\n",
        "* **Tempos de evolução muito curtos.** Quando $t$ é pequeno o suficiente para que uma única fórmula de Trotter de baixa ordem já seja precisa, o esforço adicional de executar vários circuitos torna-se desnecessário.\n",
        "* **Tarefas de preparação para o exame estadual.** Os MPFs produzem um *valor esperado* corrigido, e não um estado quântico corrigido. Se você precisar do estado real ao longo do tempo (por exemplo, como entrada para outra sub-rotina quântica), os MPFs não se aplicam.\n",
        "* **Contagens de passos do método Trotter que violam o regime de convergência.** A derivação do coeficiente estático expande cada “ $\\left[S_{2\\chi}(t/k_j)\\right]^{k_j}$ ” individual como uma série em “ $t/k_j$ ”; essa expansão só converge bem quando “ $t/k_{\\min} \\lesssim 1$ ”. Se “ $k_{\\min}$ ” for escolhido muito pequeno para o “ $t$ ” dado, o circuito mais superficial fica bem fora do regime perturbativo, os termos de erro de ordem superior que o MPF deixa sem cancelar tornam-se grandes, e o cancelamento pode exigir coeficientes grandes. A norma de $L_1$ $\\|x\\|_1$ serve como um diagnóstico prático: quando $\\|x\\|_1 \\gg 1$, a sobrecarga de amostragem $\\propto \\|x\\|_1^2$ pode superar a redução do erro de Trotter. Consulte o [guia sobre como escolher degraus Trotter](https://qiskit.github.io/qiskit-addon-mpf/how_tos/choose_trotter_steps.html) para obter mais detalhes.\n",
        "\n",
        "<span id=\"what-this-tutorial-covers\" />\n",
        "\n",
        "### O que este tutorial aborda\n",
        "\n",
        "Este tutorial apresenta um fluxo de trabalho completo do MPF em duas etapas. Primeiro, um **exemplo de simulador em pequena escala** (cadeia de Heisenberg de 10 qubits) demonstra como definir o problema, calcular os coeficientes MPF estáticos e dinâmicos e comparar os valores esperados resultantes com a diagonalização exata. Em seguida, um **exemplo de hardware em grande escala** (cadeia XXZ de 50 qubits) mostra como fazer a transpilagem, executar em um hardwar IBM Quantum e com mitigação de erros e processar os resultados posteriormente usando os coeficientes MPF. Ao longo do texto, utilizamos o `qiskit_addon_mpf` pacote em conjunto com as ferramentas padrão do Qiskit.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b5d478ce",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisitos\n",
        "\n",
        "Antes de iniciar este tutorial, verifique se você tem os seguintes itens instalados:\n",
        "\n",
        "* Qiskit SDK v2.0 ou versão posterior, com suporte [à visualização](/docs/api/qiskit/visualization)\n",
        "* Qiskit Runtime v0.22 ou posterior (`pip install qiskit-ibm-runtime`)\n",
        "* Simulador Qiskit Aer (`pip install qiskit-aer`)\n",
        "* Complemento MPF Qiskit com o backend TeNPy (`pip install \"qiskit-addon-mpf[tenpy]\"`)\n",
        "* Utilitários do complemento Qiskit (`pip install qiskit-addon-utils`)\n",
        "* SciPy (`pip install scipy`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2584c37e",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Instalação\n",
        "\n",
        "A seguir, reunimos *todas* as importações de pacotes utilizadas ao longo deste tutorial em uma única célula. Também definimos uma `CollectAndCollapse` etapa do transpiler que funde rotações adjacentes `rxx` e `ryy` em uma única `XXPlusYYGate`rotação. Essa etapa é aplicada tanto durante a construção do circuito na Etapa 1 (para manter baixo o número de portas) quanto indiretamente quando extraímos a estrutura em camadas para o MPF dinâmico na Etapa 4 (o TeNPy espera portas de dois qubits, e não pares de rotações não fundidas).\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "bf79f9e7",
      "metadata": {},
      "outputs": [],
      "source": [
        "import warnings\n",
        "\n",
        "import numpy as np\n",
        "import matplotlib.pyplot as plt\n",
        "from functools import partial\n",
        "from copy import deepcopy\n",
        "\n",
        "from qiskit import QuantumCircuit\n",
        "from qiskit.quantum_info import Pauli, SparsePauliOp, Statevector\n",
        "from qiskit.synthesis import SuzukiTrotter\n",
        "from qiskit.transpiler import CouplingMap, PassManager\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "from qiskit.circuit.library import XXPlusYYGate\n",
        "from qiskit.transpiler.passes.optimization.collect_and_collapse import (\n",
        "    CollectAndCollapse,\n",
        "    collect_using_filter_function,\n",
        "    collapse_to_operation,\n",
        ")\n",
        "\n",
        "from qiskit_aer import AerSimulator\n",
        "from qiskit_ibm_runtime import EstimatorV2 as Estimator, QiskitRuntimeService\n",
        "\n",
        "from qiskit_addon_utils.problem_generators import (\n",
        "    generate_xyz_hamiltonian,\n",
        "    generate_time_evolution_circuit,\n",
        ")\n",
        "from qiskit_addon_utils.slicing import slice_by_depth\n",
        "from qiskit_addon_mpf.static import setup_static_lse\n",
        "from qiskit_addon_mpf.dynamic import setup_dynamic_lse\n",
        "from qiskit_addon_mpf.costs import (\n",
        "    setup_exact_problem,\n",
        "    setup_sum_of_squares_problem,\n",
        "    setup_frobenius_problem,\n",
        ")\n",
        "from qiskit_addon_mpf.backends.tenpy_layers import (\n",
        "    LayerModel,\n",
        "    LayerwiseEvolver,\n",
        ")\n",
        "from qiskit_addon_mpf.backends.tenpy_tebd import MPOState, MPS_neel_state\n",
        "\n",
        "from scipy.linalg import expm\n",
        "\n",
        "# Suppress TeNPy's `unit_cell_width` future-API warning. The default\n",
        "# (`unit_cell_width=len(sites)`) is correct for Chain lattices, which is what\n",
        "# `CouplingMap.from_line(...)` produces here, so the warning is informational.\n",
        "warnings.filterwarnings(\n",
        "    \"ignore\",\n",
        "    message=r\".*unit_cell_width.*\",\n",
        "    category=UserWarning,\n",
        ")\n",
        "\n",
        "\n",
        "# --- Helper: collect XX + YY rotations into a single gate ---\n",
        "def filter_function(node):\n",
        "    return node.op.name in {\"rxx\", \"ryy\"}\n",
        "\n",
        "\n",
        "collect_function = partial(\n",
        "    collect_using_filter_function,\n",
        "    filter_function=filter_function,\n",
        "    split_blocks=True,\n",
        "    min_block_size=1,\n",
        ")\n",
        "\n",
        "\n",
        "def collapse_to_xx_plus_yy(block):\n",
        "    param = 0.0\n",
        "    for node in block.data:\n",
        "        param += node.operation.params[0]\n",
        "    return XXPlusYYGate(param)\n",
        "\n",
        "\n",
        "collapse_function = partial(\n",
        "    collapse_to_operation,\n",
        "    collapse_function=collapse_to_xx_plus_yy,\n",
        ")\n",
        "\n",
        "pm = PassManager()\n",
        "pm.append(CollectAndCollapse(collect_function, collapse_function))"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "24f08467",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-simulator-example\" />\n",
        "\n",
        "## Exemplo de simulador em pequena escala\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "378e82ba",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "### Passo 1: Mapear entradas clássicas para um problema quântico\n",
        "\n",
        "Começamos com um modelo de Heisenberg de 10 qubits em uma reta, utilizando o estado de Néel $\\vert 0101\\ldots01 \\rangle$ como estado inicial. O hamiltoniano é:\n",
        "\n",
        "$$\n",
        "\\hat{\\mathcal{H}}_{\\text{Heis}} = J \\sum_{i=1}^{L-1} \\left(X_i X_{i+1} + Y_i Y_{i+1} + Z_i Z_{i+1}\\right),\n",
        "$$\n",
        "\n",
        "onde $J$ é a intensidade do acoplamento entre vizinhos mais próximos. Medimos o correlacionador ZZ $Z_{L/2-1} Z_{L/2}$ em um par de qubits no meio da cadeia e utilizamos os passos de Trotter $k_j = [1, 2, 4]$ com uma fórmula de produto de segunda ordem.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "bdd0d4fc",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "SparsePauliOp(['IIIIIIIXXI', 'IIIIIIIYYI', 'IIIIIIIZZI', 'IIIIIXXIII', 'IIIIIYYIII', 'IIIIIZZIII', 'IIIXXIIIII', 'IIIYYIIIII', 'IIIZZIIIII', 'IXXIIIIIII', 'IYYIIIIIII', 'IZZIIIIIII', 'IIIIIIIIXX', 'IIIIIIIIYY', 'IIIIIIIIZZ', 'IIIIIIXXII', 'IIIIIIYYII', 'IIIIIIZZII', 'IIIIXXIIII', 'IIIIYYIIII', 'IIIIZZIIII', 'IIXXIIIIII', 'IIYYIIIIII', 'IIZZIIIIII', 'XXIIIIIIII', 'YYIIIIIIII', 'ZZIIIIIIII'],\n",
            "              coeffs=[1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j,\n",
            " 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j,\n",
            " 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j])\n"
          ]
        }
      ],
      "source": [
        "L = 10\n",
        "\n",
        "# Generate coupling map and Hamiltonian\n",
        "coupling_map = CouplingMap.from_line(L, bidirectional=False)\n",
        "\n",
        "hamiltonian = generate_xyz_hamiltonian(\n",
        "    coupling_map,\n",
        "    coupling_constants=(1.0, 1.0, 1.0),\n",
        "    ext_magnetic_field=(0.0, 0.0, 0.0),\n",
        ")\n",
        "print(hamiltonian)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "fd3dc9c8",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "SparsePauliOp(['IIIIZZIIII'],\n",
            "              coeffs=[1.+0.j])\n"
          ]
        }
      ],
      "source": [
        "# Observable: ZZ on the middle pair of qubits\n",
        "observable = SparsePauliOp.from_sparse_list(\n",
        "    [(\"ZZ\", (L // 2 - 1, L // 2), 1.0)], num_qubits=L\n",
        ")\n",
        "print(observable)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "398c33b2",
      "metadata": {},
      "outputs": [],
      "source": [
        "# MPF parameters\n",
        "mpf_trotter_steps = [1, 2, 4]\n",
        "order = 2\n",
        "symmetric = False\n",
        "\n",
        "trotter_times = np.arange(0.5, 1.55, 0.1)\n",
        "exact_evolution_times = np.arange(trotter_times[0], 1.55, 0.05)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1512ba0e",
      "metadata": {},
      "source": [
        "<span id=\"build-trotter-circuits\" />\n",
        "\n",
        "#### Construir circuitos de Trotter\n",
        "\n",
        "Criamos os circuitos implementando as evoluções temporais aproximadas de Trotter para cada ponto no tempo e cada número de passos de Trotter. A `CollectAndCollapse` etapa definida na seção “Configuração” agrupa as rotações XX e YY em portas únicas do tipo XX+YY, a fim de preparar uma simulação mais eficiente da rede tensorial posteriormente.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "1c194d2b",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Initial Neel state preparation\n",
        "initial_state_circ = QuantumCircuit(L)\n",
        "initial_state_circ.x([i for i in range(L) if i % 2 != 0])\n",
        "\n",
        "\n",
        "all_circs = []\n",
        "for total_time in trotter_times:\n",
        "    mpf_trotter_circs = [\n",
        "        generate_time_evolution_circuit(\n",
        "            hamiltonian,\n",
        "            time=total_time,\n",
        "            synthesis=SuzukiTrotter(reps=num_steps, order=order),\n",
        "        )\n",
        "        for num_steps in mpf_trotter_steps\n",
        "    ]\n",
        "\n",
        "    mpf_trotter_circs = pm.run(\n",
        "        mpf_trotter_circs\n",
        "    )  # Collect XX and YY into XX + YY\n",
        "\n",
        "    mpf_circuits = [\n",
        "        initial_state_circ.compose(circuit) for circuit in mpf_trotter_circs\n",
        "    ]\n",
        "    all_circs.append(mpf_circuits)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "c7ee61e7",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/multi-product-formula/extracted-outputs/c7ee61e7-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 6,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "mpf_circuits[-1].draw(\"mpl\", fold=-1)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4cd6c782",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "### Etapa 2: Otimizar o problema para execução em hardware quântico\n",
        "\n",
        "Para o exemplo em pequena escala, vamos usar o simulador Aer. Duas transformações ocorrem antes que os circuitos estejam prontos para serem executados:\n",
        "\n",
        "1. **Coleta de portas no nível da simulação hamiltoniana.** `XXPlusYYGate`Na célula “Setup”, criamos uma `CollectAndCollapse` passagem que combina as rotações adjacentes `rxx` e `ryy` em uma única. Já aplicamos essa etapa quando construímos os circuitos de Trotter na Etapa 1 (a `pm.run(...)` chamada). Isso reduz o número de portas de dois qubits e, ao mesmo tempo, gera uma estrutura mais adequada para a simulação por rede tensorial no cálculo posterior dos coeficientes dinâmicos.\n",
        "\n",
        "2. **Conversão para o ISA do simulador.** A seguir, executamos o gerenciador de passagens com a predefinição do Qiskit para `optimization_level=3` adaptar cada circuito de Trotter à arquitetura do conjunto de instruções (ISA) do simulador.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "03590a05",
      "metadata": {},
      "outputs": [],
      "source": [
        "aer_sim = AerSimulator()\n",
        "pm_sim = generate_preset_pass_manager(backend=aer_sim, optimization_level=3)\n",
        "\n",
        "isa_circs_all_times = [\n",
        "    pm_sim.run([deepcopy(c) for c in mpf_circuits])\n",
        "    for mpf_circuits in all_circs\n",
        "]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ab6588d3",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-using-qiskit-primitives\" />\n",
        "\n",
        "### Passo 3: Execute usando Qiskit primitives\n",
        "\n",
        "Para o exemplo em pequena escala, executamos os circuitos de Trotter reduzidos por ISA por meio da `EstimatorV2` primitiva implementada pelo Aer. Isso nos proporciona um valor de referência *sem ruído* para cada par $(k_j, t)$ — esses são os valores $\\langle A \\rangle_{k_j}(t)$ que o MPF combinará na Etapa 4. Analisamos os períodos de evolução para que, posteriormente, possamos traçar a curva completa da série temporal de cada fórmula de produto individual e da MPF.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "7225d782",
      "metadata": {},
      "outputs": [],
      "source": [
        "estimator = Estimator(mode=aer_sim)\n",
        "\n",
        "mpf_expvals_all_times, mpf_stds_all_times = [], []\n",
        "for isa_circuits in isa_circs_all_times:\n",
        "    result = estimator.run(\n",
        "        [(circuit, observable) for circuit in isa_circuits], precision=0.005\n",
        "    ).result()\n",
        "    mpf_expvals_all_times.append([res.data.evs for res in result])\n",
        "    mpf_stds_all_times.append([res.data.stds for res in result])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a384f017",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-step-4\" />\n",
        "\n",
        "<span id=\"step-4-post-process-and-return-result-in-desired-classical-format\" />\n",
        "\n",
        "### Etapa 4: Pós-processamento e retorno do resultado no formato clássico desejado\n",
        "\n",
        "A Etapa 4 é onde o MPF é efetivamente construído. Embora os coeficientes $x_j$ sejam *calculados* aqui (e, para a variante dinâmica, esse cálculo possa ser intensivo), conceitualmente eles constituem uma fórmula clássica para combinar as medições quânticas da Etapa 3 em um único valor esperado corrigido — portanto, tratamos todo o fluxo de trabalho relacionado aos coeficientes e à combinação como pós-processamento.\n",
        "\n",
        "Para avaliar até que ponto o MPF reflete a dinâmica real, calculamos primeiro os valores esperados exatos ao longo do tempo, elevando diretamente o hamiltoniano à potência de um. Isso só é viável porque $L = 10$; no exemplo de hardware em grande escala a seguir, teremos que recorrer a estimativas de redes tensoriais.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "a223a360",
      "metadata": {},
      "outputs": [],
      "source": [
        "exact_expvals = []\n",
        "for t in exact_evolution_times:\n",
        "    exp_H = expm(-1j * t * hamiltonian.to_matrix())\n",
        "    initial_state = Statevector(initial_state_circ).data\n",
        "    time_evolved_state = exp_H @ initial_state\n",
        "\n",
        "    exact_obs = (\n",
        "        time_evolved_state.conj()\n",
        "        @ observable.to_matrix()\n",
        "        @ time_evolved_state\n",
        "    ).real\n",
        "    exact_expvals.append(exact_obs)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "361e726e",
      "metadata": {},
      "source": [
        "<span id=\"static-mpf-coefficients\" />\n",
        "\n",
        "#### Coeficientes MPF estáticos\n",
        "\n",
        "Os MPFs estáticos utilizam coeficientes $x_j$ que são independentes do tempo de evolução, do hamiltoniano e do estado inicial. Estabelecemos o sistema linear $Ax = b$ descrito na seção “Contexto” e calculamos os coeficientes. A matriz $A$ é determinada pelo número de passos de Trotter $k_j$, pela ordem $\\chi$ da fórmula do produto e pelo fato de a fórmula ser simétrica (o que determina os expoentes $\\eta_n$ ).\n",
        "\n",
        "Para o nosso exemplo em pequena escala, utilizamos $k_j = [1, 2, 4]$ com uma fórmula de Suzuki-Trotter de ordem não simétrica — $2\\chi=2$ (portanto, $\\chi=1$ e $\\eta_n = 2 + n$, o que resulta em $\\eta_0 = 2,\\, \\eta_1 = 3$ ). O sistema passa a ser:\n",
        "\n",
        "$$\n",
        "A =\n",
        "\\begin{bmatrix}\n",
        "1 & 1 & 1\\\\\n",
        "1 & \\frac{1}{2^2} & \\frac{1}{4^2}  \\\\\n",
        "1 & \\frac{1}{2^3} & \\frac{1}{4^3}  \\\\\n",
        "\\end{bmatrix}, \\quad\n",
        "b =\n",
        "\\begin{bmatrix}\n",
        "1 \\\\\n",
        "0 \\\\\n",
        "0\n",
        "\\end{bmatrix}.\n",
        "$$\n",
        "\n",
        "A primeira linha garante a imparcialidade ( $\\sum_j x_j = 1$ ); a segunda e a terceira linhas cancelam, respectivamente, os termos de erro de Trotter de ordem principal $1/k^2$ e de ordem seguinte $1/k^3$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4f2ca1e2",
      "metadata": {},
      "source": [
        "<span id=\"set-up-the-lse\" />\n",
        "\n",
        "##### Configure o LSE\n",
        "\n",
        "Utilizamos `setup_static_lse` de `qiskit_addon_mpf.static` para construir a matriz $A$ e o vetor do lado direito $b$ descritos acima. A matriz $A$ depende não apenas de $k_j$, mas também da nossa escolha da fórmula do produto — em particular, sua *ordem* $\\chi$ e se ela é *simétrica*. O `symmetric` sinalizador controla o padrão do expoente $\\eta_n$ (fórmulas simétricas produzem apenas termos de erro de Trotter de potência par; ver Ref. [\\[1\\]](#references) ). Observe que, conforme mostrado na Ref. [\\[2\\]](#references), definir `symmetric=True` não é estritamente necessário, mesmo quando a função de potencial subjacente é simétrica — a LSE não simétrica continua válida (ela impõe restrições adicionais desnecessárias).\n",
        "\n",
        "Para o nosso exemplo, já definimos `order = 2` e `symmetric = False` na Etapa 1.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "827b0b42",
      "metadata": {},
      "outputs": [],
      "source": [
        "lse = setup_static_lse(mpf_trotter_steps, order=order, symmetric=symmetric)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "003e4bdd",
      "metadata": {},
      "source": [
        "Verifique a matriz construída $A$ e o vetor $b$ para confirmar se eles correspondem ao sistema descrito acima.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "6f879978",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "array([[1.      , 1.      , 1.      ],\n",
              "       [1.      , 0.25    , 0.0625  ],\n",
              "       [1.      , 0.125   , 0.015625]])"
            ]
          },
          "execution_count": 11,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "lse.A"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "64fe7db9",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "array([1., 0., 0.])"
            ]
          },
          "execution_count": 12,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "lse.b"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "16ab9f79",
      "metadata": {},
      "source": [
        "Com a equação de LSE em mãos, calculamos os coeficientes estáticos $x_j$ por meio de `lse.solve()` (essa é a solução direta $x = A^{-1}b$ ).\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "7b69192a",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "The static coefficients associated with the ansatze are: [ 0.04761905 -0.57142857  1.52380952]\n"
          ]
        }
      ],
      "source": [
        "mpf_coeffs = lse.solve()\n",
        "print(\n",
        "    f\"The static coefficients associated with the ansatze are: {mpf_coeffs}\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1c238a59",
      "metadata": {},
      "source": [
        "<span id=\"optimize-for-$x$-using-an-exact-model\" />\n",
        "\n",
        "##### Otimize para $x$ usando um modelo exato\n",
        "\n",
        "Como alternativa ao cálculo de $x = A^{-1}b$, você pode usar [`setup\\_exact\\_model`](https://qiskit.github.io/qiskit-addon-mpf/stubs/qiskit_addon_mpf.static.setup_exact_model.html) para construir uma instância de [cvxpy.Problem](https://www.cvxpy.org/api_reference/cvxpy.problems.html#cvxpy.Problem) que utilize o LSE como restrições e cuja solução ótima resulte em $x$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "993465e9",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[ 0.04761905 -0.57142857  1.52380952]\n"
          ]
        }
      ],
      "source": [
        "model_exact, coeffs_exact = setup_exact_problem(lse)\n",
        "model_exact.solve()\n",
        "print(coeffs_exact.value)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "eb61ea70",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "L1 norm of the exact coefficients: 2.1428571428556378\n"
          ]
        }
      ],
      "source": [
        "print(\n",
        "    \"L1 norm of the exact coefficients:\",\n",
        "    np.linalg.norm(coeffs_exact.value, ord=1),\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "dac0472b",
      "metadata": {},
      "source": [
        "<span id=\"optimize-for-$x$-using-an-approximate-model\" />\n",
        "\n",
        "##### Otimize para $x$ usando um modelo aproximado\n",
        "\n",
        "Pode acontecer que a norma “ $L_1$ ” para o conjunto escolhido de valores de “ $k_j$ ” seja considerada muito alta. Se for esse o caso e você não puder escolher um conjunto diferente de valores de $k_j$, é possível usar uma solução aproximada que restrinja a norma $L_1$ a um limite escolhido, ao mesmo tempo em que minimiza $\\|Ax - b\\|$. Confira o guia sobre [Como usar o modelo aproximado](https://qiskit.github.io/qiskit-addon-mpf/how_tos/using_approximate_model.html).\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "0cd7dea4",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[-1.10294118e-03 -2.48897059e-01  1.25000000e+00]\n",
            "L1 norm of the approximate coefficients: 1.5\n"
          ]
        }
      ],
      "source": [
        "model_approx, coeffs_approx = setup_sum_of_squares_problem(\n",
        "    lse, max_l1_norm=1.5\n",
        ")\n",
        "model_approx.solve()\n",
        "print(coeffs_approx.value)\n",
        "print(\n",
        "    \"L1 norm of the approximate coefficients:\",\n",
        "    np.linalg.norm(coeffs_approx.value, ord=1),\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "10fd05eb",
      "metadata": {},
      "source": [
        "<span id=\"dynamic-mpf-coefficients\" />\n",
        "\n",
        "#### Coeficientes dinâmicos do MPF\n",
        "\n",
        "O MPF estático cancela os termos de erro de Trotter de uma forma independente do hamiltoniano e do estado; portanto, ele não produz necessariamente o menor erro de aproximação possível para um determinado hamiltoniano e estado inicial. O MPF dinâmico (Ref. [\\[2\\]](#references), [\\[3\\]](#references) ), por outro lado, determina coeficientes dependentes do tempo $x_i(t)$ que minimizam a distância na norma de Frobenius $\\|\\rho(t) - \\mu^D(t)\\|_F^2$ em cada instante $t$. Conforme mostrado na seção “Contexto”, isso requer a matriz de sobreposição $M_{ij}(t)$ entre os estados evoluídos por Trotter e a sobreposição $L_i(t)$ com o estado exato — ambos estimados por meio de backends de rede tensorial ( TeNPy ) em `qiskit_addon_mpf`.\n",
        "\n",
        "Para configurar o LSE dinâmico, precisamos de três elementos:\n",
        "\n",
        "1. Uma **fábrica de evolutores aproximada** que o complemento executará para cada $k_j$ a fim de produzir $\\rho_{k_j}(t)$ como um MPS/MPO. Nós o construímos a partir da estrutura em camadas do circuito de Trotter de ordem $2$ (uma camada por `slice_by_depth`), envolvido como um `LayerwiseEvolver` com parâmetros de truncamento de TeNPy.\n",
        "2. **Uma fábrica de evolutores exatos** que produz um $\\rho(t)$ de referência de alta precisão. Utilizamos um circuito de Suzuki-Trotter de quarta ordem com pequeno passo de tempo (`dt=0.1`, `order=4`) como substituto da evolução exata.\n",
        "3. Uma **fábrica de identidades** e um **MPS de estado inicial** que inicializam a simulação do TeNPy.\n",
        "\n",
        "A célula abaixo cria a fábrica de evolutores aproximados.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "52d78403",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Create approximate time-evolution circuits\n",
        "single_2nd_order_circ = generate_time_evolution_circuit(\n",
        "    hamiltonian, time=1.0, synthesis=SuzukiTrotter(reps=1, order=order)\n",
        ")\n",
        "single_2nd_order_circ = pm.run(single_2nd_order_circ)  # collect XX and YY\n",
        "\n",
        "# Find layers in the circuit\n",
        "layers = slice_by_depth(single_2nd_order_circ, max_slice_depth=1)\n",
        "\n",
        "# Create tensor network models\n",
        "models = [\n",
        "    LayerModel.from_quantum_circuit(layer, conserve=\"Sz\") for layer in layers\n",
        "]\n",
        "\n",
        "# Create the time-evolution object\n",
        "approx_factory = partial(\n",
        "    LayerwiseEvolver,\n",
        "    layers=models,\n",
        "    options={\n",
        "        \"preserve_norm\": False,\n",
        "        \"trunc_params\": {\n",
        "            \"chi_max\": 64,\n",
        "            \"svd_min\": 1e-8,\n",
        "            \"trunc_cut\": None,\n",
        "        },\n",
        "        \"max_delta_t\": 2,\n",
        "    },\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "91d2783e",
      "metadata": {},
      "source": [
        "<Admonition type=\"warning\">\n",
        "  As opções do site `LayerwiseEvolver` que determinam os detalhes da simulação da rede de tensores devem ser escolhidas cuidadosamente para evitar a configuração de um problema de otimização mal definido.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1c702f67",
      "metadata": {},
      "source": [
        "`dt=0.1`Aproximamos o estado exato ao longo do tempo com uma fórmula de Suzuki-Trotter de quarta ordem, utilizando um pequeno intervalo de tempo. Os parâmetros de truncamento do `TeNPy` podem afetar a precisão; portanto, é importante explorar uma variedade de valores.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "abab8bfc",
      "metadata": {},
      "outputs": [],
      "source": [
        "single_4th_order_circ = generate_time_evolution_circuit(\n",
        "    hamiltonian, time=1.0, synthesis=SuzukiTrotter(reps=1, order=4)\n",
        ")\n",
        "single_4th_order_circ = pm.run(single_4th_order_circ)\n",
        "exact_model_layers = [\n",
        "    LayerModel.from_quantum_circuit(layer, conserve=\"Sz\")\n",
        "    for layer in slice_by_depth(single_4th_order_circ, max_slice_depth=1)\n",
        "]\n",
        "\n",
        "exact_factory = partial(\n",
        "    LayerwiseEvolver,\n",
        "    layers=exact_model_layers,\n",
        "    dt=0.1,\n",
        "    options={\n",
        "        \"preserve_norm\": False,\n",
        "        \"trunc_params\": {\n",
        "            \"chi_max\": 64,\n",
        "            \"svd_min\": 1e-8,\n",
        "            \"trunc_cut\": None,\n",
        "        },\n",
        "        \"max_delta_t\": 2,\n",
        "    },\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2486594c",
      "metadata": {},
      "source": [
        "Por fim, definimos um `identity_factory` que gera o estado inicial do MPO e preparamos o estado inicial de Néel como um MPS que corresponde à rede utilizada pelo modelo de Trotter em camadas.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "id": "1e216575",
      "metadata": {},
      "outputs": [],
      "source": [
        "def identity_factory():\n",
        "    return MPOState.initialize_from_lattice(models[0].lat, conserve=True)\n",
        "\n",
        "\n",
        "mps_initial_state = MPS_neel_state(models[0].lat)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5658315c",
      "metadata": {},
      "source": [
        "Com as fábricas definidas, calculamos agora os coeficientes dinâmicos em cada momento de evolução. Para cada $t$, `setup_dynamic_lse` calcula as matrizes de sobreposição relevantes por meio de TeNPy, e `setup_frobenius_problem` retorna um `cvxpy.Problem` que minimiza o custo da norma de Frobenius. O solucionador retorna coeficientes $x_j(t)$ específicos para esse momento; nós os reunimos em `mpf_dynamic_coeffs_list`. Se o solucionador falhar para um determinado $t$, voltamos a usar coeficientes iguais a zero para que o loop continue.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 20,
      "id": "b05dc012",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Computing dynamic coefficients for time=0.5\n",
            "\n",
            "Computing dynamic coefficients for time=0.6\n",
            "\n",
            "Computing dynamic coefficients for time=0.7\n",
            "\n",
            "Computing dynamic coefficients for time=0.7999999999999999\n",
            "\n",
            "Computing dynamic coefficients for time=0.8999999999999999\n",
            "\n",
            "Computing dynamic coefficients for time=0.9999999999999999\n",
            "\n",
            "Computing dynamic coefficients for time=1.0999999999999999\n",
            "\n",
            "Computing dynamic coefficients for time=1.1999999999999997\n",
            "\n",
            "Computing dynamic coefficients for time=1.2999999999999998\n",
            "\n",
            "Computing dynamic coefficients for time=1.4\n",
            "\n",
            "Computing dynamic coefficients for time=1.4999999999999998\n",
            "\n"
          ]
        }
      ],
      "source": [
        "mpf_dynamic_coeffs_list = []\n",
        "for t in trotter_times:\n",
        "    print(f\"Computing dynamic coefficients for time={t}\")\n",
        "    lse = setup_dynamic_lse(\n",
        "        mpf_trotter_steps,\n",
        "        t,\n",
        "        identity_factory,\n",
        "        exact_factory,\n",
        "        approx_factory,\n",
        "        mps_initial_state,\n",
        "    )\n",
        "    problem, coeffs = setup_frobenius_problem(lse)\n",
        "    try:\n",
        "        problem.solve()\n",
        "        mpf_dynamic_coeffs_list.append(coeffs.value)\n",
        "    except Exception as error:\n",
        "        mpf_dynamic_coeffs_list.append(np.zeros(len(mpf_trotter_steps)))\n",
        "        print(error, \"Calculation Failed for time\", t)\n",
        "    print(\"\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4f8814e3",
      "metadata": {},
      "source": [
        "<span id=\"combine-trotter-expectation-values-with-the-mpf-coefficients\" />\n",
        "\n",
        "#### Combinar os valores esperados de Trotter com os coeficientes do MPF\n",
        "\n",
        "Agora, calculamos o valor de “ $\\langle A \\rangle_{\\text{MPF}}(t) = \\sum_j x_j \\, \\langle A \\rangle_{k_j}(t)$ ” para cada conjunto de coeficientes (estático-exato, estático-aproximado e dinâmico), propagamos os erros-padrão por circuito e representamos graficamente as séries temporais resultantes em relação à curva de diagonalização exata.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 21,
      "id": "35042576",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/multi-product-formula/extracted-outputs/35042576-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "sym = {1: \"^\", 2: \"s\", 4: \"p\"}\n",
        "# Get expectation values at all times for each Trotter step\n",
        "for k, step in enumerate(mpf_trotter_steps):\n",
        "    trotter_curve, trotter_curve_error = [], []\n",
        "    for trotter_expvals, trotter_stds in zip(\n",
        "        mpf_expvals_all_times, mpf_stds_all_times\n",
        "    ):\n",
        "        trotter_curve.append(trotter_expvals[k])\n",
        "        trotter_curve_error.append(trotter_stds[k])\n",
        "\n",
        "    plt.errorbar(\n",
        "        trotter_times,\n",
        "        trotter_curve,\n",
        "        yerr=trotter_curve_error,\n",
        "        alpha=0.5,\n",
        "        markersize=4,\n",
        "        marker=sym[step],\n",
        "        color=\"grey\",\n",
        "        label=f\"{mpf_trotter_steps[k]} Trotter steps\",\n",
        "    )\n",
        "\n",
        "# Get expectation values at all times for the static MPF with exact coeffs\n",
        "exact_mpf_curve, exact_mpf_curve_error = [], []\n",
        "for trotter_expvals, trotter_stds in zip(\n",
        "    mpf_expvals_all_times, mpf_stds_all_times\n",
        "):\n",
        "    mpf_std = np.sqrt(\n",
        "        sum(\n",
        "            [\n",
        "                (coeff**2) * (std**2)\n",
        "                for coeff, std in zip(coeffs_exact.value, trotter_stds)\n",
        "            ]\n",
        "        )\n",
        "    )\n",
        "    exact_mpf_curve_error.append(mpf_std)\n",
        "    exact_mpf_curve.append(trotter_expvals @ coeffs_exact.value)\n",
        "\n",
        "plt.errorbar(\n",
        "    trotter_times,\n",
        "    exact_mpf_curve,\n",
        "    yerr=exact_mpf_curve_error,\n",
        "    markersize=4,\n",
        "    marker=\"o\",\n",
        "    label=\"Static MPF - Exact\",\n",
        "    color=\"purple\",\n",
        ")\n",
        "\n",
        "\n",
        "# Get expectation values at all times for the static MPF with approximate coeffs\n",
        "approx_mpf_curve, approx_mpf_curve_error = [], []\n",
        "for trotter_expvals, trotter_stds in zip(\n",
        "    mpf_expvals_all_times, mpf_stds_all_times\n",
        "):\n",
        "    mpf_std = np.sqrt(\n",
        "        sum(\n",
        "            [\n",
        "                (coeff**2) * (std**2)\n",
        "                for coeff, std in zip(coeffs_approx.value, trotter_stds)\n",
        "            ]\n",
        "        )\n",
        "    )\n",
        "    approx_mpf_curve_error.append(mpf_std)\n",
        "    approx_mpf_curve.append(trotter_expvals @ coeffs_approx.value)\n",
        "\n",
        "plt.errorbar(\n",
        "    trotter_times,\n",
        "    approx_mpf_curve,\n",
        "    yerr=approx_mpf_curve_error,\n",
        "    markersize=4,\n",
        "    marker=\"o\",\n",
        "    label=\"Static MPF - Approx\",\n",
        "    color=\"orange\",\n",
        ")\n",
        "\n",
        "\n",
        "# Get expectation values at all times for the dynamic MPF\n",
        "dynamic_mpf_curve, dynamic_mpf_curve_error = [], []\n",
        "for trotter_expvals, trotter_stds, dynamic_coeffs in zip(\n",
        "    mpf_expvals_all_times, mpf_stds_all_times, mpf_dynamic_coeffs_list\n",
        "):\n",
        "    mpf_std = np.sqrt(\n",
        "        sum(\n",
        "            [\n",
        "                (coeff**2) * (std**2)\n",
        "                for coeff, std in zip(dynamic_coeffs, trotter_stds)\n",
        "            ]\n",
        "        )\n",
        "    )\n",
        "    dynamic_mpf_curve_error.append(mpf_std)\n",
        "    dynamic_mpf_curve.append(trotter_expvals @ dynamic_coeffs)\n",
        "\n",
        "plt.errorbar(\n",
        "    trotter_times,\n",
        "    dynamic_mpf_curve,\n",
        "    yerr=dynamic_mpf_curve_error,\n",
        "    markersize=4,\n",
        "    marker=\"o\",\n",
        "    label=\"Dynamic MPF\",\n",
        "    color=\"pink\",\n",
        ")\n",
        "\n",
        "\n",
        "# Exact expectation values\n",
        "plt.plot(\n",
        "    exact_evolution_times,\n",
        "    exact_expvals,\n",
        "    color=\"red\",\n",
        "    linestyle=\"--\",\n",
        "    label=\"Exact time-evolution\",\n",
        ")\n",
        "\n",
        "plt.title(f\"$\\\\langle Z_{{{L//2-1}}} Z_{{{L//2}}} \\\\rangle$ vs time\")\n",
        "plt.xlabel(\"Time\")\n",
        "plt.ylabel(\"Expectation Value\")\n",
        "plt.legend(loc=\"upper center\", bbox_to_anchor=(0.5, -0.2), ncol=2)\n",
        "plt.grid(alpha=0.1)\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "34923748",
      "metadata": {},
      "source": [
        "O gráfico acima ilustra a interação entre o erro de Trotter e o erro amostral.\n",
        "\n",
        "* **Erro de Trotter.** As fórmulas individuais dos produtos (marcadores cinza) se afastam cada vez mais da curva exata à medida que o tempo passa. O circuito $k=1$ apresenta o maior desvio e é o mais raso, mas também já se encontra no regime em que $t/k \\gtrsim 1$, de modo que o principal termo de erro $1/k^{2}$ é grande. As combinações de MPF (marcadores coloridos) anulam vários desses termos de erro de Trotter principais, de modo que acompanham a curva exata com muito mais precisão do que qualquer circuito de “ $k_j$ ” isolado. A lacuna restante reflete os termos de Trotter de ordem superior que o MPF *não* cancela: um MPF estático de ordem $2$, $r=3$ elimina apenas as duas primeiras ordens de erro e, em $t/k_{\\min}$, a cauda não cancelada acaba por se tornar dominante — portanto, o MPF não garante que circuitos muito rasos permaneçam precisos em momentos arbitrários.\n",
        "\n",
        "* **Erro amostral.** As barras de erro mais largas nas curvas do MPF são uma consequência direta da combinação linear: a propagação dos erros-padrão independentes por circuito $\\sigma_{k_j}$ resulta em uma variância total $\\sigma_{\\text{MPF}}^2 = \\sum_j x_j^2 \\, \\sigma_{k_j}^2$. Portanto, quanto maior for o $\\|x\\|_2$ (e, na prática, o $\\|x\\|_1$, que é o que controlamos), mais medições serão necessárias para atingir uma determinada incerteza alvo. Essa é a compensação por trás da opção “solucionador aproximado” na seção “Contexto”: limitamos o valor de $\\|x\\|_1$ para manter essa sobrecarga dentro de limites razoáveis. É fundamental ressaltar que, ao contrário do erro de Trotter, o erro amostral diminui com o aumento d $1/\\sqrt{N_{\\text{shots}}}$; portanto, ele sempre pode ser reduzido com o aumento do número de ensaios.\n",
        "\n",
        "No exemplo de hardware em grande escala abaixo, o ruído do hardware surge como uma fonte adicional de erro em cada $\\langle A \\rangle_{k_j}$, que, da mesma forma, é amplificado pelos coeficientes do MPF. Veremos, nessa seção, como a mitigação de erros interage com os MPFs.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6fa763ff",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## Exemplo de hardware em grande escala\n",
        "\n",
        "Nesta seção, ampliemos a escala do problema para além do que é possível simular com exatidão. Reproduzimos alguns dos resultados apresentados na Ref. [\\[3\\]](#references), utilizando uma cadeia XXZ de 50 qubits no tempo $t = 3$. Seguimos o mesmo fluxo de trabalho de quatro etapas do exemplo em pequena escala, agora voltado para hardware quântico real com mitigação de erros. Assim como no modelo, cada etapa é marcada diretamente no código, e uma única etapa pode abranger várias células quando vale a pena examinar seus resultados intermediários.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "481fea70",
      "metadata": {},
      "source": [
        "O mapeamento reflete o exemplo em pequena escala: definir um hamiltoniano, escolher os parâmetros de Trotter, calcular os coeficientes MPF (estáticos e dinâmicos) e construir circuitos. As principais diferenças são:\n",
        "\n",
        "* Um **hamiltoniano XXZ** em 50 nós com acoplamentos aleatórios extraídos de $\\mathcal{U}(0.5, 1.5)$ (Ref. [\\[3\\]](#references) ).\n",
        "* Uma fórmula de Trotter **simétrica** de segunda ordem com $k_j = [3, 4, 6]$ o (portanto, $\\chi=1$, `symmetric=True`).\n",
        "* Um único tempo de evolução fixo $t = 3$. Com $k_{\\min}=3$, isso resulta em $t/k_{\\min}=1$, mantendo os constituintes de profundidade rasa dentro do regime de convergência de Trotter, onde o modelo de erro dominante no qual o MPF se baseia é válido.\n",
        "* Uma **comparação adicional de circuito único foi executada com etapas de Trotter $k = 10$**, usada como linha de base. Escolhemos o $k = 10$ porque sua profundidade de dois qubits no hardware é maior do que a do constituinte MPF mais profundo ( $k_{\\max}=6$ ) somada à sobrecarga de executar múltiplos circuitos MPF — profundidade suficiente para ser limitada pelo ruído, que é o regime no qual se espera que a combinação de MPFs supere o desempenho da linha de base de circuito único. Trata-se de uma comparação de “circuito único profundo” com a combinação MPF, e não de um circuito que tenha como alvo o erro de Trotter efetivo do MPF (o que exigiria muito mais etapas).\n",
        "\n",
        "Observe que, embora ainda estejamos na Etapa 1 (mapeamento e construção do circuito), também pré-calculamos os coeficientes dinâmicos juntamente com os estáticos nesta célula. Os coeficientes dinâmicos dependem de $H$ e $t$, mas não das medições quânticas; portanto, podem ser calculados a qualquer momento antes da Etapa 4. Fazemos isso agora para manter todas as configurações específicas do MPF em um único lugar.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 22,
      "id": "a019ac32",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Static coefficients: [ 0.42857143 -1.82857143  2.4       ]\n",
            "L1 norm: 4.65714285714286\n",
            "Approximate coefficients: [-0.4942491   0.40206845  1.09218065]\n",
            "L1 norm (approx): 1.9884981979026675\n",
            "Computing dynamic coefficients for time=3\n"
          ]
        }
      ],
      "source": [
        "# -------------------------Step 1-------------------------\n",
        "L = 50\n",
        "coupling_map = CouplingMap.from_line(L, bidirectional=False)\n",
        "\n",
        "# XXZ Hamiltonian with random couplings (Ref. [3])\n",
        "np.random.seed(0)\n",
        "even_edges = list(coupling_map.get_edges())[::2]\n",
        "odd_edges = list(coupling_map.get_edges())[1::2]\n",
        "\n",
        "Js = np.random.uniform(0.5, 1.5, size=L)\n",
        "hamiltonian = SparsePauliOp(Pauli(\"I\" * L))\n",
        "for i, edge in enumerate(even_edges + odd_edges):\n",
        "    hamiltonian += SparsePauliOp.from_sparse_list(\n",
        "        [\n",
        "            (\"XX\", (edge), 2 * Js[i]),\n",
        "            (\"YY\", (edge), 2 * Js[i]),\n",
        "            (\"ZZ\", (edge), 4 * Js[i]),\n",
        "        ],\n",
        "        num_qubits=L,\n",
        "    )\n",
        "\n",
        "observable = SparsePauliOp.from_sparse_list(\n",
        "    [(\"ZZ\", (L // 2 - 1, L // 2), 1.0)], num_qubits=L\n",
        ")\n",
        "\n",
        "total_time = 3\n",
        "mpf_trotter_steps = [3, 4, 6]\n",
        "order = 2\n",
        "symmetric = True\n",
        "\n",
        "# Static coefficients\n",
        "lse = setup_static_lse(mpf_trotter_steps, order=order, symmetric=symmetric)\n",
        "mpf_coeffs = lse.solve()\n",
        "print(f\"Static coefficients: {mpf_coeffs}\")\n",
        "print(f\"L1 norm: {np.linalg.norm(mpf_coeffs, ord=1)}\")\n",
        "\n",
        "model_approx, coeffs_approx = setup_sum_of_squares_problem(\n",
        "    lse, max_l1_norm=2.0\n",
        ")\n",
        "model_approx.solve()\n",
        "print(f\"Approximate coefficients: {coeffs_approx.value}\")\n",
        "print(f\"L1 norm (approx): {np.linalg.norm(coeffs_approx.value, ord=1)}\")\n",
        "\n",
        "# -------------------------Dynamic coefficients-------------------------\n",
        "single_2nd_order_circ = generate_time_evolution_circuit(\n",
        "    hamiltonian, time=1.0, synthesis=SuzukiTrotter(reps=1, order=order)\n",
        ")\n",
        "single_2nd_order_circ = pm.run(single_2nd_order_circ)\n",
        "\n",
        "layers = slice_by_depth(single_2nd_order_circ, max_slice_depth=1)\n",
        "models = [\n",
        "    LayerModel.from_quantum_circuit(layer, conserve=\"Sz\") for layer in layers\n",
        "]\n",
        "\n",
        "approx_factory = partial(\n",
        "    LayerwiseEvolver,\n",
        "    layers=models,\n",
        "    options={\n",
        "        \"preserve_norm\": False,\n",
        "        \"trunc_params\": {\"chi_max\": 64, \"svd_min\": 1e-8, \"trunc_cut\": None},\n",
        "        \"max_delta_t\": 4,\n",
        "    },\n",
        ")\n",
        "\n",
        "single_4th_order_circ = generate_time_evolution_circuit(\n",
        "    hamiltonian, time=1.0, synthesis=SuzukiTrotter(reps=1, order=4)\n",
        ")\n",
        "single_4th_order_circ = pm.run(single_4th_order_circ)\n",
        "exact_model_layers = [\n",
        "    LayerModel.from_quantum_circuit(layer, conserve=\"Sz\")\n",
        "    for layer in slice_by_depth(single_4th_order_circ, max_slice_depth=1)\n",
        "]\n",
        "\n",
        "exact_factory = partial(\n",
        "    LayerwiseEvolver,\n",
        "    layers=exact_model_layers,\n",
        "    dt=0.1,\n",
        "    options={\n",
        "        \"preserve_norm\": False,\n",
        "        \"trunc_params\": {\"chi_max\": 64, \"svd_min\": 1e-8, \"trunc_cut\": None},\n",
        "        \"max_delta_t\": 3,\n",
        "    },\n",
        ")\n",
        "\n",
        "\n",
        "def identity_factory():\n",
        "    return MPOState.initialize_from_lattice(models[0].lat, conserve=True)\n",
        "\n",
        "\n",
        "mps_initial_state = MPS_neel_state(models[0].lat)\n",
        "\n",
        "print(f\"Computing dynamic coefficients for time={total_time}\")\n",
        "lse_dyn = setup_dynamic_lse(\n",
        "    mpf_trotter_steps,\n",
        "    total_time,\n",
        "    identity_factory,\n",
        "    exact_factory,\n",
        "    approx_factory,\n",
        "    mps_initial_state,\n",
        ")\n",
        "problem, coeffs_dyn = setup_frobenius_problem(lse_dyn)\n",
        "try:\n",
        "    problem.solve()\n",
        "    mpf_dynamic_coeffs = coeffs_dyn.value\n",
        "except Exception as error:\n",
        "    mpf_dynamic_coeffs = np.zeros(len(mpf_trotter_steps))\n",
        "    print(error, \"Calculation Failed\")\n",
        "\n",
        "# -------------------------Step 1 (cont): Build circuits-------------------------\n",
        "mpf_circuits = []\n",
        "for k in mpf_trotter_steps:\n",
        "    circuit = QuantumCircuit(L)\n",
        "    circuit.x([i for i in range(L) if i % 2])\n",
        "    trotter_circ = generate_time_evolution_circuit(\n",
        "        hamiltonian,\n",
        "        synthesis=SuzukiTrotter(reps=k, order=order),\n",
        "        time=total_time,\n",
        "    )\n",
        "    circuit.compose(trotter_circ, qubits=range(L), inplace=True)\n",
        "    mpf_circuits.append(circuit)\n",
        "\n",
        "# Baseline \"single deep circuit\" comparison run with k=10 Trotter steps.\n",
        "# Its two-qubit depth is deeper than the deepest MPF constituent (k_max=6) plus\n",
        "# the overhead of running multiple circuits, pushing it into the noise-limited\n",
        "# regime where MPF is expected to outperform. It does NOT target the MPF's effective\n",
        "# Trotter error (which would require many more steps).\n",
        "comp_circuit = QuantumCircuit(L)\n",
        "comp_circuit.x([i for i in range(L) if i % 2])\n",
        "trotter_circ = generate_time_evolution_circuit(\n",
        "    hamiltonian,\n",
        "    synthesis=SuzukiTrotter(reps=10, order=order),\n",
        "    time=total_time,\n",
        ")\n",
        "comp_circuit.compose(trotter_circ, qubits=range(L), inplace=True)\n",
        "mpf_circuits.append(comp_circuit)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b5d388b1",
      "metadata": {},
      "source": [
        "Agora otimizamos os circuitos para o backend escolhido. `optimization_level=3`Utilizamos o gerenciador de passagens predefinido do Qiskit, que seleciona automaticamente um bom conjunto de qubits físicos e direciona cada circuito para a topologia do dispositivo.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 23,
      "id": "05bad997",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "<IBMBackend('ibm_fez')>\n"
          ]
        }
      ],
      "source": [
        "# -------------------------Step 2-------------------------\n",
        "service = QiskitRuntimeService()\n",
        "# backend = service.least_busy(operational=True, simulator=False, min_num_qubits=L)\n",
        "backend = service.backend(\"ibm_fez\")\n",
        "print(backend)\n",
        "\n",
        "transpiler = generate_preset_pass_manager(\n",
        "    optimization_level=3, backend=backend\n",
        ")\n",
        "transpiled_circuits = [transpiler.run(circ) for circ in mpf_circuits]\n",
        "\n",
        "isa_observables = [\n",
        "    observable.apply_layout(circ.layout) for circ in transpiled_circuits\n",
        "]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b578c285",
      "metadata": {},
      "source": [
        "A execução de circuitos mais complexos em hardware real exige medidas agressivas de mitigação de erros. Oferecemos descoplamento dinâmico, rotação de portas e medições, mitigação de erros de medição e extrapolação sem ruído (ZNE). Observe que os fatores de ruído ZNE que utilizamos aqui (`1, 1.2, 1.4`) são menores do que em um cenário de circuito raso, uma vez que os constituintes MPF mais profundos já estão próximos do limiar de ruído e grandes amplificações de ruído os levariam além do ponto em que a extrapolação ZNE é confiável.\n",
        "\n",
        "Enviamos todos os quatro circuitos (três componentes do MPF em $k_j = [3, 4, 6]$, além da linha de base do $k = 10$ ) em uma única tarefa do Estimator.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 24,
      "id": "e2722b61",
      "metadata": {},
      "outputs": [],
      "source": [
        "# -------------------------Step 3-------------------------\n",
        "estimator = Estimator(mode=backend)\n",
        "estimator.options.default_shots = 30000\n",
        "\n",
        "# Error suppression/mitigation\n",
        "estimator.options.dynamical_decoupling.enable = True\n",
        "estimator.options.twirling.enable_gates = True\n",
        "estimator.options.twirling.enable_measure = True\n",
        "estimator.options.twirling.num_randomizations = \"auto\"\n",
        "estimator.options.twirling.strategy = \"active-accum\"\n",
        "estimator.options.resilience.measure_mitigation = True\n",
        "estimator.options.experimental.execution_path = \"gen3-turbo\"\n",
        "\n",
        "estimator.options.resilience.zne_mitigation = True\n",
        "estimator.options.resilience.zne.noise_factors = (1, 1.2, 1.4)\n",
        "estimator.options.resilience.zne.extrapolator = \"linear\"\n",
        "\n",
        "estimator.options.environment.job_tags = [\"TUT_MPF\"]\n",
        "\n",
        "job_50 = estimator.run(\n",
        "    [\n",
        "        (circ, observable)\n",
        "        for circ, observable in zip(transpiled_circuits, isa_observables)\n",
        "    ]\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "afc0c029",
      "metadata": {},
      "source": [
        "Extraímos os valores esperados e os desvios-padrão por circuito dos resultados do trabalho e, em seguida, combinamo-los com cada conjunto de coeficientes MPF exatamente como no exemplo em pequena escala: $\\langle A \\rangle_{\\text{MPF}} = \\sum_j x_j \\, \\langle A \\rangle_{k_j}$, com variância propagada $\\sigma^2 = \\sum_j x_j^2 \\sigma_{k_j}^2$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 25,
      "id": "a924d79c",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[array(-0.07916195), array(-0.04479681), array(-0.2560756), array(-0.06045848)]\n",
            "[array(0.04605538), array(0.10056336), array(0.14426151), array(0.04059092)]\n"
          ]
        }
      ],
      "source": [
        "# -------------------------Step 4-------------------------\n",
        "result = job_50.result()\n",
        "evs = [res.data.evs for res in result]\n",
        "std = [res.data.stds for res in result]\n",
        "\n",
        "print(evs)\n",
        "print(std)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 26,
      "id": "1071de0d",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Exact static MPF expectation value:  -0.5665938395816946 +- 0.3925273058119915\n",
            "Approximate static MPF expectation value:  -0.25856647611537903 +- 0.164249927266166\n",
            "Dynamic MPF expectation value:  -0.12667812062949296 +- 0.06059471006973169\n"
          ]
        }
      ],
      "source": [
        "exact_mpf_std = np.sqrt(\n",
        "    sum([(coeff**2) * (std**2) for coeff, std in zip(mpf_coeffs, std[:3])])\n",
        ")\n",
        "print(\n",
        "    \"Exact static MPF expectation value: \",\n",
        "    evs[:3] @ mpf_coeffs,\n",
        "    \"+-\",\n",
        "    exact_mpf_std,\n",
        ")\n",
        "approx_mpf_std = np.sqrt(\n",
        "    sum(\n",
        "        [\n",
        "            (coeff**2) * (std**2)\n",
        "            for coeff, std in zip(coeffs_approx.value, std[:3])\n",
        "        ]\n",
        "    )\n",
        ")\n",
        "print(\n",
        "    \"Approximate static MPF expectation value: \",\n",
        "    evs[:3] @ coeffs_approx.value,\n",
        "    \"+-\",\n",
        "    approx_mpf_std,\n",
        ")\n",
        "dynamic_mpf_std = np.sqrt(\n",
        "    sum(\n",
        "        [\n",
        "            (coeff**2) * (std**2)\n",
        "            for coeff, std in zip(mpf_dynamic_coeffs, std[:3])\n",
        "        ]\n",
        "    )\n",
        ")\n",
        "print(\n",
        "    \"Dynamic MPF expectation value: \",\n",
        "    evs[:3] @ mpf_dynamic_coeffs,\n",
        "    \"+-\",\n",
        "    dynamic_mpf_std,\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 27,
      "id": "64360d85",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/multi-product-formula/extracted-outputs/64360d85-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "sym = {3: \"^\", 4: \"s\", 6: \"p\"}\n",
        "for k, step in enumerate(mpf_trotter_steps):\n",
        "    plt.errorbar(\n",
        "        k,\n",
        "        evs[k],\n",
        "        yerr=std[k],\n",
        "        alpha=0.5,\n",
        "        markersize=4,\n",
        "        marker=sym[step],\n",
        "        color=\"grey\",\n",
        "        label=f\"{mpf_trotter_steps[k]} Trotter steps\",\n",
        "    )\n",
        "\n",
        "plt.errorbar(\n",
        "    3,\n",
        "    evs[-1],\n",
        "    yerr=std[-1],\n",
        "    alpha=0.5,\n",
        "    markersize=8,\n",
        "    marker=\"x\",\n",
        "    color=\"blue\",\n",
        "    label=\"10 Trotter steps\",\n",
        ")\n",
        "\n",
        "plt.errorbar(\n",
        "    4,\n",
        "    evs[:3] @ mpf_coeffs,\n",
        "    yerr=exact_mpf_std,\n",
        "    markersize=4,\n",
        "    marker=\"o\",\n",
        "    color=\"purple\",\n",
        "    label=\"Static MPF\",\n",
        ")\n",
        "\n",
        "plt.errorbar(\n",
        "    5,\n",
        "    evs[:3] @ coeffs_approx.value,\n",
        "    yerr=approx_mpf_std,\n",
        "    markersize=4,\n",
        "    marker=\"o\",\n",
        "    color=\"orange\",\n",
        "    label=\"Approximate static MPF\",\n",
        ")\n",
        "\n",
        "plt.errorbar(\n",
        "    6,\n",
        "    evs[:3] @ mpf_dynamic_coeffs,\n",
        "    yerr=dynamic_mpf_std,\n",
        "    markersize=4,\n",
        "    marker=\"o\",\n",
        "    color=\"pink\",\n",
        "    label=\"Dynamic MPF\",\n",
        ")\n",
        "\n",
        "exact_obs = -0.24384471447172074  # Calculated via Tensor Network calculation\n",
        "plt.axhline(\n",
        "    y=exact_obs, linestyle=\"--\", color=\"red\", label=\"Exact time-evolution\"\n",
        ")\n",
        "\n",
        "plt.title(\n",
        "    f\"$\\\\langle Z_{{{L//2-1}}} Z_{{{L//2}}} \\\\rangle$ at time {total_time} for the different methods\"\n",
        ")\n",
        "plt.xlabel(\"Method\")\n",
        "plt.ylabel(\"Expectation Value\")\n",
        "plt.legend(loc=\"upper center\", bbox_to_anchor=(0.5, -0.2), ncol=2)\n",
        "plt.grid(alpha=0.1)\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "31effe5f",
      "metadata": {},
      "source": [
        "Algumas observações sobre os resultados de hardware apresentados acima:\n",
        "\n",
        "* **Aprofundar-se não é gratuito em termos de hardware.** As linhas de base de circuito único mostram claramente o que acontece: o circuito $k = 6$ é praticamente exato ( $-0.256$ em comparação com a referência $-0.244$ ), mas a linha de base mais profunda $k = 10$ apresenta *um resultado pior* ( $-0.061$, com um desvio de $\\sim 0.18$ ), e não melhor. Quando o erro de Trotter já é pequeno, adicionar etapas basicamente aumenta a profundidade do circuito e acumula mais ruído de porta e decoerência. É exatamente para esse regime que os MPFs foram concebidos: alcançar a precisão de um circuito profundo utilizando apenas componentes superficiais.\n",
        "\n",
        "* **Um MPF de norma pequena supera o circuito único profundo.** O MPF estático aproximado (limitado a $\\|x\\|_1 \\approx 2$ ) fica em $-0.259$, a $\\sim 0.015$ do valor de referência e muito mais próximo do que a linha de base de $k = 10$. O MPF dinâmico ( $-0.127$ ) também supera com folga essa referência. Ambos combinam apenas os circuitos rasos $k_j = [3, 4, 6]$, mas conseguem chegar a uma resposta que o circuito único profundo não conseguiu.\n",
        "\n",
        "* **A norma do coeficiente é mais importante do que a otimização matemática.** O MPF estático exato possui um $\\|x\\|_1 = 4.66$ e e é o *pior* estimador de todos ( $-0.567$, com um desvio de mais de $0.3$ ): a grande norma do coeficiente amplifica o ruído residual do portão, a decoerência e o erro ZNE em cada $\\langle A \\rangle_{k_j}$ aproximadamente pelo mesmo fator, anulando o cancelamento do erro de Trotter que ele proporciona. A limitação da norma (o solucionador estático aproximado, $\\|x\\|_1 \\approx 2$ ) elimina essa sobrecarga e fornece a melhor estimativa — mesmo que seus coeficientes não cancelem mais exatamente o erro principal de Trotter.\n",
        "\n",
        "* **Circuitos individuais de pouca profundidade ainda podem ser competitivos.** O único componente do método de fluxo de massa ( $k = 6$ ) ( $-0.256$ ) é, por si só, essencialmente exato neste caso — nesta simulação, ele é até mesmo ligeiramente mais preciso do que o MPF estático aproximado. O problema é que você não sabe de antemão *qual* $k$ se encontra no ponto ideal de “convergência alcançada, mas ainda não limitada pelo ruído”, e a opção que parece segura — simplesmente ir mais fundo ( $k = 10$ ) para garantir a convergência de Trotter — é exatamente aquela que falha. O MPF oferece uma combinação baseada em princípios de circuitos rasos que não exige adivinhar a profundidade correta.\n",
        "\n",
        "A lição prática é que, no hardware, os MPFs devem ser combinados com uma forte mitigação de erros em cada $\\langle A \\rangle_{k_j}$ individual; a norma do coeficiente $L_1$ deve ser mantida modesta (use o solucionador aproximado ou o MPF dinâmico); e os passos de Trotter $k_j$ devem ser escolhidos de modo que $t/k_{\\min} \\lesssim 1$ — aqui, $k_{\\min} = 3$ em $t = 3$ resulta em $t/k_{\\min} = 1$, mantendo os constituintes dentro do regime de convergência, onde o modelo de erro principal no qual o MPF estático se baseia é válido. Com essas escolhas, os MPFs de norma pequena aqui apresentados se equiparam a um circuito único convergente, ao passo que a linha de base ingênua do tipo “basta ir mais fundo” não o faz, recuperando a vantagem de profundidade versus precisão demonstrada na Ref. [\\[3\\]](#references). Observe também que as execuções individuais apresentam ruído — em um envio diferente do mesmo trabalho (ou em um backend diferente), a ordem exata pode mudar; as tendências consistentes são: os MPFs de pequeno porte $\\|x\\|_1$ apresentam bom desempenho, o MPF de grande porte $\\|x\\|_1$ com estática exata é amplificado pelo ruído do hardware e o circuito único excessivamente profundo é limitado pelo ruído.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5ac2f8a8",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Próximas etapas\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Recomendações\">\n",
        "  Se você achou este trabalho interessante, talvez se interesse pelo material a seguir:\n",
        "\n",
        "  * [Como escolher os passos de Trotter para um MPF](https://qiskit.github.io/qiskit-addon-mpf/how_tos/choose_trotter_steps.html) — orientações práticas sobre a seleção dos valores d $k_j$ o para evitar instabilidades\n",
        "  * [Como usar o modelo aproximado](https://qiskit.github.io/qiskit-addon-mpf/how_tos/using_approximate_model.html) — ajuste da restrição da norma de $L_1$ e das opções do solucionador para o MPF estático aproximado\n",
        "  * [`qiskit-addon-mpf` Referência da API](https://qiskit.github.io/qiskit-addon-mpf/) — documentação completa para módulos estáticos, dinâmicos e de back-end\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "70be41e1",
      "metadata": {},
      "source": [
        "<span id=\"references\" />\n",
        "\n",
        "## Referências\n",
        "\n",
        "\\[1] Vázquez, A. C., Egger, D. J., Ochsner, D., e Woerner, S. Fórmulas multiproduto bem condicionadas para simulação hamiltoniana otimizada para hardware. [Quantum, 7, 1067 (2023)](https://quantum-journal.org/papers/q-2023-07-25-1067/)\n",
        "\n",
        "\\[2] Zhuk, S., Robertson, N. F., & Bravyi, S. Limites de erro de Trotter e fórmulas dinâmicas multiproduto para simulação hamiltoniana. [Physical Review Research, 6(3), 033309 (2024)](https://journals.aps.org/prresearch/abstract/10.1103/PhysRevResearch.6.033309)\n",
        "\n",
        "\\[3] Robertson, N. F., et al. Fórmulas dinâmicas multiproduto aprimoradas por redes tensoriais. [arXiv:2407.17405 (2024)](https://arxiv.org/abs/2407.17405)\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"
    },
    "hours": 1.5,
    "qpuSeconds": 240
  },
  "nbformat": 4,
  "nbformat_minor": 5
}