diff --git a/docs/explanations/double-factorized.ipynb b/docs/explanations/double-factorized.ipynb new file mode 100644 index 000000000..f77dd4c99 --- /dev/null +++ b/docs/explanations/double-factorized.ipynb @@ -0,0 +1,123 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Double-factorized representation of the molecular Hamiltonian\n", + "\n", + "This page discusses the double-factorized representation of the molecular Hamiltonian.\n", + "\n", + "## Double-factorized representation\n", + "\n", + "The molecular Hamiltonian is commonly written in the form\n", + "\n", + "$$\n", + " H = \\sum_{\\sigma, pq} h_{pq} a^\\dagger_{\\sigma, p} a_{\\sigma, q}\n", + " + \\frac12 \\sum_{\\sigma \\tau, pqrs} h_{pqrs}\n", + " a^\\dagger_{\\sigma, p} a^\\dagger_{\\tau, r} a_{\\tau, s} a_{\\sigma, q}\n", + " + \\text{constant}.\n", + "$$\n", + "\n", + "This representation of the Hamiltonian is daunting for quantum simulations because the number of terms in the two-body part scales as $N^4$ where $N$ is the number of spatial orbitals. An alternative representation can be obtained by performing a \"double-factorization\" of the two-body tensor $h_{pqrs}$:\n", + "\n", + "$$\n", + " H = \\sum_{\\sigma, pq} h'_{pq} a^\\dagger_{\\sigma, p} a_{\\sigma, q}\n", + " + \\sum_{k=1}^L \\mathcal{W}_k \\mathcal{J}_k \\mathcal{W}_k^\\dagger\n", + " + \\text{constant}'.\n", + "$$\n", + "\n", + "Here each $\\mathcal{W}_k$ is an [orbital rotation](orbital-rotation.ipynb) and each $\\mathcal{J}_k$ is a so-called diagonal Coulomb operator of the form\n", + "\n", + "$$\n", + " \\mathcal{J} = \\frac12\\sum_{\\sigma \\tau, ij} \\mathbf{J}_{ij} n_{\\sigma, i} n_{\\tau, j},\n", + "$$\n", + "\n", + "where $n_{\\sigma, i} = a^\\dagger_{\\sigma, i} a_{\\sigma, i}$ is the occupation number operator and $\\mathbf{J}_{ij}$ is a real symmetric matrix. The one-body tensor and the constant have been updated to accomodate extra terms that arise from reordering fermionic ladder operators.\n", + "\n", + "For an exact factorization, $L$ is proportional to $N^2$. However, the two-body tensor often has low-rank structure, and the decomposition can be truncated while still maintaining an accurate representation. Thus, in practice, $L$ often scales only linearly with $N$, resulting in a much more compact representation that gives rise to more efficient schemes for simulating and measuring the Hamiltonian, which utilize efficient quantum circuits for orbital rotations and time evolution by a diagonal Coulomb operator.\n", + "\n", + "## Application to time evolution via Trotter-Suzuki formulas\n", + "\n", + "In this section, we discuss how the double-factorized Hamiltonian can be simulated using Trotter-Suzuki formulas.\n", + "\n", + "### Brief background on Trotter-Suzuki formulas\n", + "\n", + "Trotter-Suzuki formulas are used to approximate time evolution by a Hamiltonian $H$ which is decomposed as a sum of terms:\n", + "\n", + "$$\n", + "H = \\sum_k H_k.\n", + "$$\n", + "\n", + "Time evolution by time $t$ is given by the unitary operator\n", + "\n", + "$$\n", + "e^{i H t}.\n", + "$$\n", + "\n", + "To approximate this operator, the total evolution time is first divided into a number of smaller time steps, called \"Trotter steps\":\n", + "\n", + "$$\n", + "e^{i H t} = (e^{i H t / r})^r.\n", + "$$\n", + "\n", + "The time evolution for a single Trotter step is then approximated using a product formula, which approximates the exponential of a sum of terms by a product of exponentials of the individual terms. The formulas are approximate because the terms do not in general commute. A first-order asymmetric product formula has the form\n", + "\n", + "$$\n", + "e^{i H \\tau} \\approx \\prod_k e^{i H_k \\tau}.\n", + "$$\n", + "\n", + "Higher-order formulas can be derived which yield better approximations.\n", + "\n", + "### Application to the double-factorized Hamiltonian\n", + "\n", + "Using the double-factorized representation, the Hamiltonian is decomposed into $L+1$ terms (ignoring the constant term),\n", + "\n", + "$$\n", + "H = \\sum_{k=0}^L H_k,\n", + "$$\n", + "\n", + "where\n", + "\n", + "$$\n", + "H_0 = \\sum_{\\sigma, pq} h'_{pq} a^\\dagger_{\\sigma, p} a_{\\sigma, q}\n", + "$$\n", + "\n", + "and for $k = 1, \\ldots, L$,\n", + "\n", + "$$\n", + "H_k = \\mathcal{W}_k \\mathcal{J}_k \\mathcal{W}_k^\\dagger.\n", + "$$\n", + "\n", + "We have that\n", + "\n", + "- $H_0$ is a quadratic Hamiltonian and can be simulated as described in [Orbital rotations and quadratic Hamiltonians](orbital-rotation.ipynb#Time-evolution-by-a-quadratic-Hamiltonian), and\n", + "- $H_k$ is a diagonal Coulomb operator (up to an orbital rotation) for $k = 1, \\ldots, L$, and ffsim includes the function [apply_diag_coulomb_evolution](../api/ffsim.rst#ffsim.apply_diag_coulomb_evolution) for simulating time evolution by such an operator.\n", + "\n", + "Given the ability to simulate each of the individual terms, we can implement Trotter-Suzuki formulas for time evolution by the entire Hamiltonian. This is implemented in ffsim by the function [simulate_trotter_double_factorized](../api/ffsim.rst#ffsim.simulate_trotter_double_factorized), which implements higher-order Trotter-Suzuki formulas in addition to the first-order asymmetric formula mentioned previously. The first-order asymmetric formula corresponds to setting the argument `order=0`, which is the default. `order=1` corresponds to the first-order symmetric (commonly known as the second-order) formula, `order=2` corresponds to the second-order symmetric (fourth-order) formula, and so on." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ffsim-1cfkSnAR", + "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.11.8" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/explanations/index.md b/docs/explanations/index.md index 3ec544436..81ffec764 100644 --- a/docs/explanations/index.md +++ b/docs/explanations/index.md @@ -6,4 +6,5 @@ state-vectors-and-gates hamiltonians orbital-rotation +double-factorized ``` diff --git a/docs/tutorials/double-factorized.ipynb b/docs/tutorials/double-factorized-trotter.ipynb similarity index 57% rename from docs/tutorials/double-factorized.ipynb rename to docs/tutorials/double-factorized-trotter.ipynb index 32bdf9a57..a59e505e2 100644 --- a/docs/tutorials/double-factorized.ipynb +++ b/docs/tutorials/double-factorized-trotter.ipynb @@ -4,38 +4,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Diagonal Coulomb operators and double-factorized Trotter simulation\n", + "# Implementing Trotter simulation of the double-factorized Hamiltonian\n", "\n", - "In this tutorial, we show how to use ffsim to simulate diagonal Coulomb operators and approximate time evolution by a molecular Hamiltonian in the double-factorized representation.\n", + "In this tutorial, we'll write a function to implement approximate time evolution of the double-factorized Hamiltonian via a Trotter-Suzuki formula. See [Double-factorized representation of the molecular Hamiltonian](../explanations/double-factorized.ipynb) for background information. We'll compare our implementation with exact time evolution computed using direct operator exponentiation, as well as ffsim's built-in implementation [simulate_trotter_double_factorized](../api/ffsim.rst#ffsim.simulate_trotter_double_factorized).\n", "\n", - "## Double-factorized representation of the molecular Hamiltonian\n", + "## Build the Hamiltonian\n", "\n", - "The molecular Hamiltonian is\n", - "\n", - "$$\n", - " H = \\sum_{\\sigma, pq} h_{pq} a^\\dagger_{\\sigma, p} a_{\\sigma, q}\n", - " + \\frac12 \\sum_{\\sigma \\tau, pqrs} h_{pqrs}\n", - " a^\\dagger_{\\sigma, p} a^\\dagger_{\\tau, r} a_{\\tau, s} a_{\\sigma, q}\n", - " + \\text{constant}.\n", - "$$\n", - "\n", - "This representation of the Hamiltonian is daunting for quantum simulations because the number of terms scales as $N^4$ where $N$ is the number of spatial orbitals. An alternative representation can be obtained by performing a \"double-factorization\" of the two-body tensor $h_{pqrs}$:\n", - "\n", - "$$\n", - " H = \\sum_{\\sigma, pq} h'_{pq} a^\\dagger_{\\sigma, p} a_{\\sigma, q}\n", - " + \\sum_{k=1}^L \\mathcal{W}_k \\mathcal{J}_k \\mathcal{W}_k^\\dagger\n", - " + \\text{constant}'.\n", - "$$\n", - "\n", - "Here each $\\mathcal{W}_k$ is an [orbital rotation](../explanations/orbital-rotation.ipynb) and each $\\mathcal{J}_k$ is a so-called diagonal Coulomb operator, which is an operator of the form\n", - "\n", - "$$\n", - " \\mathcal{J} = \\frac12\\sum_{\\sigma \\tau, ij} \\mathbf{J}_{ij} n_{\\sigma, i} n_{\\tau, j},\n", - "$$\n", - "\n", - "where $n_{\\sigma, i} = a^\\dagger_{\\sigma, i} a_{\\sigma, i}$ is the occupation number operator and $\\mathbf{J}_{ij}$ is a real symmetric matrix.\n", - "\n", - "In the cell below, we construct the Hamiltonian for a nitrogen molecule in a (10e, 8o) active space and then get the double-factorized representation of the Hamiltonian." + "We begin by building a molecular Hamiltonian to test our code on. We'll create a nitrogen molecule in an active space of 8 orbitals and 10 electrons. We use ffsim's [MolecularData](../api/ffsim.rst#ffsim.MolecularData) class, which implements a simplistic wrapper around PySCF to compute a representation of the Hamiltonian as an instance of [MolecularHamiltonian](../api/ffsim.rst#ffsim.MolecularHamiltonian), which we store in the `mol_hamiltonian` variable." ] }, { @@ -47,7 +22,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "converged SCF energy = -108.464957764796\n" + "converged SCF energy = -108.464957764796\n", + "norb = 8\n", + "nelec = (5, 5)\n" ] } ], @@ -68,12 +45,28 @@ "n_frozen = pyscf.data.elements.chemcore(mol)\n", "active_space = range(n_frozen, mol.nao_nr())\n", "\n", - "# Get molecular data and molecular Hamiltonian (one- and two-body tensors)\n", + "# Get molecular data and Hamiltonian\n", "mol_data = ffsim.MolecularData.from_mole(mol, active_space=active_space)\n", - "norb = mol_data.norb\n", - "nelec = mol_data.nelec\n", + "norb, nelec = mol_data.norb, mol_data.nelec\n", "mol_hamiltonian = mol_data.hamiltonian\n", "\n", + "print(f\"norb = {norb}\")\n", + "print(f\"nelec = {nelec}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we compute the double-factorized representation of the Hamiltonian. In ffsim, the [DoubleFactorizedHamiltonian](../api/ffsim.rst#ffsim.DoubleFactorizedHamiltonian) class is used to store this Hamiltonian representation." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ "# Get the Hamiltonian in the double-factorized representation\n", "df_hamiltonian = ffsim.DoubleFactorizedHamiltonian.from_molecular_hamiltonian(\n", " mol_hamiltonian\n", @@ -84,12 +77,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Here, `mol_hamiltonian` is an instance of `MolecularHamiltonian`, a dataclass that stores the one- and two-body tensors, and `df_hamiltonian` is an instance of `DoubleFactorizedHamiltonian`, a dataclass that stores the updated one-body-tensor, diagonal Coulomb matrices, and orbital rotations. In the cell below, we print out the shapes of the tensors describing the original and double-factorized representations." + "To get a sense of how the two different Hamiltonian representations differ, let's print out the shapes of the tensors describing the original and double-factorized representations." ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -143,52 +136,121 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Trotter simulation of the double-factorized Hamiltonian\n", - "\n", - "In the rest of this tutorial, we'll show how to use ffsim to implement time evolution of the double-factorized Hamiltonian via Trotter-Suzuki formulas. Although ffsim already has this functionality built-in, we will first manually implement a first-order asymmetric product formula to demonstrate the use of ffsim's basic operations.\n", - "\n", - "### Brief background on Trotter-Suzuki formulas\n", - "Trotter-Suzuki formulas are used to approximate time evolution by a Hamiltonian $H$ which is decomposed as a sum of terms:\n", - "\n", - "$$\n", - "H = \\sum_k H_k.\n", - "$$\n", - "\n", - "Time evolution by time $t$ is given by the unitary operator\n", + "We see that instead of an $N \\times N \\times N \\times N$ two-body tensor ($N$ is the number of spatial orbitals), the double-factorized representation stores a list of $L$ diagonal Coulomb matrices as well as a list of $L$ orbital rotations. Here, $L = 35$. The value of $L$ depends on the per-tensor-entry error tolerance allowed in the double factorization, which defaults to $10^{-8}$. Setting a higher error tolerance may yield a more compact representation with a smaller $L$. Let's see this in action by setting the error tolerance to $10^{-3}$." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of terms: 26\n" + ] + } + ], + "source": [ + "df_hamiltonian_alt = ffsim.DoubleFactorizedHamiltonian.from_molecular_hamiltonian(\n", + " mol_hamiltonian, tol=1e-3\n", + ")\n", + "print(f\"Number of terms: {len(df_hamiltonian_alt.diag_coulomb_mats)}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With an error tolerance of $10^{-3}$, the factorization results in $L = 26$.\n", "\n", - "$$\n", - "e^{i H t}.\n", - "$$\n", + "In addition to setting the error tolerance, you can also specify a maximum value for $L$ via the `max_vecs` argument. Bear in mind that setting a low value for the maximum number of terms may introduce significant error in the decomposition. The `max_vecs` argument is always respected, so the resulting decomposition may exceed the error tolerance specified by the `tol` argument. Let's try setting the maximum number of terms to 10." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of terms: 10\n" + ] + } + ], + "source": [ + "df_hamiltonian_alt = ffsim.DoubleFactorizedHamiltonian.from_molecular_hamiltonian(\n", + " mol_hamiltonian, max_vecs=10\n", + ")\n", + "print(f\"Number of terms: {len(df_hamiltonian_alt.diag_coulomb_mats)}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The error in the decomposition can be computed by reconstructing the two-body tensor from its factorized form. The following code cell performs this reconstruction using `np.einsum`, then prints out the maximum error in one of the tensor entries." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Maximum error in a tensor entry: 0.03668541730983643\n" + ] + } + ], + "source": [ + "import numpy as np\n", "\n", - "To approximate this operator, the total evolution time is first divided into a number of smaller time steps, called \"Trotter steps\":\n", + "reconstructed = np.einsum(\n", + " \"kij,kpi,kqi,krj,ksj->pqrs\",\n", + " df_hamiltonian_alt.diag_coulomb_mats,\n", + " df_hamiltonian_alt.orbital_rotations,\n", + " df_hamiltonian_alt.orbital_rotations,\n", + " df_hamiltonian_alt.orbital_rotations,\n", + " df_hamiltonian_alt.orbital_rotations,\n", + ")\n", + "max_error = np.max(np.abs(reconstructed - mol_hamiltonian.two_body_tensor))\n", "\n", - "$$\n", - "e^{i H t} = (e^{i H t / r})^r.\n", - "$$\n", + "print(f\"Maximum error in a tensor entry: {max_error}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Implement Trotter simulation\n", "\n", - "The time evolution for a single Trotter step is then approximated using a product formula, which approximates the exponential of a sum of terms by a product of exponentials of the individual terms. The formulas are approximate because the terms do not in general commute. A first-order asymmetric product formula has the form\n", + "As explained in [Double-factorized representation of the molecular Hamiltonian](../explanations/double-factorized.ipynb#Application-to-the-double-factorized-Hamiltonian), the doubled-factorized Hamiltonian can be expressed as a sum of $L + 1$ terms,\n", "\n", "$$\n", - "e^{i H \\tau} \\approx \\prod_k e^{i H_k \\tau}.\n", + "H = \\sum_{k=0}^L H_k,\n", "$$\n", "\n", - "Higher-order formulas can be derived which yield better approximations.\n", + "where\n", "\n", - "### Implementing Trotter simulation of the double-factorized Hamiltonian\n", + "- $H_0$ is a [quadratic Hamiltonian](../explanations/orbital-rotation.ipynb#Time-evolution-by-a-quadratic-Hamiltonian), and\n", + "- $H_k$ is a rotated [diagonal Coulomb operator](../explanations/double-factorized.ipynb#Double-factorized-representation) for $k = 1, \\ldots, L$.\n", "\n", - "First, we'll write a function to simulate a single Trotter step of the Hamiltonian. Recall the form of the Hamiltonian (ignoring the additive constant):\n", + "Let's write a function to simulate a single Trotter step of the Hamiltonian. Our function will perform the following steps:\n", "\n", - "$$\n", - " H = \\sum_{\\sigma, pq} h'_{pq} a^\\dagger_{\\sigma, p} a_{\\sigma, q}\n", - " + \\sum_{k=1}^L \\mathcal{W}_k \\mathcal{J}_k \\mathcal{W}_k^\\dagger\n", - "$$\n", - "\n", - "We think of this Hamiltonian as composed of $L + 1$ terms: the one-body term, which is a quadratic Hamiltonian, and the $L$ \"rotated diagonal Coulomb operators.\" As described in [Orbital rotations and quadratic Hamiltonians](../explanations/orbital-rotation.ipynb), time evolution by the quadratic Hamiltonian can be implemented using the `apply_num_op_sum_evolution` function. Similarly, time evolution by a rotated diagonal coulomb operator can be implemented using the `apply_diag_coulomb_evolution` function." + "1. Diagonalize $H_0$ to obtain the orbital energies and rotation needed to simulate it.\n", + "2. Apply time evolution by $H_0$ using the function [apply_num_op_sum_evolution](../api/ffsim.rst#ffsim.apply_num_op_sum_evolution).\n", + "3. For $k = 1, \\ldots, L$, apply time evolution by $H_k$ using the function [apply_diag_coulomb_evolution](../api/ffsim.rst#ffsim.apply_diag_coulomb_evolution)." ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -234,12 +296,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We finish by writing a higher-level function that handles splitting the total time evolution into multiple Trotter steps, simulating each Trotter step using the function we just wrote." + "To finish, we need to write a higher-level function that handles splitting the total time evolution into multiple Trotter steps and simulates each Trotter step using the function we just wrote." ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -267,86 +329,51 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "To test our implementation, we'll apply the time evolution to the Hartree-Fock state, which is a Slater determinant with electrons occupying the lowest-energy molecular orbitals. In the following code cell, we'll create this state and calculate its energy. It should match the value output by pySCF when we first created the molecule. To calculate the energy, we convert the Hamiltonian to a SciPy `LinearOperator`." + "To test our implementation, let's apply time evolution to the Hartree-Fock state. Before calling our Trotter simulation function, let's first compute the exact result of time evolution by directly exponentiating the Hamiltonian using SciPy. Later, we'll compare the result of our approximate time evolution with this exact result. In order to perform the operator exponentiation, we convert the Hamiltonian to a Scipy [LinearOperator](https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.linalg.LinearOperator.html) (see [Hamiltonians](../explanations/hamiltonians.ipynb))." ] }, { "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Hartree Fock energy: -108.46495776479554\n" - ] - } - ], - "source": [ - "# Construct the Hartree-Fock state\n", - "initial_state = ffsim.hartree_fock_state(norb, nelec)\n", - "\n", - "# Get the Hamiltonian as a LinearOperator\n", - "linop = ffsim.linear_operator(mol_hamiltonian, norb=norb, nelec=nelec)\n", - "\n", - "# Check the energy ⟨ψ|H|ψ⟩ of the Hartree-Fock state\n", - "hf_energy = np.real(np.vdot(initial_state, linop @ initial_state))\n", - "print(f\"Hartree Fock energy: {hf_energy}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, we set the evolution time and calculate the exact result of time evolution by directly exponentiating the Hamiltonian using SciPy. Later, we will compare the result of our approximate time evolution with this exact result." - ] - }, - { - "cell_type": "code", - "execution_count": 6, + "execution_count": 9, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Fidelity of evolved state w.r.t. initial state: 0.923324845090985\n" - ] - } - ], + "outputs": [], "source": [ "import scipy.sparse.linalg\n", "\n", + "# Construct the initial state.\n", + "initial_state = ffsim.hartree_fock_state(norb, nelec)\n", + "\n", + "# Set the evolution time.\n", "time = 1.0\n", "\n", + "# Convert the Hamiltonian to a LinearOperator\n", + "linop = ffsim.linear_operator(mol_hamiltonian, norb=norb, nelec=nelec)\n", + "\n", + "# Compute the exact result of time evolution\n", "exact_state = scipy.sparse.linalg.expm_multiply(\n", " -1j * time * linop,\n", " initial_state,\n", " traceA=ffsim.trace(mol_hamiltonian, norb=norb, nelec=nelec),\n", - ")\n", - "\n", - "fidelity = abs(np.vdot(exact_state, initial_state))\n", - "print(f\"Fidelity of evolved state w.r.t. initial state: {fidelity}\")" + ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Now, let's test our implementation." + "Now, let's test our implementation. First, let's evolve the initial state using a single Trotter step." ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Fidelity of Trotter-evolved state with exact state: 0.9402393562196549\n" + "Fidelity of Trotter-evolved state with exact state: 0.940243511515844\n" ] } ], @@ -373,14 +400,14 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Fidelity of Trotter-evolved state with exact state: 0.9985211214230586\n" + "Fidelity of Trotter-evolved state with exact state: 0.9985212854199639\n" ] } ], @@ -402,21 +429,19 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "As mentioned above, ffsim already includes functionality for Trotter simulation of double-factorized Hamiltonians. The implementation in ffsim includes higher-order Trotter-Suzuki formulas. The first-order asymmetric formula that we just implemented corresponds to `order=0` in ffsim's implementation. `order=1` corresponds to the first-order symmetric (commonly known as the second-order) formula, `order=2` corresponds to the second-order symmetric (fourth-order) formula, and so on.\n", - "\n", "In the code cell below, we reproduce the results of our manually implemented function using ffsim's built-in implementation." ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Fidelity of Trotter-evolved state with exact state: 0.9985211214230586\n" + "Fidelity of Trotter-evolved state with exact state: 0.9985212854199639\n" ] } ], @@ -444,14 +469,14 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Fidelity of Trotter-evolved state with exact state: 0.9996731166933209\n" + "Fidelity of Trotter-evolved state with exact state: 0.9996731164187629\n" ] } ], @@ -469,6 +494,13 @@ "fidelity = abs(np.vdot(final_state, exact_state))\n", "print(f\"Fidelity of Trotter-evolved state with exact state: {fidelity}\")" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You've made it to the end of this tutorial!" + ] } ], "metadata": { @@ -488,8 +520,7 @@ "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.8" - }, - "orig_nbformat": 4 + } }, "nbformat": 4, "nbformat_minor": 2 diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md index 873d87c8c..d2f4352ad 100644 --- a/docs/tutorials/index.md +++ b/docs/tutorials/index.md @@ -3,5 +3,5 @@ ```{toctree} :maxdepth: 1 -double-factorized +double-factorized-trotter ```