diff --git a/docs/make.jl b/docs/make.jl index 582b21dbc..f1220b3c7 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -14,13 +14,26 @@ using DocumenterInterLinks include("overrides.jl") include("src/assets/models.jl") -# examples +# examples — grouped by computational task; each group is a subdirectory of src/examples/ example_dir = joinpath(@__DIR__, "src", "examples") -classic_pages = map(readdir(joinpath(example_dir, "classic2d"))) do dir - return joinpath("examples", "classic2d", dir, "index.md") -end -quantum_pages = map(readdir(joinpath(example_dir, "quantum1d"))) do dir - return joinpath("examples", "quantum1d", dir, "index.md") +example_groups = [ + "Ground states" => "groundstates", + "Excitations & dispersions" => "excitations", + "Dynamics & finite temperature" => "dynamics", + "Statistical mechanics" => "statmech", +] +# the sidebar follows the reading order of the gallery index; examples it does not link come last +example_order = [ + m.captures[1] for m in eachmatch( + r"\]\(([^)\s]+/index\.md)\)", read(joinpath(example_dir, "index.md"), String) + ) +] +example_pages = map(example_groups) do (title, group) + pages = map(readdir(joinpath(example_dir, group))) do dir + return joinpath(group, dir, "index.md") + end + sort!(pages; by = page -> something(findfirst(==(page), example_order), typemax(Int))) + return title => map(page -> joinpath("examples", page), pages) end # contributing guide: `CONTRIBUTING.md` in the repository root is canonical, since that is the @@ -73,7 +86,10 @@ makedocs(; "man/parallelism.md", "man/lattices.md", ], - "Examples" => "examples/index.md", + "Examples" => [ + "Overview" => "examples/index.md", + example_pages..., + ], "Library" => "lib/lib.md", "References" => "references.md", "Changelog" => "changelog.md", diff --git a/docs/src/examples/classic2d/1.hard-hexagon/figure-1.png b/docs/src/examples/classic2d/1.hard-hexagon/figure-1.png deleted file mode 100644 index 7a590f3bf..000000000 Binary files a/docs/src/examples/classic2d/1.hard-hexagon/figure-1.png and /dev/null differ diff --git a/docs/src/examples/quantum1d/3.ising-dqpt/finite_timeev.png b/docs/src/examples/dynamics/ising-dqpt/finite_timeev.png similarity index 100% rename from docs/src/examples/quantum1d/3.ising-dqpt/finite_timeev.png rename to docs/src/examples/dynamics/ising-dqpt/finite_timeev.png diff --git a/docs/src/examples/quantum1d/3.ising-dqpt/index.md b/docs/src/examples/dynamics/ising-dqpt/index.md similarity index 59% rename from docs/src/examples/quantum1d/3.ising-dqpt/index.md rename to docs/src/examples/dynamics/ising-dqpt/index.md index 98f515216..c7d406254 100644 --- a/docs/src/examples/quantum1d/3.ising-dqpt/index.md +++ b/docs/src/examples/dynamics/ising-dqpt/index.md @@ -1,15 +1,15 @@ ```@meta -EditURL = "../../../../../examples/quantum1d/3.ising-dqpt/main.jl" +EditURL = "../../../../../examples/dynamics/ising-dqpt/main.jl" ``` -[![](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/quantum1d/3.ising-dqpt/main.ipynb) -[![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/quantum1d/3.ising-dqpt/main.ipynb) -[![](https://img.shields.io/badge/download-project-orange)](https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/QuantumKitHub/MPSKit.jl/examples/tree/gh-pages/dev/examples/quantum1d/3.ising-dqpt) +[![](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/dynamics/ising-dqpt/main.ipynb) +[![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/dynamics/ising-dqpt/main.ipynb) +[![](https://img.shields.io/badge/download-project-orange)](https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/QuantumKitHub/MPSKit.jl/examples/tree/gh-pages/dev/examples/dynamics/ising-dqpt) # DQPT in the Ising model -In this tutorial we will try to reproduce the results from -[this paper](https://arxiv.org/pdf/1206.2505.pdf). The needed packages are +In this tutorial we will try to reproduce the results from [this paper](https://arxiv.org/pdf/1206.2505.pdf). +The needed packages are ````julia using MPSKit, TensorKit @@ -17,17 +17,18 @@ using .ExampleModels ```` Dynamical quantum phase transitions (DQPT in short) are signatures of equilibrium phase transitions in a dynamical quantity - the Loschmidt echo. -This quantity is given by ``L(t) = \frac{-2}{N} ln(| < \psi(t) | \psi(0) > |) `` where ``N`` is the system size. +This quantity is given by ``L(t) = \frac{-2}{N} \ln |⟨ψ(t)|ψ(0)⟩|`` where ``N`` is the system size. One typically starts from a ground state and then quenches the Hamiltonian to a different point. -Non analycities in the Loschmidt echo are called 'dynamical quantum phase transitions'. +Non-analyticities in the Loschmidt echo are called 'dynamical quantum phase transitions'. In the mentioned paper they work with -``H(g) = - \sum^{N-1}_{i=1} \sigma^z_i \sigma^z_{i+1} + g \sum_{i=1}^N \sigma^x_i`` +``H(g) = - \sum^{N-1}_{i=1} σ^z_i σ^z_{i+1} + g \sum_{i=1}^N σ^x_i`` -and show that divergences occur when quenching across the critical point (g₀ → g₁) for ``t^*_n = t^*(n+\frac{1}{2})`` with ``t^* = \pi/e(g_1,k^*)``, ``cos(k^*) = (1+g_0 g_1) / (g_0 + g_1)``, `` e(g,k) = \sqrt{(g-cos k)^2 + sin^2 k}``. +and show that divergences occur when quenching across the critical point (g₀ → g₁) for ``t^*_n = t^*(n+\frac{1}{2})`` with ``t^* = π/e(g_1,k^*)``, ``cos(k^*) = (1+g_0 g_1) / (g_0 + g_1)``, `` e(g,k) = \sqrt{(g-cos k)^2 + sin^2 k}``. -The outline of the tutorial is as follows. We will pick ``g₀ = 0.5``, ``g₁ = 2.0``, and perform the time evolution at different system sizes and compare with the thermodynamic limit. +The outline of the tutorial is as follows. +We will pick ``g₀ = 0.5``, ``g₁ = 2.0``, and perform the time evolution at different system sizes and compare with the thermodynamic limit. For those ``g`` we expect non-analyticities to occur at ``t_n ≈ 2.35 (n + 1/2)``. First we construct the Hamiltonian in MPO form, and obtain the pre-quenched ground state: @@ -36,21 +37,7 @@ First we construct the Hamiltonian in MPO form, and obtain the pre-quenched grou L = 20 H₀ = transverse_field_ising(; L, g = -0.5) ψ₀ = FiniteMPS(L, ℂ^2, ℂ^10) -ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG()); -```` - -```` -[ Info: DMRG init: obj = +9.789608054020e+00 err = 1.0000e+00 -[ Info: DMRG 1: obj = -2.040016740045e+01 err = 1.7077445107e-02 time = 0.01 sec -[ Info: DMRG 2: obj = -2.040021715177e+01 err = 3.5394180072e-05 time = 0.01 sec -[ Info: DMRG 3: obj = -2.040021715349e+01 err = 2.1033336427e-06 time = 0.02 sec -[ Info: DMRG 4: obj = -2.040021748658e+01 err = 1.5656718877e-05 time = 0.02 sec -[ Info: DMRG 5: obj = -2.040021759890e+01 err = 8.8241952375e-06 time = 0.01 sec -[ Info: DMRG 6: obj = -2.040021767905e+01 err = 1.1092508692e-05 time = 0.01 sec -[ Info: DMRG 7: obj = -2.040021786701e+01 err = 2.4581655330e-06 time = 0.01 sec -[ Info: DMRG 8: obj = -2.040021786703e+01 err = 9.9647269587e-09 time = 0.01 sec -[ Info: DMRG conv 9: obj = -2.040021786703e+01 err = 8.2188000056e-11 time = 0.12 sec - +ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG(; verbosity = 0)); ```` ## Finite MPS quenching @@ -62,7 +49,8 @@ echo(ψ₀::FiniteMPS, ψₜ::FiniteMPS) = -2 * log(abs(dot(ψ₀, ψₜ))) / le @assert isapprox(echo(ψ₀, ψ₀), 0, atol = 1.0e-10) ```` -We will initially use a two-site TDVP scheme to dynamically increase the bond dimension while time evolving, and later on switch to a faster one-site scheme. A single timestep can be done using +We will initially use a two-site TDVP scheme to dynamically increase the bond dimension while time evolving, and later on switch to a faster one-site scheme. +A single timestep can be done using ````julia H₁ = transverse_field_ising(; L, g = -2.0) @@ -71,7 +59,8 @@ dt = 0.01 ψₜ, envs, info = timestep(ψₜ, H₁, 0, dt, TDVP2(; trunc = truncrank(20))); ```` -"envs" is a kind of cache object that keeps track of all environments in `ψ`. It is often advantageous to re-use the environment, so that MPSKit doesn't need to recalculate everything. +"envs" is a kind of cache object that keeps track of all environments in `ψ`. +It is often advantageous to re-use the environment, so that MPSKit doesn't need to recalculate everything. Putting it all together, we get @@ -79,7 +68,7 @@ Putting it all together, we get function finite_sim(L; dt = 0.05, finaltime = 5.0) ψ₀ = FiniteMPS(L, ℂ^2, ℂ^10) H₀ = transverse_field_ising(; L, g = -0.5) - ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG()) + ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG(; verbosity = 0)) H₁ = transverse_field_ising(; L, g = -2.0) ψₜ = deepcopy(ψ₀) @@ -111,21 +100,10 @@ Similarly we could start with an initial infinite state and find the pre-quench ````julia ψ₀ = InfiniteMPS([ℂ^2], [ℂ^10]) H₀ = transverse_field_ising(; g = -0.5) -ψ₀, _ = find_groundstate(ψ₀, H₀, VUMPS()); -```` - -```` -[ Info: VUMPS init: obj = +4.958532341527e-01 err = 4.0184e-01 -[ Info: VUMPS 1: obj = -1.014182971769e+00 err = 1.6819729194e-01 time = 9.57 sec -[ Info: VUMPS 2: obj = -1.063544208631e+00 err = 4.2583303913e-04 time = 0.01 sec -[ Info: VUMPS 3: obj = -1.063544409973e+00 err = 6.1082631880e-07 time = 0.01 sec -[ Info: VUMPS 4: obj = -1.063544409973e+00 err = 2.0420569870e-08 time = 0.00 sec -[ Info: VUMPS 5: obj = -1.063544409973e+00 err = 8.6924174444e-10 time = 0.00 sec -[ Info: VUMPS conv 6: obj = -1.063544409973e+00 err = 5.3767504258e-11 time = 9.59 sec - +ψ₀, _ = find_groundstate(ψ₀, H₀, VUMPS(; verbosity = 0)); ```` -The dot product of two infinite matrix product states scales as ``\alpha ^N`` where ``α`` is the dominant eigenvalue of the transfer matrix. +The dot product of two infinite matrix product states scales as ``α ^N`` where ``α`` is the dominant eigenvalue of the transfer matrix. It is this ``α`` that is returned when calling ````julia @@ -133,7 +111,7 @@ dot(ψ₀, ψ₀) ```` ```` -0.9999999999999998 + 2.8068402690374434e-16im +0.9999999999999999 + 6.62289120209154e-17im ```` so the Loschmidt echo takes on the pleasant form @@ -143,7 +121,8 @@ echo(ψ₀::InfiniteMPS, ψₜ::InfiniteMPS) = -2 * log(abs(dot(ψ₀, ψₜ))) @assert isapprox(echo(ψ₀, ψ₀), 0, atol = 1.0e-10) ```` -We make use of the `changebonds` machinery to grow the bond dimension. This can also be achieved through a two-site scheme. +We make use of the `changebonds` machinery to grow the bond dimension. +This can also be achieved through a two-site scheme. Multiple algorithms are available, but we will only focus on `OptimalExpand()`. Growing the bond dimension by ``5`` can be done by calling: @@ -166,7 +145,7 @@ The final code is ````julia function infinite_sim(dt = 0.05, finaltime = 5.0) ψ₀ = InfiniteMPS([ℂ^2], [ℂ^10]) - ψ₀, _ = find_groundstate(ψ₀, H₀, VUMPS()) + ψ₀, _ = find_groundstate(ψ₀, H₀, VUMPS(; verbosity = 0)) ψₜ = deepcopy(ψ₀) envs = environments(ψₜ, H₁, ψₜ) diff --git a/docs/src/examples/quantum1d/3.ising-dqpt/infinite_timeev.png b/docs/src/examples/dynamics/ising-dqpt/infinite_timeev.png similarity index 100% rename from docs/src/examples/quantum1d/3.ising-dqpt/infinite_timeev.png rename to docs/src/examples/dynamics/ising-dqpt/infinite_timeev.png diff --git a/docs/src/examples/quantum1d/3.ising-dqpt/main.ipynb b/docs/src/examples/dynamics/ising-dqpt/main.ipynb similarity index 89% rename from docs/src/examples/quantum1d/3.ising-dqpt/main.ipynb rename to docs/src/examples/dynamics/ising-dqpt/main.ipynb index 1d4893fb7..7093b71a5 100644 --- a/docs/src/examples/quantum1d/3.ising-dqpt/main.ipynb +++ b/docs/src/examples/dynamics/ising-dqpt/main.ipynb @@ -6,8 +6,8 @@ "source": [ "# DQPT in the Ising model\n", "\n", - "In this tutorial we will try to reproduce the results from\n", - "[this paper](https://arxiv.org/pdf/1206.2505.pdf). The needed packages are" + "In this tutorial we will try to reproduce the results from [this paper](https://arxiv.org/pdf/1206.2505.pdf).\n", + "The needed packages are" ] }, { @@ -130,17 +130,18 @@ "metadata": {}, "source": [ "Dynamical quantum phase transitions (DQPT in short) are signatures of equilibrium phase transitions in a dynamical quantity - the Loschmidt echo.\n", - "This quantity is given by $L(t) = \\frac{-2}{N} ln(| < \\psi(t) | \\psi(0) > |) $ where $N$ is the system size.\n", + "This quantity is given by $L(t) = \\frac{-2}{N} \\ln |⟨ψ(t)|ψ(0)⟩|$ where $N$ is the system size.\n", "One typically starts from a ground state and then quenches the Hamiltonian to a different point.\n", - "Non analycities in the Loschmidt echo are called 'dynamical quantum phase transitions'.\n", + "Non-analyticities in the Loschmidt echo are called 'dynamical quantum phase transitions'.\n", "\n", "In the mentioned paper they work with\n", "\n", - "$H(g) = - \\sum^{N-1}_{i=1} \\sigma^z_i \\sigma^z_{i+1} + g \\sum_{i=1}^N \\sigma^x_i$\n", + "$H(g) = - \\sum^{N-1}_{i=1} σ^z_i σ^z_{i+1} + g \\sum_{i=1}^N σ^x_i$\n", "\n", - "and show that divergences occur when quenching across the critical point (g₀ → g₁) for $t^*_n = t^*(n+\\frac{1}{2})$ with $t^* = \\pi/e(g_1,k^*)$, $cos(k^*) = (1+g_0 g_1) / (g_0 + g_1)$, $ e(g,k) = \\sqrt{(g-cos k)^2 + sin^2 k}$.\n", + "and show that divergences occur when quenching across the critical point (g₀ → g₁) for $t^*_n = t^*(n+\\frac{1}{2})$ with $t^* = π/e(g_1,k^*)$, $cos(k^*) = (1+g_0 g_1) / (g_0 + g_1)$, $ e(g,k) = \\sqrt{(g-cos k)^2 + sin^2 k}$.\n", "\n", - "The outline of the tutorial is as follows. We will pick $g₀ = 0.5$, $g₁ = 2.0$, and perform the time evolution at different system sizes and compare with the thermodynamic limit.\n", + "The outline of the tutorial is as follows.\n", + "We will pick $g₀ = 0.5$, $g₁ = 2.0$, and perform the time evolution at different system sizes and compare with the thermodynamic limit.\n", "For those $g$ we expect non-analyticities to occur at $t_n ≈ 2.35 (n + 1/2)$.\n", "\n", "First we construct the Hamiltonian in MPO form, and obtain the pre-quenched ground state:" @@ -155,7 +156,7 @@ "L = 20\n", "H₀ = transverse_field_ising(; L, g = -0.5)\n", "ψ₀ = FiniteMPS(L, ℂ^2, ℂ^10)\n", - "ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG());" + "ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG(; verbosity = 0));" ] }, { @@ -181,7 +182,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We will initially use a two-site TDVP scheme to dynamically increase the bond dimension while time evolving, and later on switch to a faster one-site scheme. A single timestep can be done using" + "We will initially use a two-site TDVP scheme to dynamically increase the bond dimension while time evolving, and later on switch to a faster one-site scheme.\n", + "A single timestep can be done using" ] }, { @@ -200,7 +202,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\"envs\" is a kind of cache object that keeps track of all environments in `ψ`. It is often advantageous to re-use the environment, so that MPSKit doesn't need to recalculate everything.\n", + "\"envs\" is a kind of cache object that keeps track of all environments in `ψ`.\n", + "It is often advantageous to re-use the environment, so that MPSKit doesn't need to recalculate everything.\n", "\n", "Putting it all together, we get" ] @@ -214,7 +217,7 @@ "function finite_sim(L; dt = 0.05, finaltime = 5.0)\n", " ψ₀ = FiniteMPS(L, ℂ^2, ℂ^10)\n", " H₀ = transverse_field_ising(; L, g = -0.5)\n", - " ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG())\n", + " ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG(; verbosity = 0))\n", "\n", " H₁ = transverse_field_ising(; L, g = -2.0)\n", " ψₜ = deepcopy(ψ₀)\n", @@ -257,14 +260,14 @@ "source": [ "ψ₀ = InfiniteMPS([ℂ^2], [ℂ^10])\n", "H₀ = transverse_field_ising(; g = -0.5)\n", - "ψ₀, _ = find_groundstate(ψ₀, H₀, VUMPS());" + "ψ₀, _ = find_groundstate(ψ₀, H₀, VUMPS(; verbosity = 0));" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The dot product of two infinite matrix product states scales as $\\alpha ^N$ where $α$ is the dominant eigenvalue of the transfer matrix.\n", + "The dot product of two infinite matrix product states scales as $α ^N$ where $α$ is the dominant eigenvalue of the transfer matrix.\n", "It is this $α$ that is returned when calling" ] }, @@ -298,7 +301,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We make use of the `changebonds` machinery to grow the bond dimension. This can also be achieved through a two-site scheme.\n", + "We make use of the `changebonds` machinery to grow the bond dimension.\n", + "This can also be achieved through a two-site scheme.\n", "Multiple algorithms are available, but we will only focus on `OptimalExpand()`.\n", "Growing the bond dimension by $5$ can be done by calling:" ] @@ -347,7 +351,7 @@ "source": [ "function infinite_sim(dt = 0.05, finaltime = 5.0)\n", " ψ₀ = InfiniteMPS([ℂ^2], [ℂ^10])\n", - " ψ₀, _ = find_groundstate(ψ₀, H₀, VUMPS())\n", + " ψ₀, _ = find_groundstate(ψ₀, H₀, VUMPS(; verbosity = 0))\n", "\n", " ψₜ = deepcopy(ψ₀)\n", " envs = environments(ψₜ, H₁, ψₜ)\n", diff --git a/docs/src/examples/quantum1d/7.xy-finiteT/figure-1.png b/docs/src/examples/dynamics/xy-finiteT/figure-1.png similarity index 100% rename from docs/src/examples/quantum1d/7.xy-finiteT/figure-1.png rename to docs/src/examples/dynamics/xy-finiteT/figure-1.png diff --git a/docs/src/examples/quantum1d/7.xy-finiteT/figure-2.png b/docs/src/examples/dynamics/xy-finiteT/figure-2.png similarity index 100% rename from docs/src/examples/quantum1d/7.xy-finiteT/figure-2.png rename to docs/src/examples/dynamics/xy-finiteT/figure-2.png diff --git a/docs/src/examples/quantum1d/7.xy-finiteT/figure-3.png b/docs/src/examples/dynamics/xy-finiteT/figure-3.png similarity index 100% rename from docs/src/examples/quantum1d/7.xy-finiteT/figure-3.png rename to docs/src/examples/dynamics/xy-finiteT/figure-3.png diff --git a/docs/src/examples/quantum1d/7.xy-finiteT/figure-4.png b/docs/src/examples/dynamics/xy-finiteT/figure-4.png similarity index 100% rename from docs/src/examples/quantum1d/7.xy-finiteT/figure-4.png rename to docs/src/examples/dynamics/xy-finiteT/figure-4.png diff --git a/docs/src/examples/quantum1d/7.xy-finiteT/figure-5.png b/docs/src/examples/dynamics/xy-finiteT/figure-5.png similarity index 100% rename from docs/src/examples/quantum1d/7.xy-finiteT/figure-5.png rename to docs/src/examples/dynamics/xy-finiteT/figure-5.png diff --git a/docs/src/examples/dynamics/xy-finiteT/figure-6.png b/docs/src/examples/dynamics/xy-finiteT/figure-6.png new file mode 100644 index 000000000..37ddfbd45 Binary files /dev/null and b/docs/src/examples/dynamics/xy-finiteT/figure-6.png differ diff --git a/docs/src/examples/quantum1d/7.xy-finiteT/index.md b/docs/src/examples/dynamics/xy-finiteT/index.md similarity index 79% rename from docs/src/examples/quantum1d/7.xy-finiteT/index.md rename to docs/src/examples/dynamics/xy-finiteT/index.md index f55c6a9c3..04e0d6c1d 100644 --- a/docs/src/examples/quantum1d/7.xy-finiteT/index.md +++ b/docs/src/examples/dynamics/xy-finiteT/index.md @@ -1,10 +1,10 @@ ```@meta -EditURL = "../../../../../examples/quantum1d/7.xy-finiteT/main.jl" +EditURL = "../../../../../examples/dynamics/xy-finiteT/main.jl" ``` -[![](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/quantum1d/7.xy-finiteT/main.ipynb) -[![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/quantum1d/7.xy-finiteT/main.ipynb) -[![](https://img.shields.io/badge/download-project-orange)](https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/QuantumKitHub/MPSKit.jl/examples/tree/gh-pages/dev/examples/quantum1d/7.xy-finiteT) +[![](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/dynamics/xy-finiteT/main.ipynb) +[![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/dynamics/xy-finiteT/main.ipynb) +[![](https://img.shields.io/badge/download-project-orange)](https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/QuantumKitHub/MPSKit.jl/examples/tree/gh-pages/dev/examples/dynamics/xy-finiteT) ````julia using Markdown @@ -28,7 +28,7 @@ As a result, many properties have analytical expressions that can be used to ver Here, we use [BenchmarkFreeFermions.jl](https://github.com/Qiaoyi-Li/BenchmarkFreeFermions.jl/) to compare our results. ```math - H = J \sum_{i=1}^{N} \left( \sigma^x_i \sigma^x_{i+1} + \sigma^y_i \sigma^y_{i+1} \right) + H = J \sum_{i=1}^{N} \left( σ^x_i σ^x_{i+1} + σ^y_i σ^y_{i+1} \right) ``` Here we will consider the anti-ferromagnetic ($J > 0$) chain, and restrict ourselves to $J = 1/2$. @@ -55,15 +55,13 @@ XY_hamiltonian (generic function with 3 methods) ## Diagonalization of the Hamiltonian -The Hamiltonian can be diagonalized through a Bogoliubov transformation, leading to the following expression for the ground state energy -The Hamiltonian can be diagonalized in terms of fermionic creation and annihilation operators, leading to the following expression in terms of [an incomplete elliptic integral of the second kind](https://en.wikipedia.org/wiki/Elliptic_integral). +The Hamiltonian can be diagonalized in terms of fermionic creation and annihilation operators, which yields an expression for the ground state energy in terms of [an incomplete elliptic integral of the second kind](https://en.wikipedia.org/wiki/Elliptic_integral). ```math - E_0 = -\frac{1}{\pi} \text{EllipticE}\left( \sqrt{1 - \gamma^2} \right) + E_0 = -\frac{1}{π} \text{EllipticE}\left( \sqrt{1 - γ^2} \right) ``` -!!! todo - Show the derivation of the ground state energy by diagonalizing the Hamiltonian in terms of fermionic operators. +The derivation, via a Jordan-Wigner transformation to free fermions followed by a Bogoliubov rotation, can be found in [Lieb, Schultz & Mattis, Ann. Phys. 16, 407 (1961)](https://doi.org/10.1016/0003-4916(61)90115-4). ````julia function groundstate_energy(J, N) @@ -112,7 +110,7 @@ D = 64 V_init = symmetry === Trivial ? ℂ^32 : U1Space(i => 10 for i in -1:(1 // 2):1) psi_init = FiniteMPS(N, physicalspace(H, 1), V_init) trunc = truncrank(D) -psi, envs, = find_groundstate(psi_init, H, DMRG2(; trunc, maxiter = 5)); +psi, envs, = find_groundstate(psi_init, H, DMRG2(; trunc, maxiter = 5, verbosity = 0)); E_0 = expectation_value(psi, H, envs) / N println("Numerical:\t", real(E_0)) @@ -121,12 +119,7 @@ println("Exact (N=Inf):\t", groundstate_energy(J, Inf)) ```` ```` -[ Info: DMRG2 init: obj = +4.372173567940e+00 err = 1.0000e+00 -[ Info: DMRG2 1: obj = -5.003682441423e+00 err = 3.0044527697e-02 time = 1.23 min -[ Info: DMRG2 2: obj = -5.004096972575e+00 err = 4.0291060276e-04 time = 0.44 sec -[ Info: DMRG2 3: obj = -5.004096975044e+00 err = 4.3913120671e-06 time = 0.18 sec -[ Info: DMRG2 conv 4: obj = -5.004096975044e+00 err = 7.5912983728e-07 time = 1.24 min -Numerical: -0.1563780304701152 +Numerical: -0.15637803047011115 Exact (N=32): -0.15637803047254015 Exact (N=Inf): -0.15915494309189535 @@ -138,40 +131,39 @@ To go beyond the ground state, we can extract several properties at finite tempe This is given by ```math - Z(\beta) = \text{Tr} \left( e^{-\beta H} \right) + Z(β) = \text{Tr} \left( e^{-β H} \right) ``` -where $\beta = 1 / T$ is the inverse temperature. +where $β = 1 / T$ is the inverse temperature. Given the partition function, we can compute the free energy as ```math - F(\beta) = -\frac{1}{\beta} \log Z(\beta) + F(β) = -\frac{1}{β} \log Z(β) ``` We can also compute observables using ```math - \langle O \rangle = \frac{1}{Z} \text{Tr} \left( O e^{-\beta H} \right) + ⟨O⟩ = \frac{1}{Z} \text{Tr} \left( O e^{-β H} \right) ``` In particular, we can compute the energy as ```math - U = \langle H \rangle = \frac{1}{Z} \text{Tr} \left( H e^{-\beta H} \right) + U = ⟨H⟩ = \frac{1}{Z} \text{Tr} \left( H e^{-β H} \right) ``` Finally, the specific heat can be computed as ```math - \chi = \frac{\partial U}{\partial T} = -\beta^2 \frac{\partial U}{\partial \beta} + χ = \frac{∂ U}{∂ T} = -β^2 \frac{∂ U}{∂ β} ``` Luckily, the partition function can be computed analytically for the XY model. The resulting expression is ```math - Z(\beta) = \prod_{k=1}^{N} \left( 1 + e^{-\beta \epsilon_k} \right)^{1/N} + Z(β) = \prod_{k=1}^{N} \left( 1 + e^{-β ε_k} \right)^{1/N} ``` -!!! todo - Show the derivation of the partition function for the XY model. +This expression follows from the same free-fermion diagonalization as the ground-state energy above: each single-particle mode $ε_k$ is independently occupied or empty, giving the usual free-fermion partition function (see again [Lieb, Schultz & Mattis (1961)](https://doi.org/10.1016/0003-4916(61)90115-4)). ````julia function partition_function(β::Number, J::Number, N::Number) @@ -194,10 +186,10 @@ F_analytic = free_energy.(βs, J, N); ### MPO approach We can numerically compute the partition function by explicitly computing the trace of the time-evolution operator. -To that end, we first need to build the time-evolution operator $e^{-\beta H}$, and then compute its trace. +To that end, we first need to build the time-evolution operator $e^{-β H}$, and then compute its trace. In order to build the time-evolution operator, we can repurpose the `make_time_mpo` function, which constructs the time-evolution operator for the ground state. -However, since we are interested in $e^{-\beta H}$, instead of $e^{-iH dt}$, we work with $dt = -i \beta$. +However, since we are interested in $e^{-β H}$, instead of $e^{-iH dt}$, we work with $dt = -i β$. In particular, we can approximate the exponential using a Taylor series through the `TaylorCluster` algorithm. ````julia @@ -239,11 +231,11 @@ end Some observations: - The first order approximation fails to capture the behavior of the partition function. -- The higher order approximations are in good agreement with the analytical result, as long as $\beta$ is not too large. -- The computational cost of the approximations does not depend on $\beta$, but on the order of the approximation. +- The higher order approximations are in good agreement with the analytical result, as long as $β$ is not too large. +- The computational cost of the approximations does not depend on $β$, but on the order of the approximation. To address the first point, we can have a look at the particular form of the time-evolution operator. -Here we see that for this particular Hamiltonian, all the terms with factors $d\tau$ are either zero or have trace zero. +Here we see that for this particular Hamiltonian, all the terms with factors $dτ$ are either zero or have trace zero. As a result, the trace of the time-evolution operator is equal to the trace of the identity, hence the result is always $2$. ```math @@ -254,9 +246,9 @@ H &= \begin{pmatrix} 0 & 0 & 1 \end{pmatrix} \\ -e^{\tau H} &= \begin{pmatrix} - 1 + \tau D + \frac{\tau^2}{2} D^2 & C + \frac{\tau}{2} (CD + DC) \\ - \tau (B + \frac{\tau}{2} (BD + DB)) & A + \frac{\tau^2}{2} (AD + DA + CB + BC) +e^{τ H} &= \begin{pmatrix} + 1 + τ D + \frac{τ^2}{2} D^2 & C + \frac{τ}{2} (CD + DC) \\ + τ (B + \frac{τ}{2} (BD + DB)) & A + \frac{τ^2}{2} (AD + DA + CB + BC) \end{pmatrix} \end{align} ``` @@ -290,23 +282,20 @@ end ![](figure-2.png) -We can now clearly see that, somewhat unsurprisingly, the error increases the larger $\beta$ becomes. -Given that we are computing Taylor expansions around $\beta = 0$, this is to be expected. +We can now clearly see that, somewhat unsurprisingly, the error increases the larger $β$ becomes. +Given that we are computing Taylor expansions around $β = 0$, this is to be expected. However, there is a trick we can use to improve our results slightly. To that end, we first rewrite the partition function as ```math -Z(\beta) = - \text{Tr} \left( e^{-\beta H} \right) = - \text{Tr} \left( e^{-\beta H / 2} e^{-\beta H / 2} \right) = - \left\langle e^{-\beta H^\dagger / 2}, e^{-\beta H / 2} \right\rangle +Z(β) = + \text{Tr} \left( e^{-β H} \right) = + \text{Tr} \left( e^{-β H / 2} e^{-β H / 2} \right) = + \left\langle e^{-β H^† / 2}, e^{-β H / 2} \right\rangle ``` -In other words, we can compute the partition function at $\beta$ by computing the overlap of two states evolved for $\beta / 2$, as long as the Hamiltonian is Hermitian. -Otherwise, we could still use the same trick, but we would have to compute the evolved states twice, once for $H$ and once for $H^\dagger$. - -!!! todo - Add a figure to illustrate this trick. +In other words, we can compute the partition function at $β$ by computing the overlap of two states evolved for $β / 2$, as long as the Hamiltonian is Hermitian. +Otherwise, we could still use the same trick, but we would have to compute the evolved states twice, once for $H$ and once for $H^†$. ````julia double_logpartition(ρ₁, ρ₂ = ρ₁) = log(real(dot(ρ₁, ρ₂))) / length(ρ₁) @@ -346,15 +335,15 @@ end ### MPO multiplication approach (linear) -While the Taylor series approach is useful, we can only push that so far, since we are always expanding around $\beta = 0$. -However, inspired by the trick we used to improve the results, we can use MPO multiplication techniques to compute partition functions at larger $\beta$. -In particular, we can implement the following algorithm to scan over a linear range of $\beta$ values. +While the Taylor series approach is useful, we can only push that so far, since we are always expanding around $β = 0$. +However, inspired by the trick we used to improve the results, we can use MPO multiplication techniques to compute partition functions at larger $β$. +In particular, we can implement the following algorithm to scan over a linear range of $β$ values. ```math \begin{align} -Z(2\beta) &= Z(\beta) \cdot Z(\beta) \\ -Z(3\beta) &= Z(\beta) \cdot Z(\beta) \cdot Z(\beta) = Z(\beta) \cdot Z(2\beta) \\ -\vdots &= \vdots +Z(2β) &= Z(β) · Z(β) \\ +Z(3β) &= Z(β) · Z(β) · Z(β) = Z(β) · Z(2β) \\ +⋮ &= ⋮ \end{align} ``` @@ -421,10 +410,10 @@ end ![](figure-4.png) -This approach clearly improves the accuracy of the results, indicating that we can indeed compute partition functions at larger $\beta$ values. -However, the computational cost of this approach (at fixed maximal bond dimension) is now linear in $\beta$, since we need to compute the partition function at each $\beta$ value. -Often, this is fine, since we are typically interested in a range of $\beta$ values, rather than a single one. -However, to really push this to larger $\beta$ values, this can still turn out to be a bottleneck. +This approach clearly improves the accuracy of the results, indicating that we can indeed compute partition functions at larger $β$ values. +However, the computational cost of this approach (at fixed maximal bond dimension) is now linear in $β$, since we need to compute the partition function at each $β$ value. +Often, this is fine, since we are typically interested in a range of $β$ values, rather than a single one. +However, to really push this to larger $β$ values, this can still turn out to be a bottleneck. We also have to be careful with the accuracy of our results. In particular, the error in the partition function will accumulate over the iterations, which might turn the results into garbage. @@ -432,23 +421,23 @@ Typically, the entanglement entropy of the density matrix is a good measure of t Apart from the bond dimension, we have two other parameters to tune: the accuracy of the initial density matrix, and the size of the step. The accuracy of the initial density matrix can be improved by increasing the order of the Taylor expansion, but this will result in a larger MPO bond dimension. -On the other hand, if we improve the accuracy of the initial density matrix, we could also increase the step size, which would reduce the number of iterations required to reach a certain $\beta$ value. +On the other hand, if we improve the accuracy of the initial density matrix, we could also increase the step size, which would reduce the number of iterations required to reach a certain $β$ value. Keeping these parameters in balance is necessary to obtain accurate results, and this might require some trial and error. ### MPO multiplication approach (exponential) -If we wish to push the results to even larger $\beta$ values, we can note that taking linear steps in $\beta$ is not the only option. -To that end, we can use another trick to scan over an exponential range of $\beta$ values: [exponentiating by squaring](https://en.wikipedia.org/wiki/Exponentiation_by_squaring). -In particular, we note that computing $x^n$ for integer (large) $n$ can typically be done more efficiently than computing $x \cdot x \cdot \dots \cdot x$. +If we wish to push the results to even larger $β$ values, we can note that taking linear steps in $β$ is not the only option. +To that end, we can use another trick to scan over an exponential range of $β$ values: [exponentiating by squaring](https://en.wikipedia.org/wiki/Exponentiation_by_squaring). +In particular, we note that computing $x^n$ for integer (large) $n$ can typically be done more efficiently than computing $x · x · … · x$. To do so, we note that multiplication is associative, and regroup the factors in such a way that we can compute the result in a logarithmic number of steps. Here, we assume $n = 2^m$ for some integer $m$, and note that this could be generalized to any $n$ by decomposing $n$ into a sum of powers of $2$. Then, we can write ```math -x^n = x^{2^m} = x^{2^{m-1}} \cdot x^{2^{m-1}} = (x^{2^{m-2}} \cdot x^{2^{m-2}}) \cdot (x^{2^{m-2}} \cdot x^{2^{m-2}}) = \dots +x^n = x^{2^m} = x^{2^{m-1}} · x^{2^{m-1}} = (x^{2^{m-2}} · x^{2^{m-2}}) · (x^{2^{m-2}} · x^{2^{m-2}}) = … ``` -In other words, we can scan a range of exponentially increasing $\beta$ values by squaring the density matrix at each step. +In other words, we can scan a range of exponentially increasing $β$ values by squaring the density matrix at each step. ````julia βs_exp = 2.0 .^ (-3:3) @@ -501,7 +490,7 @@ end ![](figure-5.png) -Clearly, the exponential approach allows us to reach larger $\beta$ values much quicker, but there is again a trade-off. +Clearly, the exponential approach allows us to reach larger $β$ values much quicker, but there is again a trade-off. Since the size of the steps are increasing, we need to be more careful with the accuracy of our approximations. !!! warning @@ -513,14 +502,14 @@ Since the size of the steps are increasing, we need to be more careful with the Finally, we can also note that the partition function is characterized by the following differential equation: ```math -\frac{dZ}{d\beta} = -H \cdot Z -\implies Z(\beta) = e^{-\beta H} \cdot Z(0) +\frac{dZ}{dβ} = -H · Z +⟹ Z(β) = e^{-β H} · Z(0) ``` -In other words, we can compute the partition function at $\beta$ by evolving the partition function at $0$ for a time $d\tau = -i \beta$. +In other words, we can compute the partition function at $β$ by evolving the partition function at $0$ for a time $dτ = -i β$. The starting point for this approach could be either achieved through one of the techniques we have already discussed, but we can also start from the infinite temperature state directly. -In particular, this state is given by the identity MPO, and we can evolve this state to compute the partition function at any $\beta$ value. +In particular, this state is given by the identity MPO, and we can evolve this state to compute the partition function at any $β$ value. ````julia Z_tdvp = zeros(length(βs)) @@ -572,7 +561,7 @@ end ![](figure-6.png) !!! note - We could further improve the accuracy of the TDVP approach by evolving with $(H \otimes \mathbb{1} + \mathbb{1} \otimes H^\dagger)$, rather than $H \otimes \mathbb{1}$ which is the current implementation. + We could further improve the accuracy of the TDVP approach by evolving with $(H ⊗ \mathbb{1} + \mathbb{1} ⊗ H^†)$, rather than $H ⊗ \mathbb{1}$ which is the current implementation. This is known to improve the stability of the positive semidefinite property of the density matrix, and could lead to more accurate results. --- diff --git a/docs/src/examples/quantum1d/7.xy-finiteT/main.ipynb b/docs/src/examples/dynamics/xy-finiteT/main.ipynb similarity index 87% rename from docs/src/examples/quantum1d/7.xy-finiteT/main.ipynb rename to docs/src/examples/dynamics/xy-finiteT/main.ipynb index f46ed94fe..fed2a1e4f 100644 --- a/docs/src/examples/quantum1d/7.xy-finiteT/main.ipynb +++ b/docs/src/examples/dynamics/xy-finiteT/main.ipynb @@ -136,7 +136,7 @@ "Here, we use [BenchmarkFreeFermions.jl](https://github.com/Qiaoyi-Li/BenchmarkFreeFermions.jl/) to compare our results.\n", "\n", "$$\n", - " H = J \\sum_{i=1}^{N} \\left( \\sigma^x_i \\sigma^x_{i+1} + \\sigma^y_i \\sigma^y_{i+1} \\right)\n", + " H = J \\sum_{i=1}^{N} \\left( σ^x_i σ^x_{i+1} + σ^y_i σ^y_{i+1} \\right)\n", "$$\n", "\n", "Here we will consider the anti-ferromagnetic ($J > 0$) chain, and restrict ourselves to $J = 1/2$." @@ -174,16 +174,13 @@ "source": [ "## Diagonalization of the Hamiltonian\n", "\n", - "The Hamiltonian can be diagonalized through a Bogoliubov transformation, leading to the following expression for the ground state energy\n", - "The Hamiltonian can be diagonalized in terms of fermionic creation and annihilation operators, leading to the following expression in terms of [an incomplete elliptic integral of the second kind](https://en.wikipedia.org/wiki/Elliptic_integral).\n", + "The Hamiltonian can be diagonalized in terms of fermionic creation and annihilation operators, which yields an expression for the ground state energy in terms of [an incomplete elliptic integral of the second kind](https://en.wikipedia.org/wiki/Elliptic_integral).\n", "\n", "$$\n", - " E_0 = -\\frac{1}{\\pi} \\text{EllipticE}\\left( \\sqrt{1 - \\gamma^2} \\right)\n", + " E_0 = -\\frac{1}{π} \\text{EllipticE}\\left( \\sqrt{1 - γ^2} \\right)\n", "$$\n", "\n", - "> **Todo**\n", - ">\n", - "> Show the derivation of the ground state energy by diagonalizing the Hamiltonian in terms of fermionic operators." + "The derivation, via a Jordan-Wigner transformation to free fermions followed by a Bogoliubov rotation, can be found in [Lieb, Schultz & Mattis, Ann. Phys. 16, 407 (1961)](https://doi.org/10.1016/0003-4916(61)90115-4)." ] }, { @@ -247,7 +244,7 @@ "V_init = symmetry === Trivial ? ℂ^32 : U1Space(i => 10 for i in -1:(1 // 2):1)\n", "psi_init = FiniteMPS(N, physicalspace(H, 1), V_init)\n", "trunc = truncrank(D)\n", - "psi, envs, = find_groundstate(psi_init, H, DMRG2(; trunc, maxiter = 5));\n", + "psi, envs, = find_groundstate(psi_init, H, DMRG2(; trunc, maxiter = 5, verbosity = 0));\n", "E_0 = expectation_value(psi, H, envs) / N\n", "\n", "println(\"Numerical:\\t\", real(E_0))\n", @@ -265,41 +262,39 @@ "This is given by\n", "\n", "$$\n", - " Z(\\beta) = \\text{Tr} \\left( e^{-\\beta H} \\right)\n", + " Z(β) = \\text{Tr} \\left( e^{-β H} \\right)\n", "$$\n", "\n", - "where $\\beta = 1 / T$ is the inverse temperature.\n", + "where $β = 1 / T$ is the inverse temperature.\n", "\n", "Given the partition function, we can compute the free energy as\n", "$$\n", - " F(\\beta) = -\\frac{1}{\\beta} \\log Z(\\beta)\n", + " F(β) = -\\frac{1}{β} \\log Z(β)\n", "$$\n", "\n", "We can also compute observables using\n", "$$\n", - " \\langle O \\rangle = \\frac{1}{Z} \\text{Tr} \\left( O e^{-\\beta H} \\right)\n", + " ⟨O⟩ = \\frac{1}{Z} \\text{Tr} \\left( O e^{-β H} \\right)\n", "$$\n", "\n", "In particular, we can compute the energy as\n", "$$\n", - " U = \\langle H \\rangle = \\frac{1}{Z} \\text{Tr} \\left( H e^{-\\beta H} \\right)\n", + " U = ⟨H⟩ = \\frac{1}{Z} \\text{Tr} \\left( H e^{-β H} \\right)\n", "$$\n", "\n", "Finally, the specific heat can be computed as\n", "$$\n", - " \\chi = \\frac{\\partial U}{\\partial T} = -\\beta^2 \\frac{\\partial U}{\\partial \\beta}\n", + " χ = \\frac{∂ U}{∂ T} = -β^2 \\frac{∂ U}{∂ β}\n", "$$\n", "\n", "Luckily, the partition function can be computed analytically for the XY model.\n", "The resulting expression is\n", "\n", "$$\n", - " Z(\\beta) = \\prod_{k=1}^{N} \\left( 1 + e^{-\\beta \\epsilon_k} \\right)^{1/N}\n", + " Z(β) = \\prod_{k=1}^{N} \\left( 1 + e^{-β ε_k} \\right)^{1/N}\n", "$$\n", "\n", - "> **Todo**\n", - ">\n", - "> Show the derivation of the partition function for the XY model." + "This expression follows from the same free-fermion diagonalization as the ground-state energy above: each single-particle mode $ε_k$ is independently occupied or empty, giving the usual free-fermion partition function (see again [Lieb, Schultz & Mattis (1961)](https://doi.org/10.1016/0003-4916(61)90115-4))." ] }, { @@ -332,10 +327,10 @@ "### MPO approach\n", "\n", "We can numerically compute the partition function by explicitly computing the trace of the time-evolution operator.\n", - "To that end, we first need to build the time-evolution operator $e^{-\\beta H}$, and then compute its trace.\n", + "To that end, we first need to build the time-evolution operator $e^{-β H}$, and then compute its trace.\n", "\n", "In order to build the time-evolution operator, we can repurpose the `make_time_mpo` function, which constructs the time-evolution operator for the ground state.\n", - "However, since we are interested in $e^{-\\beta H}$, instead of $e^{-iH dt}$, we work with $dt = -i \\beta$.\n", + "However, since we are interested in $e^{-β H}$, instead of $e^{-iH dt}$, we work with $dt = -i β$.\n", "In particular, we can approximate the exponential using a Taylor series through the `TaylorCluster` algorithm." ] }, @@ -385,8 +380,8 @@ "source": [ "Some observations:\n", "- The first order approximation fails to capture the behavior of the partition function.\n", - "- The higher order approximations are in good agreement with the analytical result, as long as $\\beta$ is not too large.\n", - "- The computational cost of the approximations does not depend on $\\beta$, but on the order of the approximation." + "- The higher order approximations are in good agreement with the analytical result, as long as $β$ is not too large.\n", + "- The computational cost of the approximations does not depend on $β$, but on the order of the approximation." ] }, { @@ -394,7 +389,7 @@ "metadata": {}, "source": [ "To address the first point, we can have a look at the particular form of the time-evolution operator.\n", - "Here we see that for this particular Hamiltonian, all the terms with factors $d\\tau$ are either zero or have trace zero.\n", + "Here we see that for this particular Hamiltonian, all the terms with factors $dτ$ are either zero or have trace zero.\n", "As a result, the trace of the time-evolution operator is equal to the trace of the identity, hence the result is always $2$.\n", "\n", "$$\n", @@ -405,9 +400,9 @@ " 0 & 0 & 1\n", "\\end{pmatrix} \\\\\n", "\n", - "e^{\\tau H} &= \\begin{pmatrix}\n", - " 1 + \\tau D + \\frac{\\tau^2}{2} D^2 & C + \\frac{\\tau}{2} (CD + DC) \\\\\n", - " \\tau (B + \\frac{\\tau}{2} (BD + DB)) & A + \\frac{\\tau^2}{2} (AD + DA + CB + BC)\n", + "e^{τ H} &= \\begin{pmatrix}\n", + " 1 + τ D + \\frac{τ^2}{2} D^2 & C + \\frac{τ}{2} (CD + DC) \\\\\n", + " τ (B + \\frac{τ}{2} (BD + DB)) & A + \\frac{τ^2}{2} (AD + DA + CB + BC)\n", "\\end{pmatrix}\n", "\\end{align}\n", "$$\n", @@ -449,24 +444,20 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can now clearly see that, somewhat unsurprisingly, the error increases the larger $\\beta$ becomes.\n", - "Given that we are computing Taylor expansions around $\\beta = 0$, this is to be expected.\n", + "We can now clearly see that, somewhat unsurprisingly, the error increases the larger $β$ becomes.\n", + "Given that we are computing Taylor expansions around $β = 0$, this is to be expected.\n", "\n", "However, there is a trick we can use to improve our results slightly.\n", "To that end, we first rewrite the partition function as\n", "$$\n", - "Z(\\beta) =\n", - " \\text{Tr} \\left( e^{-\\beta H} \\right) =\n", - " \\text{Tr} \\left( e^{-\\beta H / 2} e^{-\\beta H / 2} \\right) =\n", - " \\left\\langle e^{-\\beta H^\\dagger / 2}, e^{-\\beta H / 2} \\right\\rangle\n", + "Z(β) =\n", + " \\text{Tr} \\left( e^{-β H} \\right) =\n", + " \\text{Tr} \\left( e^{-β H / 2} e^{-β H / 2} \\right) =\n", + " \\left\\langle e^{-β H^† / 2}, e^{-β H / 2} \\right\\rangle\n", "$$\n", "\n", - "In other words, we can compute the partition function at $\\beta$ by computing the overlap of two states evolved for $\\beta / 2$, as long as the Hamiltonian is Hermitian.\n", - "Otherwise, we could still use the same trick, but we would have to compute the evolved states twice, once for $H$ and once for $H^\\dagger$.\n", - "\n", - "> **Todo**\n", - ">\n", - "> Add a figure to illustrate this trick." + "In other words, we can compute the partition function at $β$ by computing the overlap of two states evolved for $β / 2$, as long as the Hamiltonian is Hermitian.\n", + "Otherwise, we could still use the same trick, but we would have to compute the evolved states twice, once for $H$ and once for $H^†$." ] }, { @@ -514,15 +505,15 @@ "source": [ "### MPO multiplication approach (linear)\n", "\n", - "While the Taylor series approach is useful, we can only push that so far, since we are always expanding around $\\beta = 0$.\n", - "However, inspired by the trick we used to improve the results, we can use MPO multiplication techniques to compute partition functions at larger $\\beta$.\n", - "In particular, we can implement the following algorithm to scan over a linear range of $\\beta$ values.\n", + "While the Taylor series approach is useful, we can only push that so far, since we are always expanding around $β = 0$.\n", + "However, inspired by the trick we used to improve the results, we can use MPO multiplication techniques to compute partition functions at larger $β$.\n", + "In particular, we can implement the following algorithm to scan over a linear range of $β$ values.\n", "\n", "$$\n", "\\begin{align}\n", - "Z(2\\beta) &= Z(\\beta) \\cdot Z(\\beta) \\\\\n", - "Z(3\\beta) &= Z(\\beta) \\cdot Z(\\beta) \\cdot Z(\\beta) = Z(\\beta) \\cdot Z(2\\beta) \\\\\n", - "\\vdots &= \\vdots\n", + "Z(2β) &= Z(β) · Z(β) \\\\\n", + "Z(3β) &= Z(β) · Z(β) · Z(β) = Z(β) · Z(2β) \\\\\n", + "⋮ &= ⋮\n", "\\end{align}\n", "$$\n", "\n", @@ -598,10 +589,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This approach clearly improves the accuracy of the results, indicating that we can indeed compute partition functions at larger $\\beta$ values.\n", - "However, the computational cost of this approach (at fixed maximal bond dimension) is now linear in $\\beta$, since we need to compute the partition function at each $\\beta$ value.\n", - "Often, this is fine, since we are typically interested in a range of $\\beta$ values, rather than a single one.\n", - "However, to really push this to larger $\\beta$ values, this can still turn out to be a bottleneck.\n", + "This approach clearly improves the accuracy of the results, indicating that we can indeed compute partition functions at larger $β$ values.\n", + "However, the computational cost of this approach (at fixed maximal bond dimension) is now linear in $β$, since we need to compute the partition function at each $β$ value.\n", + "Often, this is fine, since we are typically interested in a range of $β$ values, rather than a single one.\n", + "However, to really push this to larger $β$ values, this can still turn out to be a bottleneck.\n", "\n", "We also have to be careful with the accuracy of our results.\n", "In particular, the error in the partition function will accumulate over the iterations, which might turn the results into garbage.\n", @@ -609,7 +600,7 @@ "\n", "Apart from the bond dimension, we have two other parameters to tune: the accuracy of the initial density matrix, and the size of the step.\n", "The accuracy of the initial density matrix can be improved by increasing the order of the Taylor expansion, but this will result in a larger MPO bond dimension.\n", - "On the other hand, if we improve the accuracy of the initial density matrix, we could also increase the step size, which would reduce the number of iterations required to reach a certain $\\beta$ value.\n", + "On the other hand, if we improve the accuracy of the initial density matrix, we could also increase the step size, which would reduce the number of iterations required to reach a certain $β$ value.\n", "Keeping these parameters in balance is necessary to obtain accurate results, and this might require some trial and error." ] }, @@ -619,18 +610,18 @@ "source": [ "### MPO multiplication approach (exponential)\n", "\n", - "If we wish to push the results to even larger $\\beta$ values, we can note that taking linear steps in $\\beta$ is not the only option.\n", - "To that end, we can use another trick to scan over an exponential range of $\\beta$ values: [exponentiating by squaring](https://en.wikipedia.org/wiki/Exponentiation_by_squaring).\n", - "In particular, we note that computing $x^n$ for integer (large) $n$ can typically be done more efficiently than computing $x \\cdot x \\cdot \\dots \\cdot x$.\n", + "If we wish to push the results to even larger $β$ values, we can note that taking linear steps in $β$ is not the only option.\n", + "To that end, we can use another trick to scan over an exponential range of $β$ values: [exponentiating by squaring](https://en.wikipedia.org/wiki/Exponentiation_by_squaring).\n", + "In particular, we note that computing $x^n$ for integer (large) $n$ can typically be done more efficiently than computing $x · x · … · x$.\n", "To do so, we note that multiplication is associative, and regroup the factors in such a way that we can compute the result in a logarithmic number of steps.\n", "Here, we assume $n = 2^m$ for some integer $m$, and note that this could be generalized to any $n$ by decomposing $n$ into a sum of powers of $2$.\n", "Then, we can write\n", "\n", "$$\n", - "x^n = x^{2^m} = x^{2^{m-1}} \\cdot x^{2^{m-1}} = (x^{2^{m-2}} \\cdot x^{2^{m-2}}) \\cdot (x^{2^{m-2}} \\cdot x^{2^{m-2}}) = \\dots\n", + "x^n = x^{2^m} = x^{2^{m-1}} · x^{2^{m-1}} = (x^{2^{m-2}} · x^{2^{m-2}}) · (x^{2^{m-2}} · x^{2^{m-2}}) = …\n", "$$\n", "\n", - "In other words, we can scan a range of exponentially increasing $\\beta$ values by squaring the density matrix at each step." + "In other words, we can scan a range of exponentially increasing $β$ values by squaring the density matrix at each step." ] }, { @@ -691,7 +682,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Clearly, the exponential approach allows us to reach larger $\\beta$ values much quicker, but there is again a trade-off.\n", + "Clearly, the exponential approach allows us to reach larger $β$ values much quicker, but there is again a trade-off.\n", "Since the size of the steps are increasing, we need to be more careful with the accuracy of our approximations.\n", "\n", "> **Warning**\n", @@ -709,14 +700,14 @@ "Finally, we can also note that the partition function is characterized by the following differential equation:\n", "\n", "$$\n", - "\\frac{dZ}{d\\beta} = -H \\cdot Z\n", - "\\implies Z(\\beta) = e^{-\\beta H} \\cdot Z(0)\n", + "\\frac{dZ}{dβ} = -H · Z\n", + "⟹ Z(β) = e^{-β H} · Z(0)\n", "$$\n", "\n", - "In other words, we can compute the partition function at $\\beta$ by evolving the partition function at $0$ for a time $d\\tau = -i \\beta$.\n", + "In other words, we can compute the partition function at $β$ by evolving the partition function at $0$ for a time $dτ = -i β$.\n", "\n", "The starting point for this approach could be either achieved through one of the techniques we have already discussed, but we can also start from the infinite temperature state directly.\n", - "In particular, this state is given by the identity MPO, and we can evolve this state to compute the partition function at any $\\beta$ value." + "In particular, this state is given by the identity MPO, and we can evolve this state to compute the partition function at any $β$ value." ] }, { @@ -777,7 +768,7 @@ "source": [ "> **Note**\n", ">\n", - "> We could further improve the accuracy of the TDVP approach by evolving with $(H \\otimes \\mathbb{1} + \\mathbb{1} \\otimes H^\\dagger)$, rather than $H \\otimes \\mathbb{1}$ which is the current implementation.\n", + "> We could further improve the accuracy of the TDVP approach by evolving with $(H ⊗ \\mathbb{1} + \\mathbb{1} ⊗ H^†)$, rather than $H ⊗ \\mathbb{1}$ which is the current implementation.\n", "> This is known to improve the stability of the positive semidefinite property of the density matrix, and could lead to more accurate results." ] }, diff --git a/docs/src/examples/quantum1d/2.haldane/figure-1.png b/docs/src/examples/excitations/haldane/figure-1.png similarity index 100% rename from docs/src/examples/quantum1d/2.haldane/figure-1.png rename to docs/src/examples/excitations/haldane/figure-1.png diff --git a/docs/src/examples/quantum1d/2.haldane/figure-2.png b/docs/src/examples/excitations/haldane/figure-2.png similarity index 100% rename from docs/src/examples/quantum1d/2.haldane/figure-2.png rename to docs/src/examples/excitations/haldane/figure-2.png diff --git a/docs/src/examples/quantum1d/2.haldane/figure-3.png b/docs/src/examples/excitations/haldane/figure-3.png similarity index 100% rename from docs/src/examples/quantum1d/2.haldane/figure-3.png rename to docs/src/examples/excitations/haldane/figure-3.png diff --git a/docs/src/examples/quantum1d/2.haldane/index.md b/docs/src/examples/excitations/haldane/index.md similarity index 78% rename from docs/src/examples/quantum1d/2.haldane/index.md rename to docs/src/examples/excitations/haldane/index.md index e888595d3..3c37b066a 100644 --- a/docs/src/examples/quantum1d/2.haldane/index.md +++ b/docs/src/examples/excitations/haldane/index.md @@ -1,10 +1,10 @@ ```@meta -EditURL = "../../../../../examples/quantum1d/2.haldane/main.jl" +EditURL = "../../../../../examples/excitations/haldane/main.jl" ``` -[![](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/quantum1d/2.haldane/main.ipynb) -[![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/quantum1d/2.haldane/main.ipynb) -[![](https://img.shields.io/badge/download-project-orange)](https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/QuantumKitHub/MPSKit.jl/examples/tree/gh-pages/dev/examples/quantum1d/2.haldane) +[![](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/excitations/haldane/main.ipynb) +[![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/excitations/haldane/main.ipynb) +[![](https://img.shields.io/badge/download-project-orange)](https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/QuantumKitHub/MPSKit.jl/examples/tree/gh-pages/dev/examples/excitations/haldane) # The Haldane gap @@ -62,7 +62,7 @@ En_2, st_2 = excitations(H, QuasiparticleAnsatz(), ψ, envs; sector = SU2Irrep(2 ```` ```` -0.7989253589480417 +0.7989253589480334 ```` We can go even further and doublecheck the claim that ``S = 1`` is an edge excitation, by plotting the energy density. @@ -101,7 +101,7 @@ f = fit(Ls .^ (-2), ΔEs, 1) ```` ```` -0.4517340158582783 +0.4517340158583773 ```` ````julia @@ -129,30 +129,14 @@ virtual_space_inf = Rep[SU₂](1 // 2 => 16, 3 // 2 => 16, 5 // 2 => 8, 7 // 2 = ψ_inf, envs_inf, info_inf = find_groundstate(ψ₀_inf, H; verbosity = 0) kspace = range(0, π, 16) -Es, _ = excitations(H, QuasiparticleAnsatz(), kspace, ψ_inf, envs_inf; sector = SU2Irrep(1)) +Es, _ = excitations(H, QuasiparticleAnsatz(), kspace, ψ_inf, envs_inf; sector = SU2Irrep(1), verbosity = 0) ΔE, idx = findmin(real.(Es)) println("minimum @k = $(kspace[idx]):\t ΔE = $(ΔE)") ```` ```` -[ Info: Found excitations for momentum = 0.0 -[ Info: Found excitations for momentum = 0.20943951023931953 -[ Info: Found excitations for momentum = 0.41887902047863906 -[ Info: Found excitations for momentum = 0.6283185307179586 -[ Info: Found excitations for momentum = 0.8377580409572781 -[ Info: Found excitations for momentum = 1.0471975511965976 -[ Info: Found excitations for momentum = 1.2566370614359172 -[ Info: Found excitations for momentum = 1.4660765716752369 -[ Info: Found excitations for momentum = 1.6755160819145563 -[ Info: Found excitations for momentum = 1.8849555921538759 -[ Info: Found excitations for momentum = 2.0943951023931953 -[ Info: Found excitations for momentum = 2.303834612632515 -[ Info: Found excitations for momentum = 2.5132741228718345 -[ Info: Found excitations for momentum = 2.722713633111154 -[ Info: Found excitations for momentum = 2.9321531433504737 -[ Info: Found excitations for momentum = 3.141592653589793 -minimum @k = 3.141592653589793: ΔE = 0.4104792486506801 +minimum @k = 3.141592653589793: ΔE = 0.41047924868496444 ```` diff --git a/docs/src/examples/quantum1d/2.haldane/main.ipynb b/docs/src/examples/excitations/haldane/main.ipynb similarity index 99% rename from docs/src/examples/quantum1d/2.haldane/main.ipynb rename to docs/src/examples/excitations/haldane/main.ipynb index 537558604..5137d13ae 100644 --- a/docs/src/examples/quantum1d/2.haldane/main.ipynb +++ b/docs/src/examples/excitations/haldane/main.ipynb @@ -269,7 +269,7 @@ "ψ_inf, envs_inf, info_inf = find_groundstate(ψ₀_inf, H; verbosity = 0)\n", "\n", "kspace = range(0, π, 16)\n", - "Es, _ = excitations(H, QuasiparticleAnsatz(), kspace, ψ_inf, envs_inf; sector = SU2Irrep(1))\n", + "Es, _ = excitations(H, QuasiparticleAnsatz(), kspace, ψ_inf, envs_inf; sector = SU2Irrep(1), verbosity = 0)\n", "\n", "ΔE, idx = findmin(real.(Es))\n", "println(\"minimum @k = $(kspace[idx]):\\t ΔE = $(ΔE)\")\n", diff --git a/docs/src/examples/groundstates/bose-hubbard/figure-1.png b/docs/src/examples/groundstates/bose-hubbard/figure-1.png new file mode 100644 index 000000000..7bd25fdea Binary files /dev/null and b/docs/src/examples/groundstates/bose-hubbard/figure-1.png differ diff --git a/docs/src/examples/quantum1d/8.bose-hubbard/figure-2.png b/docs/src/examples/groundstates/bose-hubbard/figure-2.png similarity index 100% rename from docs/src/examples/quantum1d/8.bose-hubbard/figure-2.png rename to docs/src/examples/groundstates/bose-hubbard/figure-2.png diff --git a/docs/src/examples/groundstates/bose-hubbard/figure-3.png b/docs/src/examples/groundstates/bose-hubbard/figure-3.png new file mode 100644 index 000000000..680f6e6bb Binary files /dev/null and b/docs/src/examples/groundstates/bose-hubbard/figure-3.png differ diff --git a/docs/src/examples/groundstates/bose-hubbard/figure-4.png b/docs/src/examples/groundstates/bose-hubbard/figure-4.png new file mode 100644 index 000000000..7f22fb7f5 Binary files /dev/null and b/docs/src/examples/groundstates/bose-hubbard/figure-4.png differ diff --git a/docs/src/examples/groundstates/bose-hubbard/figure-5.png b/docs/src/examples/groundstates/bose-hubbard/figure-5.png new file mode 100644 index 000000000..394cdb16e Binary files /dev/null and b/docs/src/examples/groundstates/bose-hubbard/figure-5.png differ diff --git a/docs/src/examples/groundstates/bose-hubbard/figure-6.png b/docs/src/examples/groundstates/bose-hubbard/figure-6.png new file mode 100644 index 000000000..9cd7a5d2e Binary files /dev/null and b/docs/src/examples/groundstates/bose-hubbard/figure-6.png differ diff --git a/docs/src/examples/groundstates/bose-hubbard/index.md b/docs/src/examples/groundstates/bose-hubbard/index.md new file mode 100644 index 000000000..b73d62760 --- /dev/null +++ b/docs/src/examples/groundstates/bose-hubbard/index.md @@ -0,0 +1,401 @@ +```@meta +EditURL = "../../../../../examples/groundstates/bose-hubbard/main.jl" +``` + +[![](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/groundstates/bose-hubbard/main.ipynb) +[![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/groundstates/bose-hubbard/main.ipynb) +[![](https://img.shields.io/badge/download-project-orange)](https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/QuantumKitHub/MPSKit.jl/examples/tree/gh-pages/dev/examples/groundstates/bose-hubbard) + +````julia +using Markdown +using TensorKitTensors.BosonOperators: b_min, b_plus_b_min, b_min_b_plus, b_num +using MPSKit, TensorKit +using .ExampleModels +using Plots, LaTeXStrings + + +theme(:wong) +default(fontfamily = "Computer Modern", label = nothing, dpi = 100, framestyle = :box) +```` + +# 1D Bose-Hubbard model + +In this tutorial, we will explore the physics of the one-dimensional Bose–Hubbard model using matrix product states. +For the most part, we replicate the results presented in [**Phys. Rev. B 105, 134502**](https://journals.aps.org/prb/abstract/10.1103/PhysRevB.105.134502), which can be consulted for any statements in this tutorial that are not otherwise cited. +The Hamiltonian under study is defined as follows: + +$$H = -t \sum_{i} (â_i^{†} â_{i+1} + â_{i+1}^{†} â_i) + \frac{U}{2} \sum_i \hat{n}_i(\hat{n}_i - 1) - μ \sum_i \hat{n}_i$$ + +where the bosonic creation and annihilation operators satisfy the canonical commutation relations (CCR): + +$$[â_i, â_j^{†}] = δ_{ij}.$$ + +Each lattice site hosts a local Hilbert space corresponding to bosonic occupation states $|n⟩$, where $(n = 0, 1, 2, …)$. +Since this space is formally infinite-dimensional, numerical simulations typically impose a truncation at some maximum occupation number $(n_{\text{max}})$. +Such a treatment is justified since it can be observed that the simulation results quickly converge with the cutoff if the filling fraction is kept sufficiently low. + +Within this truncated space, the local creation and annihilation operators are represented by finite-dimensional matrices. +For example, with cutoff $n_{\text{max}}$, the annihilation operator takes the form + +```math +â = +\begin{bmatrix} +0 & \sqrt{1} & 0 & 0 & ⋯ & 0 \\ +0 & 0 & \sqrt{2} & 0 & ⋯ & 0 \\ +0 & 0 & 0 & \sqrt{3} & ⋯ & 0 \\ +⋮ & & & ⋱ & ⋱ & ⋮ \\ +0 & 0 & 0 & ⋯ & 0 & \sqrt{n_{\text{max}}} \\ +0 & 0 & 0 & ⋯ & 0 & 0 +\end{bmatrix} +``` + +and the creation operator is simply its Hermitian conjugate, + +```math +â^† = +\begin{bmatrix} +0 & 0 & 0 & ⋯ & 0 & 0 \\ +\sqrt{1} & 0 & 0 & ⋯ & 0 & 0 \\ +0 & \sqrt{2} & 0 & ⋯ & 0 & 0 \\ +⋮ & & ⋱ & ⋱ & & ⋮ \\ +0 & 0 & 0 & ⋯ & 0 & 0 \\ +0 & 0 & 0 & ⋯ & \sqrt{n_{\text{max}}} & 0 +\end{bmatrix} +``` + +The number operator is then given by + +$$\hat{n} = â^† â = \mathrm{diag}(0, 1, 2, …, n_{\text{max}}).$$ + +Before moving on, notice that the Hamiltonian is uniform and translationally invariant. +Typically, such models are studied on a finite chain of $N$ sites with periodic boundary conditions, but this introduces finite-size effects that are rather annoying to deal with. +In contrast, the MPS framework allows us to work directly in the thermodynamic limit, avoiding such artifacts. +We will follow this line of exploration in this tutorial and leave finite systems for another example. + +In order to work in the thermodynamic limit, we will have to create an [`InfiniteMPS`](@ref). +A complete specification of the MPS requires us to define the physical space and the virtual space of the constituent tensors. +At this point is it useful to note that `MPSKit.jl` is powered by [`TensorKit.jl`](https://github.com/QuantumKitHub/TensorKit.jl) under the hood and has some very generic interfaces in order to allow imposing symmetries of all kinds. +As a result, it is sometimes necessary to be a bit more explicit about what we want to do in terms of the vector spaces involved. +In this case, we will not consider any symmetries and simply take the most naive approach of working within the [`Trivial`](@extref TensorKitSectors.Trivial) sector. +The physical space is then `ℂ^(nmax+1)`, and the virtual space is `ℂ^D` where $D$ is some integer chosen to be the bond dimension of the MPS, and `ℂ` is an alias for [`ComplexSpace`](@extref TensorKit.ComplexSpace) (typeset as `\bbC`). +As $D$ is increased, one increases the amount of entanglement, i.e, quantum correlations that can be captured by the state. + +````julia +cutoff, D = 4, 5 +initial_state = InfiniteMPS(ℂ^(cutoff + 1), ℂ^D) +```` + +```` +1-site InfiniteMPS(ComplexF64, TensorKit.ComplexSpace) with maximal dimension 5: +| ⋮ +| ℂ^5 +├─[1]─ ℂ^5 +│ ℂ^5 +| ⋮ + +```` + +This simply initializes a tensor filled with random entries (check out the documentation for other useful constructors). +Next, we need the creation and annihilation operators. +While we could construct them from scratch, here we will use [TensorKitTensors.jl](https://github.com/QuantumKitHub/TensorKitTensors.jl), which provides predefined operators. +In particular, `BosonOperators.b_min` creates the bosonic annihilation operator. + +````julia +a_op = b_min(cutoff = cutoff) # creates a bosonic annihilation operator without any symmetries +display(a_op[]) +display((a_op' * a_op)[]) +```` + +The [] accessor lets us see the underlying array, and indeed the operators are exactly what we require. +The shared [model helpers](https://github.com/QuantumKitHub/MPSKit.jl/blob/main/docs/src/assets/models.jl) define `bose_hubbard_model` using these operators, although we will construct our own variant later on. + +````julia +hamiltonian = bose_hubbard_model(; cutoff = cutoff, U = 1, mu = 0.5, t = 0.2) # Infinite chain by default; pass L = N for an open finite chain +```` + +```` +1-site InfiniteMPOHamiltonian(ComplexF64, TensorKit.ComplexSpace) with maximal dimension 4: +| ⋮ +| (ℂ^1 ⊞ ℂ^2 ⊞ ℂ^1) +┼─[1]─ ℂ^5 +│ (ℂ^1 ⊞ ℂ^2 ⊞ ℂ^1) +| ⋮ + +```` + +This has created the Hamiltonian operator as a [matrix product operator](@ref InfiniteMPOHamiltonian) (MPO) which is a convenient form to use in conjunction with MPS. +Finally, the ground state optimization may be performed with either [`iDMRG`](@ref IDMRG) or [`VUMPS`](@ref). +Both should take similar arguments but it is known that VUMPS is typically more efficient for these systems so we proceed with that. + +````julia +ground_state, _, _ = find_groundstate(initial_state, hamiltonian, VUMPS(tol = 1.0e-6, verbosity = 2, maxiter = 200)) +println("Energy: ", expectation_value(ground_state, hamiltonian)) +```` + +```` +[ Info: VUMPS init: obj = +3.565540128712e-01 err = 5.8200e-01 +[ Info: VUMPS conv 35: obj = -6.757777651150e-01 err = 9.9874991150e-07 time = 6.10 sec +Energy: -0.6757777651149788 + 3.9753559577548215e-17im + +```` + +This automatically runs the algorithm until a certain [error measure](@ref MPSKit.calc_galerkin) falls below the specified tolerance or the maximum iterations is reached. +Let us wrap all this into a convenient function. + +````julia +function get_ground_state(mu, t, cutoff, D; kwargs...) + hamiltonian = bose_hubbard_model(; cutoff = cutoff, U = 1, mu = mu, t = t) + state = InfiniteMPS(ℂ^(cutoff + 1), ℂ^D) + state, _, _ = find_groundstate(state, hamiltonian, VUMPS(; kwargs...)) + + return state +end + +ground_state = get_ground_state(0.5, 0.01, cutoff, D; tol = 1.0e-6, verbosity = 2, maxiter = 500) +```` + +```` +1-site InfiniteMPS(ComplexF64, TensorKit.ComplexSpace) with maximal dimension 5: +| ⋮ +| ℂ^5 +├─[1]─ ℂ^5 +│ ℂ^5 +| ⋮ + +```` + +Now that we have the state, we may compute observables using the [`expectation_value`](@ref) function. +It typically expects a `Pair`, `(i1, i2, .., ik) => op` where `op` is a `TensorMap` or `InfiniteMPO` acting over `k` sites. +In case of the Hamiltonian, it is not necessary to specify the indices as it spans the whole lattice. +We can now plot the correlation function $⟨â^{†}_i â_j⟩$. + +````julia +plot(map(i -> real.(expectation_value(ground_state, (0, i) => a_op' ⊗ a_op)), 1:50), lw = 2, xlabel = "Site index", ylabel = "Correlation function", yscale = :log10) +hline!([abs2(expectation_value(ground_state, (0,) => a_op))], ls = :dash, c = :black) +```` + +![](figure-1.png) + +We see that the correlations drop off exponentially, indicating the existence of a gapped Mott insulating phase. +Let us now shift our parameters to probe other phases. + +````julia +ground_state = get_ground_state(0.5, 0.2, cutoff, D; tol = 1.0e-6, verbosity = 2, maxiter = 500) + +plot(map(i -> real.(expectation_value(ground_state, (0, i) => a_op' ⊗ a_op)), 1:100), lw = 2, xlabel = "Site index", ylabel = "Correlation function", yscale = :log10, xscale = :log10) +hline!([abs2(expectation_value(ground_state, (0,) => a_op))], ls = :dash, c = :black) +```` + +![](figure-2.png) + +In this case, the correlation function drops off algebraically and eventually saturates as $\lim_{i → ∞}⟨â_i^{†} â_j⟩ ≈ ⟨â_i^{†}⟩ ⟨â_j⟩ = |⟨a_i⟩|^2 ≠ 0$. +This is a signature of long-range order and suggests the existence of a Bose-Einstein condensate. +However, this is a bit odd since at zero temperature, the Bose Hubbard model is not expected to break any continuous symmetries ($U(1)$ in this case, corresponding to particle number conservation) due to the [Mermin-Wagner theorem](https://en.wikipedia.org/wiki/Mermin%E2%80%93Wagner_theorem). +The source of this contradiction lies in the fact that the true 1D superfluid ground state is an extended critical phase exhibiting algebraic decay, however, a finite bond-dimension MPS can only capture exponentially decaying correlations. +As a result, the finite bond dimension effectively introduces a length scale into the system in a similar manner as finite-size effects. +We can see this clearly by increasing the bond dimension. +We also see that the correlation length seems to depend algebraically on the bond dimension as expected from finite-entanglement scaling arguments. + +````julia +cutoff = 4 +Ds = 20:5:50 +mu, t = 0.5, 0.2 +states = Vector{InfiniteMPS}(undef, length(Ds)) + +Threads.@threads for idx in eachindex(Ds) + states[idx] = get_ground_state(mu, t, cutoff, Ds[idx]; tol = 1.0e-7, verbosity = 1, maxiter = 500) +end + +npoints = 400 +two_point_correlation = zeros(length(Ds), npoints) +a_op = b_min(cutoff = cutoff) + +Threads.@threads for idx in eachindex(Ds) + two_point_correlation[idx, :] .= real.(expectation_value(states[idx], (1, i) => a_op' ⊗ a_op) for i in 1:npoints) +end + +p = plot( + framestyle = :box, ylabel = "Correlation function " * L"\langle a_i^{\dagger}a_j \rangle", + xlabel = "Distance " * L"|i-j|", xscale = :log10, yscale = :log10, + xticks = ([10, 100], ["10", "100"]), + yticks = ([0.25, 0.5, 1.0], ["0.25", "0.5", "1.0"]) +) + +plot!( + p, 2:npoints, two_point_correlation[:, 2:end]', + lab = "D = " .* string.(permutedims(Ds)), lw = 2 +) + +scatter!( + p, Ds, map(ψ -> correlation_length(ψ; sector = leftunit(ψ)), states), + ylabel = "Correlation length", xlabel = "Bond dimension", + xscale = :log10, yscale = :log10, + inset = bbox(0.2, 0.51, 0.25, 0.25), + subplot = 2, + xticks = (20:10:50, string.(20:10:50)), + yticks = ([50, 100], string.([50, 100])), + xlabelfontsize = 8, + ylabelfontsize = 8, + ylims = [20, 130], + xlims = [15, 60] +) +```` + +![](figure-3.png) + +This shows that any finite bond dimension MPS necessarily breaks the symmetry of the system, forming a Bose-Einstein condensate which introduces erroneous long-distance behaviour of correlation functions. +In case of finite bond dimension, it is thus reasonable to associate the finite expectation value of the field operator to the 'quasicondensate' density of the system which vanishes as $D → ∞$. + +````julia +quasicondensate_density = map(state -> abs2(expectation_value(state, (0,) => a_op)), states) +```` + +```` +7-element Vector{Float64}: + 0.3097427801948516 + 0.2881477638457307 + 0.2702001643897378 + 0.2571272792016688 + 0.2468538581332228 + 0.23539756507406545 + 0.22799654612658096 +```` + +We may now also visualize the momentum distribution function, which is obtained as the Fourier transform of the single-particle density matrix. +Starting from the definition of the momentum occupation operators: + +```math +â_k = \frac{1}{\sqrt{L}} \sum_j e^{-ikj} â_j, \qquad +â_k^† = \frac{1}{\sqrt{L}} \sum_{j'} e^{ikj'} â_{j'}^† +``` + +the momentum distribution is + +```math +⟨\hat{n}_k⟩ = ⟨â_k^† â_k⟩ += \frac{1}{L} \sum_{j',j} e^{ik(j'-j)} ⟨â_{j'}^† â_j⟩. +``` + +For a translationally invariant system, the correlation depends only on the distance $r = j' - j$: + +$$⟨â_{j'}^† â_j⟩ = C(r) = ⟨â_r^† â_0⟩.$$ + +Changing variables ($j' = j + r$) gives + +$$⟨\hat{n}_k⟩ = \frac{1}{L} \sum_j \sum_r e^{ikr} C(r).$$ + +The sum over $j$ yields a factor of $L$, which cancels the prefactor, leading to + +$$⟨\hat{n}_k⟩ = \sum_{r ∈ \mathbb{Z}} e^{ikr} ⟨â_r^† â_0⟩$$ + +However, we know that a finite bond dimension MPS introduces a non-zero quasi-condensate density which would give rise to an $\mathcal{O}(N)$ divergence in the momentum distribution that is not indicative of the true physics of the system. +Since we know this contribution vanishes in the infinite bond dimension limit, we instead work with $⟨â_r^{†} â_0⟩_c = ⟨â_r^{†} â_0⟩ - |⟨â⟩|^2$. + +````julia +ks = range(-0.05, 0.15, 500) +momentum_distribution = map( + ((corr, qc),) -> sum( + 2 .* cos.(ks' .* (2:npoints)) .* (corr[2:end] .- qc), dims = 1 + ) .+ (corr[1] .- qc), + zip( + eachrow(two_point_correlation), + quasicondensate_density + ) +) +momentum_distribution = vcat(momentum_distribution...)' +plot(ks, momentum_distribution, lab = "D = " .* string.(permutedims(Ds)), lw = 1.5, xlabel = "Momentum k", ylabel = L"\langle n_k \rangle", ylim = [0, 50]) +```` + +![](figure-4.png) + +We see that the density seems to peak around $k=0$, this time seemingly becoming more prominent as $D → ∞$ which seems to suggest again that there is a condensate. +However, going by the Penrose-Onsager criterion, the existence of a condensate can be quantified by requiring the leading eigenvalue of the single particle density matrix (i.e, $⟨\hat{n}_{k=0}⟩ = \sum_j ⟨â_j^{†} â_0⟩$) to diverge as $O(N)$ in the thermodynamic limit. +In this case, since the correlations decay as a power law, there is naturally a divergence at low momenta. +But this does not imply the existence of a condensate since the order of divergence is much weaker. +However, this does indicate the remnants of some kind of condensation in the 1D model despite the quantum fluctuations, leading to the practical utility of defining the concept of a quasicondensate where there is still a notion of phase coherence over short distances. + +What this means for us is that, as far as MPS simulations go, we may still utilize the quasicondensate density as an effective order parameter, although it will be less robust as the bond dimension is increased. +Alternatively, we realize that the true phase is characterized as being a superfluid (a concept distinct from Bose-Einstein condensation) and can be identified by a non-zero value of the superfluid stiffness (also known as helicity modulus, $Υ$) as defined by Leggett. +Upon applying a phase twist $Φ$ to the boundaries of the system, a superfluid phase would suffer an increase in energy whereas an insulating phase would not. +In the thermodynamic limit, one could show that the boundary conditions may be considered as periodic and instead uniformly distribute the phase across the chain as $â_i → â_i e^{iΦ/L}$. +Concretely, in the limit of $Φ/L → 0$, we have: + +$$\frac{E[Φ] - E[0]}{L} ≈ \frac{1}{2} Υ(L) \bigg (\frac{Φ}{L}\bigg)^2 + ⋯$$ + +In order to find the ground state under these twisted boundary conditions, we must construct our own variant of the Bose-Hubbard Hamiltonian. +Typically you would want to take a peek at the [model helper source](https://github.com/QuantumKitHub/MPSKit.jl/blob/main/docs/src/assets/models.jl) to see how these models are defined and tweak it as per your needs. +Here we see that applying twisted boundary conditions is equivalent to adding a prefactor of $e^{± iφ}$ in front of the hopping amplitudes. + +````julia +function bose_hubbard_model_twisted_bc( + elt::Type{<:Number} = ComplexF64, symmetry::Type{<:Sector} = Trivial; + cutoff::Integer = 5, t = 1.0, U = 1.0, mu = 0.0, phi = 0 + ) + + a_pm = b_plus_b_min(elt, symmetry; cutoff = cutoff) + a_mp = b_min_b_plus(elt, symmetry; cutoff = cutoff) + N = b_num(elt, symmetry; cutoff = cutoff) + + interaction_term = N * (N - id(domain(N))) + + hopping = -t * (exp(1im * phi) * a_pm + exp(-1im * phi) * a_mp) + onsite = U / 2 * interaction_term - mu * N + return chain_hamiltonian(hopping, onsite) +end + +function superfluid_stiffness_profile(t, mu, D, cutoff, ϵ = 1.0e-4, npoints = 11) + phis = range(-ϵ, ϵ, npoints) + energies = zeros(length(phis)) + + Threads.@threads for idx in eachindex(phis) + hamiltonian_twisted = bose_hubbard_model_twisted_bc(; + cutoff = cutoff, t = t, mu = mu, U = 1, phi = phis[idx] + ) + state = InfiniteMPS(ℂ^(cutoff + 1), ℂ^D) + state_twisted, _, _ = find_groundstate( + state, hamiltonian_twisted, VUMPS(; tol = 1.0e-8, verbosity = 0) + ) + energies[idx] = real(expectation_value(state_twisted, hamiltonian_twisted)) + end + + return plot(phis, energies, lw = 2, xlabel = "Phase twist per site" * L"(\phi)", ylabel = "Ground state energy", title = "t = $t | μ = $mu | D = $D | cutoff = $cutoff") +end + +superfluid_stiffness_profile(0.2, 0.3, 5, 4) # superfluid + +superfluid_stiffness_profile(0.01, 0.3, 5, 4) # mott insulator +```` + +![](figure-5.png) + +Now that we know what phases to expect, we can plot the phase diagram by scanning over a range of parameters. +In general, one could do better by performing a bisection algorithm for each chemical potential to determine the value of the hopping parameter at the transition point, however the 1D Bose-Hubbard model may have two transition points at the same chemical potential which makes this a bit cumbersome to implement robustly. +Furthermore, we stick to using the quasi-condensate density as an order parameter since extracting the superfluid density accurately requires a more robust scheme to compute second derivatives which takes us away from the focus of this tutorial. + +````julia +cutoff, D = 4, 10 +mus = range(0, 0.75, 40) +ts = range(0, 0.3, 40) + +a_op = b_min(cutoff = cutoff) +order_parameters = zeros(length(ts), length(mus)) + +Threads.@threads for (i, j) in collect(Iterators.product(eachindex(mus), eachindex(ts))) + hamiltonian = bose_hubbard_model(; cutoff = cutoff, U = 1, mu = mus[i], t = ts[j]) + init_state = InfiniteMPS(ℂ^(cutoff + 1), ℂ^D) + state, _, _ = find_groundstate(init_state, hamiltonian, VUMPS(; tol = 1.0e-8, verbosity = 0)) + order_parameters[i, j] = abs(expectation_value(state, 0 => a_op)) +end + +heatmap(ts, mus, order_parameters, xlabel = L"t/U", ylabel = L"\mu/U", title = L"\langle \hat{a}_i \rangle") +```` + +![](figure-6.png) + +Although the bond dimension here is quite low, we already see the deformation of the Mott insulator lobes to give way to the well known BKT transition that happens at commensurate density. +One can go further and estimate the critical exponents using finite-entanglement scaling procedures on the correlation functions, but these may now be performed with ease using what we have learnt in this tutorial. + +--- + +*This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* + diff --git a/docs/src/examples/quantum1d/8.bose-hubbard/main.ipynb b/docs/src/examples/groundstates/bose-hubbard/main.ipynb similarity index 60% rename from docs/src/examples/quantum1d/8.bose-hubbard/main.ipynb rename to docs/src/examples/groundstates/bose-hubbard/main.ipynb index 47d1e89ed..35dfe1fe8 100644 --- a/docs/src/examples/quantum1d/8.bose-hubbard/main.ipynb +++ b/docs/src/examples/groundstates/bose-hubbard/main.ipynb @@ -128,82 +128,65 @@ "source": [ "# 1D Bose-Hubbard model\n", "\n", - "In this tutorial, we will explore the physics of the one-dimensional Bose–Hubbard model\n", - "using matrix product states. For the most part, we replicate the results presented in\n", - "[**Phys. Rev. B 105,\n", - "134502**](https://journals.aps.org/prb/abstract/10.1103/PhysRevB.105.134502), which can be\n", - "consulted for any statements in this tutorial that are not otherwise cited. The Hamiltonian\n", - "under study is defined as follows:\n", + "In this tutorial, we will explore the physics of the one-dimensional Bose–Hubbard model using matrix product states.\n", + "For the most part, we replicate the results presented in [**Phys. Rev. B 105, 134502**](https://journals.aps.org/prb/abstract/10.1103/PhysRevB.105.134502), which can be consulted for any statements in this tutorial that are not otherwise cited.\n", + "The Hamiltonian under study is defined as follows:\n", "\n", - "$$H = -t \\sum_{i} (\\hat{a}_i^{\\dagger} \\hat{a}_{i+1} + \\hat{a}_{i+1}^{\\dagger} \\hat{a}_i) + \\frac{U}{2} \\sum_i \\hat{n}_i(\\hat{n}_i - 1) - \\mu \\sum_i \\hat{n}_i$$\n", + "$$H = -t \\sum_{i} (â_i^{†} â_{i+1} + â_{i+1}^{†} â_i) + \\frac{U}{2} \\sum_i \\hat{n}_i(\\hat{n}_i - 1) - μ \\sum_i \\hat{n}_i$$\n", "\n", - "where the bosonic creation and annihilation operators satisfy the canonical commutation\n", - "relations (CCR):\n", + "where the bosonic creation and annihilation operators satisfy the canonical commutation relations (CCR):\n", "\n", - "$$[\\hat{a}_i, \\hat{a}_j^{\\dagger}] = \\delta_{ij}.$$\n", + "$$[â_i, â_j^{†}] = δ_{ij}.$$\n", "\n", - "Each lattice site hosts a local Hilbert space corresponding to bosonic occupation states\n", - "$|n\\rangle$, where $(n = 0, 1, 2, \\ldots)$. Since this space is formally\n", - "infinite-dimensional, numerical simulations typically impose a truncation at some maximum\n", - "occupation number $(n_{\\text{max}})$. Such a treatment is justified since it can be observed\n", - "that the simulation results quickly converge with the cutoff if the filling fraction is kept\n", - "sufficiently low.\n", + "Each lattice site hosts a local Hilbert space corresponding to bosonic occupation states $|n⟩$, where $(n = 0, 1, 2, …)$.\n", + "Since this space is formally infinite-dimensional, numerical simulations typically impose a truncation at some maximum occupation number $(n_{\\text{max}})$.\n", + "Such a treatment is justified since it can be observed that the simulation results quickly converge with the cutoff if the filling fraction is kept sufficiently low.\n", "\n", - "Within this truncated space, the local creation and annihilation operators are represented\n", - "by finite-dimensional matrices. For example, with cutoff $n_{\\text{max}}$, the annihilation\n", - "operator takes the form\n", + "Within this truncated space, the local creation and annihilation operators are represented by finite-dimensional matrices.\n", + "For example, with cutoff $n_{\\text{max}}$, the annihilation operator takes the form\n", "\n", "$$\n", - "\\hat{a} =\n", + "â =\n", "\\begin{bmatrix}\n", - "0 & \\sqrt{1} & 0 & 0 & \\cdots & 0 \\\\\n", - "0 & 0 & \\sqrt{2} & 0 & \\cdots & 0 \\\\\n", - "0 & 0 & 0 & \\sqrt{3} & \\cdots & 0 \\\\\n", - "\\vdots & & & \\ddots & \\ddots & \\vdots \\\\\n", - "0 & 0 & 0 & \\cdots & 0 & \\sqrt{n_{\\text{max}}} \\\\\n", - "0 & 0 & 0 & \\cdots & 0 & 0\n", + "0 & \\sqrt{1} & 0 & 0 & ⋯ & 0 \\\\\n", + "0 & 0 & \\sqrt{2} & 0 & ⋯ & 0 \\\\\n", + "0 & 0 & 0 & \\sqrt{3} & ⋯ & 0 \\\\\n", + "⋮ & & & ⋱ & ⋱ & ⋮ \\\\\n", + "0 & 0 & 0 & ⋯ & 0 & \\sqrt{n_{\\text{max}}} \\\\\n", + "0 & 0 & 0 & ⋯ & 0 & 0\n", "\\end{bmatrix}\n", "$$\n", "\n", "and the creation operator is simply its Hermitian conjugate,\n", "\n", "$$\n", - "\\hat{a}^\\dagger =\n", + "â^† =\n", "\\begin{bmatrix}\n", - "0 & 0 & 0 & \\cdots & 0 & 0 \\\\\n", - "\\sqrt{1} & 0 & 0 & \\cdots & 0 & 0 \\\\\n", - "0 & \\sqrt{2} & 0 & \\cdots & 0 & 0 \\\\\n", - "\\vdots & & \\ddots & \\ddots & & \\vdots \\\\\n", - "0 & 0 & 0 & \\cdots & 0 & 0 \\\\\n", - "0 & 0 & 0 & \\cdots & \\sqrt{n_{\\text{max}}} & 0\n", + "0 & 0 & 0 & ⋯ & 0 & 0 \\\\\n", + "\\sqrt{1} & 0 & 0 & ⋯ & 0 & 0 \\\\\n", + "0 & \\sqrt{2} & 0 & ⋯ & 0 & 0 \\\\\n", + "⋮ & & ⋱ & ⋱ & & ⋮ \\\\\n", + "0 & 0 & 0 & ⋯ & 0 & 0 \\\\\n", + "0 & 0 & 0 & ⋯ & \\sqrt{n_{\\text{max}}} & 0\n", "\\end{bmatrix}\n", "$$\n", "\n", "The number operator is then given by\n", "\n", - "$$\\hat{n} = \\hat{a}^\\dagger \\hat{a} = \\mathrm{diag}(0, 1, 2, \\ldots, n_{\\text{max}}).$$\n", + "$$\\hat{n} = â^† â = \\mathrm{diag}(0, 1, 2, …, n_{\\text{max}}).$$\n", "\n", "Before moving on, notice that the Hamiltonian is uniform and translationally invariant.\n", - "Typically, such models are studied on a finite chain of $N$ sites with periodic boundary\n", - "conditions, but this introduces finite-size effects that are rather annoying to deal with.\n", - "In contrast, the MPS framework allows us to work directly in the thermodynamic limit,\n", - "avoiding such artifacts. We will follow this line of exploration in this tutorial and leave\n", - "finite systems for another example.\n", - "\n", - "In order to work in the thermodynamic limit, we will have to create an\n", - "`InfiniteMPS`. A complete specification of the MPS requires us to define the\n", - "physical space and the virtual space of the constituent tensors. At this point is it useful\n", - "to note that `MPSKit.jl` is powered by\n", - "[`TensorKit.jl`](https://github.com/QuantumKitHub/TensorKit.jl) under the hood and has some\n", - "very generic interfaces in order to allow imposing symmetries of all kinds. As a result, it\n", - "is sometimes necessary to be a bit more explicit about what we want to do in terms of the\n", - "vector spaces involved. In this case, we will not consider any symmetries and simply take\n", - "the most naive approach of working within the `Trivial`\n", - "sector. The physical space is then `ℂ^(nmax+1)`, and the virtual space is `ℂ^D` where $D$ is\n", - "some integer chosen to be the bond dimension of the MPS, and `ℂ` is an alias for\n", - "`ComplexSpace` (typeset as `\\bbC`). As $D$ is increased,\n", - "one increases the amount of entanglement, i.e, quantum correlations that can be captured by\n", - "the state." + "Typically, such models are studied on a finite chain of $N$ sites with periodic boundary conditions, but this introduces finite-size effects that are rather annoying to deal with.\n", + "In contrast, the MPS framework allows us to work directly in the thermodynamic limit, avoiding such artifacts.\n", + "We will follow this line of exploration in this tutorial and leave finite systems for another example.\n", + "\n", + "In order to work in the thermodynamic limit, we will have to create an `InfiniteMPS`.\n", + "A complete specification of the MPS requires us to define the physical space and the virtual space of the constituent tensors.\n", + "At this point is it useful to note that `MPSKit.jl` is powered by [`TensorKit.jl`](https://github.com/QuantumKitHub/TensorKit.jl) under the hood and has some very generic interfaces in order to allow imposing symmetries of all kinds.\n", + "As a result, it is sometimes necessary to be a bit more explicit about what we want to do in terms of the vector spaces involved.\n", + "In this case, we will not consider any symmetries and simply take the most naive approach of working within the `Trivial` sector.\n", + "The physical space is then `ℂ^(nmax+1)`, and the virtual space is `ℂ^D` where $D$ is some integer chosen to be the bond dimension of the MPS, and `ℂ` is an alias for `ComplexSpace` (typeset as `\\bbC`).\n", + "As $D$ is increased, one increases the amount of entanglement, i.e, quantum correlations that can be captured by the state." ] }, { @@ -220,12 +203,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This simply initializes a tensor filled with random entries (check out the documentation for\n", - "other useful constructors). Next, we need the creation and annihilation operators. While we\n", - "could construct them from scratch, here we will use\n", - "[TensorKitTensors.jl](https://github.com/QuantumKitHub/TensorKitTensors.jl), which provides\n", - "predefined operators. In particular, `BosonOperators.b_min` creates the bosonic annihilation\n", - "operator." + "This simply initializes a tensor filled with random entries (check out the documentation for other useful constructors).\n", + "Next, we need the creation and annihilation operators.\n", + "While we could construct them from scratch, here we will use [TensorKitTensors.jl](https://github.com/QuantumKitHub/TensorKitTensors.jl), which provides predefined operators.\n", + "In particular, `BosonOperators.b_min` creates the bosonic annihilation operator." ] }, { @@ -243,10 +224,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The [] accessor lets us see the underlying array, and indeed the operators are exactly what\n", - "we require. The shared [model helpers](https://github.com/QuantumKitHub/MPSKit.jl/blob/main/docs/src/assets/models.jl) define\n", - "`bose_hubbard_model` using these operators, although we will construct our own variant\n", - "later on." + "The [] accessor lets us see the underlying array, and indeed the operators are exactly what we require.\n", + "The shared [model helpers](https://github.com/QuantumKitHub/MPSKit.jl/blob/main/docs/src/assets/models.jl) define `bose_hubbard_model` using these operators, although we will construct our own variant later on." ] }, { @@ -262,11 +241,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This has created the Hamiltonian operator as a [matrix product operator](@ref\n", - "InfiniteMPOHamiltonian) (MPO) which is a convenient form to use in conjunction with MPS.\n", - "Finally, the ground state optimization may be performed with either `iDMRG` or\n", - "`VUMPS`. Both should take similar arguments but it is known that VUMPS is typically\n", - "more efficient for these systems so we proceed with that." + "This has created the Hamiltonian operator as a matrix product operator (MPO) which is a convenient form to use in conjunction with MPS.\n", + "Finally, the ground state optimization may be performed with either `iDMRG` or `VUMPS`.\n", + "Both should take similar arguments but it is known that VUMPS is typically more efficient for these systems so we proceed with that." ] }, { @@ -283,9 +260,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This automatically runs the algorithm until a certain [error measure](@ref\n", - "MPSKit.calc_galerkin) falls below the specified tolerance or the maximum iterations is\n", - "reached. Let us wrap all this into a convenient function." + "This automatically runs the algorithm until a certain error measure falls below the specified tolerance or the maximum iterations is reached.\n", + "Let us wrap all this into a convenient function." ] }, { @@ -309,11 +285,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now that we have the state, we may compute observables using the `expectation_value`\n", - "function. It typically expects a `Pair`, `(i1, i2, .., ik) => op` where `op` is a\n", - "`TensorMap` or `InfiniteMPO` acting over `k` sites. In case of the Hamiltonian, it is not\n", - "necessary to specify the indices as it spans the whole lattice. We can now plot the\n", - "correlation function $\\langle \\hat{a}^{\\dagger}_i \\hat{a}_j\\rangle$." + "Now that we have the state, we may compute observables using the `expectation_value` function.\n", + "It typically expects a `Pair`, `(i1, i2, .., ik) => op` where `op` is a `TensorMap` or `InfiniteMPO` acting over `k` sites.\n", + "In case of the Hamiltonian, it is not necessary to specify the indices as it spans the whole lattice.\n", + "We can now plot the correlation function $⟨â^{†}_i â_j⟩$." ] }, { @@ -330,8 +305,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We see that the correlations drop off exponentially, indicating the existence of a gapped\n", - "Mott insulating phase. Let us now shift our parameters to probe other phases." + "We see that the correlations drop off exponentially, indicating the existence of a gapped Mott insulating phase.\n", + "Let us now shift our parameters to probe other phases." ] }, { @@ -350,20 +325,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In this case, the correlation function drops off algebraically and eventually saturates as\n", - "$\\lim_{i \\to \\infty}\\langle\\hat{a}_i^{\\dagger} \\hat{a}_j\\rangle ≈ \\langle \\hat{a}_i^{\\dagger}\\rangle \\langle \\hat{a}_j \\rangle = |\\langle a_i \\rangle|^2 \\neq 0$.\n", - "This is a signature of long-range order and suggests the existence of a Bose-Einstein\n", - "condensate. However, this is a bit odd since at zero temperature, the Bose Hubbard model is\n", - "not expected to break any continuous symmetries ($U(1)$ in this case, corresponding to\n", - "particle number conservation) due to the\n", - "[Mermin-Wagner theorem](https://en.wikipedia.org/wiki/Mermin%E2%80%93Wagner_theorem). The\n", - "source of this contradiction lies in the fact that the true 1D superfluid ground state is an\n", - "extended critical phase exhibiting algebraic decay, however, a finite bond-dimension MPS can\n", - "only capture exponentially decaying correlations. As a result, the finite bond dimension\n", - "effectively introduces a length scale into the system in a similar manner as finite-size\n", - "effects. We can see this clearly by increasing the bond dimension. We also see that the\n", - "correlation length seems to depend algebraically on the bond dimension as expected from\n", - "finite-entanglement scaling arguments." + "In this case, the correlation function drops off algebraically and eventually saturates as $\\lim_{i → ∞}⟨â_i^{†} â_j⟩ ≈ ⟨â_i^{†}⟩ ⟨â_j⟩ = |⟨a_i⟩|^2 ≠ 0$.\n", + "This is a signature of long-range order and suggests the existence of a Bose-Einstein condensate.\n", + "However, this is a bit odd since at zero temperature, the Bose Hubbard model is not expected to break any continuous symmetries ($U(1)$ in this case, corresponding to particle number conservation) due to the [Mermin-Wagner theorem](https://en.wikipedia.org/wiki/Mermin%E2%80%93Wagner_theorem).\n", + "The source of this contradiction lies in the fact that the true 1D superfluid ground state is an extended critical phase exhibiting algebraic decay, however, a finite bond-dimension MPS can only capture exponentially decaying correlations.\n", + "As a result, the finite bond dimension effectively introduces a length scale into the system in a similar manner as finite-size effects.\n", + "We can see this clearly by increasing the bond dimension.\n", + "We also see that the correlation length seems to depend algebraically on the bond dimension as expected from finite-entanglement scaling arguments." ] }, { @@ -420,11 +388,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This shows that any finite bond dimension MPS necessarily breaks the symmetry of the system,\n", - "forming a Bose-Einstein condensate which introduces erroneous long-distance behaviour of\n", - "correlation functions. In case of finite bond dimension, it is thus reasonable to associate\n", - "the finite expectation value of the field operator to the 'quasicondensate' density of the\n", - "system which vanishes as $D \\to \\infty$." + "This shows that any finite bond dimension MPS necessarily breaks the symmetry of the system, forming a Bose-Einstein condensate which introduces erroneous long-distance behaviour of correlation functions.\n", + "In case of finite bond dimension, it is thus reasonable to associate the finite expectation value of the field operator to the 'quasicondensate' density of the system which vanishes as $D → ∞$." ] }, { @@ -440,40 +405,35 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We may now also visualize the momentum distribution function, which is obtained as the\n", - "Fourier transform of the single-particle density matrix. Starting from the definition of the\n", - "momentum occupation operators:\n", + "We may now also visualize the momentum distribution function, which is obtained as the Fourier transform of the single-particle density matrix.\n", + "Starting from the definition of the momentum occupation operators:\n", "\n", "$$\n", - "\\hat{a}_k = \\frac{1}{\\sqrt{L}} \\sum_j e^{-ikj} \\hat{a}_j, \\qquad\n", - "\\hat{a}_k^\\dagger = \\frac{1}{\\sqrt{L}} \\sum_{j'} e^{ikj'} \\hat{a}_{j'}^\\dagger\n", + "â_k = \\frac{1}{\\sqrt{L}} \\sum_j e^{-ikj} â_j, \\qquad\n", + "â_k^† = \\frac{1}{\\sqrt{L}} \\sum_{j'} e^{ikj'} â_{j'}^†\n", "$$\n", "\n", "the momentum distribution is\n", "\n", "$$\n", - "\\langle \\hat{n}_k \\rangle = \\langle \\hat{a}_k^\\dagger \\hat{a}_k \\rangle\n", - "= \\frac{1}{L} \\sum_{j',j} e^{ik(j'-j)} \\langle \\hat{a}_{j'}^\\dagger \\hat{a}_j \\rangle.\n", + "⟨\\hat{n}_k⟩ = ⟨â_k^† â_k⟩\n", + "= \\frac{1}{L} \\sum_{j',j} e^{ik(j'-j)} ⟨â_{j'}^† â_j⟩.\n", "$$\n", "\n", - "For a translationally invariant system, the correlation depends only on the distance\n", - "$r = j' - j$:\n", + "For a translationally invariant system, the correlation depends only on the distance $r = j' - j$:\n", "\n", - "$$\\langle \\hat{a}_{j'}^\\dagger \\hat{a}_j \\rangle = C(r) = \\langle \\hat{a}_r^\\dagger \\hat{a}_0 \\rangle.$$\n", + "$$⟨â_{j'}^† â_j⟩ = C(r) = ⟨â_r^† â_0⟩.$$\n", "\n", "Changing variables ($j' = j + r$) gives\n", "\n", - "$$\\langle \\hat{n}_k \\rangle = \\frac{1}{L} \\sum_j \\sum_r e^{ikr} C(r).$$\n", + "$$⟨\\hat{n}_k⟩ = \\frac{1}{L} \\sum_j \\sum_r e^{ikr} C(r).$$\n", "\n", "The sum over $j$ yields a factor of $L$, which cancels the prefactor, leading to\n", "\n", - "$$\\langle \\hat{n}_k \\rangle = \\sum_{r \\in \\mathbb{Z}} e^{ikr} \\langle \\hat{a}_r^\\dagger \\hat{a}_0 \\rangle$$\n", + "$$⟨\\hat{n}_k⟩ = \\sum_{r ∈ \\mathbb{Z}} e^{ikr} ⟨â_r^† â_0⟩$$\n", "\n", - "However, we know that a finite bond dimension MPS introduces a non-zero quasi-condensate\n", - "density which would give rise to an $\\mathcal{O}(N)$ divergence in the momentum distribution\n", - "that is not indicative of the true physics of the system. Since we know this contribution\n", - "vanishes in the infinite bond dimension limit, we instead work with\n", - "$\\langle \\hat{a}_r^{\\dagger} \\hat{a}_0 \\rangle_c = \\langle \\hat{a}_r^{\\dagger} \\hat{a}_0 \\rangle - |\\langle \\hat{a}\\rangle|^2$." + "However, we know that a finite bond dimension MPS introduces a non-zero quasi-condensate density which would give rise to an $\\mathcal{O}(N)$ divergence in the momentum distribution that is not indicative of the true physics of the system.\n", + "Since we know this contribution vanishes in the infinite bond dimension limit, we instead work with $⟨â_r^{†} â_0⟩_c = ⟨â_r^{†} â_0⟩ - |⟨â⟩|^2$." ] }, { @@ -500,38 +460,23 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We see that the density seems to peak around $k=0$, this time seemingly becoming more\n", - "prominent as $D \\to \\infty$ which seems to suggest again that there is a condensate.\n", - "However, going by the Penrose-Onsager criterion, the existence of a condensate can be\n", - "quantified by requiring the leading eigenvalue of the single particle density matrix (i.e,\n", - "$\\langle \\hat{n}_{k=0}\\rangle = \\sum_j \\langle \\hat{a}_j^{\\dagger} \\hat{a}_0\\rangle$) to\n", - "diverge as $O(N)$ in the thermodynamic limit. In this case, since the correlations decay as\n", - "a power law, there is naturally a divergence at low momenta. But this does not imply the\n", - "existence of a condensate since the order of divergence is much weaker. However, this does\n", - "indicate the remnants of some kind of condensation in the 1D model despite the quantum\n", - "fluctuations, leading to the practical utility of defining the concept of a quasicondensate\n", - "where there is still a notion of phase coherence over short distances.\n", - "\n", - "What this means for us is that, as far as MPS simulations go, we may still utilize the\n", - "quasicondensate density as an effective order parameter, although it will be less robust as\n", - "the bond dimension is increased. Alternatively, we realize that the true phase is\n", - "characterized as being a superfluid (a concept distinct from Bose-Einstein condensation) and\n", - "can be identified by a non-zero value of the superfluid stiffness (also known as helicity\n", - "modulus, $\\Upsilon$) as defined by Leggett. Upon applying a phase twist $\\Phi$ to the\n", - "boundaries of the system, a superfluid phase would suffer an increase in energy whereas an\n", - "insulating phase would not. In the thermodynamic limit, one could show that the boundary\n", - "conditions may be considered as periodic and instead uniformly distribute the phase across\n", - "the chain as $\\hat{a}_i \\to \\hat{a}_i e^{i\\Phi/L}$. Concretely, in the limit of\n", - "$\\Phi/L \\to 0$, we have:\n", - "\n", - "$$\\frac{E[\\Phi] - E[0]}{L} \\approx \\frac{1}{2} \\Upsilon(L) \\bigg (\\frac{\\Phi}{L}\\bigg)^2 + \\cdots$$\n", - "\n", - "In order to find the ground state under these twisted boundary conditions, we must construct\n", - "our own variant of the Bose-Hubbard Hamiltonian. Typically you would want to take a peek at\n", - "the\n", - "[model helper source](https://github.com/QuantumKitHub/MPSKit.jl/blob/main/docs/src/assets/models.jl) to see how these models are defined and tweak it as per your needs.\n", - "Here we see that applying twisted boundary conditions is equivalent to adding a prefactor of\n", - "$e^{\\pm i\\phi}$ in front of the hopping amplitudes." + "We see that the density seems to peak around $k=0$, this time seemingly becoming more prominent as $D → ∞$ which seems to suggest again that there is a condensate.\n", + "However, going by the Penrose-Onsager criterion, the existence of a condensate can be quantified by requiring the leading eigenvalue of the single particle density matrix (i.e, $⟨\\hat{n}_{k=0}⟩ = \\sum_j ⟨â_j^{†} â_0⟩$) to diverge as $O(N)$ in the thermodynamic limit.\n", + "In this case, since the correlations decay as a power law, there is naturally a divergence at low momenta.\n", + "But this does not imply the existence of a condensate since the order of divergence is much weaker.\n", + "However, this does indicate the remnants of some kind of condensation in the 1D model despite the quantum fluctuations, leading to the practical utility of defining the concept of a quasicondensate where there is still a notion of phase coherence over short distances.\n", + "\n", + "What this means for us is that, as far as MPS simulations go, we may still utilize the quasicondensate density as an effective order parameter, although it will be less robust as the bond dimension is increased.\n", + "Alternatively, we realize that the true phase is characterized as being a superfluid (a concept distinct from Bose-Einstein condensation) and can be identified by a non-zero value of the superfluid stiffness (also known as helicity modulus, $Υ$) as defined by Leggett.\n", + "Upon applying a phase twist $Φ$ to the boundaries of the system, a superfluid phase would suffer an increase in energy whereas an insulating phase would not.\n", + "In the thermodynamic limit, one could show that the boundary conditions may be considered as periodic and instead uniformly distribute the phase across the chain as $â_i → â_i e^{iΦ/L}$.\n", + "Concretely, in the limit of $Φ/L → 0$, we have:\n", + "\n", + "$$\\frac{E[Φ] - E[0]}{L} ≈ \\frac{1}{2} Υ(L) \\bigg (\\frac{Φ}{L}\\bigg)^2 + ⋯$$\n", + "\n", + "In order to find the ground state under these twisted boundary conditions, we must construct our own variant of the Bose-Hubbard Hamiltonian.\n", + "Typically you would want to take a peek at the [model helper source](https://github.com/QuantumKitHub/MPSKit.jl/blob/main/docs/src/assets/models.jl) to see how these models are defined and tweak it as per your needs.\n", + "Here we see that applying twisted boundary conditions is equivalent to adding a prefactor of $e^{± iφ}$ in front of the hopping amplitudes." ] }, { @@ -583,14 +528,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now that we know what phases to expect, we can plot the phase diagram by scanning over a\n", - "range of parameters. In general, one could do better by performing a bisection algorithm for\n", - "each chemical potential to determine the value of the hopping parameter at the transition\n", - "point, however the 1D Bose-Hubbard model may have two transition points at the same chemical\n", - "potential which makes this a bit cumbersome to implement robustly. Furthermore, we stick to\n", - "using the quasi-condensate density as an order parameter since extracting the superfluid\n", - "density accurately requires a more robust scheme to compute second derivatives which takes\n", - "us away from the focus of this tutorial." + "Now that we know what phases to expect, we can plot the phase diagram by scanning over a range of parameters.\n", + "In general, one could do better by performing a bisection algorithm for each chemical potential to determine the value of the hopping parameter at the transition point, however the 1D Bose-Hubbard model may have two transition points at the same chemical potential which makes this a bit cumbersome to implement robustly.\n", + "Furthermore, we stick to using the quasi-condensate density as an order parameter since extracting the superfluid density accurately requires a more robust scheme to compute second derivatives which takes us away from the focus of this tutorial." ] }, { @@ -620,11 +560,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Although the bond dimension here is quite low, we already see the deformation of the Mott\n", - "insulator lobes to give way to the well known BKT transition that happens at commensurate\n", - "density. One can go further and estimate the critical exponents using finite-entanglement\n", - "scaling procedures on the correlation functions, but these may now be performed with ease\n", - "using what we have learnt in this tutorial." + "Although the bond dimension here is quite low, we already see the deformation of the Mott insulator lobes to give way to the well known BKT transition that happens at commensurate density.\n", + "One can go further and estimate the critical exponents using finite-entanglement scaling procedures on the correlation functions, but these may now be performed with ease using what we have learnt in this tutorial." ] }, { diff --git a/docs/src/examples/groundstates/haldane-spt/figure-1.png b/docs/src/examples/groundstates/haldane-spt/figure-1.png new file mode 100644 index 000000000..9afbbea19 Binary files /dev/null and b/docs/src/examples/groundstates/haldane-spt/figure-1.png differ diff --git a/docs/src/examples/groundstates/haldane-spt/figure-2.png b/docs/src/examples/groundstates/haldane-spt/figure-2.png new file mode 100644 index 000000000..97219add5 Binary files /dev/null and b/docs/src/examples/groundstates/haldane-spt/figure-2.png differ diff --git a/docs/src/examples/groundstates/haldane-spt/figure-3.png b/docs/src/examples/groundstates/haldane-spt/figure-3.png new file mode 100644 index 000000000..42c179c9f Binary files /dev/null and b/docs/src/examples/groundstates/haldane-spt/figure-3.png differ diff --git a/docs/src/examples/groundstates/haldane-spt/index.md b/docs/src/examples/groundstates/haldane-spt/index.md new file mode 100644 index 000000000..97d3cae9d --- /dev/null +++ b/docs/src/examples/groundstates/haldane-spt/index.md @@ -0,0 +1,177 @@ +```@meta +EditURL = "../../../../../examples/groundstates/haldane-spt/main.jl" +``` + +[![](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/groundstates/haldane-spt/main.ipynb) +[![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/groundstates/haldane-spt/main.ipynb) +[![](https://img.shields.io/badge/download-project-orange)](https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/QuantumKitHub/MPSKit.jl/examples/tree/gh-pages/dev/examples/groundstates/haldane-spt) + +# Spin 1 Heisenberg model + +The quantum Heisenberg model is a model often used in the study of critical points and phase transitions of magnetic systems, in which the spins are treated quantum mechanically. +It models magnetic interactions between neighbouring spins through the so-called Heisenberg interaction term, which causes the spins to either align ($J > 0$) or anti-align ($J < 0$), thus modeling a (anti-) ferromagnetic system. +Here, we will focus on the case of $S = 1$, with anti-ferromagnetic interactions. + +```math +H = -J \sum_{⟨i, j⟩} \vec{S}_i · \vec{S}_j +``` + +Importantly, the Hamiltonian of the isotropic model is invariant under $SU(2)$ rotations, which can be exploited to increase efficiency, as well as interpretability of the MPS simulations. +To see this, we can make use of the following derivation for the interaction term: + +```math +(\vec{S}_i + \vec{S}_j)^2 = \vec{S}_i^2 + 2 \vec{S}_i · \vec{S}_j + \vec{S}_j^2 +⟹ \vec{S}_i · \vec{S}_j = \frac{1}{2} \left( (\vec{S}_i + \vec{S}_j)^2 - \vec{S}_i^2 - \vec{S}_j^2 \right) +``` + +Here, we recognize the quadratic [Casimir element](https://en.wikipedia.org/wiki/Casimir_element) $\vec{S}^2$, which commutes with the elements of $SU(2)$. +Consequently, the Hamiltonian also commutes with all elements of $SU(2)$. + +````julia +using TensorKit +using MPSKit +using Plots + +casimir(s::SU2Irrep) = s.j * (s.j + 1) + +function heisenberg_hamiltonian(; J = -1.0) + s = SU2Irrep(1) + ℋ = SU2Space(1 => 1) + SS = zeros(ComplexF64, ℋ ⊗ ℋ ← ℋ ⊗ ℋ) + for (S, data) in blocks(SS) + data .= -0.5J * (casimir(S) - casimir(s) - casimir(s)) + end + return InfiniteMPOHamiltonian(SS) +end +H = heisenberg_hamiltonian() +```` + +```` +1-site InfiniteMPOHamiltonian(ComplexF64, Rep[SU₂]) with maximal dimension 5: +| ⋮ +| ((0=>1) ⊞ (1=>1) ⊞ (0=>1)) +┼─[1]─ (1 => 1) +│ ((0=>1) ⊞ (1=>1) ⊞ (0=>1)) +| ⋮ + +```` + +## Symmetry-Protected Topological Order + +The representations of $SU(2)$ possess additional structure, known as a $\mathbb{Z}_2$-grading. +This means, that they can be partitioned in integer $(+)$ and half-integer $(-)$ spins, and the fusion rules will respect this grading. +In other words, the following table holds: + +| $s_1$ | $s_2$ | $s_1 ⊗ s_2$ | +| --- | --- | --- | +| $+$ | $+$ | $+$ | +| $+$ | $-$ | $-$ | +| $-$ | $+$ | $-$ | +| $-$ | $-$ | $+$ | + +This has important consequences for the MPS representation of an $SU(2)$-symmetric state. +If the physical spin consists of only integer representations, this means that the left and right virtual spaces of the MPS tensor belong to the same grading, i.e. are either both integer, or both half-integer. +Thus, naively constructing a MPS tensor which contains spins from both classes, will necessarily be the direct sum of the two, which yields a non-injective MPS. + +```math +|ψ⟩ = |ψ_+⟩ ⊕ |ψ_-⟩ +``` + +Because of this direct sum, many of the usual MPS algorithms will fail, as they typically cannot deal with non-injective MPS. +The resulting MPS will have multiple values of the transfer matrix spectrum that have a magnitude close to 1, which is a clear sign of a non-injective MPS. + +````julia +ℋ = SU2Space(1 => 1) +V_wrong = SU2Space(0 => 8, 1 // 2 => 8, 1 => 3, 3 // 2 => 3) +ψ = InfiniteMPS(ℋ, V_wrong) +ψ, environments, info = find_groundstate(ψ, H, VUMPS(; maxiter = 10)) +sectors = SU2Irrep[0, 1 // 2, 1, 3 // 2] +transferplot(ψ; sectors, title = "Transfer matrix spectrum", legend = :outertop) +```` + +![](figure-1.png) + +Nevertheless, using the symmetry, this can be remedied rather easily, by imposing the ground state to belong to a single class, and comparing the results. +We can readily obtain 3 different criteria for determining the SPT phase of the ground state. + +Firstly, we can compare variational energies for states of similar bond dimensions. +As we expect the state of the wrong SPT phase to have to expend some of its expressiveness in correcting the SPT, it should have a harder time reaching lower energies. + +Secondly, when inspecting the spectrum of the transfer matrix, we should see that the wrong SPT phase has a dominant value that is not in the trivial sector, which leads to a non-injective MPS. + +Finally, the entanglement spectrum of the wrong SPT phase will show degeneracies of all singular values, which can again be attributed to an attempt to mimic the spectrum of the right SPT phase. + +````julia +V_plus = SU2Space(0 => 10, 1 => 5, 2 => 3) +ψ_plus = InfiniteMPS(ℋ, V_plus) +ψ_plus, = find_groundstate(ψ_plus, H, VUMPS(; maxiter = 100)) +E_plus = expectation_value(ψ_plus, H) +```` + +```` +-1.4014193313393006 - 2.5835641861230988e-17im +```` + +````julia +V_minus = SU2Space(1 // 2 => 10, 3 // 2 => 5, 5 // 2 => 3) +ψ_minus = InfiniteMPS(ℋ, V_minus) +ψ_minus, = find_groundstate(ψ_minus, H, VUMPS(; maxiter = 100)) +E_minus = expectation_value(ψ_minus, H) +```` + +```` +-1.4014839739630836 + 1.5092213828325467e-16im +```` + +````julia +transferp_plus = transferplot( + ψ_plus; + sectors = SU2Irrep[0, 1, 2], title = "ψ_plus", legend = :outertop +) +transferp_minus = transferplot( + ψ_minus; + sectors = SU2Irrep[0, 1, 2], title = "ψ_minus", legend = :outertop +) +plot(transferp_plus, transferp_minus; layout = (1, 2), size = (800, 400)) +```` + +![](figure-2.png) + +````julia +entanglementp_plus = entanglementplot(ψ_plus; title = "ψ_plus", legend = :outertop) +entanglementp_minus = entanglementplot(ψ_minus; title = "ψ_minus", legend = :outertop) +plot(entanglementp_plus, entanglementp_minus; layout = (1, 2), size = (800, 400)) +```` + +![](figure-3.png) + +As we can see, the ground state can be found in the non-trivial SPT phase, $|ψ_-⟩$. +We can obtain an intuitive understanding of $|ψ_+⟩$ by considering the following diagram. +If we denote the MPS tensors that make up the ground state as $A_-$, we can construct a state in the trivial SPT phase that approximates the ground state as follows: + +```@raw html +SPT tensors +``` + +In other words, we can factorize a purely virtual isomorphism of $S = 1/2$ in order to obtain the ground state. +This then also explains the degeneracies in the entanglement spectrum as well as in the transfer matrix spectrum. +Finally, we can further confirm this intuition by looking at the entanglement entropy of the ground state. +As we can see, the entanglement entropy of the state in the wrong SPT phase is exactly $log(2)$ higher than the one in the right SPT phase, which is exactly what we would expect from the diagram above. + +````julia +S_minus = sum(real, entropy(ψ_minus)) +S_plus = sum(real, entropy(ψ_plus)) +println("S_minus + log(2) = $(S_minus + log(2))") +println("S_plus = $S_plus") +```` + +```` +S_minus + log(2) = 1.5486227235411327 +S_plus = 1.5450323530595034 + +```` + +--- + +*This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* + diff --git a/docs/src/examples/quantum1d/5.haldane-spt/main.ipynb b/docs/src/examples/groundstates/haldane-spt/main.ipynb similarity index 54% rename from docs/src/examples/quantum1d/5.haldane-spt/main.ipynb rename to docs/src/examples/groundstates/haldane-spt/main.ipynb index 3f0c3574d..3f2a918be 100644 --- a/docs/src/examples/quantum1d/5.haldane-spt/main.ipynb +++ b/docs/src/examples/groundstates/haldane-spt/main.ipynb @@ -6,31 +6,24 @@ "source": [ "# Spin 1 Heisenberg model\n", "\n", - "The quantum Heisenberg model is a model often used in the study of critical points and phase\n", - "transitions of magnetic systems, in which the spins are treated quantum mechanically. It\n", - "models magnetic interactions between neighbouring spins through the so-called Heisenberg\n", - "interaction term, which causes the spins to either align ($J > 0$) or anti-align ($J < 0$),\n", - "thus modeling a (anti-) ferromagnetic system. Here, we will focus on the case of $S = 1$,\n", - "with anti-ferromagnetic interactions.\n", + "The quantum Heisenberg model is a model often used in the study of critical points and phase transitions of magnetic systems, in which the spins are treated quantum mechanically.\n", + "It models magnetic interactions between neighbouring spins through the so-called Heisenberg interaction term, which causes the spins to either align ($J > 0$) or anti-align ($J < 0$), thus modeling a (anti-) ferromagnetic system.\n", + "Here, we will focus on the case of $S = 1$, with anti-ferromagnetic interactions.\n", "\n", "$$\n", - "H = -J \\sum_{\\langle i, j \\rangle} \\vec{S}_i \\cdot \\vec{S}_j\n", + "H = -J \\sum_{⟨i, j⟩} \\vec{S}_i · \\vec{S}_j\n", "$$\n", "\n", - "Importantly, the Hamiltonian of the isotropic model is invariant under $SU(2)$ rotations,\n", - "which can be exploited to increase efficiency, as well as interpretability of the MPS\n", - "simulations. To see this, we can make use of the following derivation for the interaction\n", - "term:\n", + "Importantly, the Hamiltonian of the isotropic model is invariant under $SU(2)$ rotations, which can be exploited to increase efficiency, as well as interpretability of the MPS simulations.\n", + "To see this, we can make use of the following derivation for the interaction term:\n", "\n", "$$\n", - "(\\vec{S}_i + \\vec{S}_j)^2 = \\vec{S}_i^2 + 2 \\vec{S}_i \\cdot \\vec{S}_j + \\vec{S}_j^2\n", - "\\implies \\vec{S}_i \\cdot \\vec{S}_j = \\frac{1}{2} \\left( (\\vec{S}_i + \\vec{S}_j)^2 - \\vec{S}_i^2 - \\vec{S}_j^2 \\right)\n", + "(\\vec{S}_i + \\vec{S}_j)^2 = \\vec{S}_i^2 + 2 \\vec{S}_i · \\vec{S}_j + \\vec{S}_j^2\n", + "⟹ \\vec{S}_i · \\vec{S}_j = \\frac{1}{2} \\left( (\\vec{S}_i + \\vec{S}_j)^2 - \\vec{S}_i^2 - \\vec{S}_j^2 \\right)\n", "$$\n", "\n", - "Here, we recognize the quadratic\n", - "[Casimir element](https://en.wikipedia.org/wiki/Casimir_element) $\\vec{S}^2$, which commutes\n", - "with the elements of $SU(2)$. Consequently, the Hamiltonian also commutes with all elements\n", - "of $SU(2)$." + "Here, we recognize the quadratic [Casimir element](https://en.wikipedia.org/wiki/Casimir_element) $\\vec{S}^2$, which commutes with the elements of $SU(2)$.\n", + "Consequently, the Hamiltonian also commutes with all elements of $SU(2)$." ] }, { @@ -63,33 +56,27 @@ "source": [ "## Symmetry-Protected Topological Order\n", "\n", - "The representations of $SU(2)$ possess additional structure, known as a\n", - "$\\mathbb{Z}_2$-grading. This means, that they can be partitioned in integer $(+)$ and\n", - "half-integer $(-)$ spins, and the fusion rules will respect this grading. In other words,\n", - "the following table holds:\n", + "The representations of $SU(2)$ possess additional structure, known as a $\\mathbb{Z}_2$-grading.\n", + "This means, that they can be partitioned in integer $(+)$ and half-integer $(-)$ spins, and the fusion rules will respect this grading.\n", + "In other words, the following table holds:\n", "\n", - "| $s_1$ | $s_2$ | $s_1 \\otimes s_2$ |\n", + "| $s_1$ | $s_2$ | $s_1 ⊗ s_2$ |\n", "| --- | --- | --- |\n", "| $+$ | $+$ | $+$ |\n", "| $+$ | $-$ | $-$ |\n", "| $-$ | $+$ | $-$ |\n", "| $-$ | $-$ | $+$ |\n", "\n", - "This has important consequences for the MPS representation of an $SU(2)$-symmetric state. If\n", - "the physical spin consists of only integer representations, this means that the left and\n", - "right virtual spaces of the MPS tensor belong to the same grading, i.e. are either both\n", - "integer, or both half-integer. Thus, naively constructing a MPS tensor which contains spins\n", - "from both classes, will necessarily be the direct sum of the two, which yields a\n", - "non-injective MPS.\n", + "This has important consequences for the MPS representation of an $SU(2)$-symmetric state.\n", + "If the physical spin consists of only integer representations, this means that the left and right virtual spaces of the MPS tensor belong to the same grading, i.e. are either both integer, or both half-integer.\n", + "Thus, naively constructing a MPS tensor which contains spins from both classes, will necessarily be the direct sum of the two, which yields a non-injective MPS.\n", "\n", "$$\n", - "\\ket{\\psi} = \\ket{\\psi_+} \\oplus \\ket{\\psi_-}\n", + "|ψ⟩ = |ψ_+⟩ ⊕ |ψ_-⟩\n", "$$\n", "\n", - "Because of this direct sum, many of the usual MPS algorithms will fail, as they typically\n", - "cannot deal with non-injective MPS. The resulting MPS will have multiple values of the\n", - "transfer matrix spectrum that have a magnitude close to 1, which is a clear sign of a\n", - "non-injective MPS." + "Because of this direct sum, many of the usual MPS algorithms will fail, as they typically cannot deal with non-injective MPS.\n", + "The resulting MPS will have multiple values of the transfer matrix spectrum that have a magnitude close to 1, which is a clear sign of a non-injective MPS." ] }, { @@ -110,21 +97,15 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Nevertheless, using the symmetry, this can be remedied rather easily, by imposing the\n", - "ground state to belong to a single class, and comparing the results. We can readily obtain 3\n", - "different criteria for determining the SPT phase of the ground state.\n", + "Nevertheless, using the symmetry, this can be remedied rather easily, by imposing the ground state to belong to a single class, and comparing the results.\n", + "We can readily obtain 3 different criteria for determining the SPT phase of the ground state.\n", "\n", - "Firstly, we can compare variational energies for states of similar bond dimensions. As we\n", - "expect the state of the wrong SPT phase to have to expend some of its expressiveness in\n", - "correcting the SPT, it should have a harder time reaching lower energies.\n", + "Firstly, we can compare variational energies for states of similar bond dimensions.\n", + "As we expect the state of the wrong SPT phase to have to expend some of its expressiveness in correcting the SPT, it should have a harder time reaching lower energies.\n", "\n", - "Secondly, when inspecting the spectrum of the transfer matrix, we should see that the wrong\n", - "SPT phase has a dominant value that is not in the trivial sector, which leads to a\n", - "non-injective MPS.\n", + "Secondly, when inspecting the spectrum of the transfer matrix, we should see that the wrong SPT phase has a dominant value that is not in the trivial sector, which leads to a non-injective MPS.\n", "\n", - "Finally, the entanglement spectrum of the wrong SPT phase will show degeneracies of all\n", - "singular values, which can again be attributed to an attempt to mimic the spectrum of the\n", - "right SPT phase." + "Finally, the entanglement spectrum of the wrong SPT phase will show degeneracies of all singular values, which can again be attributed to an attempt to mimic the spectrum of the right SPT phase." ] }, { @@ -159,21 +140,18 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "As we can see, the ground state can be found in the non-trivial SPT phase, $\\ket{\\psi_-}$. We\n", - "can obtain an intuitive understanding of $\\ket{\\psi_+}$ by considering the following\n", - "diagram. If we denote the MPS tensors that make up the ground state as $A_-$, we can\n", - "construct a state in the trivial SPT phase that approximates the ground state as follows:\n", + "As we can see, the ground state can be found in the non-trivial SPT phase, $|ψ_-⟩$.\n", + "We can obtain an intuitive understanding of $|ψ_+⟩$ by considering the following diagram.\n", + "If we denote the MPS tensors that make up the ground state as $A_-$, we can construct a state in the trivial SPT phase that approximates the ground state as follows:\n", "\n", "\n", "\"SPT\n", "\n", "\n", - "In other words, we can factorize a purely virtual isomorphism of $S = 1/2$ in order to\n", - "obtain the ground state. This then also explains the degeneracies in the entanglement\n", - "spectrum as well as in the transfer matrix spectrum. Finally, we can further confirm this\n", - "intuition by looking at the entanglement entropy of the ground state. As we can see, the\n", - "entanglement entropy of the state in the wrong SPT phase is exactly $log(2)$ higher than the\n", - "one in the right SPT phase, which is exactly what we would expect from the diagram above." + "In other words, we can factorize a purely virtual isomorphism of $S = 1/2$ in order to obtain the ground state.\n", + "This then also explains the degeneracies in the entanglement spectrum as well as in the transfer matrix spectrum.\n", + "Finally, we can further confirm this intuition by looking at the entanglement entropy of the ground state.\n", + "As we can see, the entanglement entropy of the state in the wrong SPT phase is exactly $log(2)$ higher than the one in the right SPT phase, which is exactly what we would expect from the diagram above." ] }, { diff --git a/docs/src/examples/quantum1d/5.haldane-spt/spt-tensors.svg b/docs/src/examples/groundstates/haldane-spt/spt-tensors.svg similarity index 100% rename from docs/src/examples/quantum1d/5.haldane-spt/spt-tensors.svg rename to docs/src/examples/groundstates/haldane-spt/spt-tensors.svg diff --git a/docs/src/examples/groundstates/hubbard/figure-1.png b/docs/src/examples/groundstates/hubbard/figure-1.png new file mode 100644 index 000000000..1689abf2b Binary files /dev/null and b/docs/src/examples/groundstates/hubbard/figure-1.png differ diff --git a/docs/src/examples/groundstates/hubbard/figure-2.png b/docs/src/examples/groundstates/hubbard/figure-2.png new file mode 100644 index 000000000..49b5daff9 Binary files /dev/null and b/docs/src/examples/groundstates/hubbard/figure-2.png differ diff --git a/docs/src/examples/quantum1d/6.hubbard/figure-3.png b/docs/src/examples/groundstates/hubbard/figure-3.png similarity index 100% rename from docs/src/examples/quantum1d/6.hubbard/figure-3.png rename to docs/src/examples/groundstates/hubbard/figure-3.png diff --git a/docs/src/examples/groundstates/hubbard/index.md b/docs/src/examples/groundstates/hubbard/index.md new file mode 100644 index 000000000..6b234a1de --- /dev/null +++ b/docs/src/examples/groundstates/hubbard/index.md @@ -0,0 +1,335 @@ +```@meta +EditURL = "../../../../../examples/groundstates/hubbard/main.jl" +``` + +[![](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/groundstates/hubbard/main.ipynb) +[![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/groundstates/hubbard/main.ipynb) +[![](https://img.shields.io/badge/download-project-orange)](https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/QuantumKitHub/MPSKit.jl/examples/tree/gh-pages/dev/examples/groundstates/hubbard) + +````julia +using Markdown +```` + +# Hubbard chain at half filling + +The Hubbard model is a model of interacting fermions on a lattice, which is often used as a somewhat realistic model for electrons in a solid. +The Hamiltonian consists of two terms that describe competing forces of each electron: a kinetic term that allows electrons to hop between neighboring sites, and a potential term reflecting on-site interactions between electrons. +Often, a third term is included which serves as a chemical potential to control the number of electrons in the system. + +```math +H = -t ∑_{⟨i, j⟩, σ} c^{†}_{i,σ} c_{j,σ} + U ∑_i n_{i,↑} n_{i,↓} - μ ∑_{i,σ} n_{i,σ} +``` + +At half-filling, the system exhibits particle-hole symmetry, which can be made explicit by rewriting the Hamiltonian slightly. +First, we fix the overall energy scale by setting `t = 1`, and then shift the total energy by adding a constant `U / 4`, as well as shifting the chemical potential to `N U / 2`. +This results in the following Hamiltonian: + +```math +H = - ∑_{⟨i, j⟩, σ} c^{†}_{i,σ} c_{j,σ} + U / 4 ∑_i (1 - 2 n_{i,↑}) (1 - 2 n_{i,↓}) - μ ∑_{i,σ} n_{i,σ} +``` + +Finally, setting `μ = 0` and defining `u = U / 4` we obtain the Hubbard model at half-filling. + +```math +H = - ∑_{⟨i, j⟩, σ} c^{†}_{i,σ} c_{j,σ} + u ∑_i (1 - 2 n_{i,↑}) (1 - 2 n_{i,↓}) +``` + +````julia +using TensorKit +using MPSKit +using .ExampleModels +using SpecialFunctions: besselj0, besselj1 +using QuadGK: quadgk +using Plots +using Interpolations +using Optim +```` + +For reproducibility of this page, we fix the seed of the random number generator: + +````julia +using Random +Random.seed!(123); + +const t = 1.0 +const mu = 0.0 +const U = 3.0 +```` + +```` +3.0 +```` + +For this case, the ground state energy has an analytic solution, which can be used to benchmark the numerical results. +It follows from Eq. (6.82) in [Essler, Frahm, Göhmann, Klümper & Korepin, The One-Dimensional Hubbard Model](https://doi.org/10.1017/CBO9780511534843). + +```math +e(u) = - u - 4 ∫₀^{∞} \frac{dω}{ω} \frac{J₀(ω) J₁(ω)}{1 + \exp(2u ω)} +``` + +We can easily verify this by comparing the numerical results to the analytic solution. + +````julia +function hubbard_energy(u; rtol = 1.0e-12) + integrandum(ω) = besselj0(ω) * besselj1(ω) / (1 + exp(2u * ω)) / ω + int, err = quadgk(integrandum, 0, Inf; rtol) + return -u - 4 * int +end + +function compute_groundstate( + psi, H; + svalue = 1.0e-3, + expansionfactor = (1 / 10), + expansioniter = 20 + ) + verbosity = 0 + psi, = find_groundstate(psi, H; tol = svalue * 10, verbosity) + for _ in 1:expansioniter + D = maximum(x -> dim(left_virtualspace(psi, x)), 1:length(psi)) + D′ = max(5, round(Int, D * expansionfactor)) + trunc = trunctol(; atol = svalue / 10) & truncrank(D′) + psi′, = changebonds(psi, H, OptimalExpand(; trunc = trunc)) + all( + left_virtualspace.(Ref(psi), 1:length(psi)) .== + left_virtualspace.(Ref(psi′), 1:length(psi)) + ) && break + psi, = find_groundstate(psi′, H, VUMPS(; tol = svalue / 5, maxiter = 10, verbosity)) + end + + # convergence steps + psi, = changebonds(psi, H, SvdCut(; trunc = trunctol(; atol = svalue))) + psi, = find_groundstate( + psi, H, + VUMPS(; tol = svalue / 100, verbosity, maxiter = 100) & + GradientGrassmann(; tol = svalue / 1000, verbosity) + ) + + return psi +end + +H = hubbard_model(; unitcell = 2, U, t, mu = U / 2) +Vspaces = fill(Vect[fℤ₂](0 => 10, 1 => 10), 2) +psi = InfiniteMPS(physicalspace(H), Vspaces) +psi = compute_groundstate(psi, H) +E = real(expectation_value(psi, H)) / 2 +@info """ +Groundstate energy: + * numerical: $E + * analytic: $(hubbard_energy(U / 4) - U / 4) +""" +```` + +```` +┌ Info: Groundstate energy: +│ * numerical: -2.1899960609745746 +└ * analytic: -2.190038374277775 + +```` + +## Symmetries + +The Hubbard model has a rich symmetry structure, which can be exploited to speed up simulations. +Apart from the fermionic parity, the model also has a ``U(1)`` particle number symmetry, along with a ``SU(2)`` spin symmetry. +Explicitly imposing these symmetries on the tensors can greatly reduce the computational cost of the simulation. + +Naively imposing these symmetries however, is not compatible with our desire to work at half-filling. +By construction, imposing symmetries restricts the optimization procedure to a single symmetry sector, which is the trivial sector. +In order to work at half-filling, we need to effectively inject one particle per site. +In MPSKit, this is achieved by the `add_physical_charge` function, which shifts the physical spaces of the tensors to the desired charge sector. + +````julia +H_u1_su2 = hubbard_model(ComplexF64, U1Irrep, SU2Irrep; unitcell = 2, U, t, mu = U / 2); +charges = fill(FermionParity(1) ⊠ U1Irrep(1) ⊠ SU2Irrep(0), 2); +H_u1_su2 = MPSKit.add_physical_charge(H_u1_su2, charges); + +pspaces = physicalspace.(Ref(H_u1_su2), 1:2) +vspaces = [unitspace(eltype(pspaces)), first(pspaces)] +psi = InfiniteMPS(pspaces, vspaces) +psi = compute_groundstate(psi, H_u1_su2; expansionfactor = 1 / 3) +E = real(expectation_value(psi, H_u1_su2)) / 2 +@info """ +Groundstate energy: + * numerical: $E + * analytic: $(hubbard_energy(U / 4) - U / 4) +""" +```` + +```` +┌ Info: Groundstate energy: +│ * numerical: -2.1900153475144615 +└ * analytic: -2.190038374277775 + +```` + +## Excitations + +Because of the integrability, it is known that the Hubbard model has a rich excitation spectrum. +The elementary excitations are known as spinons and holons, which are domain walls in the spin and charge sectors, respectively. +The fact that the spin and charge sectors are separate is a phenomenon known as spin-charge separation. + +The domain walls can be constructed by noticing that there are two equivalent groundstates, which differ by a translation over a single site. +In other words, the groundstates are ``ψ_{AB}`` and ``ψ_{BA}``, where ``A`` and ``B`` are the two sites. +These excitations can be constructed as follows: + +````julia +alg = QuasiparticleAnsatz(; tol = 1.0e-3) +momenta = range(-π, π; length = 33) +psi_AB = psi +envs_AB = environments(psi_AB, H_u1_su2, psi_AB); +psi_BA = circshift(psi, 1) +envs_BA = environments(psi_BA, H_u1_su2, psi_BA); + +spinon_charge = FermionParity(0) ⊠ U1Irrep(0) ⊠ SU2Irrep(1 // 2) +E_spinon, ϕ_spinon = excitations( + H_u1_su2, alg, momenta, psi_AB, envs_AB, psi_BA, envs_BA; + sector = spinon_charge, num = 1, verbosity = 0 +); + +holon_charge = FermionParity(1) ⊠ U1Irrep(-1) ⊠ SU2Irrep(0) +E_holon, ϕ_holon = excitations( + H_u1_su2, alg, momenta, psi_AB, envs_AB, psi_BA, envs_BA; + sector = holon_charge, num = 1, verbosity = 0 +); +```` + +Again, we can compare the numerical results to the analytic solution. +Here, the formulae for the excitation energies are expressed in terms of dressed momenta: + +````julia +function spinon_momentum(Λ, u; rtol = 1.0e-12) + integrandum(ω) = besselj0(ω) * sin(ω * Λ) / ω / cosh(ω * u) + return π / 2 - quadgk(integrandum, 0, Inf; rtol = rtol)[1] +end +function spinon_energy(Λ, u; rtol = 1.0e-12) + integrandum(ω) = besselj1(ω) * cos(ω * Λ) / ω / cosh(ω * u) + return 2 * quadgk(integrandum, 0, Inf; rtol = rtol)[1] +end + +function holon_momentum(k, u; rtol = 1.0e-12) + integrandum(ω) = besselj0(ω) * sin(ω * sin(k)) / ω / (1 + exp(2u * abs(ω))) + return π / 2 - k - 2 * quadgk(integrandum, 0, Inf; rtol = rtol)[1] +end +function holon_energy(k, u; rtol = 1.0e-12) + integrandum(ω) = besselj1(ω) * cos(ω * sin(k)) * exp(-ω * u) / ω / cosh(ω * u) + return 2 * cos(k) + 2u + 2 * quadgk(integrandum, 0, Inf; rtol = rtol)[1] +end + +Λs = range(-10, 10; length = 51) +P_spinon_analytic = rem2pi.(spinon_momentum.(Λs, U / 4), RoundNearest) +E_spinon_analytic = spinon_energy.(Λs, U / 4) +I_spinon = sortperm(P_spinon_analytic) +P_spinon_analytic = P_spinon_analytic[I_spinon] +E_spinon_analytic = E_spinon_analytic[I_spinon] +P_spinon_analytic = [reverse(-P_spinon_analytic); P_spinon_analytic] +E_spinon_analytic = [reverse(E_spinon_analytic); E_spinon_analytic]; + +ks = range(0, 2π; length = 51) +P_holon_analytic = rem2pi.(holon_momentum.(ks, U / 4), RoundNearest) +E_holon_analytic = holon_energy.(ks, U / 4) +I_holon = sortperm(P_holon_analytic) +P_holon_analytic = P_holon_analytic[I_holon] +E_holon_analytic = E_holon_analytic[I_holon]; + +p = let p_excitations = plot(; xaxis = "momentum", yaxis = "energy") + scatter!(p_excitations, momenta, real(E_spinon); label = "spinon") + plot!(p_excitations, P_spinon_analytic, E_spinon_analytic; label = "spinon (analytic)") + + scatter!(p_excitations, momenta, real(E_holon); label = "holon") + plot!(p_excitations, P_holon_analytic, E_holon_analytic; label = "holon (analytic)") + + p_excitations +end +```` + +![](figure-1.png) + +The plot shows some discrepancies between the numerical and analytic results. +First and foremost, we must realize that in the thermodynamic limit, the momentum of a domain wall is actually not well-defined. +Concretely, only the difference in momentum between the two groundstates is well-defined, as we can always shift the momentum by multiplying one of the groundstates by a phase. +Here, we can fix this shift by realizing that our choice of shifting the groundstates by a single site, differs from the formula by a factor ``π/2``. + +````julia +momenta_shifted = rem2pi.(momenta .- π / 2, RoundNearest) +p = let p_excitations = plot(; xaxis = "momentum", yaxis = "energy", xlims = (-π, π)) + scatter!(p_excitations, momenta_shifted, real(E_spinon); label = "spinon") + plot!(p_excitations, P_spinon_analytic, E_spinon_analytic; label = "spinon (analytic)") + + scatter!(p_excitations, momenta_shifted, real(E_holon); label = "holon") + plot!(p_excitations, P_holon_analytic, E_holon_analytic; label = "holon (analytic)") + + p_excitations +end +```` + +![](figure-2.png) + +The second discrepancy is that while the spinon dispersion is well-reproduced, the holon dispersion is not. +This is due to the fact that the excitation ansatz captures the lowest-energy excitation, and not the elementary single-particle excitation. +To make this explicit, we can consider the scattering states comprising of a holon and two spinons. +If these are truly scattering states, the energy of the scattering state should be the sum of the energies of the individual excitations, and the momentum is the sum of the momenta. +Thus, we can find the lowest-energy scattering states by minimizing the energy over the combination of momenta for the constituent elementary excitations. + +````julia +holon_dispersion_itp = linear_interpolation( + P_holon_analytic, E_holon_analytic; + extrapolation_bc = Line() +) +spinon_dispersion_itp = linear_interpolation( + P_spinon_analytic, E_spinon_analytic; + extrapolation_bc = Line() +) +function scattering_energy(p1, p2, p3) + p1, p2, p3 = rem2pi.((p1, p2, p3), RoundNearest) + return holon_dispersion_itp(p1) + spinon_dispersion_itp(p2) + spinon_dispersion_itp(p3) +end; + +E_scattering_min = map(momenta_shifted) do p + e = Inf + for i in 1:10 # repeat for stability + res = optimize((rand(2) .* (2π) .- π)) do (p₁, p₂) + p₃ = p - p₁ - p₂ + return scattering_energy(p₁, p₂, p₃) + end + + e = min(Optim.minimum(res), e) + end + return e +end +E_scattering_max = map(momenta_shifted) do p + e = -Inf + for i in 1:10 # repeat for stability + res = optimize((rand(Float64, 2) .* (2π) .- π)) do (p₁, p₂) + p₃ = p - p₁ - p₂ + return -scattering_energy(p₁, p₂, p₃) + end + + e = max(-Optim.minimum(res), e) + end + return e +end; + +p = let p_excitations = plot(; + xaxis = "momentum", yaxis = "energy", xlims = (-π, π), ylims = (-0.1, 5) + ) + scatter!(p_excitations, momenta_shifted, real(E_spinon); label = "spinon") + plot!(p_excitations, P_spinon_analytic, E_spinon_analytic; label = "spinon (analytic)") + + scatter!(p_excitations, momenta_shifted, real(E_holon); label = "holon") + plot!(p_excitations, P_holon_analytic, E_holon_analytic; label = "holon (analytic)") + + I = sortperm(momenta_shifted) + plot!( + p_excitations, momenta_shifted[I], E_scattering_min[I]; label = "scattering states", + fillrange = E_scattering_max[I], fillalpha = 0.3, fillstyle = :x + ) + + p_excitations +end +```` + +![](figure-3.png) + +--- + +*This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* + diff --git a/docs/src/examples/quantum1d/6.hubbard/main.ipynb b/docs/src/examples/groundstates/hubbard/main.ipynb similarity index 92% rename from docs/src/examples/quantum1d/6.hubbard/main.ipynb rename to docs/src/examples/groundstates/hubbard/main.ipynb index 16b518855..858a5f677 100644 --- a/docs/src/examples/quantum1d/6.hubbard/main.ipynb +++ b/docs/src/examples/groundstates/hubbard/main.ipynb @@ -16,12 +16,11 @@ "# Hubbard chain at half filling\n", "\n", "The Hubbard model is a model of interacting fermions on a lattice, which is often used as a somewhat realistic model for electrons in a solid.\n", - "The Hamiltonian consists of two terms that describe competing forces of each electron:\n", - "a kinetic term that allows electrons to hop between neighboring sites, and a potential term reflecting on-site interactions between electrons.\n", + "The Hamiltonian consists of two terms that describe competing forces of each electron: a kinetic term that allows electrons to hop between neighboring sites, and a potential term reflecting on-site interactions between electrons.\n", "Often, a third term is included which serves as a chemical potential to control the number of electrons in the system.\n", "\n", "$$\n", - "H = -t \\sum_{\\langle i, j \\rangle, \\sigma} c^{\\dagger}_{i,\\sigma} c_{j,\\sigma} + U \\sum_i n_{i,\\uparrow} n_{i,\\downarrow} - \\mu \\sum_{i,\\sigma} n_{i,\\sigma}\n", + "H = -t ∑_{⟨i, j⟩, σ} c^{†}_{i,σ} c_{j,σ} + U ∑_i n_{i,↑} n_{i,↓} - μ ∑_{i,σ} n_{i,σ}\n", "$$\n", "\n", "At half-filling, the system exhibits particle-hole symmetry, which can be made explicit by rewriting the Hamiltonian slightly.\n", @@ -29,13 +28,13 @@ "This results in the following Hamiltonian:\n", "\n", "$$\n", - "H = - \\sum_{\\langle i, j \\rangle, \\sigma} c^{\\dagger}_{i,\\sigma} c_{j,\\sigma} + U / 4 \\sum_i (1 - 2 n_{i,\\uparrow}) (1 - 2 n_{i,\\downarrow}) - \\mu \\sum_{i,\\sigma} n_{i,\\sigma}\n", + "H = - ∑_{⟨i, j⟩, σ} c^{†}_{i,σ} c_{j,σ} + U / 4 ∑_i (1 - 2 n_{i,↑}) (1 - 2 n_{i,↓}) - μ ∑_{i,σ} n_{i,σ}\n", "$$\n", "\n", - "Finally, setting `\\mu = 0` and defining `u = U / 4` we obtain the Hubbard model at half-filling.\n", + "Finally, setting `μ = 0` and defining `u = U / 4` we obtain the Hubbard model at half-filling.\n", "\n", "$$\n", - "H = - \\sum_{\\langle i, j \\rangle, \\sigma} c^{\\dagger}_{i,\\sigma} c_{j,\\sigma} + u \\sum_i (1 - 2 n_{i,\\uparrow}) (1 - 2 n_{i,\\downarrow})\n", + "H = - ∑_{⟨i, j⟩, σ} c^{†}_{i,σ} c_{j,σ} + u ∑_i (1 - 2 n_{i,↑}) (1 - 2 n_{i,↓})\n", "$$" ] }, @@ -157,8 +156,24 @@ "using QuadGK: quadgk\n", "using Plots\n", "using Interpolations\n", - "using Optim\n", - "\n", + "using Optim" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For reproducibility of this page, we fix the seed of the random number generator:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "using Random\n", + "Random.seed!(123);\n", "\n", "const t = 1.0\n", "const mu = 0.0\n", @@ -170,10 +185,10 @@ "metadata": {}, "source": [ "For this case, the ground state energy has an analytic solution, which can be used to benchmark the numerical results.\n", - "It follows from Eq. (6.82) in []().\n", + "It follows from Eq. (6.82) in [Essler, Frahm, Göhmann, Klümper & Korepin, The One-Dimensional Hubbard Model](https://doi.org/10.1017/CBO9780511534843).\n", "\n", "$$\n", - "e(u) = - u - 4 \\int_0^{\\infty} \\frac{d\\omega}{\\omega} \\frac{J_0(\\omega) J_1(\\omega)}{1 + \\exp(2u \\omega)}\n", + "e(u) = - u - 4 ∫₀^{∞} \\frac{dω}{ω} \\frac{J₀(ω) J₁(ω)}{1 + \\exp(2u ω)}\n", "$$\n", "\n", "We can easily verify this by comparing the numerical results to the analytic solution." @@ -187,7 +202,7 @@ "source": [ "function hubbard_energy(u; rtol = 1.0e-12)\n", " integrandum(ω) = besselj0(ω) * besselj1(ω) / (1 + exp(2u * ω)) / ω\n", - " int, err = quadgk(integrandum, 0, Inf; rtol = rtol)\n", + " int, err = quadgk(integrandum, 0, Inf; rtol)\n", " return -u - 4 * int\n", "end\n", "\n", @@ -197,7 +212,7 @@ " expansionfactor = (1 / 10),\n", " expansioniter = 20\n", " )\n", - " verbosity = 2\n", + " verbosity = 0\n", " psi, = find_groundstate(psi, H; tol = svalue * 10, verbosity)\n", " for _ in 1:expansioniter\n", " D = maximum(x -> dim(left_virtualspace(psi, x)), 1:length(psi))\n", @@ -216,7 +231,7 @@ " psi, = find_groundstate(\n", " psi, H,\n", " VUMPS(; tol = svalue / 100, verbosity, maxiter = 100) &\n", - " GradientGrassmann(; tol = svalue / 1000)\n", + " GradientGrassmann(; tol = svalue / 1000, verbosity)\n", " )\n", "\n", " return psi\n", @@ -283,7 +298,7 @@ "The fact that the spin and charge sectors are separate is a phenomenon known as spin-charge separation.\n", "\n", "The domain walls can be constructed by noticing that there are two equivalent groundstates, which differ by a translation over a single site.\n", - "In other words, the groundstates are $\\psi_{AB}` and $\\psi_{BA}$, where $A$ and $B$ are the two sites.\n", + "In other words, the groundstates are $ψ_{AB}$ and $ψ_{BA}$, where $A$ and $B$ are the two sites.\n", "These excitations can be constructed as follows:" ] }, @@ -303,13 +318,13 @@ "spinon_charge = FermionParity(0) ⊠ U1Irrep(0) ⊠ SU2Irrep(1 // 2)\n", "E_spinon, ϕ_spinon = excitations(\n", " H_u1_su2, alg, momenta, psi_AB, envs_AB, psi_BA, envs_BA;\n", - " sector = spinon_charge, num = 1\n", + " sector = spinon_charge, num = 1, verbosity = 0\n", ");\n", "\n", "holon_charge = FermionParity(1) ⊠ U1Irrep(-1) ⊠ SU2Irrep(0)\n", "E_holon, ϕ_holon = excitations(\n", " H_u1_su2, alg, momenta, psi_AB, envs_AB, psi_BA, envs_BA;\n", - " sector = holon_charge, num = 1\n", + " sector = holon_charge, num = 1, verbosity = 0\n", ");" ] }, @@ -379,7 +394,7 @@ "The plot shows some discrepancies between the numerical and analytic results.\n", "First and foremost, we must realize that in the thermodynamic limit, the momentum of a domain wall is actually not well-defined.\n", "Concretely, only the difference in momentum between the two groundstates is well-defined, as we can always shift the momentum by multiplying one of the groundstates by a phase.\n", - "Here, we can fix this shift by realizing that our choice of shifting the groundstates by a single site, differs from the formula by a factor $\\pi/2$." + "Here, we can fix this shift by realizing that our choice of shifting the groundstates by a single site, differs from the formula by a factor $π/2$." ] }, { @@ -499,4 +514,4 @@ }, "nbformat": 4, "nbformat_minor": 3 -} +} \ No newline at end of file diff --git a/docs/src/examples/quantum1d/1.ising-cft/figure-1.png b/docs/src/examples/groundstates/ising-cft/figure-1.png similarity index 100% rename from docs/src/examples/quantum1d/1.ising-cft/figure-1.png rename to docs/src/examples/groundstates/ising-cft/figure-1.png diff --git a/docs/src/examples/quantum1d/1.ising-cft/figure-2.png b/docs/src/examples/groundstates/ising-cft/figure-2.png similarity index 100% rename from docs/src/examples/quantum1d/1.ising-cft/figure-2.png rename to docs/src/examples/groundstates/ising-cft/figure-2.png diff --git a/docs/src/examples/groundstates/ising-cft/figure-3.png b/docs/src/examples/groundstates/ising-cft/figure-3.png new file mode 100644 index 000000000..92dbcc3b6 Binary files /dev/null and b/docs/src/examples/groundstates/ising-cft/figure-3.png differ diff --git a/docs/src/examples/groundstates/ising-cft/index.md b/docs/src/examples/groundstates/ising-cft/index.md new file mode 100644 index 000000000..b7acb9d8d --- /dev/null +++ b/docs/src/examples/groundstates/ising-cft/index.md @@ -0,0 +1,211 @@ +```@meta +EditURL = "../../../../../examples/groundstates/ising-cft/main.jl" +``` + +[![](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/groundstates/ising-cft/main.ipynb) +[![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/groundstates/ising-cft/main.ipynb) +[![](https://img.shields.io/badge/download-project-orange)](https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/QuantumKitHub/MPSKit.jl/examples/tree/gh-pages/dev/examples/groundstates/ising-cft) + +# The Ising CFT spectrum + +This tutorial is meant to show the finite size CFT spectrum for the quantum Ising model. +We do this by first employing an exact diagonalization technique, and then extending the analysis to larger system sizes through the use of MPS techniques. + +````julia +using MPSKit, TensorKit, Plots, KrylovKit +using .ExampleModels +using LinearAlgebra: eigvals, diagm, Hermitian +```` + +The Hamiltonian is defined on a finite lattice with periodic boundary conditions, which can be implemented as follows: + +````julia +L = 12 +H = periodic_boundary_conditions(transverse_field_ising(), L) +```` + +```` +12-site FiniteMPOHamiltonian(ComplexF64, TensorKit.ComplexSpace) with maximal dimension 6: +┬─[12]─ ℂ^2 +│ (ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ⋯ ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1) +┼─[11]─ ℂ^2 +│ (ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ⋯ ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1) +┼─[10]─ ℂ^2 +│ (ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ⋯ ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1) +┼─[9]─ ℂ^2 +│ (ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ⋯ ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1) +│ ⋮ +│ (ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ⋯ ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1) +┼─[3]─ ℂ^2 +│ (ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ⋯ ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1) +┼─[2]─ ℂ^2 +│ (ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ⋯ ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1) +┴─[1]─ ℂ^2 + +```` + +## Exact diagonalisation + +In MPSKit, there is support for exact diagonalisation by leveraging the fact that applying the Hamiltonian to an untruncated MPS will result in an effective Hamiltonian on the center site which implements the action of the entire Hamiltonian. +Thus, optimizing the middle tensor is equivalent to optimizing a state in the entire Hilbert space, as all other tensors are just unitary matrices that mix the basis. + +````julia +energies, states = exact_diagonalization(H; num = 18, alg = Lanczos(; krylovdim = 200)); +plot( + real.(energies); + seriestype = :scatter, legend = false, ylabel = "energy", xlabel = "#eigenvalue" +) +```` + +![](figure-1.png) + +!!! note "Krylov dimension" + Note that we have specified a large Krylov dimension as degenerate eigenvalues are + notoriously difficult for iterative methods. + +## Extracting momentum + +Given a state, it is possible to assign a momentum label through the use of the translation operator. +This operator can be defined in MPO language either diagrammatically as + +```@raw html +translation operator +``` + +or in the code as: + +````julia +function O_shift(L) + I = id(ComplexF64, ℂ^2) + @tensor O[W S; N E] := I[W; N] * I[S; E] + return periodic_boundary_conditions(InfiniteMPO([O]), L) +end +```` + +```` +O_shift (generic function with 1 method) +```` + +We can then calculate the momentum of the ground state as the expectation value of this operator. +However, there is a subtlety because of the degeneracies in the energy eigenvalues. +The eigensolver will find an orthonormal basis within each energy subspace, but this basis is not necessarily a basis of eigenstates of the translation operator. +In order to fix this, we diagonalize the translation operator within each energy subspace. +The resulting energy levels have one-to-one correspondence to the operators in CFT, where the momentum is related to their conformal spin as $P_n = \frac{2π}{L}S_n$. + +````julia +function fix_degeneracies(basis) + L = length(basis[1]) + M = zeros(ComplexF64, length(basis), length(basis)) + T = O_shift(L) + for j in eachindex(basis), i in eachindex(basis) + M[i, j] = dot(basis[i], T, basis[j]) + end + + vals = eigvals(M) + return angle.(vals) +end + +momenta = Float64[] +append!(momenta, fix_degeneracies(states[1:1])) +append!(momenta, fix_degeneracies(states[2:2])) +append!(momenta, fix_degeneracies(states[3:3])) +append!(momenta, fix_degeneracies(states[4:5])) +append!(momenta, fix_degeneracies(states[6:9])) +append!(momenta, fix_degeneracies(states[10:11])) +append!(momenta, fix_degeneracies(states[12:12])) +append!(momenta, fix_degeneracies(states[13:16])) +append!(momenta, fix_degeneracies(states[17:18])) +```` + +```` +18-element Vector{Float64}: + -2.1278136533158578e-17 + 2.3050344087760185e-17 + -5.0324217423779645e-17 + 0.5235987755982991 + -0.5235987755982991 + -1.0471975511965976 + 1.0471975511965974 + -0.523598775598299 + 0.5235987755982988 + 1.0471975511965979 + -1.0471975511965976 + -1.9939344511686805e-17 + -1.5707963267948966 + 1.5707963267948961 + -1.047197551196598 + 1.047197551196598 + -1.5707963267948968 + 1.5707963267948966 +```` + +We can compute the scaling dimensions $Δ_n$ of the operators in the CFT from the energy gap of the corresponding excitations as $E_n - E_0 = \frac{2π v}{L} Δ_n$, where $v = 2$. +If we plot these scaling dimensions against the conformal spin $S_n$ from above, we retrieve the familiar spectrum of the Ising CFT. + +````julia +v = 2.0 +Δ = real.(energies[1:18] .- energies[1]) ./ (2π * v / L) +S = momenta ./ (2π / L) + +p = plot( + S, real.(Δ); + seriestype = :scatter, xlabel = "conformal spin (S)", ylabel = "scaling dimension (Δ)", + legend = false +) +vline!(p, -3:3; color = "gray", linestyle = :dash) +hline!(p, [0, 1 / 8, 1, 9 / 8, 2, 17 / 8]; color = "gray", linestyle = :dash) +p +```` + +![](figure-2.png) + +## Finite bond dimension + +If we limit the maximum bond dimension of the MPS, we get an approximate solution, but we can reach higher system sizes. + +````julia +L_mps = 20 +H_mps = periodic_boundary_conditions(transverse_field_ising(), L_mps) +D = 64 +ψ, envs, info = find_groundstate(FiniteMPS(L_mps, ℂ^2, ℂ^D), H_mps, DMRG(; verbosity = 0)); +```` + +Excitations on top of the ground state can be found through the use of the quasiparticle ansatz. +This returns quasiparticle states, which can be converted to regular `FiniteMPS` objects. + +````julia +E_ex, qps = excitations(H_mps, QuasiparticleAnsatz(), ψ, envs; num = 18) +states_mps = vcat(ψ, map(qp -> convert(FiniteMPS, qp), qps)) +energies_mps = map(x -> expectation_value(x, H_mps), states_mps) + +momenta_mps = Float64[] +append!(momenta_mps, fix_degeneracies(states_mps[1:1])) +append!(momenta_mps, fix_degeneracies(states_mps[2:2])) +append!(momenta_mps, fix_degeneracies(states_mps[3:3])) +append!(momenta_mps, fix_degeneracies(states_mps[4:5])) +append!(momenta_mps, fix_degeneracies(states_mps[6:9])) +append!(momenta_mps, fix_degeneracies(states_mps[10:11])) +append!(momenta_mps, fix_degeneracies(states_mps[12:12])) +append!(momenta_mps, fix_degeneracies(states_mps[13:16])) +append!(momenta_mps, fix_degeneracies(states_mps[17:18])) + +v = 2.0 +Δ_mps = real.(energies_mps[1:18] .- energies_mps[1]) ./ (2π * v / L_mps) +S_mps = momenta_mps ./ (2π / L_mps) + +p_mps = plot( + S_mps, real.(Δ_mps); + seriestype = :scatter, xlabel = "conformal spin (S)", + ylabel = "scaling dimension (Δ)", legend = false +) +vline!(p_mps, -3:3; color = "gray", linestyle = :dash) +hline!(p_mps, [0, 1 / 8, 1, 9 / 8, 2, 17 / 8]; color = "gray", linestyle = :dash) +p_mps +```` + +![](figure-3.png) + +--- + +*This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* + diff --git a/docs/src/examples/quantum1d/1.ising-cft/main.ipynb b/docs/src/examples/groundstates/ising-cft/main.ipynb similarity index 86% rename from docs/src/examples/quantum1d/1.ising-cft/main.ipynb rename to docs/src/examples/groundstates/ising-cft/main.ipynb index db7b248c7..81e2702a7 100644 --- a/docs/src/examples/quantum1d/1.ising-cft/main.ipynb +++ b/docs/src/examples/groundstates/ising-cft/main.ipynb @@ -6,9 +6,8 @@ "source": [ "# The Ising CFT spectrum\n", "\n", - "This tutorial is meant to show the finite size CFT spectrum for the quantum Ising model. We\n", - "do this by first employing an exact diagonalization technique, and then extending the\n", - "analysis to larger system sizes through the use of MPS techniques." + "This tutorial is meant to show the finite size CFT spectrum for the quantum Ising model.\n", + "We do this by first employing an exact diagonalization technique, and then extending the analysis to larger system sizes through the use of MPS techniques." ] }, { @@ -131,8 +130,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The Hamiltonian is defined on a finite lattice with periodic boundary conditions,\n", - "which can be implemented as follows:" + "The Hamiltonian is defined on a finite lattice with periodic boundary conditions, which can be implemented as follows:" ] }, { @@ -151,11 +149,8 @@ "source": [ "## Exact diagonalisation\n", "\n", - "In MPSKit, there is support for exact diagonalisation by leveraging the fact that applying\n", - "the Hamiltonian to an untruncated MPS will result in an effective Hamiltonian on the center\n", - "site which implements the action of the entire Hamiltonian. Thus, optimizing the middle\n", - "tensor is equivalent to optimixing a state in the entire Hilbert space, as all other tensors\n", - "are just unitary matrices that mix the basis." + "In MPSKit, there is support for exact diagonalisation by leveraging the fact that applying the Hamiltonian to an untruncated MPS will result in an effective Hamiltonian on the center site which implements the action of the entire Hamiltonian.\n", + "Thus, optimizing the middle tensor is equivalent to optimizing a state in the entire Hilbert space, as all other tensors are just unitary matrices that mix the basis." ] }, { @@ -187,9 +182,8 @@ "source": [ "## Extracting momentum\n", "\n", - "Given a state, it is possible to assign a momentum label\n", - "through the use of the translation operator. This operator can be defined in MPO language\n", - "either diagramatically as\n", + "Given a state, it is possible to assign a momentum label through the use of the translation operator.\n", + "This operator can be defined in MPO language either diagrammatically as\n", "\n", "\n", "\"translation\n", @@ -215,13 +209,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can then calculate the momentum of the ground state as the expectation value of this\n", - "operator. However, there is a subtlety because of the degeneracies in the energy\n", - "eigenvalues. The eigensolver will find an orthonormal basis within each energy subspace, but\n", - "this basis is not necessarily a basis of eigenstates of the translation operator. In order\n", - "to fix this, we diagonalize the translation operator within each energy subspace.\n", - "The resulting energy levels have one-to-one correspondence to the operators in CFT, where\n", - "the momentum is related to their conformal spin as $P_n = \\frac{2\\pi}{L}S_n$." + "We can then calculate the momentum of the ground state as the expectation value of this operator.\n", + "However, there is a subtlety because of the degeneracies in the energy eigenvalues.\n", + "The eigensolver will find an orthonormal basis within each energy subspace, but this basis is not necessarily a basis of eigenstates of the translation operator.\n", + "In order to fix this, we diagonalize the translation operator within each energy subspace.\n", + "The resulting energy levels have one-to-one correspondence to the operators in CFT, where the momentum is related to their conformal spin as $P_n = \\frac{2π}{L}S_n$." ] }, { @@ -258,10 +250,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can compute the scaling dimensions $\\Delta_n$ of the operators in the CFT from the\n", - "energy gap of the corresponding excitations as $E_n - E_0 = \\frac{2\\pi v}{L} \\Delta_n$,\n", - "where $v = 2$. If we plot these scaling dimensions against the conformal spin $S_n$ from\n", - "above, we retrieve the familiar spectrum of the Ising CFT." + "We can compute the scaling dimensions $Δ_n$ of the operators in the CFT from the energy gap of the corresponding excitations as $E_n - E_0 = \\frac{2π v}{L} Δ_n$, where $v = 2$.\n", + "If we plot these scaling dimensions against the conformal spin $S_n$ from above, we retrieve the familiar spectrum of the Ising CFT." ] }, { @@ -290,8 +280,7 @@ "source": [ "## Finite bond dimension\n", "\n", - "If we limit the maximum bond dimension of the MPS, we get an approximate solution, but we\n", - "can reach higher system sizes." + "If we limit the maximum bond dimension of the MPS, we get an approximate solution, but we can reach higher system sizes." ] }, { @@ -303,16 +292,15 @@ "L_mps = 20\n", "H_mps = periodic_boundary_conditions(transverse_field_ising(), L_mps)\n", "D = 64\n", - "ψ, envs, info = find_groundstate(FiniteMPS(L_mps, ℂ^2, ℂ^D), H_mps, DMRG());" + "ψ, envs, info = find_groundstate(FiniteMPS(L_mps, ℂ^2, ℂ^D), H_mps, DMRG(; verbosity = 0));" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Excitations on top of the ground state can be found through the use of the quasiparticle\n", - "ansatz. This returns quasiparticle states, which can be converted to regular `FiniteMPS`\n", - "objects." + "Excitations on top of the ground state can be found through the use of the quasiparticle ansatz.\n", + "This returns quasiparticle states, which can be converted to regular `FiniteMPS` objects." ] }, { diff --git a/docs/src/examples/quantum1d/1.ising-cft/translation_mpo.svg b/docs/src/examples/groundstates/ising-cft/translation_mpo.svg similarity index 100% rename from docs/src/examples/quantum1d/1.ising-cft/translation_mpo.svg rename to docs/src/examples/groundstates/ising-cft/translation_mpo.svg diff --git a/docs/src/examples/groundstates/xxz-heisenberg/figure-1.png b/docs/src/examples/groundstates/xxz-heisenberg/figure-1.png new file mode 100644 index 000000000..a69545d1b Binary files /dev/null and b/docs/src/examples/groundstates/xxz-heisenberg/figure-1.png differ diff --git a/docs/src/examples/groundstates/xxz-heisenberg/figure-2.png b/docs/src/examples/groundstates/xxz-heisenberg/figure-2.png new file mode 100644 index 000000000..c19958f0d Binary files /dev/null and b/docs/src/examples/groundstates/xxz-heisenberg/figure-2.png differ diff --git a/docs/src/examples/groundstates/xxz-heisenberg/index.md b/docs/src/examples/groundstates/xxz-heisenberg/index.md new file mode 100644 index 000000000..00f23316e --- /dev/null +++ b/docs/src/examples/groundstates/xxz-heisenberg/index.md @@ -0,0 +1,212 @@ +```@meta +EditURL = "../../../../../examples/groundstates/xxz-heisenberg/main.jl" +``` + +[![](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/groundstates/xxz-heisenberg/main.ipynb) +[![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/groundstates/xxz-heisenberg/main.ipynb) +[![](https://img.shields.io/badge/download-project-orange)](https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/QuantumKitHub/MPSKit.jl/examples/tree/gh-pages/dev/examples/groundstates/xxz-heisenberg) + +# The XXZ model + +In this file we will give step by step instructions on how to analyze the spin 1/2 XXZ model. +The necessary packages to follow this tutorial are: + +````julia +using MPSKit, TensorKit, Plots +using .ExampleModels +```` + +For reproducibility of this page, we fix the seed of the random number generator: + +````julia +using Random +Random.seed!(123); +```` + +## Failure + +First we should define the Hamiltonian we want to work with. +Then we specify an initial guess, which we then further optimize. +Working directly in the thermodynamic limit, this is achieved as follows: + +````julia +H = heisenberg_XXX(; spin = 1 // 2) +```` + +```` +1-site InfiniteMPOHamiltonian(ComplexF64, TensorKit.ComplexSpace) with maximal dimension 5: +| ⋮ +| (ℂ^1 ⊞ ℂ^3 ⊞ ℂ^1) +┼─[1]─ ℂ^2 +│ (ℂ^1 ⊞ ℂ^3 ⊞ ℂ^1) +| ⋮ + +```` + +We then need an initial state, which we shall later optimize. +In this example we work directly in the thermodynamic limit. + +````julia +state = InfiniteMPS(2, 20) +```` + +```` +1-site InfiniteMPS(ComplexF64, TensorKit.ComplexSpace) with maximal dimension 20: +| ⋮ +| ℂ^20 +├─[1]─ ℂ^2 +│ ℂ^20 +| ⋮ + +```` + +The ground state can then be found by calling `find_groundstate`. + +````julia +groundstate, cache, info = find_groundstate(state, H, VUMPS(; verbosity = 1)); +info +```` + +```` +AlgorithmInfo: + converged = false after 200 iterations + galerkin = 0.370633 + +```` + +As you can see, VUMPS struggles to converge. +On its own, that is already quite curious. +Maybe we can do better using another algorithm, such as gradient descent. + +````julia +groundstate, cache, info = find_groundstate(state, H, GradientGrassmann(; maxiter = 20, verbosity = 1)); +info +```` + +```` +AlgorithmInfo: + converged = false after 20 iterations + gradientnorm = 0.00747596 + +```` + +Convergence is quite slow and even fails after sufficiently many iterations. +To understand why, we can look at the transfer matrix spectrum. + +````julia +transferplot(groundstate, groundstate) +```` + +![](figure-1.png) + +We can clearly see multiple eigenvalues close to the unit circle. +Our state is close to being non-injective, and represents the sum of multiple injective states. +This is numerically very problematic, but also indicates that we used an incorrect ansatz to approximate the groundstate. +We should retry with a larger unit cell. + +## Success + +Let's initialize a different initial state, this time with a 2-site unit cell: + +````julia +state = InfiniteMPS(fill(2, 2), fill(20, 2)) +```` + +```` +2-site InfiniteMPS(ComplexF64, TensorKit.ComplexSpace) with maximal dimension 20: +| ⋮ +| ℂ^20 +├─[2]─ ℂ^2 +│ ℂ^20 +├─[1]─ ℂ^2 +│ ℂ^20 +| ⋮ + +```` + +In MPSKit, we require that the periodicity of the Hamiltonian equals that of the state it is applied to. +This is not a big obstacle, you can simply repeat the original Hamiltonian. +Alternatively, our model helper can construct the Hamiltonian directly on a two-site unit cell. + +````julia +# H2 = repeat(H, 2); -- copies the one-site version +H2 = heisenberg_XXX(ComplexF64, Trivial; unitcell = 2, spin = 1 // 2) +groundstate, envs, info = find_groundstate( + state, H2, VUMPS(; maxiter = 100, tol = 1.0e-12, verbosity = 1) +); +```` + +```` +┌ Warning: VUMPS cancel 100: obj = -8.862417624752e-01 err = 4.2010390011e-06 time = 2.81 sec +└ @ MPSKit src/algorithms/groundstate/vumps.jl:96 + +```` + +We get convergence, but it takes an enormous amount of iterations. +The reason behind this becomes more obvious at higher bond dimensions: + +````julia +groundstate, envs, info = find_groundstate( + state, H2, IDMRG2(; trunc = truncrank(50), maxiter = 20, tol = 1.0e-12, verbosity = 1) +); +entanglementplot(groundstate) +```` + +![](figure-2.png) + +We see that some eigenvalues clearly belong to a group, and are almost degenerate. +This implies 2 things: +- there is superfluous information, if those eigenvalues are the same anyway +- poor convergence if we cut off within such a subspace + +It are precisely those problems that we can solve by using symmetries. + +## Symmetries + +The XXZ Heisenberg Hamiltonian is SU(2) symmetric and we can exploit this to greatly speed up the simulation. + +It is cumbersome to construct symmetric Hamiltonians, but luckily SU(2) symmetric XXZ is already implemented: + +````julia +H2 = heisenberg_XXX(ComplexF64, SU2Irrep; unitcell = 2, spin = 1 // 2); +```` + +Our initial state should also be SU(2) symmetric. +It now becomes apparent why we have to use a two-site periodic state. +The physical space carries a half-integer charge and the first tensor maps the first `virtual_space ⊗ the physical_space` to the second `virtual_space`. +Half-integer virtual charges will therefore map only to integer charges, and vice versa. +The staggering thus happens on the virtual level. + +An alternative constructor for the initial state is + +````julia +P = Rep[SU₂](1 // 2 => 1) +V1 = Rep[SU₂](1 // 2 => 10, 3 // 2 => 5, 5 // 2 => 2) +V2 = Rep[SU₂](0 => 15, 1 => 10, 2 => 5) +state = InfiniteMPS([P, P], [V1, V2]); +```` + +```` +┌ Warning: Constructing an MPS from tensors that are not full rank +└ @ MPSKit src/states/infinitemps.jl:188 + +```` + +Even though the bond dimension is higher than in the example without symmetry, convergence is reached much faster: + +````julia +println(dim(V1)) +println(dim(V2)) +groundstate, cache, info = find_groundstate(state, H2, VUMPS(; maxiter = 400, tol = 1.0e-12, verbosity = 1)); +```` + +```` +52 +70 + +```` + +--- + +*This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* + diff --git a/docs/src/examples/quantum1d/4.xxz-heisenberg/main.ipynb b/docs/src/examples/groundstates/xxz-heisenberg/main.ipynb similarity index 95% rename from docs/src/examples/quantum1d/4.xxz-heisenberg/main.ipynb rename to docs/src/examples/groundstates/xxz-heisenberg/main.ipynb index 2f4d6379c..e2ca4974b 100644 --- a/docs/src/examples/quantum1d/4.xxz-heisenberg/main.ipynb +++ b/docs/src/examples/groundstates/xxz-heisenberg/main.ipynb @@ -125,6 +125,23 @@ "using .ExampleModels" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For reproducibility of this page, we fix the seed of the random number generator:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "using Random\n", + "Random.seed!(123);" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -149,7 +166,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We then need an initial state, which we shall later optimize. In this example we work directly in the thermodynamic limit." + "We then need an initial state, which we shall later optimize.\n", + "In this example we work directly in the thermodynamic limit." ] }, { @@ -174,7 +192,7 @@ "metadata": {}, "outputs": [], "source": [ - "groundstate, cache, info = find_groundstate(state, H, VUMPS());\n", + "groundstate, cache, info = find_groundstate(state, H, VUMPS(; verbosity = 1));\n", "info" ] }, @@ -193,7 +211,7 @@ "metadata": {}, "outputs": [], "source": [ - "groundstate, cache, info = find_groundstate(state, H, GradientGrassmann(; maxiter = 20));\n", + "groundstate, cache, info = find_groundstate(state, H, GradientGrassmann(; maxiter = 20, verbosity = 1));\n", "info" ] }, @@ -260,7 +278,7 @@ "# H2 = repeat(H, 2); -- copies the one-site version\n", "H2 = heisenberg_XXX(ComplexF64, Trivial; unitcell = 2, spin = 1 // 2)\n", "groundstate, envs, info = find_groundstate(\n", - " state, H2, VUMPS(; maxiter = 100, tol = 1.0e-12)\n", + " state, H2, VUMPS(; maxiter = 100, tol = 1.0e-12, verbosity = 1)\n", ");" ] }, @@ -279,7 +297,7 @@ "outputs": [], "source": [ "groundstate, envs, info = find_groundstate(\n", - " state, H2, IDMRG2(; trunc = truncrank(50), maxiter = 20, tol = 1.0e-12)\n", + " state, H2, IDMRG2(; trunc = truncrank(50), maxiter = 20, tol = 1.0e-12, verbosity = 1)\n", ");\n", "entanglementplot(groundstate)" ] @@ -356,7 +374,7 @@ "source": [ "println(dim(V1))\n", "println(dim(V2))\n", - "groundstate, cache, info = find_groundstate(state, H2, VUMPS(; maxiter = 400, tol = 1.0e-12));" + "groundstate, cache, info = find_groundstate(state, H2, VUMPS(; maxiter = 400, tol = 1.0e-12, verbosity = 1));" ] }, { diff --git a/docs/src/examples/index.md b/docs/src/examples/index.md index 2d4a5a970..1f91825d8 100644 --- a/docs/src/examples/index.md +++ b/docs/src/examples/index.md @@ -1,4 +1,7 @@ -# Examples +# [Examples](@id examples_index) + +This gallery collects the full worked examples that ship with MPSKit.jl. +Each one is a complete, runnable script (also available as a Jupyter notebook, linked from the example page itself) that goes well beyond the short snippets in the how-to guides. The downloadable notebooks include the model constructors used in these examples. Their operator definitions come from @@ -12,16 +15,90 @@ include("models.jl") using .ExampleModels ``` -## Quantum (1+1)d +The examples are grouped by the kind of computation they demonstrate rather than by the physical system. +Every summary states the complexity of the example, so you can begin with an introductory one and move on to those that combine symmetries, infinite systems, and the less common algorithms. +The sidebar lists the same pages in the same order. -```@contents -Pages = map(file -> joinpath("quantum1d", file, "index.md"), readdir("quantum1d")) -Depth = 1 -``` +## Ground states -## Classical (2+0)d +### [The Ising CFT spectrum](groundstates/ising-cft/index.md) -```@contents -Pages = map(file -> joinpath("classic2d", file, "index.md"), readdir("classic2d")) -Depth = 1 -``` +![](groundstates/ising-cft/figure-2.png) + +Extracts the finite-size conformal spectrum of the critical transverse-field Ising chain, first by brute-force exact diagonalization on a small periodic chain, then by extending to larger sizes with finite DMRG and the quasiparticle ansatz, using a translation MPO to assign a momentum label to each state. +It works without any symmetries, which makes it a good entry point into the finite-MPS workflow. +**Complexity: introductory.** + +### [The XXZ model](groundstates/xxz-heisenberg/index.md) + +![](groundstates/xxz-heisenberg/figure-2.png) + +Shows how to pick a unit cell and a symmetry that suit the state you are after, using the spin-1/2 Heisenberg antiferromagnet as the case study. +The transfer-matrix and entanglement spectra serve as diagnostics: they reveal several almost-degenerate transfer-matrix eigenvalues, which is the signature of a state that a single-site ansatz cannot represent. +A two-site unit cell together with the SU(2)-symmetric Hamiltonian resolves this, after which both two-site IDMRG and VUMPS converge cleanly. +A good example for learning to read these diagnostics and act on them. +**Complexity: intermediate.** + +### [Spin 1 Heisenberg model](groundstates/haldane-spt/index.md) + +![](groundstates/haldane-spt/figure-3.png) + +Distinguishes the two symmetry-protected topological phases of the SU(2)-symmetric spin-1 Heisenberg chain by restricting the virtual space to integer or half-integer charges, then compares the two resulting ground states through their energy, transfer-matrix spectrum, entanglement spectrum, and entanglement entropy. +Builds directly on the symmetry machinery introduced in the Haldane gap example. +**Complexity: intermediate to advanced.** + +### [Hubbard chain at half filling](groundstates/hubbard/index.md) + +![](groundstates/hubbard/figure-2.png) + +Studies the one-dimensional Hubbard model at half filling with a fermionic infinite MPS. +It first benchmarks a plain ground-state search against the exact Bethe-ansatz integral for the energy, then imposes the full particle-number and spin symmetry, pinning the filling by adding a charge to the physical space, and finally constructs the spinon and holon excitation spectrum with the quasiparticle ansatz. +Combines fermionic symmetry sectors with a more elaborate ground-state recipe, growing the bond dimension in stages before refining. +**Complexity: advanced.** + +### [1D Bose-Hubbard model](groundstates/bose-hubbard/index.md) + +![](groundstates/bose-hubbard/figure-6.png) + +The most comprehensive ground-state example in the gallery: it works directly in the thermodynamic limit with a truncated bosonic local Hilbert space, extracts correlation functions and the correlation length as a function of bond dimension, computes the momentum distribution, and maps out the Mott-insulator and superfluid structure of the phase diagram from the ground-state response to an applied phase twist. +Touches most of the ground-state toolbox in a single, longer study. +**Complexity: advanced.** + +## Excitations & dispersions + +### [The Haldane gap](excitations/haldane/index.md) + +![](excitations/haldane/figure-3.png) + +Computes the Haldane gap of the spin-1 Heisenberg antiferromagnet in two complementary ways: finite-size DMRG with the quasiparticle ansatz, extrapolated over system size, and a direct infinite-chain VUMPS calculation with a momentum-resolved excitation scan. +Introduces SU(2)-symmetric tensors for both finite and infinite MPS. +**Complexity: intermediate.** + +## Dynamics & finite temperature + +### [DQPT in the Ising model](dynamics/ising-dqpt/index.md) + +![](dynamics/ising-dqpt/infinite_timeev.png) + +Quenches the transverse-field Ising chain across its critical point and tracks the Loschmidt echo in search of non-analyticities, the dynamical quantum phase transitions of the title. +This is done both on a finite chain, with two-site and then single-site TDVP, and directly in the thermodynamic limit, where the bond dimension is grown explicitly before evolving. +A compact introduction to real-time evolution and environment reuse, still without symmetries. +**Complexity: introductory to intermediate.** + +### [Finite temperature XY model](dynamics/xy-finiteT/index.md) + +![](dynamics/xy-finiteT/figure-1.png) + +Simulates the finite-temperature XY chain by purifying the infinite-temperature density matrix and evolving it in imaginary time, then compares the resulting partition function and free energy against exact diagonalization and, via BenchmarkFreeFermions.jl, the exact free-fermion solution. +A technical, comparison-heavy example built around imaginary-time evolution of a density matrix rather than a ground-state search. +**Complexity: advanced.** + +## Statistical mechanics + +### [The Hard Hexagon model](statmech/hard-hexagon/index.md) + +![](statmech/hard-hexagon/figure-1.png) + +Extracts the central charge of the hard hexagon lattice gas by finding the leading boundary MPS of its transfer matrix with VUMPS, using Fibonacci-anyon virtual spaces, then fitting the CFT-predicted scaling relation between entanglement entropy and correlation length as the bond dimension is increased. +The only classical statistical-mechanics example in the gallery, and the only one demonstrating anyonic symmetries in MPSKit. +**Complexity: advanced.** diff --git a/docs/src/examples/quantum1d/1.ising-cft/figure-3.png b/docs/src/examples/quantum1d/1.ising-cft/figure-3.png deleted file mode 100644 index a2e28ecb3..000000000 Binary files a/docs/src/examples/quantum1d/1.ising-cft/figure-3.png and /dev/null differ diff --git a/docs/src/examples/quantum1d/1.ising-cft/index.md b/docs/src/examples/quantum1d/1.ising-cft/index.md deleted file mode 100644 index e8d76048e..000000000 --- a/docs/src/examples/quantum1d/1.ising-cft/index.md +++ /dev/null @@ -1,273 +0,0 @@ -```@meta -EditURL = "../../../../../examples/quantum1d/1.ising-cft/main.jl" -``` - -[![](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/quantum1d/1.ising-cft/main.ipynb) -[![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/quantum1d/1.ising-cft/main.ipynb) -[![](https://img.shields.io/badge/download-project-orange)](https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/QuantumKitHub/MPSKit.jl/examples/tree/gh-pages/dev/examples/quantum1d/1.ising-cft) - -# The Ising CFT spectrum - -This tutorial is meant to show the finite size CFT spectrum for the quantum Ising model. We -do this by first employing an exact diagonalization technique, and then extending the -analysis to larger system sizes through the use of MPS techniques. - -````julia -using MPSKit, TensorKit, Plots, KrylovKit -using .ExampleModels -using LinearAlgebra: eigvals, diagm, Hermitian -```` - -The Hamiltonian is defined on a finite lattice with periodic boundary conditions, -which can be implemented as follows: - -````julia -L = 12 -H = periodic_boundary_conditions(transverse_field_ising(), L) -```` - -```` -12-site FiniteMPOHamiltonian(ComplexF64, TensorKit.ComplexSpace) with maximal dimension 6: -┬─[12]─ ℂ^2 -│ (ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ⋯ ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1) -┼─[11]─ ℂ^2 -│ (ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ⋯ ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1) -┼─[10]─ ℂ^2 -│ (ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ⋯ ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1) -┼─[9]─ ℂ^2 -│ (ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ⋯ ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1) -│ ⋮ -│ (ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ⋯ ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1) -┼─[3]─ ℂ^2 -│ (ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ⋯ ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1) -┼─[2]─ ℂ^2 -│ (ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ⋯ ⊞ ℂ^1 ⊞ ℂ^1 ⊞ ℂ^1) -┴─[1]─ ℂ^2 - -```` - -## Exact diagonalisation - -In MPSKit, there is support for exact diagonalisation by leveraging the fact that applying -the Hamiltonian to an untruncated MPS will result in an effective Hamiltonian on the center -site which implements the action of the entire Hamiltonian. Thus, optimizing the middle -tensor is equivalent to optimixing a state in the entire Hilbert space, as all other tensors -are just unitary matrices that mix the basis. - -````julia -energies, states = exact_diagonalization(H; num = 18, alg = Lanczos(; krylovdim = 200)); -plot( - real.(energies); - seriestype = :scatter, legend = false, ylabel = "energy", xlabel = "#eigenvalue" -) -```` - -![](figure-1.png) - -!!! note "Krylov dimension" - Note that we have specified a large Krylov dimension as degenerate eigenvalues are - notoriously difficult for iterative methods. - -## Extracting momentum - -Given a state, it is possible to assign a momentum label -through the use of the translation operator. This operator can be defined in MPO language -either diagramatically as - -```@raw html -translation operator -``` - -or in the code as: - -````julia -function O_shift(L) - I = id(ComplexF64, ℂ^2) - @tensor O[W S; N E] := I[W; N] * I[S; E] - return periodic_boundary_conditions(InfiniteMPO([O]), L) -end -```` - -```` -O_shift (generic function with 1 method) -```` - -We can then calculate the momentum of the ground state as the expectation value of this -operator. However, there is a subtlety because of the degeneracies in the energy -eigenvalues. The eigensolver will find an orthonormal basis within each energy subspace, but -this basis is not necessarily a basis of eigenstates of the translation operator. In order -to fix this, we diagonalize the translation operator within each energy subspace. -The resulting energy levels have one-to-one correspondence to the operators in CFT, where -the momentum is related to their conformal spin as $P_n = \frac{2\pi}{L}S_n$. - -````julia -function fix_degeneracies(basis) - L = length(basis[1]) - M = zeros(ComplexF64, length(basis), length(basis)) - T = O_shift(L) - for j in eachindex(basis), i in eachindex(basis) - M[i, j] = dot(basis[i], T, basis[j]) - end - - vals = eigvals(M) - return angle.(vals) -end - -momenta = Float64[] -append!(momenta, fix_degeneracies(states[1:1])) -append!(momenta, fix_degeneracies(states[2:2])) -append!(momenta, fix_degeneracies(states[3:3])) -append!(momenta, fix_degeneracies(states[4:5])) -append!(momenta, fix_degeneracies(states[6:9])) -append!(momenta, fix_degeneracies(states[10:11])) -append!(momenta, fix_degeneracies(states[12:12])) -append!(momenta, fix_degeneracies(states[13:16])) -append!(momenta, fix_degeneracies(states[17:18])) -```` - -```` -18-element Vector{Float64}: - 3.4795649708338624e-17 - 2.1864186746744933e-17 - 1.0296022899642392e-18 - -0.5235987755982991 - 0.5235987755982987 - 1.047197551196598 - -1.0471975511965972 - -0.5235987755982991 - 0.5235987755982987 - 1.047197551196598 - -1.0471975511965979 - 1.0708451244677254e-16 - -1.570796326794897 - 1.5707963267948968 - -1.0471975511965987 - 1.0471975511965976 - 1.5707963267948968 - -1.5707963267948966 -```` - -We can compute the scaling dimensions $\Delta_n$ of the operators in the CFT from the -energy gap of the corresponding excitations as $E_n - E_0 = \frac{2\pi v}{L} \Delta_n$, -where $v = 2$. If we plot these scaling dimensions against the conformal spin $S_n$ from -above, we retrieve the familiar spectrum of the Ising CFT. - -````julia -v = 2.0 -Δ = real.(energies[1:18] .- energies[1]) ./ (2π * v / L) -S = momenta ./ (2π / L) - -p = plot( - S, real.(Δ); - seriestype = :scatter, xlabel = "conformal spin (S)", ylabel = "scaling dimension (Δ)", - legend = false -) -vline!(p, -3:3; color = "gray", linestyle = :dash) -hline!(p, [0, 1 / 8, 1, 9 / 8, 2, 17 / 8]; color = "gray", linestyle = :dash) -p -```` - -![](figure-2.png) - -## Finite bond dimension - -If we limit the maximum bond dimension of the MPS, we get an approximate solution, but we -can reach higher system sizes. - -````julia -L_mps = 20 -H_mps = periodic_boundary_conditions(transverse_field_ising(), L_mps) -D = 64 -ψ, envs, info = find_groundstate(FiniteMPS(L_mps, ℂ^2, ℂ^D), H_mps, DMRG()); -```` - -```` -[ Info: DMRG init: obj = -1.943771844888e+01 err = 1.0000e+00 -[ Info: DMRG 1: obj = -2.548965337401e+01 err = 1.5606788288e-02 time = 1.50 sec -[ Info: DMRG 2: obj = -2.549098968590e+01 err = 4.2297557187e-04 time = 0.79 sec -[ Info: DMRG 3: obj = -2.549098968636e+01 err = 1.0167456345e-06 time = 0.41 sec -[ Info: DMRG 4: obj = -2.549098968636e+01 err = 2.4867732855e-08 time = 0.30 sec -[ Info: DMRG 5: obj = -2.549098968636e+01 err = 7.4062863713e-09 time = 0.47 sec -[ Info: DMRG 6: obj = -2.549098968636e+01 err = 3.8191851769e-09 time = 1.41 sec -[ Info: DMRG 7: obj = -2.549098968636e+01 err = 2.6647921898e-09 time = 0.31 sec -[ Info: DMRG 8: obj = -2.549098968636e+01 err = 2.1927504209e-09 time = 0.31 sec -[ Info: DMRG 9: obj = -2.549098968636e+01 err = 1.9177066057e-09 time = 0.29 sec -[ Info: DMRG 10: obj = -2.549098968636e+01 err = 1.6969297883e-09 time = 0.29 sec -[ Info: DMRG 11: obj = -2.549098968636e+01 err = 1.6032558073e-09 time = 0.28 sec -[ Info: DMRG 12: obj = -2.549098968636e+01 err = 1.6496928155e-09 time = 0.30 sec -[ Info: DMRG 13: obj = -2.549098968636e+01 err = 1.6728447005e-09 time = 0.30 sec -[ Info: DMRG 14: obj = -2.549098968636e+01 err = 1.6689081338e-09 time = 0.28 sec -[ Info: DMRG 15: obj = -2.549098968636e+01 err = 1.6377730200e-09 time = 0.32 sec -[ Info: DMRG 16: obj = -2.549098968636e+01 err = 1.5799295265e-09 time = 0.31 sec -[ Info: DMRG 17: obj = -2.549098968636e+01 err = 1.5009049285e-09 time = 0.30 sec -[ Info: DMRG 18: obj = -2.549098968636e+01 err = 1.4066215762e-09 time = 0.30 sec -[ Info: DMRG 19: obj = -2.549098968636e+01 err = 1.5081205257e-09 time = 0.29 sec -[ Info: DMRG 20: obj = -2.549098968636e+01 err = 1.6810911856e-09 time = 0.30 sec -[ Info: DMRG 21: obj = -2.549098968636e+01 err = 1.8220210461e-09 time = 0.29 sec -[ Info: DMRG 22: obj = -2.549098968636e+01 err = 1.9139702907e-09 time = 0.29 sec -[ Info: DMRG 23: obj = -2.549098968636e+01 err = 1.9442171571e-09 time = 0.28 sec -[ Info: DMRG 24: obj = -2.549098968636e+01 err = 1.9087464599e-09 time = 1.38 sec -[ Info: DMRG 25: obj = -2.549098968636e+01 err = 1.8142533073e-09 time = 0.28 sec -[ Info: DMRG 26: obj = -2.549098968636e+01 err = 1.6750776273e-09 time = 0.28 sec -[ Info: DMRG 27: obj = -2.549098968636e+01 err = 1.5098703467e-09 time = 0.30 sec -[ Info: DMRG 28: obj = -2.549098968636e+01 err = 1.3360360659e-09 time = 0.20 sec -[ Info: DMRG 29: obj = -2.549098968636e+01 err = 1.1657760958e-09 time = 0.16 sec -[ Info: DMRG 30: obj = -2.549098968636e+01 err = 1.0073242984e-09 time = 0.18 sec -[ Info: DMRG 31: obj = -2.549098968636e+01 err = 8.6474907924e-10 time = 0.24 sec -[ Info: DMRG 32: obj = -2.549098968636e+01 err = 7.3927413439e-10 time = 0.28 sec -[ Info: DMRG 33: obj = -2.549098968636e+01 err = 6.3048094041e-10 time = 0.28 sec -[ Info: DMRG 34: obj = -2.549098968636e+01 err = 5.3702960640e-10 time = 0.29 sec -[ Info: DMRG 35: obj = -2.549098968636e+01 err = 4.5722500117e-10 time = 0.30 sec -[ Info: DMRG 36: obj = -2.549098968636e+01 err = 3.8930325414e-10 time = 0.29 sec -[ Info: DMRG 37: obj = -2.549098968636e+01 err = 3.3159981587e-10 time = 0.28 sec -[ Info: DMRG 38: obj = -2.549098968636e+01 err = 2.8261201613e-10 time = 0.29 sec -[ Info: DMRG 39: obj = -2.549098968636e+01 err = 2.4102587357e-10 time = 0.34 sec -[ Info: DMRG 40: obj = -2.549098968636e+01 err = 2.0570571140e-10 time = 0.30 sec -[ Info: DMRG 41: obj = -2.549098968636e+01 err = 1.7569292481e-10 time = 0.29 sec -[ Info: DMRG 42: obj = -2.549098968636e+01 err = 1.5016721096e-10 time = 0.28 sec -[ Info: DMRG 43: obj = -2.549098968636e+01 err = 1.2843977109e-10 time = 0.31 sec -[ Info: DMRG 44: obj = -2.549098968636e+01 err = 1.0992971958e-10 time = 0.44 sec -[ Info: DMRG conv 45: obj = -2.549098968636e+01 err = 9.4147444802e-11 time = 18.09 sec - -```` - -Excitations on top of the ground state can be found through the use of the quasiparticle -ansatz. This returns quasiparticle states, which can be converted to regular `FiniteMPS` -objects. - -````julia -E_ex, qps = excitations(H_mps, QuasiparticleAnsatz(), ψ, envs; num = 18) -states_mps = vcat(ψ, map(qp -> convert(FiniteMPS, qp), qps)) -energies_mps = map(x -> expectation_value(x, H_mps), states_mps) - -momenta_mps = Float64[] -append!(momenta_mps, fix_degeneracies(states_mps[1:1])) -append!(momenta_mps, fix_degeneracies(states_mps[2:2])) -append!(momenta_mps, fix_degeneracies(states_mps[3:3])) -append!(momenta_mps, fix_degeneracies(states_mps[4:5])) -append!(momenta_mps, fix_degeneracies(states_mps[6:9])) -append!(momenta_mps, fix_degeneracies(states_mps[10:11])) -append!(momenta_mps, fix_degeneracies(states_mps[12:12])) -append!(momenta_mps, fix_degeneracies(states_mps[13:16])) -append!(momenta_mps, fix_degeneracies(states_mps[17:18])) - -v = 2.0 -Δ_mps = real.(energies_mps[1:18] .- energies_mps[1]) ./ (2π * v / L_mps) -S_mps = momenta_mps ./ (2π / L_mps) - -p_mps = plot( - S_mps, real.(Δ_mps); - seriestype = :scatter, xlabel = "conformal spin (S)", - ylabel = "scaling dimension (Δ)", legend = false -) -vline!(p_mps, -3:3; color = "gray", linestyle = :dash) -hline!(p_mps, [0, 1 / 8, 1, 9 / 8, 2, 17 / 8]; color = "gray", linestyle = :dash) -p_mps -```` - -![](figure-3.png) - ---- - -*This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* - diff --git a/docs/src/examples/quantum1d/4.xxz-heisenberg/figure-1.png b/docs/src/examples/quantum1d/4.xxz-heisenberg/figure-1.png deleted file mode 100644 index 3a2bcd6d4..000000000 Binary files a/docs/src/examples/quantum1d/4.xxz-heisenberg/figure-1.png and /dev/null differ diff --git a/docs/src/examples/quantum1d/4.xxz-heisenberg/figure-2.png b/docs/src/examples/quantum1d/4.xxz-heisenberg/figure-2.png deleted file mode 100644 index f25ecb820..000000000 Binary files a/docs/src/examples/quantum1d/4.xxz-heisenberg/figure-2.png and /dev/null differ diff --git a/docs/src/examples/quantum1d/4.xxz-heisenberg/index.md b/docs/src/examples/quantum1d/4.xxz-heisenberg/index.md deleted file mode 100644 index b7192038c..000000000 --- a/docs/src/examples/quantum1d/4.xxz-heisenberg/index.md +++ /dev/null @@ -1,373 +0,0 @@ -```@meta -EditURL = "../../../../../examples/quantum1d/4.xxz-heisenberg/main.jl" -``` - -[![](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/quantum1d/4.xxz-heisenberg/main.ipynb) -[![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/quantum1d/4.xxz-heisenberg/main.ipynb) -[![](https://img.shields.io/badge/download-project-orange)](https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/QuantumKitHub/MPSKit.jl/examples/tree/gh-pages/dev/examples/quantum1d/4.xxz-heisenberg) - -# The XXZ model - -In this file we will give step by step instructions on how to analyze the spin 1/2 XXZ model. -The necessary packages to follow this tutorial are: - -````julia -using MPSKit, TensorKit, Plots -using .ExampleModels -```` - -## Failure - -First we should define the Hamiltonian we want to work with. -Then we specify an initial guess, which we then further optimize. -Working directly in the thermodynamic limit, this is achieved as follows: - -````julia -H = heisenberg_XXX(; spin = 1 // 2) -```` - -```` -1-site InfiniteMPOHamiltonian(ComplexF64, TensorKit.ComplexSpace) with maximal dimension 5: -| ⋮ -| (ℂ^1 ⊞ ℂ^3 ⊞ ℂ^1) -┼─[1]─ ℂ^2 -│ (ℂ^1 ⊞ ℂ^3 ⊞ ℂ^1) -| ⋮ - -```` - -We then need an initial state, which we shall later optimize. In this example we work directly in the thermodynamic limit. - -````julia -state = InfiniteMPS(2, 20) -```` - -```` -1-site InfiniteMPS(ComplexF64, TensorKit.ComplexSpace) with maximal dimension 20: -| ⋮ -| ℂ^20 -├─[1]─ ℂ^2 -│ ℂ^20 -| ⋮ - -```` - -The ground state can then be found by calling `find_groundstate`. - -````julia -groundstate, cache, info = find_groundstate(state, H, VUMPS()); -info -```` - -```` -AlgorithmInfo: - converged = false after 200 iterations - galerkin = 0.362154 - -```` - -As you can see, VUMPS struggles to converge. -On its own, that is already quite curious. -Maybe we can do better using another algorithm, such as gradient descent. - -````julia -groundstate, cache, info = find_groundstate(state, H, GradientGrassmann(; maxiter = 20)); -info -```` - -```` -AlgorithmInfo: - converged = false after 20 iterations - gradientnorm = 0.0112804 - -```` - -Convergence is quite slow and even fails after sufficiently many iterations. -To understand why, we can look at the transfer matrix spectrum. - -````julia -transferplot(groundstate, groundstate) -```` - -![](figure-1.png) - -We can clearly see multiple eigenvalues close to the unit circle. -Our state is close to being non-injective, and represents the sum of multiple injective states. -This is numerically very problematic, but also indicates that we used an incorrect ansatz to approximate the groundstate. -We should retry with a larger unit cell. - -## Success - -Let's initialize a different initial state, this time with a 2-site unit cell: - -````julia -state = InfiniteMPS(fill(2, 2), fill(20, 2)) -```` - -```` -2-site InfiniteMPS(ComplexF64, TensorKit.ComplexSpace) with maximal dimension 20: -| ⋮ -| ℂ^20 -├─[2]─ ℂ^2 -│ ℂ^20 -├─[1]─ ℂ^2 -│ ℂ^20 -| ⋮ - -```` - -In MPSKit, we require that the periodicity of the Hamiltonian equals that of the state it is applied to. -This is not a big obstacle, you can simply repeat the original Hamiltonian. -Alternatively, our model helper can construct the Hamiltonian directly on a two-site unit cell. - -````julia -# H2 = repeat(H, 2); -- copies the one-site version -H2 = heisenberg_XXX(ComplexF64, Trivial; unitcell = 2, spin = 1 // 2) -groundstate, envs, info = find_groundstate( - state, H2, VUMPS(; maxiter = 100, tol = 1.0e-12) -); -```` - -```` -[ Info: VUMPS init: obj = +4.997527111444e-01 err = 3.2696e-02 -[ Info: VUMPS 1: obj = -4.467165998476e-01 err = 3.1609479029e-01 time = 0.02 sec -[ Info: VUMPS 2: obj = -8.771683852570e-01 err = 6.2050493215e-02 time = 0.01 sec -[ Info: VUMPS 3: obj = -8.851498316480e-01 err = 1.1961437282e-02 time = 0.01 sec -[ Info: VUMPS 4: obj = -8.859041509393e-01 err = 6.3855220428e-03 time = 0.01 sec -[ Info: VUMPS 5: obj = -8.861046661398e-01 err = 4.2618759637e-03 time = 0.01 sec -[ Info: VUMPS 6: obj = -8.861780680439e-01 err = 2.8925460191e-03 time = 0.13 sec -[ Info: VUMPS 7: obj = -8.862081622654e-01 err = 2.2292422649e-03 time = 0.02 sec -[ Info: VUMPS 8: obj = -8.862224756657e-01 err = 1.6435394411e-03 time = 0.02 sec -[ Info: VUMPS 9: obj = -8.862294034930e-01 err = 1.3092870547e-03 time = 0.02 sec -[ Info: VUMPS 10: obj = -8.862329064156e-01 err = 9.9464595759e-04 time = 0.02 sec -[ Info: VUMPS 11: obj = -8.862346804992e-01 err = 8.0850406574e-04 time = 0.02 sec -[ Info: VUMPS 12: obj = -8.862355904749e-01 err = 6.4946824659e-04 time = 0.02 sec -[ Info: VUMPS 13: obj = -8.862360590666e-01 err = 5.4465159459e-04 time = 0.02 sec -[ Info: VUMPS 14: obj = -8.862363024838e-01 err = 4.6490637106e-04 time = 0.02 sec -[ Info: VUMPS 15: obj = -8.862364302780e-01 err = 4.0282057210e-04 time = 0.02 sec -[ Info: VUMPS 16: obj = -8.862364987557e-01 err = 3.5830647316e-04 time = 0.02 sec -[ Info: VUMPS 17: obj = -8.862365362487e-01 err = 3.1735310901e-04 time = 0.02 sec -[ Info: VUMPS 18: obj = -8.862365577473e-01 err = 2.8785456855e-04 time = 0.02 sec -[ Info: VUMPS 19: obj = -8.862365705090e-01 err = 2.5781877596e-04 time = 0.02 sec -[ Info: VUMPS 20: obj = -8.862365786770e-01 err = 2.3555411931e-04 time = 0.02 sec -[ Info: VUMPS 21: obj = -8.862365840895e-01 err = 2.1202440035e-04 time = 0.02 sec -[ Info: VUMPS 22: obj = -8.862365879952e-01 err = 1.9412564250e-04 time = 0.02 sec -[ Info: VUMPS 23: obj = -8.862365908485e-01 err = 1.7507326697e-04 time = 0.02 sec -[ Info: VUMPS 24: obj = -8.862365930912e-01 err = 1.6034513739e-04 time = 0.02 sec -[ Info: VUMPS 25: obj = -8.862365948285e-01 err = 1.4470698602e-04 time = 0.10 sec -[ Info: VUMPS 26: obj = -8.862365962571e-01 err = 1.3251239826e-04 time = 0.02 sec -[ Info: VUMPS 27: obj = -8.862365973948e-01 err = 1.1960733073e-04 time = 0.02 sec -[ Info: VUMPS 28: obj = -8.862365983500e-01 err = 1.0950376923e-04 time = 0.02 sec -[ Info: VUMPS 29: obj = -8.862365991198e-01 err = 9.8841277504e-05 time = 0.02 sec -[ Info: VUMPS 30: obj = -8.862365997728e-01 err = 9.0477029620e-05 time = 0.02 sec -[ Info: VUMPS 31: obj = -8.862366003020e-01 err = 8.1667414680e-05 time = 0.02 sec -[ Info: VUMPS 32: obj = -8.862366007537e-01 err = 7.4749996341e-05 time = 0.02 sec -[ Info: VUMPS 33: obj = -8.862366011212e-01 err = 6.7475286046e-05 time = 0.02 sec -[ Info: VUMPS 34: obj = -8.862366014364e-01 err = 6.1759302086e-05 time = 0.02 sec -[ Info: VUMPS 35: obj = -8.862366016936e-01 err = 5.5756232982e-05 time = 0.02 sec -[ Info: VUMPS 36: obj = -8.862366019155e-01 err = 5.1036052619e-05 time = 0.02 sec -[ Info: VUMPS 37: obj = -8.862366020972e-01 err = 4.6085571441e-05 time = 0.02 sec -[ Info: VUMPS 38: obj = -8.862366022548e-01 err = 4.2189530975e-05 time = 0.02 sec -[ Info: VUMPS 39: obj = -8.862366023846e-01 err = 3.8109078449e-05 time = 0.02 sec -[ Info: VUMPS 40: obj = -8.862366024978e-01 err = 3.4894596256e-05 time = 0.02 sec -[ Info: VUMPS 41: obj = -8.862366025917e-01 err = 3.1532851418e-05 time = 0.02 sec -[ Info: VUMPS 42: obj = -8.862366026742e-01 err = 2.8881208593e-05 time = 0.05 sec -[ Info: VUMPS 43: obj = -8.862366027431e-01 err = 2.6112378900e-05 time = 0.02 sec -[ Info: VUMPS 44: obj = -8.862366028041e-01 err = 2.3925599243e-05 time = 0.02 sec -[ Info: VUMPS 45: obj = -8.862366028556e-01 err = 2.1645498079e-05 time = 0.02 sec -[ Info: VUMPS 46: obj = -8.862366029016e-01 err = 1.9842772827e-05 time = 0.02 sec -[ Info: VUMPS 47: obj = -8.862366029409e-01 err = 1.7965352398e-05 time = 0.02 sec -[ Info: VUMPS 48: obj = -8.862366029763e-01 err = 1.6479776112e-05 time = 0.02 sec -[ Info: VUMPS 49: obj = -8.862366030070e-01 err = 1.4934164897e-05 time = 0.02 sec -[ Info: VUMPS 50: obj = -8.862366030349e-01 err = 1.3711041185e-05 time = 0.02 sec -[ Info: VUMPS 51: obj = -8.862366030594e-01 err = 1.2438715217e-05 time = 0.02 sec -[ Info: VUMPS 52: obj = -8.862366030820e-01 err = 1.1432932435e-05 time = 0.02 sec -[ Info: VUMPS 53: obj = -8.862366031022e-01 err = 1.0385875308e-05 time = 0.02 sec -[ Info: VUMPS 54: obj = -8.862366031209e-01 err = 9.5604213133e-06 time = 0.02 sec -[ Info: VUMPS 55: obj = -8.862366031379e-01 err = 8.6992460003e-06 time = 0.04 sec -[ Info: VUMPS 56: obj = -8.862366031539e-01 err = 8.0238879246e-06 time = 0.02 sec -[ Info: VUMPS 57: obj = -8.862366031686e-01 err = 7.3163045808e-06 time = 0.02 sec -[ Info: VUMPS 58: obj = -8.862366031826e-01 err = 6.7662395359e-06 time = 0.02 sec -[ Info: VUMPS 59: obj = -8.862366031957e-01 err = 6.1858182842e-06 time = 0.02 sec -[ Info: VUMPS 60: obj = -8.862366032082e-01 err = 5.7405851073e-06 time = 0.02 sec -[ Info: VUMPS 61: obj = -8.862366032201e-01 err = 5.2657759313e-06 time = 0.02 sec -[ Info: VUMPS 62: obj = -8.862366032316e-01 err = 4.9086532906e-06 time = 0.02 sec -[ Info: VUMPS 63: obj = -8.862366032426e-01 err = 4.5216221699e-06 time = 0.02 sec -[ Info: VUMPS 64: obj = -8.862366032533e-01 err = 4.2388065454e-06 time = 0.02 sec -[ Info: VUMPS 65: obj = -8.862366032637e-01 err = 3.9249530862e-06 time = 0.02 sec -[ Info: VUMPS 66: obj = -8.862366032738e-01 err = 3.7047993738e-06 time = 0.04 sec -[ Info: VUMPS 67: obj = -8.862366032837e-01 err = 3.4517513192e-06 time = 0.02 sec -[ Info: VUMPS 68: obj = -8.862366032935e-01 err = 3.2843770942e-06 time = 0.02 sec -[ Info: VUMPS 69: obj = -8.862366033031e-01 err = 3.0817221596e-06 time = 0.02 sec -[ Info: VUMPS 70: obj = -8.862366033126e-01 err = 2.9584112144e-06 time = 0.02 sec -[ Info: VUMPS 71: obj = -8.862366033220e-01 err = 2.7971587484e-06 time = 0.02 sec -[ Info: VUMPS 72: obj = -8.862366033313e-01 err = 2.7102153339e-06 time = 0.02 sec -[ Info: VUMPS 73: obj = -8.862366033406e-01 err = 2.5825604765e-06 time = 0.02 sec -[ Info: VUMPS 74: obj = -8.862366033498e-01 err = 2.5250442735e-06 time = 0.02 sec -[ Info: VUMPS 75: obj = -8.862366033589e-01 err = 2.4242363359e-06 time = 0.04 sec -[ Info: VUMPS 76: obj = -8.862366033681e-01 err = 2.3900277294e-06 time = 0.02 sec -[ Info: VUMPS 77: obj = -8.862366033772e-01 err = 2.3102610808e-06 time = 0.02 sec -[ Info: VUMPS 78: obj = -8.862366033863e-01 err = 2.2940867965e-06 time = 0.02 sec -[ Info: VUMPS 79: obj = -8.862366033954e-01 err = 2.2305152062e-06 time = 0.02 sec -[ Info: VUMPS 80: obj = -8.862366034046e-01 err = 2.2279952080e-06 time = 0.02 sec -[ Info: VUMPS 81: obj = -8.862366034137e-01 err = 2.1766874277e-06 time = 0.02 sec -[ Info: VUMPS 82: obj = -8.862366034229e-01 err = 2.1843045604e-06 time = 0.02 sec -[ Info: VUMPS 83: obj = -8.862366034321e-01 err = 2.1421521997e-06 time = 0.02 sec -[ Info: VUMPS 84: obj = -8.862366034413e-01 err = 2.1571782976e-06 time = 0.02 sec -[ Info: VUMPS 85: obj = -8.862366034506e-01 err = 2.1217886209e-06 time = 0.04 sec -[ Info: VUMPS 86: obj = -8.862366034599e-01 err = 2.1421645111e-06 time = 0.02 sec -[ Info: VUMPS 87: obj = -8.862366034693e-01 err = 2.1117287888e-06 time = 0.02 sec -[ Info: VUMPS 88: obj = -8.862366034787e-01 err = 2.1359334516e-06 time = 0.02 sec -[ Info: VUMPS 89: obj = -8.862366034881e-01 err = 2.1182010907e-06 time = 0.02 sec -[ Info: VUMPS 90: obj = -8.862366034976e-01 err = 2.1360357707e-06 time = 0.02 sec -[ Info: VUMPS 91: obj = -8.862366035072e-01 err = 2.1293906978e-06 time = 0.02 sec -[ Info: VUMPS 92: obj = -8.862366035168e-01 err = 2.1406884482e-06 time = 0.02 sec -[ Info: VUMPS 93: obj = -8.862366035265e-01 err = 2.1413862999e-06 time = 0.02 sec -[ Info: VUMPS 94: obj = -8.862366035362e-01 err = 2.1486037196e-06 time = 0.04 sec -[ Info: VUMPS 95: obj = -8.862366035460e-01 err = 2.1540387251e-06 time = 0.02 sec -[ Info: VUMPS 96: obj = -8.862366035558e-01 err = 2.1588561110e-06 time = 0.02 sec -[ Info: VUMPS 97: obj = -8.862366035657e-01 err = 2.1672377887e-06 time = 0.02 sec -[ Info: VUMPS 98: obj = -8.862366035757e-01 err = 2.1707822036e-06 time = 0.02 sec -[ Info: VUMPS 99: obj = -8.862366035858e-01 err = 2.1808977510e-06 time = 0.02 sec -┌ Warning: VUMPS cancel 100: obj = -8.862366035959e-01 err = 2.1839074149e-06 time = 2.16 sec -└ @ MPSKit ~/Projects/MPSKit.jl/ld-release/src/algorithms/groundstate/vumps.jl:96 - -```` - -We get convergence, but it takes an enormous amount of iterations. -The reason behind this becomes more obvious at higher bond dimensions: - -````julia -groundstate, envs, info = find_groundstate( - state, H2, IDMRG2(; trunc = truncrank(50), maxiter = 20, tol = 1.0e-12) -); -entanglementplot(groundstate) -```` - -![](figure-2.png) - -We see that some eigenvalues clearly belong to a group, and are almost degenerate. -This implies 2 things: -- there is superfluous information, if those eigenvalues are the same anyway -- poor convergence if we cut off within such a subspace - -It are precisely those problems that we can solve by using symmetries. - -## Symmetries - -The XXZ Heisenberg Hamiltonian is SU(2) symmetric and we can exploit this to greatly speed up the simulation. - -It is cumbersome to construct symmetric Hamiltonians, but luckily SU(2) symmetric XXZ is already implemented: - -````julia -H2 = heisenberg_XXX(ComplexF64, SU2Irrep; unitcell = 2, spin = 1 // 2); -```` - -Our initial state should also be SU(2) symmetric. -It now becomes apparent why we have to use a two-site periodic state. -The physical space carries a half-integer charge and the first tensor maps the first `virtual_space ⊗ the physical_space` to the second `virtual_space`. -Half-integer virtual charges will therefore map only to integer charges, and vice versa. -The staggering thus happens on the virtual level. - -An alternative constructor for the initial state is - -````julia -P = Rep[SU₂](1 // 2 => 1) -V1 = Rep[SU₂](1 // 2 => 10, 3 // 2 => 5, 5 // 2 => 2) -V2 = Rep[SU₂](0 => 15, 1 => 10, 2 => 5) -state = InfiniteMPS([P, P], [V1, V2]); -```` - -```` -┌ Warning: Constructing an MPS from tensors that are not full rank -└ @ MPSKit ~/Projects/MPSKit.jl/ld-release/src/states/infinitemps.jl:188 - -```` - -Even though the bond dimension is higher than in the example without symmetry, convergence is reached much faster: - -````julia -println(dim(V1)) -println(dim(V2)) -groundstate, cache, info = find_groundstate(state, H2, VUMPS(; maxiter = 400, tol = 1.0e-12)); -```` - -```` -52 -70 -[ Info: VUMPS init: obj = +1.095066760899e-02 err = 3.8232e-01 -[ Info: VUMPS 1: obj = -8.684099588789e-01 err = 1.4192144156e-01 time = 0.04 sec -[ Info: VUMPS 2: obj = -8.856573419610e-01 err = 9.8872106961e-03 time = 0.03 sec -[ Info: VUMPS 3: obj = -8.861205883550e-01 err = 3.1733701748e-03 time = 0.02 sec -[ Info: VUMPS 4: obj = -8.862275964002e-01 err = 1.5516807918e-03 time = 0.02 sec -[ Info: VUMPS 5: obj = -8.862625967019e-01 err = 1.0267980767e-03 time = 0.03 sec -[ Info: VUMPS 6: obj = -8.862756220945e-01 err = 1.1282651615e-03 time = 0.03 sec -[ Info: VUMPS 7: obj = -8.862824937091e-01 err = 6.9628740468e-04 time = 0.04 sec -[ Info: VUMPS 8: obj = -8.862853415694e-01 err = 5.0842789444e-04 time = 0.04 sec -[ Info: VUMPS 9: obj = -8.862866503123e-01 err = 3.8421438111e-04 time = 0.04 sec -[ Info: VUMPS 10: obj = -8.862872825540e-01 err = 2.8867805561e-04 time = 0.04 sec -[ Info: VUMPS 11: obj = -8.862875933596e-01 err = 2.1457518770e-04 time = 0.04 sec -[ Info: VUMPS 12: obj = -8.862877471486e-01 err = 1.5782954819e-04 time = 0.04 sec -[ Info: VUMPS 13: obj = -8.862878233469e-01 err = 1.1508389249e-04 time = 0.04 sec -[ Info: VUMPS 14: obj = -8.862878610880e-01 err = 8.3336988600e-05 time = 0.04 sec -[ Info: VUMPS 15: obj = -8.862878797702e-01 err = 6.0038192052e-05 time = 0.04 sec -[ Info: VUMPS 16: obj = -8.862878890168e-01 err = 4.3094839313e-05 time = 0.04 sec -[ Info: VUMPS 17: obj = -8.862878935931e-01 err = 3.0849559494e-05 time = 0.04 sec -[ Info: VUMPS 18: obj = -8.862878958589e-01 err = 2.2040393489e-05 time = 0.04 sec -[ Info: VUMPS 19: obj = -8.862878969816e-01 err = 1.5724344210e-05 time = 0.04 sec -[ Info: VUMPS 20: obj = -8.862878975383e-01 err = 1.1207061829e-05 time = 0.04 sec -[ Info: VUMPS 21: obj = -8.862878978145e-01 err = 7.9802923961e-06 time = 0.05 sec -[ Info: VUMPS 22: obj = -8.862878979518e-01 err = 5.6786905881e-06 time = 0.04 sec -[ Info: VUMPS 23: obj = -8.862878980200e-01 err = 4.0384681808e-06 time = 0.04 sec -[ Info: VUMPS 24: obj = -8.862878980539e-01 err = 2.8706728032e-06 time = 0.05 sec -[ Info: VUMPS 25: obj = -8.862878980708e-01 err = 2.0396825400e-06 time = 0.04 sec -[ Info: VUMPS 26: obj = -8.862878980792e-01 err = 1.4486713999e-06 time = 0.04 sec -[ Info: VUMPS 27: obj = -8.862878980834e-01 err = 1.0285324661e-06 time = 0.04 sec -[ Info: VUMPS 28: obj = -8.862878980855e-01 err = 7.2999868426e-07 time = 0.04 sec -[ Info: VUMPS 29: obj = -8.862878980866e-01 err = 5.1797460190e-07 time = 0.04 sec -[ Info: VUMPS 30: obj = -8.862878980871e-01 err = 3.6739836299e-07 time = 0.25 sec -[ Info: VUMPS 31: obj = -8.862878980874e-01 err = 2.6052111260e-07 time = 0.04 sec -[ Info: VUMPS 32: obj = -8.862878980875e-01 err = 1.8468688278e-07 time = 0.04 sec -[ Info: VUMPS 33: obj = -8.862878980876e-01 err = 1.3089495633e-07 time = 0.04 sec -[ Info: VUMPS 34: obj = -8.862878980876e-01 err = 9.2749261799e-08 time = 0.04 sec -[ Info: VUMPS 35: obj = -8.862878980876e-01 err = 6.5706029833e-08 time = 0.04 sec -[ Info: VUMPS 36: obj = -8.862878980877e-01 err = 4.6538615080e-08 time = 0.04 sec -[ Info: VUMPS 37: obj = -8.862878980877e-01 err = 3.2956500195e-08 time = 0.04 sec -[ Info: VUMPS 38: obj = -8.862878980877e-01 err = 2.3334242105e-08 time = 0.04 sec -[ Info: VUMPS 39: obj = -8.862878980877e-01 err = 1.6518729926e-08 time = 0.04 sec -[ Info: VUMPS 40: obj = -8.862878980877e-01 err = 1.1691719289e-08 time = 0.04 sec -[ Info: VUMPS 41: obj = -8.862878980877e-01 err = 8.2744103020e-09 time = 0.04 sec -[ Info: VUMPS 42: obj = -8.862878980877e-01 err = 5.8551948016e-09 time = 0.04 sec -[ Info: VUMPS 43: obj = -8.862878980877e-01 err = 4.1428084059e-09 time = 0.04 sec -[ Info: VUMPS 44: obj = -8.862878980877e-01 err = 2.9308969106e-09 time = 0.04 sec -[ Info: VUMPS 45: obj = -8.862878980877e-01 err = 2.0733038404e-09 time = 0.04 sec -[ Info: VUMPS 46: obj = -8.862878980877e-01 err = 1.4665094165e-09 time = 0.04 sec -[ Info: VUMPS 47: obj = -8.862878980877e-01 err = 1.0372149373e-09 time = 0.04 sec -[ Info: VUMPS 48: obj = -8.862878980877e-01 err = 7.3354405997e-10 time = 0.04 sec -[ Info: VUMPS 49: obj = -8.862878980877e-01 err = 5.1872860630e-10 time = 0.04 sec -[ Info: VUMPS 50: obj = -8.862878980877e-01 err = 3.6679629721e-10 time = 0.04 sec -[ Info: VUMPS 51: obj = -8.862878980878e-01 err = 2.5934803265e-10 time = 0.04 sec -[ Info: VUMPS 52: obj = -8.862878980878e-01 err = 1.8336362201e-10 time = 0.04 sec -[ Info: VUMPS 53: obj = -8.862878980878e-01 err = 1.2963594600e-10 time = 0.04 sec -[ Info: VUMPS 54: obj = -8.862878980878e-01 err = 9.1643274941e-11 time = 0.04 sec -[ Info: VUMPS 55: obj = -8.862878980878e-01 err = 6.4784009585e-11 time = 0.04 sec -[ Info: VUMPS 56: obj = -8.862878980878e-01 err = 4.5794931902e-11 time = 0.04 sec -[ Info: VUMPS 57: obj = -8.862878980878e-01 err = 3.2368796597e-11 time = 0.04 sec -[ Info: VUMPS 58: obj = -8.862878980878e-01 err = 2.2877874652e-11 time = 0.04 sec -[ Info: VUMPS 59: obj = -8.862878980878e-01 err = 1.6173234498e-11 time = 0.18 sec -[ Info: VUMPS 60: obj = -8.862878980878e-01 err = 1.1431584803e-11 time = 0.03 sec -[ Info: VUMPS 61: obj = -8.862878980878e-01 err = 8.0783379889e-12 time = 0.03 sec -[ Info: VUMPS 62: obj = -8.862878980878e-01 err = 5.7086349534e-12 time = 0.03 sec -[ Info: VUMPS 63: obj = -8.862878980878e-01 err = 4.0356697275e-12 time = 0.03 sec -[ Info: VUMPS 64: obj = -8.862878980878e-01 err = 2.8491662815e-12 time = 0.03 sec -[ Info: VUMPS 65: obj = -8.862878980878e-01 err = 2.0151560945e-12 time = 0.03 sec -[ Info: VUMPS 66: obj = -8.862878980878e-01 err = 1.4247184277e-12 time = 0.03 sec -[ Info: VUMPS 67: obj = -8.862878980878e-01 err = 1.0046235154e-12 time = 0.02 sec -[ Info: VUMPS conv 68: obj = -8.862878980879e-01 err = 7.1313164474e-13 time = 2.95 sec - -```` - ---- - -*This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* - diff --git a/docs/src/examples/quantum1d/5.haldane-spt/figure-1.png b/docs/src/examples/quantum1d/5.haldane-spt/figure-1.png deleted file mode 100644 index 3316bbcdf..000000000 Binary files a/docs/src/examples/quantum1d/5.haldane-spt/figure-1.png and /dev/null differ diff --git a/docs/src/examples/quantum1d/5.haldane-spt/figure-2.png b/docs/src/examples/quantum1d/5.haldane-spt/figure-2.png deleted file mode 100644 index 6848d9b4b..000000000 Binary files a/docs/src/examples/quantum1d/5.haldane-spt/figure-2.png and /dev/null differ diff --git a/docs/src/examples/quantum1d/5.haldane-spt/figure-3.png b/docs/src/examples/quantum1d/5.haldane-spt/figure-3.png deleted file mode 100644 index f04065c2a..000000000 Binary files a/docs/src/examples/quantum1d/5.haldane-spt/figure-3.png and /dev/null differ diff --git a/docs/src/examples/quantum1d/5.haldane-spt/index.md b/docs/src/examples/quantum1d/5.haldane-spt/index.md deleted file mode 100644 index 1dfc56414..000000000 --- a/docs/src/examples/quantum1d/5.haldane-spt/index.md +++ /dev/null @@ -1,199 +0,0 @@ -```@meta -EditURL = "../../../../../examples/quantum1d/5.haldane-spt/main.jl" -``` - -[![](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/quantum1d/5.haldane-spt/main.ipynb) -[![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/quantum1d/5.haldane-spt/main.ipynb) -[![](https://img.shields.io/badge/download-project-orange)](https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/QuantumKitHub/MPSKit.jl/examples/tree/gh-pages/dev/examples/quantum1d/5.haldane-spt) - -# Spin 1 Heisenberg model - -The quantum Heisenberg model is a model often used in the study of critical points and phase -transitions of magnetic systems, in which the spins are treated quantum mechanically. It -models magnetic interactions between neighbouring spins through the so-called Heisenberg -interaction term, which causes the spins to either align ($J > 0$) or anti-align ($J < 0$), -thus modeling a (anti-) ferromagnetic system. Here, we will focus on the case of $S = 1$, -with anti-ferromagnetic interactions. - -```math -H = -J \sum_{\langle i, j \rangle} \vec{S}_i \cdot \vec{S}_j -``` - -Importantly, the Hamiltonian of the isotropic model is invariant under $SU(2)$ rotations, -which can be exploited to increase efficiency, as well as interpretability of the MPS -simulations. To see this, we can make use of the following derivation for the interaction -term: - -```math -(\vec{S}_i + \vec{S}_j)^2 = \vec{S}_i^2 + 2 \vec{S}_i \cdot \vec{S}_j + \vec{S}_j^2 -\implies \vec{S}_i \cdot \vec{S}_j = \frac{1}{2} \left( (\vec{S}_i + \vec{S}_j)^2 - \vec{S}_i^2 - \vec{S}_j^2 \right) -``` - -Here, we recognize the quadratic -[Casimir element](https://en.wikipedia.org/wiki/Casimir_element) $\vec{S}^2$, which commutes -with the elements of $SU(2)$. Consequently, the Hamiltonian also commutes with all elements -of $SU(2)$. - -````julia -using TensorKit -using MPSKit -using Plots - -casimir(s::SU2Irrep) = s.j * (s.j + 1) - -function heisenberg_hamiltonian(; J = -1.0) - s = SU2Irrep(1) - ℋ = SU2Space(1 => 1) - SS = zeros(ComplexF64, ℋ ⊗ ℋ ← ℋ ⊗ ℋ) - for (S, data) in blocks(SS) - data .= -0.5J * (casimir(S) - casimir(s) - casimir(s)) - end - return InfiniteMPOHamiltonian(SS) -end -H = heisenberg_hamiltonian() -```` - -```` -1-site InfiniteMPOHamiltonian(ComplexF64, Rep[SU₂]) with maximal dimension 5: -| ⋮ -| ((0=>1) ⊞ (1=>1) ⊞ (0=>1)) -┼─[1]─ (1 => 1) -│ ((0=>1) ⊞ (1=>1) ⊞ (0=>1)) -| ⋮ - -```` - -## Symmetry-Protected Topological Order - -The representations of $SU(2)$ possess additional structure, known as a -$\mathbb{Z}_2$-grading. This means, that they can be partitioned in integer $(+)$ and -half-integer $(-)$ spins, and the fusion rules will respect this grading. In other words, -the following table holds: - -| $s_1$ | $s_2$ | $s_1 \otimes s_2$ | -| --- | --- | --- | -| $+$ | $+$ | $+$ | -| $+$ | $-$ | $-$ | -| $-$ | $+$ | $-$ | -| $-$ | $-$ | $+$ | - -This has important consequences for the MPS representation of an $SU(2)$-symmetric state. If -the physical spin consists of only integer representations, this means that the left and -right virtual spaces of the MPS tensor belong to the same grading, i.e. are either both -integer, or both half-integer. Thus, naively constructing a MPS tensor which contains spins -from both classes, will necessarily be the direct sum of the two, which yields a -non-injective MPS. - -```math -\ket{\psi} = \ket{\psi_+} \oplus \ket{\psi_-} -``` - -Because of this direct sum, many of the usual MPS algorithms will fail, as they typically -cannot deal with non-injective MPS. The resulting MPS will have multiple values of the -transfer matrix spectrum that have a magnitude close to 1, which is a clear sign of a -non-injective MPS. - -````julia -ℋ = SU2Space(1 => 1) -V_wrong = SU2Space(0 => 8, 1 // 2 => 8, 1 => 3, 3 // 2 => 3) -ψ = InfiniteMPS(ℋ, V_wrong) -ψ, environments, info = find_groundstate(ψ, H, VUMPS(; maxiter = 10)) -sectors = SU2Irrep[0, 1 // 2, 1, 3 // 2] -transferplot(ψ; sectors, title = "Transfer matrix spectrum", legend = :outertop) -```` - -![](figure-1.png) - -Nevertheless, using the symmetry, this can be remedied rather easily, by imposing the -ground state to belong to a single class, and comparing the results. We can readily obtain 3 -different criteria for determining the SPT phase of the ground state. - -Firstly, we can compare variational energies for states of similar bond dimensions. As we -expect the state of the wrong SPT phase to have to expend some of its expressiveness in -correcting the SPT, it should have a harder time reaching lower energies. - -Secondly, when inspecting the spectrum of the transfer matrix, we should see that the wrong -SPT phase has a dominant value that is not in the trivial sector, which leads to a -non-injective MPS. - -Finally, the entanglement spectrum of the wrong SPT phase will show degeneracies of all -singular values, which can again be attributed to an attempt to mimic the spectrum of the -right SPT phase. - -````julia -V_plus = SU2Space(0 => 10, 1 => 5, 2 => 3) -ψ_plus = InfiniteMPS(ℋ, V_plus) -ψ_plus, = find_groundstate(ψ_plus, H, VUMPS(; maxiter = 100)) -E_plus = expectation_value(ψ_plus, H) -```` - -```` --1.4014193313393015 - 5.56966055557632e-18im -```` - -````julia -V_minus = SU2Space(1 // 2 => 10, 3 // 2 => 5, 5 // 2 => 3) -ψ_minus = InfiniteMPS(ℋ, V_minus) -ψ_minus, = find_groundstate(ψ_minus, H, VUMPS(; maxiter = 100)) -E_minus = expectation_value(ψ_minus, H) -```` - -```` --1.4014839739630829 - 1.0846362574075293e-16im -```` - -````julia -transferp_plus = transferplot( - ψ_plus; - sectors = SU2Irrep[0, 1, 2], title = "ψ_plus", legend = :outertop -) -transferp_minus = transferplot( - ψ_minus; - sectors = SU2Irrep[0, 1, 2], title = "ψ_minus", legend = :outertop -) -plot(transferp_plus, transferp_minus; layout = (1, 2), size = (800, 400)) -```` - -![](figure-2.png) - -````julia -entanglementp_plus = entanglementplot(ψ_plus; title = "ψ_plus", legend = :outertop) -entanglementp_minus = entanglementplot(ψ_minus; title = "ψ_minus", legend = :outertop) -plot(entanglementp_plus, entanglementp_minus; layout = (1, 2), size = (800, 400)) -```` - -![](figure-3.png) - -As we can see, the ground state can be found in the non-trivial SPT phase, $\ket{\psi_-}$. We -can obtain an intuitive understanding of $\ket{\psi_+}$ by considering the following -diagram. If we denote the MPS tensors that make up the ground state as $A_-$, we can -construct a state in the trivial SPT phase that approximates the ground state as follows: - -```@raw html -SPT tensors -``` - -In other words, we can factorize a purely virtual isomorphism of $S = 1/2$ in order to -obtain the ground state. This then also explains the degeneracies in the entanglement -spectrum as well as in the transfer matrix spectrum. Finally, we can further confirm this -intuition by looking at the entanglement entropy of the ground state. As we can see, the -entanglement entropy of the state in the wrong SPT phase is exactly $log(2)$ higher than the -one in the right SPT phase, which is exactly what we would expect from the diagram above. - -````julia -S_minus = sum(real, entropy(ψ_minus)) -S_plus = sum(real, entropy(ψ_plus)) -println("S_minus + log(2) = $(S_minus + log(2))") -println("S_plus = $S_plus") -```` - -```` -S_minus + log(2) = 1.5486227235391343 -S_plus = 1.5450323530587475 - -```` - ---- - -*This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* - diff --git a/docs/src/examples/quantum1d/6.hubbard/figure-1.png b/docs/src/examples/quantum1d/6.hubbard/figure-1.png deleted file mode 100644 index 667b26219..000000000 Binary files a/docs/src/examples/quantum1d/6.hubbard/figure-1.png and /dev/null differ diff --git a/docs/src/examples/quantum1d/6.hubbard/figure-2.png b/docs/src/examples/quantum1d/6.hubbard/figure-2.png deleted file mode 100644 index ec55e8749..000000000 Binary files a/docs/src/examples/quantum1d/6.hubbard/figure-2.png and /dev/null differ diff --git a/docs/src/examples/quantum1d/6.hubbard/index.md b/docs/src/examples/quantum1d/6.hubbard/index.md deleted file mode 100644 index 0ca1407ad..000000000 --- a/docs/src/examples/quantum1d/6.hubbard/index.md +++ /dev/null @@ -1,494 +0,0 @@ -```@meta -EditURL = "../../../../../examples/quantum1d/6.hubbard/main.jl" -``` - -[![](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/quantum1d/6.hubbard/main.ipynb) -[![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/quantum1d/6.hubbard/main.ipynb) -[![](https://img.shields.io/badge/download-project-orange)](https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/QuantumKitHub/MPSKit.jl/examples/tree/gh-pages/dev/examples/quantum1d/6.hubbard) - -````julia -using Markdown -```` - -# Hubbard chain at half filling - -The Hubbard model is a model of interacting fermions on a lattice, which is often used as a somewhat realistic model for electrons in a solid. -The Hamiltonian consists of two terms that describe competing forces of each electron: -a kinetic term that allows electrons to hop between neighboring sites, and a potential term reflecting on-site interactions between electrons. -Often, a third term is included which serves as a chemical potential to control the number of electrons in the system. - -```math -H = -t \sum_{\langle i, j \rangle, \sigma} c^{\dagger}_{i,\sigma} c_{j,\sigma} + U \sum_i n_{i,\uparrow} n_{i,\downarrow} - \mu \sum_{i,\sigma} n_{i,\sigma} -``` - -At half-filling, the system exhibits particle-hole symmetry, which can be made explicit by rewriting the Hamiltonian slightly. -First, we fix the overall energy scale by setting `t = 1`, and then shift the total energy by adding a constant `U / 4`, as well as shifting the chemical potential to `N U / 2`. -This results in the following Hamiltonian: - -```math -H = - \sum_{\langle i, j \rangle, \sigma} c^{\dagger}_{i,\sigma} c_{j,\sigma} + U / 4 \sum_i (1 - 2 n_{i,\uparrow}) (1 - 2 n_{i,\downarrow}) - \mu \sum_{i,\sigma} n_{i,\sigma} -``` - -Finally, setting `\mu = 0` and defining `u = U / 4` we obtain the Hubbard model at half-filling. - -```math -H = - \sum_{\langle i, j \rangle, \sigma} c^{\dagger}_{i,\sigma} c_{j,\sigma} + u \sum_i (1 - 2 n_{i,\uparrow}) (1 - 2 n_{i,\downarrow}) -``` - -````julia -using TensorKit -using MPSKit -using .ExampleModels -using SpecialFunctions: besselj0, besselj1 -using QuadGK: quadgk -using Plots -using Interpolations -using Optim - - -const t = 1.0 -const mu = 0.0 -const U = 3.0 -```` - -```` -3.0 -```` - -For this case, the ground state energy has an analytic solution, which can be used to benchmark the numerical results. -It follows from Eq. (6.82) in [](). - -```math -e(u) = - u - 4 \int_0^{\infty} \frac{d\omega}{\omega} \frac{J_0(\omega) J_1(\omega)}{1 + \exp(2u \omega)} -``` - -We can easily verify this by comparing the numerical results to the analytic solution. - -````julia -function hubbard_energy(u; rtol = 1.0e-12) - integrandum(ω) = besselj0(ω) * besselj1(ω) / (1 + exp(2u * ω)) / ω - int, err = quadgk(integrandum, 0, Inf; rtol = rtol) - return -u - 4 * int -end - -function compute_groundstate( - psi, H; - svalue = 1.0e-3, - expansionfactor = (1 / 10), - expansioniter = 20 - ) - verbosity = 2 - psi, = find_groundstate(psi, H; tol = svalue * 10, verbosity) - for _ in 1:expansioniter - D = maximum(x -> dim(left_virtualspace(psi, x)), 1:length(psi)) - D′ = max(5, round(Int, D * expansionfactor)) - trunc = trunctol(; atol = svalue / 10) & truncrank(D′) - psi′, = changebonds(psi, H, OptimalExpand(; trunc = trunc)) - all( - left_virtualspace.(Ref(psi), 1:length(psi)) .== - left_virtualspace.(Ref(psi′), 1:length(psi)) - ) && break - psi, = find_groundstate(psi′, H, VUMPS(; tol = svalue / 5, maxiter = 10, verbosity)) - end - - # convergence steps - psi, = changebonds(psi, H, SvdCut(; trunc = trunctol(; atol = svalue))) - psi, = find_groundstate( - psi, H, - VUMPS(; tol = svalue / 100, verbosity, maxiter = 100) & - GradientGrassmann(; tol = svalue / 1000) - ) - - return psi -end - -H = hubbard_model(; unitcell = 2, U, t, mu = U / 2) -Vspaces = fill(Vect[fℤ₂](0 => 10, 1 => 10), 2) -psi = InfiniteMPS(physicalspace(H), Vspaces) -psi = compute_groundstate(psi, H) -E = real(expectation_value(psi, H)) / 2 -@info """ -Groundstate energy: - * numerical: $E - * analytic: $(hubbard_energy(U / 4) - U / 4) -""" -```` - -```` -[ Info: VUMPS init: obj = -1.468489418346e+00 err = 5.1975e-01 -[ Info: VUMPS conv 9: obj = -4.377078635243e+00 err = 8.8168808972e-03 time = 9.43 sec -[ Info: VUMPS init: obj = -4.377078635243e+00 err = 1.6735e-02 -[ Info: VUMPS conv 6: obj = -4.378747252024e+00 err = 1.2361295846e-04 time = 0.12 sec -[ Info: VUMPS init: obj = -4.378747252024e+00 err = 8.0159e-03 -[ Info: VUMPS conv 6: obj = -4.379161083149e+00 err = 1.5147591757e-04 time = 0.35 sec -[ Info: VUMPS init: obj = -4.379161083149e+00 err = 6.1111e-03 -[ Info: VUMPS conv 5: obj = -4.379452158515e+00 err = 1.6672074243e-04 time = 0.27 sec -[ Info: VUMPS init: obj = -4.379452158515e+00 err = 5.6947e-03 -[ Info: VUMPS conv 4: obj = -4.379651693858e+00 err = 1.7048574253e-04 time = 0.25 sec -[ Info: VUMPS init: obj = -4.379651693858e+00 err = 4.1022e-03 -[ Info: VUMPS conv 3: obj = -4.379734900757e+00 err = 1.9568406227e-04 time = 0.22 sec -[ Info: VUMPS init: obj = -4.379734900757e+00 err = 3.5559e-03 -[ Info: VUMPS conv 3: obj = -4.379797739218e+00 err = 1.3879489646e-04 time = 0.28 sec -[ Info: VUMPS init: obj = -4.379797739218e+00 err = 2.7576e-03 -[ Info: VUMPS conv 2: obj = -4.379838449668e+00 err = 1.7162678551e-04 time = 0.23 sec -[ Info: VUMPS init: obj = -4.379838449668e+00 err = 2.7243e-03 -[ Info: VUMPS conv 3: obj = -4.379878835235e+00 err = 1.9410242419e-04 time = 0.37 sec -[ Info: VUMPS init: obj = -4.379878835235e+00 err = 2.6897e-03 -[ Info: VUMPS conv 3: obj = -4.379929227787e+00 err = 1.6816048594e-04 time = 0.48 sec -[ Info: VUMPS init: obj = -4.379929227787e+00 err = 2.5535e-03 -[ Info: VUMPS conv 3: obj = -4.379968042520e+00 err = 1.7686227682e-04 time = 0.58 sec -[ Info: VUMPS init: obj = -4.379968042520e+00 err = 1.7669e-03 -[ Info: VUMPS conv 2: obj = -4.379986883462e+00 err = 1.8114981456e-04 time = 0.53 sec -[ Info: VUMPS init: obj = -4.379986883462e+00 err = 1.5826e-03 -[ Info: VUMPS conv 2: obj = -4.380000785141e+00 err = 1.8783202881e-04 time = 2.59 sec -[ Info: VUMPS init: obj = -4.380000785141e+00 err = 1.5050e-03 -[ Info: VUMPS conv 2: obj = -4.380013154815e+00 err = 1.5341277502e-04 time = 0.66 sec -[ Info: VUMPS init: obj = -4.380013154815e+00 err = 1.4211e-03 -[ Info: VUMPS conv 2: obj = -4.380024394664e+00 err = 1.7785454577e-04 time = 0.86 sec -[ Info: VUMPS init: obj = -4.380024394664e+00 err = 1.3317e-03 -[ Info: VUMPS conv 2: obj = -4.380038147448e+00 err = 1.5752544479e-04 time = 1.16 sec -[ Info: VUMPS init: obj = -4.380038147448e+00 err = 1.0028e-03 -[ Info: VUMPS conv 1: obj = -4.380043676003e+00 err = 1.6642351068e-04 time = 0.69 sec -[ Info: VUMPS init: obj = -4.380043676003e+00 err = 9.0907e-04 -[ Info: VUMPS conv 1: obj = -4.380048631245e+00 err = 1.8550023124e-04 time = 0.99 sec -[ Info: VUMPS init: obj = -4.380048631245e+00 err = 8.3047e-04 -[ Info: VUMPS conv 1: obj = -4.380053194046e+00 err = 1.7976862986e-04 time = 1.02 sec -[ Info: VUMPS init: obj = -4.380053194046e+00 err = 6.8098e-04 -[ Info: VUMPS conv 1: obj = -4.380057135166e+00 err = 1.8942646284e-04 time = 3.55 sec -[ Info: VUMPS init: obj = -4.380057135166e+00 err = 6.0296e-04 -[ Info: VUMPS conv 1: obj = -4.380060542793e+00 err = 1.8183804864e-04 time = 1.50 sec -[ Info: VUMPS init: obj = -4.379609680843e+00 err = 4.0950e-03 -[ Info: VUMPS conv 19: obj = -4.379763262608e+00 err = 9.9349957478e-06 time = 5.15 sec -[ Info: CG: initializing with f = -4.379763262252e+00, ‖∇f‖ = 3.1505e-05 -[ Info: CG: converged after 74 iterations and time 31.48 s: f = -4.379763378976e+00, ‖∇f‖ = 9.7995e-07 -┌ Info: Groundstate energy: -│ * numerical: -2.1899959892263503 -└ * analytic: -2.190038374277775 - -```` - -## Symmetries - -The Hubbard model has a rich symmetry structure, which can be exploited to speed up simulations. -Apart from the fermionic parity, the model also has a $U(1)$ particle number symmetry, along with a $SU(2)$ spin symmetry. -Explicitly imposing these symmetries on the tensors can greatly reduce the computational cost of the simulation. - -Naively imposing these symmetries however, is not compatible with our desire to work at half-filling. -By construction, imposing symmetries restricts the optimization procedure to a single symmetry sector, which is the trivial sector. -In order to work at half-filling, we need to effectively inject one particle per site. -In MPSKit, this is achieved by the `add_physical_charge` function, which shifts the physical spaces of the tensors to the desired charge sector. - -````julia -H_u1_su2 = hubbard_model(ComplexF64, U1Irrep, SU2Irrep; unitcell = 2, U, t, mu = U / 2); -charges = fill(FermionParity(1) ⊠ U1Irrep(1) ⊠ SU2Irrep(0), 2); -H_u1_su2 = MPSKit.add_physical_charge(H_u1_su2, charges); - -pspaces = physicalspace.(Ref(H_u1_su2), 1:2) -vspaces = [unitspace(eltype(pspaces)), first(pspaces)] -psi = InfiniteMPS(pspaces, vspaces) -psi = compute_groundstate(psi, H_u1_su2; expansionfactor = 1 / 3) -E = real(expectation_value(psi, H_u1_su2)) / 2 -@info """ -Groundstate energy: - * numerical: $E - * analytic: $(hubbard_energy(U / 4) - U / 4) -""" -```` - -```` -[ Info: VUMPS init: obj = +2.614696817226e-01 err = 8.5807e-01 -[ Info: VUMPS conv 1: obj = -4.000000000000e+00 err = 1.3727089486e-15 time = 8.05 sec -[ Info: VUMPS init: obj = -4.000000000000e+00 err = 3.3634e-01 -[ Info: VUMPS conv 4: obj = -4.289650419749e+00 err = 1.8514003381e-04 time = 0.03 sec -[ Info: VUMPS init: obj = -4.289650419749e+00 err = 1.1203e-01 -[ Info: VUMPS conv 6: obj = -4.359777339602e+00 err = 1.0863018801e-04 time = 0.08 sec -[ Info: VUMPS init: obj = -4.359777339602e+00 err = 4.4013e-02 -[ Info: VUMPS conv 6: obj = -4.372880960804e+00 err = 1.2173013239e-04 time = 2.01 sec -[ Info: VUMPS init: obj = -4.372880960804e+00 err = 3.2691e-02 -[ Info: VUMPS conv 4: obj = -4.375236954195e+00 err = 1.1823474657e-04 time = 0.12 sec -[ Info: VUMPS init: obj = -4.375236954195e+00 err = 2.9487e-02 -[ Info: VUMPS conv 7: obj = -4.378159084338e+00 err = 1.1896655123e-04 time = 0.24 sec -[ Info: VUMPS init: obj = -4.378159084337e+00 err = 1.9312e-02 -[ Info: VUMPS conv 5: obj = -4.379272965967e+00 err = 1.5785504511e-04 time = 0.22 sec -[ Info: VUMPS init: obj = -4.379272965967e+00 err = 9.9128e-03 -[ Info: VUMPS conv 4: obj = -4.379592229097e+00 err = 1.5550466620e-04 time = 0.22 sec -[ Info: VUMPS init: obj = -4.379592229097e+00 err = 6.4841e-03 -[ Info: VUMPS conv 4: obj = -4.379819377211e+00 err = 1.7492244997e-04 time = 0.27 sec -[ Info: VUMPS init: obj = -4.379819377210e+00 err = 3.8754e-03 -┌ Warning: VUMPS cancel 10: obj = -4.379964033314e+00 err = 2.1228913871e-04 time = 1.09 sec -└ @ MPSKit ~/Projects/MPSKit.jl/ld-release/src/algorithms/groundstate/vumps.jl:96 -[ Info: VUMPS init: obj = -4.379964033314e+00 err = 2.8978e-03 -[ Info: VUMPS conv 3: obj = -4.380010384690e+00 err = 1.4775313775e-04 time = 0.45 sec -[ Info: VUMPS init: obj = -4.380010384690e+00 err = 2.0609e-03 -[ Info: VUMPS conv 3: obj = -4.380041751472e+00 err = 1.6327804840e-04 time = 0.79 sec -[ Info: VUMPS init: obj = -4.380041751471e+00 err = 1.2364e-03 -[ Info: VUMPS conv 2: obj = -4.380055778730e+00 err = 1.8367318970e-04 time = 0.63 sec -[ Info: VUMPS init: obj = -4.380055778730e+00 err = 8.5857e-04 -[ Info: VUMPS conv 2: obj = -4.380064749403e+00 err = 1.3905483353e-04 time = 0.86 sec -[ Info: VUMPS init: obj = -4.380064749403e+00 err = 5.2502e-04 -[ Info: VUMPS conv 1: obj = -4.380067974756e+00 err = 1.5646749713e-04 time = 0.61 sec -[ Info: VUMPS init: obj = -4.380067974756e+00 err = 3.3275e-04 -[ Info: VUMPS conv 1: obj = -4.380070351398e+00 err = 1.3124347695e-04 time = 0.90 sec -[ Info: VUMPS init: obj = -4.380070351398e+00 err = 2.0349e-04 -[ Info: VUMPS conv 1: obj = -4.380072125236e+00 err = 1.1121064710e-04 time = 1.33 sec -[ Info: VUMPS init: obj = -4.380072125236e+00 err = 1.3635e-04 -[ Info: VUMPS conv 1: obj = -4.380073467820e+00 err = 8.5044825938e-05 time = 1.86 sec -[ Info: VUMPS init: obj = -4.380073467820e+00 err = 9.7229e-05 -[ Info: VUMPS conv 1: obj = -4.380074455754e+00 err = 6.4434476042e-05 time = 3.04 sec -[ Info: VUMPS init: obj = -4.380074455754e+00 err = 7.3791e-05 -[ Info: VUMPS conv 1: obj = -4.380075159877e+00 err = 6.2156890313e-05 time = 7.86 sec -[ Info: VUMPS init: obj = -4.380075159877e+00 err = 5.9911e-05 -[ Info: VUMPS conv 1: obj = -4.380075661717e+00 err = 4.2516777791e-05 time = 9.17 sec -[ Info: VUMPS init: obj = -4.379308796614e+00 err = 7.9930e-03 -┌ Warning: VUMPS cancel 100: obj = -4.379692711577e+00 err = 1.5979759574e-05 time = 18.31 sec -└ @ MPSKit ~/Projects/MPSKit.jl/ld-release/src/algorithms/groundstate/vumps.jl:96 -[ Info: CG: initializing with f = -4.379692711577e+00, ‖∇f‖ = 5.7923e-05 -[ Info: CG: converged after 13 iterations and time 6.61 s: f = -4.379692712497e+00, ‖∇f‖ = 6.2098e-07 -┌ Info: Groundstate energy: -│ * numerical: -2.190015347514464 -└ * analytic: -2.190038374277775 - -```` - -## Excitations - -Because of the integrability, it is known that the Hubbard model has a rich excitation spectrum. -The elementary excitations are known as spinons and holons, which are domain walls in the spin and charge sectors, respectively. -The fact that the spin and charge sectors are separate is a phenomenon known as spin-charge separation. - -The domain walls can be constructed by noticing that there are two equivalent groundstates, which differ by a translation over a single site. -In other words, the groundstates are ``\psi_{AB}` and ``\psi_{BA}``, where ``A`` and ``B`` are the two sites. -These excitations can be constructed as follows: - -````julia -alg = QuasiparticleAnsatz(; tol = 1.0e-3) -momenta = range(-π, π; length = 33) -psi_AB = psi -envs_AB = environments(psi_AB, H_u1_su2, psi_AB); -psi_BA = circshift(psi, 1) -envs_BA = environments(psi_BA, H_u1_su2, psi_BA); - -spinon_charge = FermionParity(0) ⊠ U1Irrep(0) ⊠ SU2Irrep(1 // 2) -E_spinon, ϕ_spinon = excitations( - H_u1_su2, alg, momenta, psi_AB, envs_AB, psi_BA, envs_BA; - sector = spinon_charge, num = 1 -); - -holon_charge = FermionParity(1) ⊠ U1Irrep(-1) ⊠ SU2Irrep(0) -E_holon, ϕ_holon = excitations( - H_u1_su2, alg, momenta, psi_AB, envs_AB, psi_BA, envs_BA; - sector = holon_charge, num = 1 -); -```` - -```` -[ Info: Found excitations for momentum = -3.141592653589793 -[ Info: Found excitations for momentum = -2.945243112740431 -[ Info: Found excitations for momentum = -2.748893571891069 -[ Info: Found excitations for momentum = -2.552544031041707 -[ Info: Found excitations for momentum = -2.356194490192345 -[ Info: Found excitations for momentum = -2.1598449493429825 -[ Info: Found excitations for momentum = -1.9634954084936207 -[ Info: Found excitations for momentum = -1.7671458676442586 -[ Info: Found excitations for momentum = -1.5707963267948966 -[ Info: Found excitations for momentum = -1.3744467859455345 -[ Info: Found excitations for momentum = -1.1780972450961724 -[ Info: Found excitations for momentum = -0.9817477042468103 -[ Info: Found excitations for momentum = -0.7853981633974483 -[ Info: Found excitations for momentum = -0.5890486225480862 -[ Info: Found excitations for momentum = -0.39269908169872414 -[ Info: Found excitations for momentum = -0.19634954084936207 -[ Info: Found excitations for momentum = 0.0 -[ Info: Found excitations for momentum = 0.19634954084936207 -[ Info: Found excitations for momentum = 0.39269908169872414 -[ Info: Found excitations for momentum = 0.5890486225480862 -[ Info: Found excitations for momentum = 0.7853981633974483 -[ Info: Found excitations for momentum = 0.9817477042468103 -[ Info: Found excitations for momentum = 1.1780972450961724 -[ Info: Found excitations for momentum = 1.3744467859455345 -[ Info: Found excitations for momentum = 1.5707963267948966 -[ Info: Found excitations for momentum = 1.7671458676442586 -[ Info: Found excitations for momentum = 1.9634954084936207 -[ Info: Found excitations for momentum = 2.1598449493429825 -[ Info: Found excitations for momentum = 2.356194490192345 -[ Info: Found excitations for momentum = 2.552544031041707 -[ Info: Found excitations for momentum = 2.748893571891069 -[ Info: Found excitations for momentum = 2.945243112740431 -[ Info: Found excitations for momentum = 3.141592653589793 -[ Info: Found excitations for momentum = -3.141592653589793 -[ Info: Found excitations for momentum = -2.945243112740431 -[ Info: Found excitations for momentum = -2.748893571891069 -[ Info: Found excitations for momentum = -2.552544031041707 -[ Info: Found excitations for momentum = -2.356194490192345 -[ Info: Found excitations for momentum = -2.1598449493429825 -[ Info: Found excitations for momentum = -1.9634954084936207 -[ Info: Found excitations for momentum = -1.7671458676442586 -[ Info: Found excitations for momentum = -1.5707963267948966 -[ Info: Found excitations for momentum = -1.3744467859455345 -[ Info: Found excitations for momentum = -1.1780972450961724 -[ Info: Found excitations for momentum = -0.9817477042468103 -[ Info: Found excitations for momentum = -0.7853981633974483 -[ Info: Found excitations for momentum = -0.5890486225480862 -[ Info: Found excitations for momentum = -0.39269908169872414 -[ Info: Found excitations for momentum = -0.19634954084936207 -[ Info: Found excitations for momentum = 0.0 -[ Info: Found excitations for momentum = 0.19634954084936207 -[ Info: Found excitations for momentum = 0.39269908169872414 -[ Info: Found excitations for momentum = 0.5890486225480862 -[ Info: Found excitations for momentum = 0.7853981633974483 -[ Info: Found excitations for momentum = 0.9817477042468103 -[ Info: Found excitations for momentum = 1.1780972450961724 -[ Info: Found excitations for momentum = 1.3744467859455345 -[ Info: Found excitations for momentum = 1.5707963267948966 -[ Info: Found excitations for momentum = 1.7671458676442586 -[ Info: Found excitations for momentum = 1.9634954084936207 -[ Info: Found excitations for momentum = 2.1598449493429825 -[ Info: Found excitations for momentum = 2.356194490192345 -[ Info: Found excitations for momentum = 2.552544031041707 -[ Info: Found excitations for momentum = 2.748893571891069 -[ Info: Found excitations for momentum = 2.945243112740431 -[ Info: Found excitations for momentum = 3.141592653589793 - -```` - -Again, we can compare the numerical results to the analytic solution. -Here, the formulae for the excitation energies are expressed in terms of dressed momenta: - -````julia -function spinon_momentum(Λ, u; rtol = 1.0e-12) - integrandum(ω) = besselj0(ω) * sin(ω * Λ) / ω / cosh(ω * u) - return π / 2 - quadgk(integrandum, 0, Inf; rtol = rtol)[1] -end -function spinon_energy(Λ, u; rtol = 1.0e-12) - integrandum(ω) = besselj1(ω) * cos(ω * Λ) / ω / cosh(ω * u) - return 2 * quadgk(integrandum, 0, Inf; rtol = rtol)[1] -end - -function holon_momentum(k, u; rtol = 1.0e-12) - integrandum(ω) = besselj0(ω) * sin(ω * sin(k)) / ω / (1 + exp(2u * abs(ω))) - return π / 2 - k - 2 * quadgk(integrandum, 0, Inf; rtol = rtol)[1] -end -function holon_energy(k, u; rtol = 1.0e-12) - integrandum(ω) = besselj1(ω) * cos(ω * sin(k)) * exp(-ω * u) / ω / cosh(ω * u) - return 2 * cos(k) + 2u + 2 * quadgk(integrandum, 0, Inf; rtol = rtol)[1] -end - -Λs = range(-10, 10; length = 51) -P_spinon_analytic = rem2pi.(spinon_momentum.(Λs, U / 4), RoundNearest) -E_spinon_analytic = spinon_energy.(Λs, U / 4) -I_spinon = sortperm(P_spinon_analytic) -P_spinon_analytic = P_spinon_analytic[I_spinon] -E_spinon_analytic = E_spinon_analytic[I_spinon] -P_spinon_analytic = [reverse(-P_spinon_analytic); P_spinon_analytic] -E_spinon_analytic = [reverse(E_spinon_analytic); E_spinon_analytic]; - -ks = range(0, 2π; length = 51) -P_holon_analytic = rem2pi.(holon_momentum.(ks, U / 4), RoundNearest) -E_holon_analytic = holon_energy.(ks, U / 4) -I_holon = sortperm(P_holon_analytic) -P_holon_analytic = P_holon_analytic[I_holon] -E_holon_analytic = E_holon_analytic[I_holon]; - -p = let p_excitations = plot(; xaxis = "momentum", yaxis = "energy") - scatter!(p_excitations, momenta, real(E_spinon); label = "spinon") - plot!(p_excitations, P_spinon_analytic, E_spinon_analytic; label = "spinon (analytic)") - - scatter!(p_excitations, momenta, real(E_holon); label = "holon") - plot!(p_excitations, P_holon_analytic, E_holon_analytic; label = "holon (analytic)") - - p_excitations -end -```` - -![](figure-1.png) - -The plot shows some discrepancies between the numerical and analytic results. -First and foremost, we must realize that in the thermodynamic limit, the momentum of a domain wall is actually not well-defined. -Concretely, only the difference in momentum between the two groundstates is well-defined, as we can always shift the momentum by multiplying one of the groundstates by a phase. -Here, we can fix this shift by realizing that our choice of shifting the groundstates by a single site, differs from the formula by a factor ``\pi/2``. - -````julia -momenta_shifted = rem2pi.(momenta .- π / 2, RoundNearest) -p = let p_excitations = plot(; xaxis = "momentum", yaxis = "energy", xlims = (-π, π)) - scatter!(p_excitations, momenta_shifted, real(E_spinon); label = "spinon") - plot!(p_excitations, P_spinon_analytic, E_spinon_analytic; label = "spinon (analytic)") - - scatter!(p_excitations, momenta_shifted, real(E_holon); label = "holon") - plot!(p_excitations, P_holon_analytic, E_holon_analytic; label = "holon (analytic)") - - p_excitations -end -```` - -![](figure-2.png) - -The second discrepancy is that while the spinon dispersion is well-reproduced, the holon dispersion is not. -This is due to the fact that the excitation ansatz captures the lowest-energy excitation, and not the elementary single-particle excitation. -To make this explicit, we can consider the scattering states comprising of a holon and two spinons. -If these are truly scattering states, the energy of the scattering state should be the sum of the energies of the individual excitations, and the momentum is the sum of the momenta. -Thus, we can find the lowest-energy scattering states by minimizing the energy over the combination of momenta for the constituent elementary excitations. - -````julia -holon_dispersion_itp = linear_interpolation( - P_holon_analytic, E_holon_analytic; - extrapolation_bc = Line() -) -spinon_dispersion_itp = linear_interpolation( - P_spinon_analytic, E_spinon_analytic; - extrapolation_bc = Line() -) -function scattering_energy(p1, p2, p3) - p1, p2, p3 = rem2pi.((p1, p2, p3), RoundNearest) - return holon_dispersion_itp(p1) + spinon_dispersion_itp(p2) + spinon_dispersion_itp(p3) -end; - -E_scattering_min = map(momenta_shifted) do p - e = Inf - for i in 1:10 # repeat for stability - res = optimize((rand(2) .* (2π) .- π)) do (p₁, p₂) - p₃ = p - p₁ - p₂ - return scattering_energy(p₁, p₂, p₃) - end - - e = min(Optim.minimum(res), e) - end - return e -end -E_scattering_max = map(momenta_shifted) do p - e = -Inf - for i in 1:10 # repeat for stability - res = optimize((rand(Float64, 2) .* (2π) .- π)) do (p₁, p₂) - p₃ = p - p₁ - p₂ - return -scattering_energy(p₁, p₂, p₃) - end - - e = max(-Optim.minimum(res), e) - end - return e -end; - -p = let p_excitations = plot(; - xaxis = "momentum", yaxis = "energy", xlims = (-π, π), ylims = (-0.1, 5) - ) - scatter!(p_excitations, momenta_shifted, real(E_spinon); label = "spinon") - plot!(p_excitations, P_spinon_analytic, E_spinon_analytic; label = "spinon (analytic)") - - scatter!(p_excitations, momenta_shifted, real(E_holon); label = "holon") - plot!(p_excitations, P_holon_analytic, E_holon_analytic; label = "holon (analytic)") - - I = sortperm(momenta_shifted) - plot!( - p_excitations, momenta_shifted[I], E_scattering_min[I]; label = "scattering states", - fillrange = E_scattering_max[I], fillalpha = 0.3, fillstyle = :x - ) - - p_excitations -end -```` - -![](figure-3.png) - ---- - -*This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* - diff --git a/docs/src/examples/quantum1d/7.xy-finiteT/figure-6.png b/docs/src/examples/quantum1d/7.xy-finiteT/figure-6.png deleted file mode 100644 index 16501b948..000000000 Binary files a/docs/src/examples/quantum1d/7.xy-finiteT/figure-6.png and /dev/null differ diff --git a/docs/src/examples/quantum1d/8.bose-hubbard/figure-1.png b/docs/src/examples/quantum1d/8.bose-hubbard/figure-1.png deleted file mode 100644 index f3e51f8c6..000000000 Binary files a/docs/src/examples/quantum1d/8.bose-hubbard/figure-1.png and /dev/null differ diff --git a/docs/src/examples/quantum1d/8.bose-hubbard/figure-3.png b/docs/src/examples/quantum1d/8.bose-hubbard/figure-3.png deleted file mode 100644 index 47fe594f5..000000000 Binary files a/docs/src/examples/quantum1d/8.bose-hubbard/figure-3.png and /dev/null differ diff --git a/docs/src/examples/quantum1d/8.bose-hubbard/figure-4.png b/docs/src/examples/quantum1d/8.bose-hubbard/figure-4.png deleted file mode 100644 index 5587376b0..000000000 Binary files a/docs/src/examples/quantum1d/8.bose-hubbard/figure-4.png and /dev/null differ diff --git a/docs/src/examples/quantum1d/8.bose-hubbard/figure-5.png b/docs/src/examples/quantum1d/8.bose-hubbard/figure-5.png deleted file mode 100644 index 822de68c4..000000000 Binary files a/docs/src/examples/quantum1d/8.bose-hubbard/figure-5.png and /dev/null differ diff --git a/docs/src/examples/quantum1d/8.bose-hubbard/figure-6.png b/docs/src/examples/quantum1d/8.bose-hubbard/figure-6.png deleted file mode 100644 index 6582cc87c..000000000 Binary files a/docs/src/examples/quantum1d/8.bose-hubbard/figure-6.png and /dev/null differ diff --git a/docs/src/examples/quantum1d/8.bose-hubbard/index.md b/docs/src/examples/quantum1d/8.bose-hubbard/index.md deleted file mode 100644 index 80d5af64f..000000000 --- a/docs/src/examples/quantum1d/8.bose-hubbard/index.md +++ /dev/null @@ -1,464 +0,0 @@ -```@meta -EditURL = "../../../../../examples/quantum1d/8.bose-hubbard/main.jl" -``` - -[![](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/quantum1d/8.bose-hubbard/main.ipynb) -[![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/quantum1d/8.bose-hubbard/main.ipynb) -[![](https://img.shields.io/badge/download-project-orange)](https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/QuantumKitHub/MPSKit.jl/examples/tree/gh-pages/dev/examples/quantum1d/8.bose-hubbard) - -````julia -using Markdown -using TensorKitTensors.BosonOperators: b_min, b_plus_b_min, b_min_b_plus, b_num -using MPSKit, TensorKit -using .ExampleModels -using Plots, LaTeXStrings - - -theme(:wong) -default(fontfamily = "Computer Modern", label = nothing, dpi = 100, framestyle = :box) -```` - -# 1D Bose-Hubbard model - -In this tutorial, we will explore the physics of the one-dimensional Bose–Hubbard model -using matrix product states. For the most part, we replicate the results presented in -[**Phys. Rev. B 105, -134502**](https://journals.aps.org/prb/abstract/10.1103/PhysRevB.105.134502), which can be -consulted for any statements in this tutorial that are not otherwise cited. The Hamiltonian -under study is defined as follows: - -$$H = -t \sum_{i} (\hat{a}_i^{\dagger} \hat{a}_{i+1} + \hat{a}_{i+1}^{\dagger} \hat{a}_i) + \frac{U}{2} \sum_i \hat{n}_i(\hat{n}_i - 1) - \mu \sum_i \hat{n}_i$$ - -where the bosonic creation and annihilation operators satisfy the canonical commutation -relations (CCR): - -$$[\hat{a}_i, \hat{a}_j^{\dagger}] = \delta_{ij}.$$ - -Each lattice site hosts a local Hilbert space corresponding to bosonic occupation states -$|n\rangle$, where $(n = 0, 1, 2, \ldots)$. Since this space is formally -infinite-dimensional, numerical simulations typically impose a truncation at some maximum -occupation number $(n_{\text{max}})$. Such a treatment is justified since it can be observed -that the simulation results quickly converge with the cutoff if the filling fraction is kept -sufficiently low. - -Within this truncated space, the local creation and annihilation operators are represented -by finite-dimensional matrices. For example, with cutoff $n_{\text{max}}$, the annihilation -operator takes the form - -```math -\hat{a} = -\begin{bmatrix} -0 & \sqrt{1} & 0 & 0 & \cdots & 0 \\ -0 & 0 & \sqrt{2} & 0 & \cdots & 0 \\ -0 & 0 & 0 & \sqrt{3} & \cdots & 0 \\ -\vdots & & & \ddots & \ddots & \vdots \\ -0 & 0 & 0 & \cdots & 0 & \sqrt{n_{\text{max}}} \\ -0 & 0 & 0 & \cdots & 0 & 0 -\end{bmatrix} -``` - -and the creation operator is simply its Hermitian conjugate, - -```math -\hat{a}^\dagger = -\begin{bmatrix} -0 & 0 & 0 & \cdots & 0 & 0 \\ -\sqrt{1} & 0 & 0 & \cdots & 0 & 0 \\ -0 & \sqrt{2} & 0 & \cdots & 0 & 0 \\ -\vdots & & \ddots & \ddots & & \vdots \\ -0 & 0 & 0 & \cdots & 0 & 0 \\ -0 & 0 & 0 & \cdots & \sqrt{n_{\text{max}}} & 0 -\end{bmatrix} -``` - -The number operator is then given by - -$$\hat{n} = \hat{a}^\dagger \hat{a} = \mathrm{diag}(0, 1, 2, \ldots, n_{\text{max}}).$$ - -Before moving on, notice that the Hamiltonian is uniform and translationally invariant. -Typically, such models are studied on a finite chain of $N$ sites with periodic boundary -conditions, but this introduces finite-size effects that are rather annoying to deal with. -In contrast, the MPS framework allows us to work directly in the thermodynamic limit, -avoiding such artifacts. We will follow this line of exploration in this tutorial and leave -finite systems for another example. - -In order to work in the thermodynamic limit, we will have to create an -[`InfiniteMPS`](@ref). A complete specification of the MPS requires us to define the -physical space and the virtual space of the constituent tensors. At this point is it useful -to note that `MPSKit.jl` is powered by -[`TensorKit.jl`](https://github.com/QuantumKitHub/TensorKit.jl) under the hood and has some -very generic interfaces in order to allow imposing symmetries of all kinds. As a result, it -is sometimes necessary to be a bit more explicit about what we want to do in terms of the -vector spaces involved. In this case, we will not consider any symmetries and simply take -the most naive approach of working within the [`Trivial`](@extref TensorKitSectors.Trivial) -sector. The physical space is then `ℂ^(nmax+1)`, and the virtual space is `ℂ^D` where $D$ is -some integer chosen to be the bond dimension of the MPS, and `ℂ` is an alias for -[`ComplexSpace`](@extref TensorKit.ComplexSpace) (typeset as `\bbC`). As $D$ is increased, -one increases the amount of entanglement, i.e, quantum correlations that can be captured by -the state. - -````julia -cutoff, D = 4, 5 -initial_state = InfiniteMPS(ℂ^(cutoff + 1), ℂ^D) -```` - -```` -1-site InfiniteMPS(ComplexF64, TensorKit.ComplexSpace) with maximal dimension 5: -| ⋮ -| ℂ^5 -├─[1]─ ℂ^5 -│ ℂ^5 -| ⋮ - -```` - -This simply initializes a tensor filled with random entries (check out the documentation for -other useful constructors). Next, we need the creation and annihilation operators. While we -could construct them from scratch, here we will use -[TensorKitTensors.jl](https://github.com/QuantumKitHub/TensorKitTensors.jl), which provides -predefined operators. In particular, `BosonOperators.b_min` creates the bosonic annihilation -operator. - -````julia -a_op = b_min(cutoff = cutoff) # creates a bosonic annihilation operator without any symmetries -display(a_op[]) -display((a_op' * a_op)[]) -```` - -The [] accessor lets us see the underlying array, and indeed the operators are exactly what -we require. The shared [model helpers](https://github.com/QuantumKitHub/MPSKit.jl/blob/main/docs/src/assets/models.jl) define -`bose_hubbard_model` using these operators, although we will construct our own variant -later on. - -````julia -hamiltonian = bose_hubbard_model(; cutoff = cutoff, U = 1, mu = 0.5, t = 0.2) # Infinite chain by default; pass L = N for an open finite chain -```` - -```` -1-site InfiniteMPOHamiltonian(ComplexF64, TensorKit.ComplexSpace) with maximal dimension 4: -| ⋮ -| (ℂ^1 ⊞ ℂ^2 ⊞ ℂ^1) -┼─[1]─ ℂ^5 -│ (ℂ^1 ⊞ ℂ^2 ⊞ ℂ^1) -| ⋮ - -```` - -This has created the Hamiltonian operator as a [matrix product operator](@ref -InfiniteMPOHamiltonian) (MPO) which is a convenient form to use in conjunction with MPS. -Finally, the ground state optimization may be performed with either [`iDMRG`](@ref IDMRG) or -[`VUMPS`](@ref). Both should take similar arguments but it is known that VUMPS is typically -more efficient for these systems so we proceed with that. - -````julia -ground_state, _, _ = find_groundstate(initial_state, hamiltonian, VUMPS(tol = 1.0e-6, verbosity = 2, maxiter = 200)) -println("Energy: ", expectation_value(ground_state, hamiltonian)) -```` - -```` -[ Info: VUMPS init: obj = +4.779985577791e-01 err = 6.1432e-01 -[ Info: VUMPS conv 29: obj = -6.757777651157e-01 err = 9.0419343636e-07 time = 6.50 sec -Energy: -0.6757777651156838 + 2.90085953191521e-17im - -```` - -This automatically runs the algorithm until a certain [error measure](@ref -MPSKit.calc_galerkin) falls below the specified tolerance or the maximum iterations is -reached. Let us wrap all this into a convenient function. - -````julia -function get_ground_state(mu, t, cutoff, D; kwargs...) - hamiltonian = bose_hubbard_model(; cutoff = cutoff, U = 1, mu = mu, t = t) - state = InfiniteMPS(ℂ^(cutoff + 1), ℂ^D) - state, _, _ = find_groundstate(state, hamiltonian, VUMPS(; kwargs...)) - - return state -end - -ground_state = get_ground_state(0.5, 0.01, cutoff, D; tol = 1.0e-6, verbosity = 2, maxiter = 500) -```` - -```` -1-site InfiniteMPS(ComplexF64, TensorKit.ComplexSpace) with maximal dimension 5: -| ⋮ -| ℂ^5 -├─[1]─ ℂ^5 -│ ℂ^5 -| ⋮ - -```` - -Now that we have the state, we may compute observables using the [`expectation_value`](@ref) -function. It typically expects a `Pair`, `(i1, i2, .., ik) => op` where `op` is a -`TensorMap` or `InfiniteMPO` acting over `k` sites. In case of the Hamiltonian, it is not -necessary to specify the indices as it spans the whole lattice. We can now plot the -correlation function $\langle \hat{a}^{\dagger}_i \hat{a}_j\rangle$. - -````julia -plot(map(i -> real.(expectation_value(ground_state, (0, i) => a_op' ⊗ a_op)), 1:50), lw = 2, xlabel = "Site index", ylabel = "Correlation function", yscale = :log10) -hline!([abs2(expectation_value(ground_state, (0,) => a_op))], ls = :dash, c = :black) -```` - -![](figure-1.png) - -We see that the correlations drop off exponentially, indicating the existence of a gapped -Mott insulating phase. Let us now shift our parameters to probe other phases. - -````julia -ground_state = get_ground_state(0.5, 0.2, cutoff, D; tol = 1.0e-6, verbosity = 2, maxiter = 500) - -plot(map(i -> real.(expectation_value(ground_state, (0, i) => a_op' ⊗ a_op)), 1:100), lw = 2, xlabel = "Site index", ylabel = "Correlation function", yscale = :log10, xscale = :log10) -hline!([abs2(expectation_value(ground_state, (0,) => a_op))], ls = :dash, c = :black) -```` - -![](figure-2.png) - -In this case, the correlation function drops off algebraically and eventually saturates as -$\lim_{i \to \infty}\langle\hat{a}_i^{\dagger} \hat{a}_j\rangle ≈ \langle \hat{a}_i^{\dagger}\rangle \langle \hat{a}_j \rangle = |\langle a_i \rangle|^2 \neq 0$. -This is a signature of long-range order and suggests the existence of a Bose-Einstein -condensate. However, this is a bit odd since at zero temperature, the Bose Hubbard model is -not expected to break any continuous symmetries ($U(1)$ in this case, corresponding to -particle number conservation) due to the -[Mermin-Wagner theorem](https://en.wikipedia.org/wiki/Mermin%E2%80%93Wagner_theorem). The -source of this contradiction lies in the fact that the true 1D superfluid ground state is an -extended critical phase exhibiting algebraic decay, however, a finite bond-dimension MPS can -only capture exponentially decaying correlations. As a result, the finite bond dimension -effectively introduces a length scale into the system in a similar manner as finite-size -effects. We can see this clearly by increasing the bond dimension. We also see that the -correlation length seems to depend algebraically on the bond dimension as expected from -finite-entanglement scaling arguments. - -````julia -cutoff = 4 -Ds = 20:5:50 -mu, t = 0.5, 0.2 -states = Vector{InfiniteMPS}(undef, length(Ds)) - -Threads.@threads for idx in eachindex(Ds) - states[idx] = get_ground_state(mu, t, cutoff, Ds[idx]; tol = 1.0e-7, verbosity = 1, maxiter = 500) -end - -npoints = 400 -two_point_correlation = zeros(length(Ds), npoints) -a_op = b_min(cutoff = cutoff) - -Threads.@threads for idx in eachindex(Ds) - two_point_correlation[idx, :] .= real.(expectation_value(states[idx], (1, i) => a_op' ⊗ a_op) for i in 1:npoints) -end - -p = plot( - framestyle = :box, ylabel = "Correlation function " * L"\langle a_i^{\dagger}a_j \rangle", - xlabel = "Distance " * L"|i-j|", xscale = :log10, yscale = :log10, - xticks = ([10, 100], ["10", "100"]), - yticks = ([0.25, 0.5, 1.0], ["0.25", "0.5", "1.0"]) -) - -plot!( - p, 2:npoints, two_point_correlation[:, 2:end]', - lab = "D = " .* string.(permutedims(Ds)), lw = 2 -) - -scatter!( - p, Ds, map(ψ -> correlation_length(ψ; sector = leftunit(ψ)), states), - ylabel = "Correlation length", xlabel = "Bond dimension", - xscale = :log10, yscale = :log10, - inset = bbox(0.2, 0.51, 0.25, 0.25), - subplot = 2, - xticks = (20:10:50, string.(20:10:50)), - yticks = ([50, 100], string.([50, 100])), - xlabelfontsize = 8, - ylabelfontsize = 8, - ylims = [20, 130], - xlims = [15, 60] -) -```` - -![](figure-3.png) - -This shows that any finite bond dimension MPS necessarily breaks the symmetry of the system, -forming a Bose-Einstein condensate which introduces erroneous long-distance behaviour of -correlation functions. In case of finite bond dimension, it is thus reasonable to associate -the finite expectation value of the field operator to the 'quasicondensate' density of the -system which vanishes as $D \to \infty$. - -````julia -quasicondensate_density = map(state -> abs2(expectation_value(state, (0,) => a_op)), states) -```` - -```` -7-element Vector{Float64}: - 0.30974278056400306 - 0.28814773292057094 - 0.27020018663546547 - 0.25712728281504493 - 0.24685386187635147 - 0.23539733364228202 - 0.2279965627270329 -```` - -We may now also visualize the momentum distribution function, which is obtained as the -Fourier transform of the single-particle density matrix. Starting from the definition of the -momentum occupation operators: - -```math -\hat{a}_k = \frac{1}{\sqrt{L}} \sum_j e^{-ikj} \hat{a}_j, \qquad -\hat{a}_k^\dagger = \frac{1}{\sqrt{L}} \sum_{j'} e^{ikj'} \hat{a}_{j'}^\dagger -``` - -the momentum distribution is - -```math -\langle \hat{n}_k \rangle = \langle \hat{a}_k^\dagger \hat{a}_k \rangle -= \frac{1}{L} \sum_{j',j} e^{ik(j'-j)} \langle \hat{a}_{j'}^\dagger \hat{a}_j \rangle. -``` - -For a translationally invariant system, the correlation depends only on the distance -$r = j' - j$: - -$$\langle \hat{a}_{j'}^\dagger \hat{a}_j \rangle = C(r) = \langle \hat{a}_r^\dagger \hat{a}_0 \rangle.$$ - -Changing variables ($j' = j + r$) gives - -$$\langle \hat{n}_k \rangle = \frac{1}{L} \sum_j \sum_r e^{ikr} C(r).$$ - -The sum over $j$ yields a factor of $L$, which cancels the prefactor, leading to - -$$\langle \hat{n}_k \rangle = \sum_{r \in \mathbb{Z}} e^{ikr} \langle \hat{a}_r^\dagger \hat{a}_0 \rangle$$ - -However, we know that a finite bond dimension MPS introduces a non-zero quasi-condensate -density which would give rise to an $\mathcal{O}(N)$ divergence in the momentum distribution -that is not indicative of the true physics of the system. Since we know this contribution -vanishes in the infinite bond dimension limit, we instead work with -$\langle \hat{a}_r^{\dagger} \hat{a}_0 \rangle_c = \langle \hat{a}_r^{\dagger} \hat{a}_0 \rangle - |\langle \hat{a}\rangle|^2$. - -````julia -ks = range(-0.05, 0.15, 500) -momentum_distribution = map( - ((corr, qc),) -> sum( - 2 .* cos.(ks' .* (2:npoints)) .* (corr[2:end] .- qc), dims = 1 - ) .+ (corr[1] .- qc), - zip( - eachrow(two_point_correlation), - quasicondensate_density - ) -) -momentum_distribution = vcat(momentum_distribution...)' -plot(ks, momentum_distribution, lab = "D = " .* string.(permutedims(Ds)), lw = 1.5, xlabel = "Momentum k", ylabel = L"\langle n_k \rangle", ylim = [0, 50]) -```` - -![](figure-4.png) - -We see that the density seems to peak around $k=0$, this time seemingly becoming more -prominent as $D \to \infty$ which seems to suggest again that there is a condensate. -However, going by the Penrose-Onsager criterion, the existence of a condensate can be -quantified by requiring the leading eigenvalue of the single particle density matrix (i.e, -$\langle \hat{n}_{k=0}\rangle = \sum_j \langle \hat{a}_j^{\dagger} \hat{a}_0\rangle$) to -diverge as $O(N)$ in the thermodynamic limit. In this case, since the correlations decay as -a power law, there is naturally a divergence at low momenta. But this does not imply the -existence of a condensate since the order of divergence is much weaker. However, this does -indicate the remnants of some kind of condensation in the 1D model despite the quantum -fluctuations, leading to the practical utility of defining the concept of a quasicondensate -where there is still a notion of phase coherence over short distances. - -What this means for us is that, as far as MPS simulations go, we may still utilize the -quasicondensate density as an effective order parameter, although it will be less robust as -the bond dimension is increased. Alternatively, we realize that the true phase is -characterized as being a superfluid (a concept distinct from Bose-Einstein condensation) and -can be identified by a non-zero value of the superfluid stiffness (also known as helicity -modulus, $\Upsilon$) as defined by Leggett. Upon applying a phase twist $\Phi$ to the -boundaries of the system, a superfluid phase would suffer an increase in energy whereas an -insulating phase would not. In the thermodynamic limit, one could show that the boundary -conditions may be considered as periodic and instead uniformly distribute the phase across -the chain as $\hat{a}_i \to \hat{a}_i e^{i\Phi/L}$. Concretely, in the limit of -$\Phi/L \to 0$, we have: - -$$\frac{E[\Phi] - E[0]}{L} \approx \frac{1}{2} \Upsilon(L) \bigg (\frac{\Phi}{L}\bigg)^2 + \cdots$$ - -In order to find the ground state under these twisted boundary conditions, we must construct -our own variant of the Bose-Hubbard Hamiltonian. Typically you would want to take a peek at -the -[model helper source](https://github.com/QuantumKitHub/MPSKit.jl/blob/main/docs/src/assets/models.jl) to see how these models are defined and tweak it as per your needs. -Here we see that applying twisted boundary conditions is equivalent to adding a prefactor of -$e^{\pm i\phi}$ in front of the hopping amplitudes. - -````julia -function bose_hubbard_model_twisted_bc( - elt::Type{<:Number} = ComplexF64, symmetry::Type{<:Sector} = Trivial; - cutoff::Integer = 5, t = 1.0, U = 1.0, mu = 0.0, phi = 0 - ) - - a_pm = b_plus_b_min(elt, symmetry; cutoff = cutoff) - a_mp = b_min_b_plus(elt, symmetry; cutoff = cutoff) - N = b_num(elt, symmetry; cutoff = cutoff) - - interaction_term = N * (N - id(domain(N))) - - hopping = -t * (exp(1im * phi) * a_pm + exp(-1im * phi) * a_mp) - onsite = U / 2 * interaction_term - mu * N - return chain_hamiltonian(hopping, onsite) -end - -function superfluid_stiffness_profile(t, mu, D, cutoff, ϵ = 1.0e-4, npoints = 11) - phis = range(-ϵ, ϵ, npoints) - energies = zeros(length(phis)) - - Threads.@threads for idx in eachindex(phis) - hamiltonian_twisted = bose_hubbard_model_twisted_bc(; - cutoff = cutoff, t = t, mu = mu, U = 1, phi = phis[idx] - ) - state = InfiniteMPS(ℂ^(cutoff + 1), ℂ^D) - state_twisted, _, _ = find_groundstate( - state, hamiltonian_twisted, VUMPS(; tol = 1.0e-8, verbosity = 0) - ) - energies[idx] = real(expectation_value(state_twisted, hamiltonian_twisted)) - end - - return plot(phis, energies, lw = 2, xlabel = "Phase twist per site" * L"(\phi)", ylabel = "Ground state energy", title = "t = $t | μ = $mu | D = $D | cutoff = $cutoff") -end - -superfluid_stiffness_profile(0.2, 0.3, 5, 4) # superfluid - -superfluid_stiffness_profile(0.01, 0.3, 5, 4) # mott insulator -```` - -![](figure-5.png) - -Now that we know what phases to expect, we can plot the phase diagram by scanning over a -range of parameters. In general, one could do better by performing a bisection algorithm for -each chemical potential to determine the value of the hopping parameter at the transition -point, however the 1D Bose-Hubbard model may have two transition points at the same chemical -potential which makes this a bit cumbersome to implement robustly. Furthermore, we stick to -using the quasi-condensate density as an order parameter since extracting the superfluid -density accurately requires a more robust scheme to compute second derivatives which takes -us away from the focus of this tutorial. - -````julia -cutoff, D = 4, 10 -mus = range(0, 0.75, 40) -ts = range(0, 0.3, 40) - -a_op = b_min(cutoff = cutoff) -order_parameters = zeros(length(ts), length(mus)) - -Threads.@threads for (i, j) in collect(Iterators.product(eachindex(mus), eachindex(ts))) - hamiltonian = bose_hubbard_model(; cutoff = cutoff, U = 1, mu = mus[i], t = ts[j]) - init_state = InfiniteMPS(ℂ^(cutoff + 1), ℂ^D) - state, _, _ = find_groundstate(init_state, hamiltonian, VUMPS(; tol = 1.0e-8, verbosity = 0)) - order_parameters[i, j] = abs(expectation_value(state, 0 => a_op)) -end - -heatmap(ts, mus, order_parameters, xlabel = L"t/U", ylabel = L"\mu/U", title = L"\langle \hat{a}_i \rangle") -```` - -![](figure-6.png) - -Although the bond dimension here is quite low, we already see the deformation of the Mott -insulator lobes to give way to the well known BKT transition that happens at commensurate -density. One can go further and estimate the critical exponents using finite-entanglement -scaling procedures on the correlation functions, but these may now be performed with ease -using what we have learnt in this tutorial. - ---- - -*This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* - diff --git a/docs/src/examples/statmech/hard-hexagon/figure-1.png b/docs/src/examples/statmech/hard-hexagon/figure-1.png new file mode 100644 index 000000000..7174423ee Binary files /dev/null and b/docs/src/examples/statmech/hard-hexagon/figure-1.png differ diff --git a/docs/src/examples/classic2d/1.hard-hexagon/hexagon.svg b/docs/src/examples/statmech/hard-hexagon/hexagon.svg similarity index 100% rename from docs/src/examples/classic2d/1.hard-hexagon/hexagon.svg rename to docs/src/examples/statmech/hard-hexagon/hexagon.svg diff --git a/docs/src/examples/classic2d/1.hard-hexagon/index.md b/docs/src/examples/statmech/hard-hexagon/index.md similarity index 91% rename from docs/src/examples/classic2d/1.hard-hexagon/index.md rename to docs/src/examples/statmech/hard-hexagon/index.md index ed983612b..66c3d3207 100644 --- a/docs/src/examples/classic2d/1.hard-hexagon/index.md +++ b/docs/src/examples/statmech/hard-hexagon/index.md @@ -1,10 +1,10 @@ ```@meta -EditURL = "../../../../../examples/classic2d/1.hard-hexagon/main.jl" +EditURL = "../../../../../examples/statmech/hard-hexagon/main.jl" ``` -[![](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/classic2d/1.hard-hexagon/main.ipynb) -[![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/classic2d/1.hard-hexagon/main.ipynb) -[![](https://img.shields.io/badge/download-project-orange)](https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/QuantumKitHub/MPSKit.jl/examples/tree/gh-pages/dev/examples/classic2d/1.hard-hexagon) +[![](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/statmech/hard-hexagon/main.ipynb) +[![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/statmech/hard-hexagon/main.ipynb) +[![](https://img.shields.io/badge/download-project-orange)](https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/QuantumKitHub/MPSKit.jl/examples/tree/gh-pages/dev/examples/statmech/hard-hexagon) # The Hard Hexagon model @@ -60,7 +60,7 @@ println("F = $F\tS = $S\tξ = $ξ") ```` ```` -F = 0.12340715308061671 S = 1.2807829622015565 ξ = 13.849682582674015 +F = 0.12340715308061596 S = 1.2807829607025263 ξ = 13.849682470052896 ```` @@ -72,7 +72,8 @@ This can be exploited to formulate a scaling hypothesis [pollmann2009](@cite), w First we need to know the entropy and correlation length at several different bond dimensions. Our approach will be to re-use the previous approximated dominant eigenvector, and then expanding its bond dimension and re-running VUMPS. -According to the scaling hypothesis we should have ``S ∝ \frac{c}{6} log(ξ)``. Therefore we should find ``c`` using +According to the scaling hypothesis we should have ``S ∝ \frac{c}{6} log(ξ)``. +Therefore we should find ``c`` using ````julia function scaling_simulations( @@ -106,7 +107,7 @@ c = f.coeffs[2] ```` ```` -0.8025302932613355 +0.8025235781819059 ```` ````julia diff --git a/docs/src/examples/classic2d/1.hard-hexagon/main.ipynb b/docs/src/examples/statmech/hard-hexagon/main.ipynb similarity index 99% rename from docs/src/examples/classic2d/1.hard-hexagon/main.ipynb rename to docs/src/examples/statmech/hard-hexagon/main.ipynb index d8cbd100d..0791de51e 100644 --- a/docs/src/examples/classic2d/1.hard-hexagon/main.ipynb +++ b/docs/src/examples/statmech/hard-hexagon/main.ipynb @@ -200,7 +200,8 @@ "\n", "First we need to know the entropy and correlation length at several different bond dimensions.\n", "Our approach will be to re-use the previous approximated dominant eigenvector, and then expanding its bond dimension and re-running VUMPS.\n", - "According to the scaling hypothesis we should have $S ∝ \\frac{c}{6} log(ξ)$. Therefore we should find $c$ using" + "According to the scaling hypothesis we should have $S ∝ \\frac{c}{6} log(ξ)$.\n", + "Therefore we should find $c$ using" ] }, { diff --git a/docs/src/index.md b/docs/src/index.md index 26d608715..b08ef1f1e 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -46,7 +46,7 @@ features: - [Algorithms](@ref um_algorithms) - [Parallelism in julia](@ref) - [Lattices](@ref lattices) -- [Examples](@ref) +- [Examples](@ref examples_index) - [Library](@ref "Library documentation") - [References](@ref) — how to cite MPSKit, and publications that have used it - [Changelog](@ref) @@ -230,7 +230,7 @@ println(" = $(sum(real(E0)) / length(mps))") ### Additional Resources For more detailed information on the functionality and capabilities of MPSKit, refer to the -Manual section, or have a look at the [Examples](@ref) page. +Manual section, or have a look at the [Examples](@ref examples_index) page. Keep in mind that the documentation is still a work in progress, and that some features may not be fully documented yet. If you encounter any issues or have questions, please check the diff --git a/examples/Cache.toml b/examples/Cache.toml index b7b4b67b6..da0152820 100644 --- a/examples/Cache.toml +++ b/examples/Cache.toml @@ -1,12 +1,16 @@ -[quantum1d] -"5.haldane-spt" = "036357a831de62bc8c11b47d1459cc4025d82907c093405a54f8dff58199c217" -"4.xxz-heisenberg" = "e233902efef4a795d219cdaa33cb7062ae10343d53d50f59a720888a4c5ac108" -"2.haldane" = "3731b764455869cd544549d5960607e6ef9e478772d38e1176622e944d32da94" -"6.hubbard" = "7b2755b20106bf7396873d98eb17c3e6eafff8911b25402bc1d3a8656a276827" -"1.ising-cft" = "f47f09646cd0f5ee1d64df56ba63e65641aeef677854ebe538ced17800f63ba1" -"8.bose-hubbard" = "b85f1b30afd2e8560aeb1f5f146c713805bfc4fa28a16d987800cee52bbe5c7c" -"3.ising-dqpt" = "036eef5420674454faa9ba2311c3230ce3b10b7a1db103d8fe554439a03ded6d" -"7.xy-finiteT" = "c1e7b05098090b86af3bf2b266392890d986821259b4c60e10aead3559d421fc" +[excitations] +haldane = "6303c13abd414504a3a2a74f787ca409e8b4259222b875dc2790960d26eff603" -[classic2d] -"1.hard-hexagon" = "3891f44506135f70a1ce2b05349e61754b5d57abf23da2f61fcea5594180a8fa" +[statmech] +hard-hexagon = "1532649b29e1d4584cbf823a35ef66ed7c5aea40f58021a0e0a59588b2857bee" + +[dynamics] +ising-dqpt = "ed3256a3aa9bf17a6e717e0758c560e85b5c151d0041fd6951d396f211d31121" +xy-finiteT = "bb3866372b40c75b32f1f6ea540ed398fa0ca914ebd72de17688677d7ba05226" + +[groundstates] +bose-hubbard = "547847f63135707738efd75cf589b666a15839a7758ad3e2147a84ca5ddb7695" +haldane-spt = "d61d0ef25c51e5fcbf00499b51295260fbbf818714a4eeede2f20a0af21d1a86" +xxz-heisenberg = "a1f4d49cc4518c4773a0422a23a1f5998bd2d9b5016dda7e64b19fbc18c2c3c0" +ising-cft = "78fd4f151783014b824a68fcd08b3865b5e268a619924c48ba11829038b81374" +hubbard = "5b0dae1f6c0de939b7cbfdeaf16fb7eb945b4a8574acbbb375d4e4bb9aa1586f" diff --git a/examples/README.md b/examples/README.md index 0aea9a7d9..b717655c0 100644 --- a/examples/README.md +++ b/examples/README.md @@ -8,14 +8,23 @@ In order to Trigger the file generation, run: ``julia examples/make.jl` By default, this will only generate files when the input file has not changed. This is -achieved by keeping a checksum of the `main.jl` file in each example in a `cache.toml`. +achieved by keeping a checksum of the `main.jl` file in each example in `Cache.toml`. Total recompilation can be achieved by deleting this file, or alternatively you can just delete the entries for which you wish to generate new files. ## Contributing Contributions are welcome! Please open an issue or a pull request if you have any questions -or suggestions. The code should be placed in a folder in either of the `classic2d` or -`quantum1d` folders, and the `main.jl` file should be the entry point. Any other files will +or suggestions. The code should be placed in a folder in one of the topic groups (`groundstates`, +`excitations`, `dynamics`, `statmech`), and the `main.jl` file should be the entry point. +A new topic group is picked up automatically: any subdirectory of `examples/` is built, so +only the sidebar grouping in `docs/make.jl` needs updating. Any other files will be copied over to the `docs/src/examples` folder, so you can use this to include images or -other files. \ No newline at end of file +other files. + +The folder name is the published URL of the page, so name it after the model or method and do +not prefix it with a number. Numeric prefixes force every later example to be renamed — and +therefore every URL to change — as soon as one is inserted in the middle. The reading order +lives in the summaries on the gallery index page (`docs/src/examples/index.md`), which is also +where a new example should be described: the sidebar lists each group in the order of that page, +with examples it does not link placed last. \ No newline at end of file diff --git a/examples/quantum1d/3.ising-dqpt/finite_timeev.png b/examples/dynamics/ising-dqpt/finite_timeev.png similarity index 100% rename from examples/quantum1d/3.ising-dqpt/finite_timeev.png rename to examples/dynamics/ising-dqpt/finite_timeev.png diff --git a/examples/quantum1d/3.ising-dqpt/infinite_timeev.png b/examples/dynamics/ising-dqpt/infinite_timeev.png similarity index 100% rename from examples/quantum1d/3.ising-dqpt/infinite_timeev.png rename to examples/dynamics/ising-dqpt/infinite_timeev.png diff --git a/examples/quantum1d/3.ising-dqpt/main.jl b/examples/dynamics/ising-dqpt/main.jl similarity index 75% rename from examples/quantum1d/3.ising-dqpt/main.jl rename to examples/dynamics/ising-dqpt/main.jl index 6545ae1f5..3c83366f3 100644 --- a/examples/quantum1d/3.ising-dqpt/main.jl +++ b/examples/dynamics/ising-dqpt/main.jl @@ -1,8 +1,8 @@ md""" # DQPT in the Ising model -In this tutorial we will try to reproduce the results from -[this paper](https://arxiv.org/pdf/1206.2505.pdf). The needed packages are +In this tutorial we will try to reproduce the results from [this paper](https://arxiv.org/pdf/1206.2505.pdf). +The needed packages are """ using MPSKit, TensorKit @@ -11,17 +11,18 @@ using .ExampleModels md""" Dynamical quantum phase transitions (DQPT in short) are signatures of equilibrium phase transitions in a dynamical quantity - the Loschmidt echo. -This quantity is given by ``L(t) = \frac{-2}{N} ln(| < \psi(t) | \psi(0) > |) `` where ``N`` is the system size. +This quantity is given by ``L(t) = \frac{-2}{N} \ln |⟨ψ(t)|ψ(0)⟩|`` where ``N`` is the system size. One typically starts from a ground state and then quenches the Hamiltonian to a different point. -Non analycities in the Loschmidt echo are called 'dynamical quantum phase transitions'. +Non-analyticities in the Loschmidt echo are called 'dynamical quantum phase transitions'. In the mentioned paper they work with -``H(g) = - \sum^{N-1}_{i=1} \sigma^z_i \sigma^z_{i+1} + g \sum_{i=1}^N \sigma^x_i`` +``H(g) = - \sum^{N-1}_{i=1} σ^z_i σ^z_{i+1} + g \sum_{i=1}^N σ^x_i`` -and show that divergences occur when quenching across the critical point (g₀ → g₁) for ``t^*_n = t^*(n+\frac{1}{2})`` with ``t^* = \pi/e(g_1,k^*)``, ``cos(k^*) = (1+g_0 g_1) / (g_0 + g_1)``, `` e(g,k) = \sqrt{(g-cos k)^2 + sin^2 k}``. +and show that divergences occur when quenching across the critical point (g₀ → g₁) for ``t^*_n = t^*(n+\frac{1}{2})`` with ``t^* = π/e(g_1,k^*)``, ``cos(k^*) = (1+g_0 g_1) / (g_0 + g_1)``, `` e(g,k) = \sqrt{(g-cos k)^2 + sin^2 k}``. -The outline of the tutorial is as follows. We will pick ``g₀ = 0.5``, ``g₁ = 2.0``, and perform the time evolution at different system sizes and compare with the thermodynamic limit. +The outline of the tutorial is as follows. +We will pick ``g₀ = 0.5``, ``g₁ = 2.0``, and perform the time evolution at different system sizes and compare with the thermodynamic limit. For those ``g`` we expect non-analyticities to occur at ``t_n ≈ 2.35 (n + 1/2)``. First we construct the Hamiltonian in MPO form, and obtain the pre-quenched ground state: @@ -30,7 +31,7 @@ First we construct the Hamiltonian in MPO form, and obtain the pre-quenched grou L = 20 H₀ = transverse_field_ising(; L, g = -0.5) ψ₀ = FiniteMPS(L, ℂ^2, ℂ^10) -ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG()); +ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG(; verbosity = 0)); md""" ## Finite MPS quenching @@ -42,7 +43,8 @@ echo(ψ₀::FiniteMPS, ψₜ::FiniteMPS) = -2 * log(abs(dot(ψ₀, ψₜ))) / le @assert isapprox(echo(ψ₀, ψ₀), 0, atol = 1.0e-10) md""" -We will initially use a two-site TDVP scheme to dynamically increase the bond dimension while time evolving, and later on switch to a faster one-site scheme. A single timestep can be done using +We will initially use a two-site TDVP scheme to dynamically increase the bond dimension while time evolving, and later on switch to a faster one-site scheme. +A single timestep can be done using """ H₁ = transverse_field_ising(; L, g = -2.0) @@ -51,7 +53,8 @@ dt = 0.01 ψₜ, envs, info = timestep(ψₜ, H₁, 0, dt, TDVP2(; trunc = truncrank(20))); md""" -"envs" is a kind of cache object that keeps track of all environments in `ψ`. It is often advantageous to re-use the environment, so that MPSKit doesn't need to recalculate everything. +"envs" is a kind of cache object that keeps track of all environments in `ψ`. +It is often advantageous to re-use the environment, so that MPSKit doesn't need to recalculate everything. Putting it all together, we get """ @@ -59,7 +62,7 @@ Putting it all together, we get function finite_sim(L; dt = 0.05, finaltime = 5.0) ψ₀ = FiniteMPS(L, ℂ^2, ℂ^10) H₀ = transverse_field_ising(; L, g = -0.5) - ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG()) + ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG(; verbosity = 0)) H₁ = transverse_field_ising(; L, g = -2.0) ψₜ = deepcopy(ψ₀) @@ -87,10 +90,10 @@ Similarly we could start with an initial infinite state and find the pre-quench ψ₀ = InfiniteMPS([ℂ^2], [ℂ^10]) H₀ = transverse_field_ising(; g = -0.5) -ψ₀, _ = find_groundstate(ψ₀, H₀, VUMPS()); +ψ₀, _ = find_groundstate(ψ₀, H₀, VUMPS(; verbosity = 0)); md""" -The dot product of two infinite matrix product states scales as ``\alpha ^N`` where ``α`` is the dominant eigenvalue of the transfer matrix. +The dot product of two infinite matrix product states scales as ``α ^N`` where ``α`` is the dominant eigenvalue of the transfer matrix. It is this ``α`` that is returned when calling """ @@ -104,7 +107,8 @@ echo(ψ₀::InfiniteMPS, ψₜ::InfiniteMPS) = -2 * log(abs(dot(ψ₀, ψₜ))) @assert isapprox(echo(ψ₀, ψ₀), 0, atol = 1.0e-10) md""" -We make use of the `changebonds` machinery to grow the bond dimension. This can also be achieved through a two-site scheme. +We make use of the `changebonds` machinery to grow the bond dimension. +This can also be achieved through a two-site scheme. Multiple algorithms are available, but we will only focus on `OptimalExpand()`. Growing the bond dimension by ``5`` can be done by calling: """ @@ -125,7 +129,7 @@ The final code is function infinite_sim(dt = 0.05, finaltime = 5.0) ψ₀ = InfiniteMPS([ℂ^2], [ℂ^10]) - ψ₀, _ = find_groundstate(ψ₀, H₀, VUMPS()) + ψ₀, _ = find_groundstate(ψ₀, H₀, VUMPS(; verbosity = 0)) ψₜ = deepcopy(ψ₀) envs = environments(ψₜ, H₁, ψₜ) diff --git a/examples/quantum1d/7.xy-finiteT/main.jl b/examples/dynamics/xy-finiteT/main.jl similarity index 81% rename from examples/quantum1d/7.xy-finiteT/main.jl rename to examples/dynamics/xy-finiteT/main.jl index a44fa8121..ea457a35f 100644 --- a/examples/quantum1d/7.xy-finiteT/main.jl +++ b/examples/dynamics/xy-finiteT/main.jl @@ -24,7 +24,7 @@ As a result, many properties have analytical expressions that can be used to ver Here, we use [BenchmarkFreeFermions.jl](https://github.com/Qiaoyi-Li/BenchmarkFreeFermions.jl/) to compare our results. ```math - H = J \sum_{i=1}^{N} \left( \sigma^x_i \sigma^x_{i+1} + \sigma^y_i \sigma^y_{i+1} \right) + H = J \sum_{i=1}^{N} \left( σ^x_i σ^x_{i+1} + σ^y_i σ^y_{i+1} \right) ``` Here we will consider the anti-ferromagnetic ($J > 0$) chain, and restrict ourselves to $J = 1/2$. @@ -46,15 +46,13 @@ end md""" ## Diagonalization of the Hamiltonian -The Hamiltonian can be diagonalized through a Bogoliubov transformation, leading to the following expression for the ground state energy -The Hamiltonian can be diagonalized in terms of fermionic creation and annihilation operators, leading to the following expression in terms of [an incomplete elliptic integral of the second kind](https://en.wikipedia.org/wiki/Elliptic_integral). +The Hamiltonian can be diagonalized in terms of fermionic creation and annihilation operators, which yields an expression for the ground state energy in terms of [an incomplete elliptic integral of the second kind](https://en.wikipedia.org/wiki/Elliptic_integral). ```math - E_0 = -\frac{1}{\pi} \text{EllipticE}\left( \sqrt{1 - \gamma^2} \right) + E_0 = -\frac{1}{π} \text{EllipticE}\left( \sqrt{1 - γ^2} \right) ``` -!!! todo - Show the derivation of the ground state energy by diagonalizing the Hamiltonian in terms of fermionic operators. +The derivation, via a Jordan-Wigner transformation to free fermions followed by a Bogoliubov rotation, can be found in [Lieb, Schultz & Mattis, Ann. Phys. 16, 407 (1961)](https://doi.org/10.1016/0003-4916(61)90115-4). """ function groundstate_energy(J, N) @@ -92,7 +90,7 @@ D = 64 V_init = symmetry === Trivial ? ℂ^32 : U1Space(i => 10 for i in -1:(1 // 2):1) psi_init = FiniteMPS(N, physicalspace(H, 1), V_init) trunc = truncrank(D) -psi, envs, = find_groundstate(psi_init, H, DMRG2(; trunc, maxiter = 5)); +psi, envs, = find_groundstate(psi_init, H, DMRG2(; trunc, maxiter = 5, verbosity = 0)); E_0 = expectation_value(psi, H, envs) / N println("Numerical:\t", real(E_0)) @@ -106,40 +104,39 @@ To go beyond the ground state, we can extract several properties at finite tempe This is given by ```math - Z(\beta) = \text{Tr} \left( e^{-\beta H} \right) + Z(β) = \text{Tr} \left( e^{-β H} \right) ``` -where $\beta = 1 / T$ is the inverse temperature. +where $β = 1 / T$ is the inverse temperature. Given the partition function, we can compute the free energy as ```math - F(\beta) = -\frac{1}{\beta} \log Z(\beta) + F(β) = -\frac{1}{β} \log Z(β) ``` We can also compute observables using ```math - \langle O \rangle = \frac{1}{Z} \text{Tr} \left( O e^{-\beta H} \right) + ⟨O⟩ = \frac{1}{Z} \text{Tr} \left( O e^{-β H} \right) ``` In particular, we can compute the energy as ```math - U = \langle H \rangle = \frac{1}{Z} \text{Tr} \left( H e^{-\beta H} \right) + U = ⟨H⟩ = \frac{1}{Z} \text{Tr} \left( H e^{-β H} \right) ``` Finally, the specific heat can be computed as ```math - \chi = \frac{\partial U}{\partial T} = -\beta^2 \frac{\partial U}{\partial \beta} + χ = \frac{∂ U}{∂ T} = -β^2 \frac{∂ U}{∂ β} ``` Luckily, the partition function can be computed analytically for the XY model. The resulting expression is ```math - Z(\beta) = \prod_{k=1}^{N} \left( 1 + e^{-\beta \epsilon_k} \right)^{1/N} + Z(β) = \prod_{k=1}^{N} \left( 1 + e^{-β ε_k} \right)^{1/N} ``` -!!! todo - Show the derivation of the partition function for the XY model. +This expression follows from the same free-fermion diagonalization as the ground-state energy above: each single-particle mode $ε_k$ is independently occupied or empty, giving the usual free-fermion partition function (see again [Lieb, Schultz & Mattis (1961)](https://doi.org/10.1016/0003-4916(61)90115-4)). """ function partition_function(β::Number, J::Number, N::Number) @@ -162,10 +159,10 @@ md""" ### MPO approach We can numerically compute the partition function by explicitly computing the trace of the time-evolution operator. -To that end, we first need to build the time-evolution operator $e^{-\beta H}$, and then compute its trace. +To that end, we first need to build the time-evolution operator $e^{-β H}$, and then compute its trace. In order to build the time-evolution operator, we can repurpose the `make_time_mpo` function, which constructs the time-evolution operator for the ground state. -However, since we are interested in $e^{-\beta H}$, instead of $e^{-iH dt}$, we work with $dt = -i \beta$. +However, since we are interested in $e^{-β H}$, instead of $e^{-iH dt}$, we work with $dt = -i β$. In particular, we can approximate the exponential using a Taylor series through the `TaylorCluster` algorithm. """ @@ -205,13 +202,13 @@ end md""" Some observations: - The first order approximation fails to capture the behavior of the partition function. -- The higher order approximations are in good agreement with the analytical result, as long as $\beta$ is not too large. -- The computational cost of the approximations does not depend on $\beta$, but on the order of the approximation. +- The higher order approximations are in good agreement with the analytical result, as long as $β$ is not too large. +- The computational cost of the approximations does not depend on $β$, but on the order of the approximation. """ md""" To address the first point, we can have a look at the particular form of the time-evolution operator. -Here we see that for this particular Hamiltonian, all the terms with factors $d\tau$ are either zero or have trace zero. +Here we see that for this particular Hamiltonian, all the terms with factors $dτ$ are either zero or have trace zero. As a result, the trace of the time-evolution operator is equal to the trace of the identity, hence the result is always $2$. ```math @@ -222,9 +219,9 @@ H &= \begin{pmatrix} 0 & 0 & 1 \end{pmatrix} \\ -e^{\tau H} &= \begin{pmatrix} - 1 + \tau D + \frac{\tau^2}{2} D^2 & C + \frac{\tau}{2} (CD + DC) \\ - \tau (B + \frac{\tau}{2} (BD + DB)) & A + \frac{\tau^2}{2} (AD + DA + CB + BC) +e^{τ H} &= \begin{pmatrix} + 1 + τ D + \frac{τ^2}{2} D^2 & C + \frac{τ}{2} (CD + DC) \\ + τ (B + \frac{τ}{2} (BD + DB)) & A + \frac{τ^2}{2} (AD + DA + CB + BC) \end{pmatrix} \end{align} ``` @@ -256,23 +253,21 @@ p_taylor_diff = let end md""" -We can now clearly see that, somewhat unsurprisingly, the error increases the larger $\beta$ becomes. -Given that we are computing Taylor expansions around $\beta = 0$, this is to be expected. +We can now clearly see that, somewhat unsurprisingly, the error increases the larger $β$ becomes. +Given that we are computing Taylor expansions around $β = 0$, this is to be expected. However, there is a trick we can use to improve our results slightly. To that end, we first rewrite the partition function as ```math -Z(\beta) = - \text{Tr} \left( e^{-\beta H} \right) = - \text{Tr} \left( e^{-\beta H / 2} e^{-\beta H / 2} \right) = - \left\langle e^{-\beta H^\dagger / 2}, e^{-\beta H / 2} \right\rangle +Z(β) = + \text{Tr} \left( e^{-β H} \right) = + \text{Tr} \left( e^{-β H / 2} e^{-β H / 2} \right) = + \left\langle e^{-β H^† / 2}, e^{-β H / 2} \right\rangle ``` -In other words, we can compute the partition function at $\beta$ by computing the overlap of two states evolved for $\beta / 2$, as long as the Hamiltonian is Hermitian. -Otherwise, we could still use the same trick, but we would have to compute the evolved states twice, once for $H$ and once for $H^\dagger$. +In other words, we can compute the partition function at $β$ by computing the overlap of two states evolved for $β / 2$, as long as the Hamiltonian is Hermitian. +Otherwise, we could still use the same trick, but we would have to compute the evolved states twice, once for $H$ and once for $H^†$. -!!! todo - Add a figure to illustrate this trick. """ double_logpartition(ρ₁, ρ₂ = ρ₁) = log(real(dot(ρ₁, ρ₂))) / length(ρ₁) @@ -310,15 +305,15 @@ end md""" ### MPO multiplication approach (linear) -While the Taylor series approach is useful, we can only push that so far, since we are always expanding around $\beta = 0$. -However, inspired by the trick we used to improve the results, we can use MPO multiplication techniques to compute partition functions at larger $\beta$. -In particular, we can implement the following algorithm to scan over a linear range of $\beta$ values. +While the Taylor series approach is useful, we can only push that so far, since we are always expanding around $β = 0$. +However, inspired by the trick we used to improve the results, we can use MPO multiplication techniques to compute partition functions at larger $β$. +In particular, we can implement the following algorithm to scan over a linear range of $β$ values. ```math \begin{align} -Z(2\beta) &= Z(\beta) \cdot Z(\beta) \\ -Z(3\beta) &= Z(\beta) \cdot Z(\beta) \cdot Z(\beta) = Z(\beta) \cdot Z(2\beta) \\ -\vdots &= \vdots +Z(2β) &= Z(β) · Z(β) \\ +Z(3β) &= Z(β) · Z(β) · Z(β) = Z(β) · Z(2β) \\ +⋮ &= ⋮ \end{align} ``` @@ -383,10 +378,10 @@ p_mpo_mul_diff = let end md""" -This approach clearly improves the accuracy of the results, indicating that we can indeed compute partition functions at larger $\beta$ values. -However, the computational cost of this approach (at fixed maximal bond dimension) is now linear in $\beta$, since we need to compute the partition function at each $\beta$ value. -Often, this is fine, since we are typically interested in a range of $\beta$ values, rather than a single one. -However, to really push this to larger $\beta$ values, this can still turn out to be a bottleneck. +This approach clearly improves the accuracy of the results, indicating that we can indeed compute partition functions at larger $β$ values. +However, the computational cost of this approach (at fixed maximal bond dimension) is now linear in $β$, since we need to compute the partition function at each $β$ value. +Often, this is fine, since we are typically interested in a range of $β$ values, rather than a single one. +However, to really push this to larger $β$ values, this can still turn out to be a bottleneck. We also have to be careful with the accuracy of our results. In particular, the error in the partition function will accumulate over the iterations, which might turn the results into garbage. @@ -394,25 +389,25 @@ Typically, the entanglement entropy of the density matrix is a good measure of t Apart from the bond dimension, we have two other parameters to tune: the accuracy of the initial density matrix, and the size of the step. The accuracy of the initial density matrix can be improved by increasing the order of the Taylor expansion, but this will result in a larger MPO bond dimension. -On the other hand, if we improve the accuracy of the initial density matrix, we could also increase the step size, which would reduce the number of iterations required to reach a certain $\beta$ value. +On the other hand, if we improve the accuracy of the initial density matrix, we could also increase the step size, which would reduce the number of iterations required to reach a certain $β$ value. Keeping these parameters in balance is necessary to obtain accurate results, and this might require some trial and error. """ md""" ### MPO multiplication approach (exponential) -If we wish to push the results to even larger $\beta$ values, we can note that taking linear steps in $\beta$ is not the only option. -To that end, we can use another trick to scan over an exponential range of $\beta$ values: [exponentiating by squaring](https://en.wikipedia.org/wiki/Exponentiation_by_squaring). -In particular, we note that computing $x^n$ for integer (large) $n$ can typically be done more efficiently than computing $x \cdot x \cdot \dots \cdot x$. +If we wish to push the results to even larger $β$ values, we can note that taking linear steps in $β$ is not the only option. +To that end, we can use another trick to scan over an exponential range of $β$ values: [exponentiating by squaring](https://en.wikipedia.org/wiki/Exponentiation_by_squaring). +In particular, we note that computing $x^n$ for integer (large) $n$ can typically be done more efficiently than computing $x · x · … · x$. To do so, we note that multiplication is associative, and regroup the factors in such a way that we can compute the result in a logarithmic number of steps. Here, we assume $n = 2^m$ for some integer $m$, and note that this could be generalized to any $n$ by decomposing $n$ into a sum of powers of $2$. Then, we can write ```math -x^n = x^{2^m} = x^{2^{m-1}} \cdot x^{2^{m-1}} = (x^{2^{m-2}} \cdot x^{2^{m-2}}) \cdot (x^{2^{m-2}} \cdot x^{2^{m-2}}) = \dots +x^n = x^{2^m} = x^{2^{m-1}} · x^{2^{m-1}} = (x^{2^{m-2}} · x^{2^{m-2}}) · (x^{2^{m-2}} · x^{2^{m-2}}) = … ``` -In other words, we can scan a range of exponentially increasing $\beta$ values by squaring the density matrix at each step. +In other words, we can scan a range of exponentially increasing $β$ values by squaring the density matrix at each step. """ βs_exp = 2.0 .^ (-3:3) @@ -463,7 +458,7 @@ p_mpo_mul_exp_diff = let end md""" -Clearly, the exponential approach allows us to reach larger $\beta$ values much quicker, but there is again a trade-off. +Clearly, the exponential approach allows us to reach larger $β$ values much quicker, but there is again a trade-off. Since the size of the steps are increasing, we need to be more careful with the accuracy of our approximations. !!! warning @@ -477,14 +472,14 @@ md""" Finally, we can also note that the partition function is characterized by the following differential equation: ```math -\frac{dZ}{d\beta} = -H \cdot Z -\implies Z(\beta) = e^{-\beta H} \cdot Z(0) +\frac{dZ}{dβ} = -H · Z +⟹ Z(β) = e^{-β H} · Z(0) ``` -In other words, we can compute the partition function at $\beta$ by evolving the partition function at $0$ for a time $d\tau = -i \beta$. +In other words, we can compute the partition function at $β$ by evolving the partition function at $0$ for a time $dτ = -i β$. The starting point for this approach could be either achieved through one of the techniques we have already discussed, but we can also start from the infinite temperature state directly. -In particular, this state is given by the identity MPO, and we can evolve this state to compute the partition function at any $\beta$ value. +In particular, this state is given by the identity MPO, and we can evolve this state to compute the partition function at any $β$ value. """ Z_tdvp = zeros(length(βs)) @@ -534,6 +529,6 @@ end md""" !!! note - We could further improve the accuracy of the TDVP approach by evolving with $(H \otimes \mathbb{1} + \mathbb{1} \otimes H^\dagger)$, rather than $H \otimes \mathbb{1}$ which is the current implementation. + We could further improve the accuracy of the TDVP approach by evolving with $(H ⊗ \mathbb{1} + \mathbb{1} ⊗ H^†)$, rather than $H ⊗ \mathbb{1}$ which is the current implementation. This is known to improve the stability of the positive semidefinite property of the density matrix, and could lead to more accurate results. """ diff --git a/examples/quantum1d/2.haldane/main.jl b/examples/excitations/haldane/main.jl similarity index 99% rename from examples/quantum1d/2.haldane/main.jl rename to examples/excitations/haldane/main.jl index a7213b12a..49c2a6486 100644 --- a/examples/quantum1d/2.haldane/main.jl +++ b/examples/excitations/haldane/main.jl @@ -107,7 +107,7 @@ virtual_space_inf = Rep[SU₂](1 // 2 => 16, 3 // 2 => 16, 5 // 2 => 8, 7 // 2 = ψ_inf, envs_inf, info_inf = find_groundstate(ψ₀_inf, H; verbosity = 0) kspace = range(0, π, 16) -Es, _ = excitations(H, QuasiparticleAnsatz(), kspace, ψ_inf, envs_inf; sector = SU2Irrep(1)) +Es, _ = excitations(H, QuasiparticleAnsatz(), kspace, ψ_inf, envs_inf; sector = SU2Irrep(1), verbosity = 0) ΔE, idx = findmin(real.(Es)) println("minimum @k = $(kspace[idx]):\t ΔE = $(ΔE)") diff --git a/examples/groundstates/bose-hubbard/main.jl b/examples/groundstates/bose-hubbard/main.jl new file mode 100644 index 000000000..a06049764 --- /dev/null +++ b/examples/groundstates/bose-hubbard/main.jl @@ -0,0 +1,335 @@ +using Markdown +using TensorKitTensors.BosonOperators: b_min, b_plus_b_min, b_min_b_plus, b_num +using MPSKit, TensorKit +include(joinpath(@__DIR__, "..", "..", "..", "docs", "src", "assets", "models.jl")) #hide +using .ExampleModels +using Plots, LaTeXStrings + +#src # for reproducibility: +#src using Random +#src Random.seed!(123) + +theme(:wong) +default(fontfamily = "Computer Modern", label = nothing, dpi = 100, framestyle = :box) + +md""" +# 1D Bose-Hubbard model + +In this tutorial, we will explore the physics of the one-dimensional Bose–Hubbard model using matrix product states. +For the most part, we replicate the results presented in [**Phys. Rev. B 105, 134502**](https://journals.aps.org/prb/abstract/10.1103/PhysRevB.105.134502), which can be consulted for any statements in this tutorial that are not otherwise cited. +The Hamiltonian under study is defined as follows: + +$$H = -t \sum_{i} (â_i^{†} â_{i+1} + â_{i+1}^{†} â_i) + \frac{U}{2} \sum_i \hat{n}_i(\hat{n}_i - 1) - μ \sum_i \hat{n}_i$$ + +where the bosonic creation and annihilation operators satisfy the canonical commutation relations (CCR): + +$$[â_i, â_j^{†}] = δ_{ij}.$$ + +Each lattice site hosts a local Hilbert space corresponding to bosonic occupation states $|n⟩$, where $(n = 0, 1, 2, …)$. +Since this space is formally infinite-dimensional, numerical simulations typically impose a truncation at some maximum occupation number $(n_{\text{max}})$. +Such a treatment is justified since it can be observed that the simulation results quickly converge with the cutoff if the filling fraction is kept sufficiently low. + +Within this truncated space, the local creation and annihilation operators are represented by finite-dimensional matrices. +For example, with cutoff $n_{\text{max}}$, the annihilation operator takes the form + +```math +â = +\begin{bmatrix} +0 & \sqrt{1} & 0 & 0 & ⋯ & 0 \\ +0 & 0 & \sqrt{2} & 0 & ⋯ & 0 \\ +0 & 0 & 0 & \sqrt{3} & ⋯ & 0 \\ +⋮ & & & ⋱ & ⋱ & ⋮ \\ +0 & 0 & 0 & ⋯ & 0 & \sqrt{n_{\text{max}}} \\ +0 & 0 & 0 & ⋯ & 0 & 0 +\end{bmatrix} +``` + +and the creation operator is simply its Hermitian conjugate, + +```math +â^† = +\begin{bmatrix} +0 & 0 & 0 & ⋯ & 0 & 0 \\ +\sqrt{1} & 0 & 0 & ⋯ & 0 & 0 \\ +0 & \sqrt{2} & 0 & ⋯ & 0 & 0 \\ +⋮ & & ⋱ & ⋱ & & ⋮ \\ +0 & 0 & 0 & ⋯ & 0 & 0 \\ +0 & 0 & 0 & ⋯ & \sqrt{n_{\text{max}}} & 0 +\end{bmatrix} +``` + +The number operator is then given by + +$$\hat{n} = â^† â = \mathrm{diag}(0, 1, 2, …, n_{\text{max}}).$$ + +Before moving on, notice that the Hamiltonian is uniform and translationally invariant. +Typically, such models are studied on a finite chain of $N$ sites with periodic boundary conditions, but this introduces finite-size effects that are rather annoying to deal with. +In contrast, the MPS framework allows us to work directly in the thermodynamic limit, avoiding such artifacts. +We will follow this line of exploration in this tutorial and leave finite systems for another example. + +In order to work in the thermodynamic limit, we will have to create an [`InfiniteMPS`](@ref). +A complete specification of the MPS requires us to define the physical space and the virtual space of the constituent tensors. +At this point is it useful to note that `MPSKit.jl` is powered by [`TensorKit.jl`](https://github.com/QuantumKitHub/TensorKit.jl) under the hood and has some very generic interfaces in order to allow imposing symmetries of all kinds. +As a result, it is sometimes necessary to be a bit more explicit about what we want to do in terms of the vector spaces involved. +In this case, we will not consider any symmetries and simply take the most naive approach of working within the [`Trivial`](@extref TensorKitSectors.Trivial) sector. +The physical space is then `ℂ^(nmax+1)`, and the virtual space is `ℂ^D` where $D$ is some integer chosen to be the bond dimension of the MPS, and `ℂ` is an alias for [`ComplexSpace`](@extref TensorKit.ComplexSpace) (typeset as `\bbC`). +As $D$ is increased, one increases the amount of entanglement, i.e, quantum correlations that can be captured by the state. +""" + +cutoff, D = 4, 5 +initial_state = InfiniteMPS(ℂ^(cutoff + 1), ℂ^D) + +md""" +This simply initializes a tensor filled with random entries (check out the documentation for other useful constructors). +Next, we need the creation and annihilation operators. +While we could construct them from scratch, here we will use [TensorKitTensors.jl](https://github.com/QuantumKitHub/TensorKitTensors.jl), which provides predefined operators. +In particular, `BosonOperators.b_min` creates the bosonic annihilation operator. +""" + +a_op = b_min(cutoff = cutoff) # creates a bosonic annihilation operator without any symmetries +display(a_op[]) +display((a_op' * a_op)[]) + +md""" + +The [] accessor lets us see the underlying array, and indeed the operators are exactly what we require. +The shared [model helpers](https://github.com/QuantumKitHub/MPSKit.jl/blob/main/docs/src/assets/models.jl) define `bose_hubbard_model` using these operators, although we will construct our own variant later on. + +""" + +hamiltonian = bose_hubbard_model(; cutoff = cutoff, U = 1, mu = 0.5, t = 0.2) # Infinite chain by default; pass L = N for an open finite chain + +md""" +This has created the Hamiltonian operator as a [matrix product operator](@ref InfiniteMPOHamiltonian) (MPO) which is a convenient form to use in conjunction with MPS. +Finally, the ground state optimization may be performed with either [`iDMRG`](@ref IDMRG) or [`VUMPS`](@ref). +Both should take similar arguments but it is known that VUMPS is typically more efficient for these systems so we proceed with that. +""" + +ground_state, _, _ = find_groundstate(initial_state, hamiltonian, VUMPS(tol = 1.0e-6, verbosity = 2, maxiter = 200)) +println("Energy: ", expectation_value(ground_state, hamiltonian)) + +md""" +This automatically runs the algorithm until a certain [error measure](@ref MPSKit.calc_galerkin) falls below the specified tolerance or the maximum iterations is reached. +Let us wrap all this into a convenient function. +""" + +function get_ground_state(mu, t, cutoff, D; kwargs...) + hamiltonian = bose_hubbard_model(; cutoff = cutoff, U = 1, mu = mu, t = t) + state = InfiniteMPS(ℂ^(cutoff + 1), ℂ^D) + state, _, _ = find_groundstate(state, hamiltonian, VUMPS(; kwargs...)) + + return state +end + +ground_state = get_ground_state(0.5, 0.01, cutoff, D; tol = 1.0e-6, verbosity = 2, maxiter = 500) + +md""" + +Now that we have the state, we may compute observables using the [`expectation_value`](@ref) function. +It typically expects a `Pair`, `(i1, i2, .., ik) => op` where `op` is a `TensorMap` or `InfiniteMPO` acting over `k` sites. +In case of the Hamiltonian, it is not necessary to specify the indices as it spans the whole lattice. +We can now plot the correlation function $⟨â^{†}_i â_j⟩$. +""" + +plot(map(i -> real.(expectation_value(ground_state, (0, i) => a_op' ⊗ a_op)), 1:50), lw = 2, xlabel = "Site index", ylabel = "Correlation function", yscale = :log10) +hline!([abs2(expectation_value(ground_state, (0,) => a_op))], ls = :dash, c = :black) + +md""" +We see that the correlations drop off exponentially, indicating the existence of a gapped Mott insulating phase. +Let us now shift our parameters to probe other phases. +""" + +ground_state = get_ground_state(0.5, 0.2, cutoff, D; tol = 1.0e-6, verbosity = 2, maxiter = 500) + +plot(map(i -> real.(expectation_value(ground_state, (0, i) => a_op' ⊗ a_op)), 1:100), lw = 2, xlabel = "Site index", ylabel = "Correlation function", yscale = :log10, xscale = :log10) +hline!([abs2(expectation_value(ground_state, (0,) => a_op))], ls = :dash, c = :black) + +md""" +In this case, the correlation function drops off algebraically and eventually saturates as $\lim_{i → ∞}⟨â_i^{†} â_j⟩ ≈ ⟨â_i^{†}⟩ ⟨â_j⟩ = |⟨a_i⟩|^2 ≠ 0$. +This is a signature of long-range order and suggests the existence of a Bose-Einstein condensate. +However, this is a bit odd since at zero temperature, the Bose Hubbard model is not expected to break any continuous symmetries ($U(1)$ in this case, corresponding to particle number conservation) due to the [Mermin-Wagner theorem](https://en.wikipedia.org/wiki/Mermin%E2%80%93Wagner_theorem). +The source of this contradiction lies in the fact that the true 1D superfluid ground state is an extended critical phase exhibiting algebraic decay, however, a finite bond-dimension MPS can only capture exponentially decaying correlations. +As a result, the finite bond dimension effectively introduces a length scale into the system in a similar manner as finite-size effects. +We can see this clearly by increasing the bond dimension. +We also see that the correlation length seems to depend algebraically on the bond dimension as expected from finite-entanglement scaling arguments. +""" + +cutoff = 4 +Ds = 20:5:50 +mu, t = 0.5, 0.2 +states = Vector{InfiniteMPS}(undef, length(Ds)) + +Threads.@threads for idx in eachindex(Ds) + states[idx] = get_ground_state(mu, t, cutoff, Ds[idx]; tol = 1.0e-7, verbosity = 1, maxiter = 500) +end + +npoints = 400 +two_point_correlation = zeros(length(Ds), npoints) +a_op = b_min(cutoff = cutoff) + +Threads.@threads for idx in eachindex(Ds) + two_point_correlation[idx, :] .= real.(expectation_value(states[idx], (1, i) => a_op' ⊗ a_op) for i in 1:npoints) +end + +p = plot( + framestyle = :box, ylabel = "Correlation function " * L"\langle a_i^{\dagger}a_j \rangle", + xlabel = "Distance " * L"|i-j|", xscale = :log10, yscale = :log10, + xticks = ([10, 100], ["10", "100"]), + yticks = ([0.25, 0.5, 1.0], ["0.25", "0.5", "1.0"]) +) + +plot!( + p, 2:npoints, two_point_correlation[:, 2:end]', + lab = "D = " .* string.(permutedims(Ds)), lw = 2 +) + +scatter!( + p, Ds, map(ψ -> correlation_length(ψ; sector = leftunit(ψ)), states), + ylabel = "Correlation length", xlabel = "Bond dimension", + xscale = :log10, yscale = :log10, + inset = bbox(0.2, 0.51, 0.25, 0.25), + subplot = 2, + xticks = (20:10:50, string.(20:10:50)), + yticks = ([50, 100], string.([50, 100])), + xlabelfontsize = 8, + ylabelfontsize = 8, + ylims = [20, 130], + xlims = [15, 60] +) + +md""" +This shows that any finite bond dimension MPS necessarily breaks the symmetry of the system, forming a Bose-Einstein condensate which introduces erroneous long-distance behaviour of correlation functions. +In case of finite bond dimension, it is thus reasonable to associate the finite expectation value of the field operator to the 'quasicondensate' density of the system which vanishes as $D → ∞$. +""" + +quasicondensate_density = map(state -> abs2(expectation_value(state, (0,) => a_op)), states) + +md""" +We may now also visualize the momentum distribution function, which is obtained as the Fourier transform of the single-particle density matrix. +Starting from the definition of the momentum occupation operators: + +```math +â_k = \frac{1}{\sqrt{L}} \sum_j e^{-ikj} â_j, \qquad +â_k^† = \frac{1}{\sqrt{L}} \sum_{j'} e^{ikj'} â_{j'}^† +``` + +the momentum distribution is + +```math +⟨\hat{n}_k⟩ = ⟨â_k^† â_k⟩ += \frac{1}{L} \sum_{j',j} e^{ik(j'-j)} ⟨â_{j'}^† â_j⟩. +``` + +For a translationally invariant system, the correlation depends only on the distance $r = j' - j$: + +$$⟨â_{j'}^† â_j⟩ = C(r) = ⟨â_r^† â_0⟩.$$ + +Changing variables ($j' = j + r$) gives + +$$⟨\hat{n}_k⟩ = \frac{1}{L} \sum_j \sum_r e^{ikr} C(r).$$ + +The sum over $j$ yields a factor of $L$, which cancels the prefactor, leading to + +$$⟨\hat{n}_k⟩ = \sum_{r ∈ \mathbb{Z}} e^{ikr} ⟨â_r^† â_0⟩$$ + +However, we know that a finite bond dimension MPS introduces a non-zero quasi-condensate density which would give rise to an $\mathcal{O}(N)$ divergence in the momentum distribution that is not indicative of the true physics of the system. +Since we know this contribution vanishes in the infinite bond dimension limit, we instead work with $⟨â_r^{†} â_0⟩_c = ⟨â_r^{†} â_0⟩ - |⟨â⟩|^2$. +""" + +ks = range(-0.05, 0.15, 500) +momentum_distribution = map( + ((corr, qc),) -> sum( + 2 .* cos.(ks' .* (2:npoints)) .* (corr[2:end] .- qc), dims = 1 + ) .+ (corr[1] .- qc), + zip( + eachrow(two_point_correlation), + quasicondensate_density + ) +) +momentum_distribution = vcat(momentum_distribution...)' +plot(ks, momentum_distribution, lab = "D = " .* string.(permutedims(Ds)), lw = 1.5, xlabel = "Momentum k", ylabel = L"\langle n_k \rangle", ylim = [0, 50]) + +md""" +We see that the density seems to peak around $k=0$, this time seemingly becoming more prominent as $D → ∞$ which seems to suggest again that there is a condensate. +However, going by the Penrose-Onsager criterion, the existence of a condensate can be quantified by requiring the leading eigenvalue of the single particle density matrix (i.e, $⟨\hat{n}_{k=0}⟩ = \sum_j ⟨â_j^{†} â_0⟩$) to diverge as $O(N)$ in the thermodynamic limit. +In this case, since the correlations decay as a power law, there is naturally a divergence at low momenta. +But this does not imply the existence of a condensate since the order of divergence is much weaker. +However, this does indicate the remnants of some kind of condensation in the 1D model despite the quantum fluctuations, leading to the practical utility of defining the concept of a quasicondensate where there is still a notion of phase coherence over short distances. + +What this means for us is that, as far as MPS simulations go, we may still utilize the quasicondensate density as an effective order parameter, although it will be less robust as the bond dimension is increased. +Alternatively, we realize that the true phase is characterized as being a superfluid (a concept distinct from Bose-Einstein condensation) and can be identified by a non-zero value of the superfluid stiffness (also known as helicity modulus, $Υ$) as defined by Leggett. +Upon applying a phase twist $Φ$ to the boundaries of the system, a superfluid phase would suffer an increase in energy whereas an insulating phase would not. +In the thermodynamic limit, one could show that the boundary conditions may be considered as periodic and instead uniformly distribute the phase across the chain as $â_i → â_i e^{iΦ/L}$. +Concretely, in the limit of $Φ/L → 0$, we have: + +$$\frac{E[Φ] - E[0]}{L} ≈ \frac{1}{2} Υ(L) \bigg (\frac{Φ}{L}\bigg)^2 + ⋯$$ + +In order to find the ground state under these twisted boundary conditions, we must construct our own variant of the Bose-Hubbard Hamiltonian. +Typically you would want to take a peek at the [model helper source](https://github.com/QuantumKitHub/MPSKit.jl/blob/main/docs/src/assets/models.jl) to see how these models are defined and tweak it as per your needs. +Here we see that applying twisted boundary conditions is equivalent to adding a prefactor of $e^{± iφ}$ in front of the hopping amplitudes. +""" + +function bose_hubbard_model_twisted_bc( + elt::Type{<:Number} = ComplexF64, symmetry::Type{<:Sector} = Trivial; + cutoff::Integer = 5, t = 1.0, U = 1.0, mu = 0.0, phi = 0 + ) + + a_pm = b_plus_b_min(elt, symmetry; cutoff = cutoff) + a_mp = b_min_b_plus(elt, symmetry; cutoff = cutoff) + N = b_num(elt, symmetry; cutoff = cutoff) + + interaction_term = N * (N - id(domain(N))) + + hopping = -t * (exp(1im * phi) * a_pm + exp(-1im * phi) * a_mp) + onsite = U / 2 * interaction_term - mu * N + return chain_hamiltonian(hopping, onsite) +end + +function superfluid_stiffness_profile(t, mu, D, cutoff, ϵ = 1.0e-4, npoints = 11) + phis = range(-ϵ, ϵ, npoints) + energies = zeros(length(phis)) + + Threads.@threads for idx in eachindex(phis) + hamiltonian_twisted = bose_hubbard_model_twisted_bc(; + cutoff = cutoff, t = t, mu = mu, U = 1, phi = phis[idx] + ) + state = InfiniteMPS(ℂ^(cutoff + 1), ℂ^D) + state_twisted, _, _ = find_groundstate( + state, hamiltonian_twisted, VUMPS(; tol = 1.0e-8, verbosity = 0) + ) + energies[idx] = real(expectation_value(state_twisted, hamiltonian_twisted)) + end + + return plot(phis, energies, lw = 2, xlabel = "Phase twist per site" * L"(\phi)", ylabel = "Ground state energy", title = "t = $t | μ = $mu | D = $D | cutoff = $cutoff") +end + +superfluid_stiffness_profile(0.2, 0.3, 5, 4) # superfluid + +superfluid_stiffness_profile(0.01, 0.3, 5, 4) # mott insulator + +md""" +Now that we know what phases to expect, we can plot the phase diagram by scanning over a range of parameters. +In general, one could do better by performing a bisection algorithm for each chemical potential to determine the value of the hopping parameter at the transition point, however the 1D Bose-Hubbard model may have two transition points at the same chemical potential which makes this a bit cumbersome to implement robustly. +Furthermore, we stick to using the quasi-condensate density as an order parameter since extracting the superfluid density accurately requires a more robust scheme to compute second derivatives which takes us away from the focus of this tutorial. +""" + +cutoff, D = 4, 10 +mus = range(0, 0.75, 40) +ts = range(0, 0.3, 40) + +a_op = b_min(cutoff = cutoff) +order_parameters = zeros(length(ts), length(mus)) + +Threads.@threads for (i, j) in collect(Iterators.product(eachindex(mus), eachindex(ts))) + hamiltonian = bose_hubbard_model(; cutoff = cutoff, U = 1, mu = mus[i], t = ts[j]) + init_state = InfiniteMPS(ℂ^(cutoff + 1), ℂ^D) + state, _, _ = find_groundstate(init_state, hamiltonian, VUMPS(; tol = 1.0e-8, verbosity = 0)) + order_parameters[i, j] = abs(expectation_value(state, 0 => a_op)) +end + +heatmap(ts, mus, order_parameters, xlabel = L"t/U", ylabel = L"\mu/U", title = L"\langle \hat{a}_i \rangle") + +md""" +Although the bond dimension here is quite low, we already see the deformation of the Mott insulator lobes to give way to the well known BKT transition that happens at commensurate density. +One can go further and estimate the critical exponents using finite-entanglement scaling procedures on the correlation functions, but these may now be performed with ease using what we have learnt in this tutorial. +""" diff --git a/examples/groundstates/haldane-spt/main.jl b/examples/groundstates/haldane-spt/main.jl new file mode 100644 index 000000000..d566a35f5 --- /dev/null +++ b/examples/groundstates/haldane-spt/main.jl @@ -0,0 +1,135 @@ +md""" +# Spin 1 Heisenberg model + +The quantum Heisenberg model is a model often used in the study of critical points and phase transitions of magnetic systems, in which the spins are treated quantum mechanically. +It models magnetic interactions between neighbouring spins through the so-called Heisenberg interaction term, which causes the spins to either align ($J > 0$) or anti-align ($J < 0$), thus modeling a (anti-) ferromagnetic system. +Here, we will focus on the case of $S = 1$, with anti-ferromagnetic interactions. + +```math +H = -J \sum_{⟨i, j⟩} \vec{S}_i · \vec{S}_j +``` + +Importantly, the Hamiltonian of the isotropic model is invariant under $SU(2)$ rotations, which can be exploited to increase efficiency, as well as interpretability of the MPS simulations. +To see this, we can make use of the following derivation for the interaction term: + +```math +(\vec{S}_i + \vec{S}_j)^2 = \vec{S}_i^2 + 2 \vec{S}_i · \vec{S}_j + \vec{S}_j^2 +⟹ \vec{S}_i · \vec{S}_j = \frac{1}{2} \left( (\vec{S}_i + \vec{S}_j)^2 - \vec{S}_i^2 - \vec{S}_j^2 \right) +``` + +Here, we recognize the quadratic [Casimir element](https://en.wikipedia.org/wiki/Casimir_element) $\vec{S}^2$, which commutes with the elements of $SU(2)$. +Consequently, the Hamiltonian also commutes with all elements of $SU(2)$. +""" + +using TensorKit +using MPSKit +using Plots + +casimir(s::SU2Irrep) = s.j * (s.j + 1) + +function heisenberg_hamiltonian(; J = -1.0) + s = SU2Irrep(1) + ℋ = SU2Space(1 => 1) + SS = zeros(ComplexF64, ℋ ⊗ ℋ ← ℋ ⊗ ℋ) + for (S, data) in blocks(SS) + data .= -0.5J * (casimir(S) - casimir(s) - casimir(s)) + end + return InfiniteMPOHamiltonian(SS) +end +H = heisenberg_hamiltonian() + +md""" +## Symmetry-Protected Topological Order + +The representations of $SU(2)$ possess additional structure, known as a $\mathbb{Z}_2$-grading. +This means, that they can be partitioned in integer $(+)$ and half-integer $(-)$ spins, and the fusion rules will respect this grading. +In other words, the following table holds: + +| $s_1$ | $s_2$ | $s_1 ⊗ s_2$ | +| --- | --- | --- | +| $+$ | $+$ | $+$ | +| $+$ | $-$ | $-$ | +| $-$ | $+$ | $-$ | +| $-$ | $-$ | $+$ | + +This has important consequences for the MPS representation of an $SU(2)$-symmetric state. +If the physical spin consists of only integer representations, this means that the left and right virtual spaces of the MPS tensor belong to the same grading, i.e. are either both integer, or both half-integer. +Thus, naively constructing a MPS tensor which contains spins from both classes, will necessarily be the direct sum of the two, which yields a non-injective MPS. + +```math +|ψ⟩ = |ψ_+⟩ ⊕ |ψ_-⟩ +``` + +Because of this direct sum, many of the usual MPS algorithms will fail, as they typically cannot deal with non-injective MPS. +The resulting MPS will have multiple values of the transfer matrix spectrum that have a magnitude close to 1, which is a clear sign of a non-injective MPS. +""" + +ℋ = SU2Space(1 => 1) +V_wrong = SU2Space(0 => 8, 1 // 2 => 8, 1 => 3, 3 // 2 => 3) +ψ = InfiniteMPS(ℋ, V_wrong) +ψ, environments, info = find_groundstate(ψ, H, VUMPS(; maxiter = 10)) +sectors = SU2Irrep[0, 1 // 2, 1, 3 // 2] +transferplot(ψ; sectors, title = "Transfer matrix spectrum", legend = :outertop) + +md""" +Nevertheless, using the symmetry, this can be remedied rather easily, by imposing the ground state to belong to a single class, and comparing the results. +We can readily obtain 3 different criteria for determining the SPT phase of the ground state. + +Firstly, we can compare variational energies for states of similar bond dimensions. +As we expect the state of the wrong SPT phase to have to expend some of its expressiveness in correcting the SPT, it should have a harder time reaching lower energies. + +Secondly, when inspecting the spectrum of the transfer matrix, we should see that the wrong SPT phase has a dominant value that is not in the trivial sector, which leads to a non-injective MPS. + +Finally, the entanglement spectrum of the wrong SPT phase will show degeneracies of all singular values, which can again be attributed to an attempt to mimic the spectrum of the right SPT phase. +""" + +V_plus = SU2Space(0 => 10, 1 => 5, 2 => 3) +ψ_plus = InfiniteMPS(ℋ, V_plus) +ψ_plus, = find_groundstate(ψ_plus, H, VUMPS(; maxiter = 100)) +E_plus = expectation_value(ψ_plus, H) + +#+ + +V_minus = SU2Space(1 // 2 => 10, 3 // 2 => 5, 5 // 2 => 3) +ψ_minus = InfiniteMPS(ℋ, V_minus) +ψ_minus, = find_groundstate(ψ_minus, H, VUMPS(; maxiter = 100)) +E_minus = expectation_value(ψ_minus, H) + +#+ + +transferp_plus = transferplot( + ψ_plus; + sectors = SU2Irrep[0, 1, 2], title = "ψ_plus", legend = :outertop +) +transferp_minus = transferplot( + ψ_minus; + sectors = SU2Irrep[0, 1, 2], title = "ψ_minus", legend = :outertop +) +plot(transferp_plus, transferp_minus; layout = (1, 2), size = (800, 400)) + +#+ + +entanglementp_plus = entanglementplot(ψ_plus; title = "ψ_plus", legend = :outertop) +entanglementp_minus = entanglementplot(ψ_minus; title = "ψ_minus", legend = :outertop) +plot(entanglementp_plus, entanglementp_minus; layout = (1, 2), size = (800, 400)) + +md""" + +As we can see, the ground state can be found in the non-trivial SPT phase, $|ψ_-⟩$. +We can obtain an intuitive understanding of $|ψ_+⟩$ by considering the following diagram. +If we denote the MPS tensors that make up the ground state as $A_-$, we can construct a state in the trivial SPT phase that approximates the ground state as follows: + +```@raw html +SPT tensors +``` + +In other words, we can factorize a purely virtual isomorphism of $S = 1/2$ in order to obtain the ground state. +This then also explains the degeneracies in the entanglement spectrum as well as in the transfer matrix spectrum. +Finally, we can further confirm this intuition by looking at the entanglement entropy of the ground state. +As we can see, the entanglement entropy of the state in the wrong SPT phase is exactly $log(2)$ higher than the one in the right SPT phase, which is exactly what we would expect from the diagram above. +""" + +S_minus = sum(real, entropy(ψ_minus)) +S_plus = sum(real, entropy(ψ_plus)) +println("S_minus + log(2) = $(S_minus + log(2))") +println("S_plus = $S_plus") diff --git a/examples/quantum1d/5.haldane-spt/spt-tensors.svg b/examples/groundstates/haldane-spt/spt-tensors.svg similarity index 100% rename from examples/quantum1d/5.haldane-spt/spt-tensors.svg rename to examples/groundstates/haldane-spt/spt-tensors.svg diff --git a/examples/quantum1d/6.hubbard/main.jl b/examples/groundstates/hubbard/main.jl similarity index 88% rename from examples/quantum1d/6.hubbard/main.jl rename to examples/groundstates/hubbard/main.jl index 2482f3db7..ef0f9f398 100644 --- a/examples/quantum1d/6.hubbard/main.jl +++ b/examples/groundstates/hubbard/main.jl @@ -4,12 +4,11 @@ md""" # Hubbard chain at half filling The Hubbard model is a model of interacting fermions on a lattice, which is often used as a somewhat realistic model for electrons in a solid. -The Hamiltonian consists of two terms that describe competing forces of each electron: -a kinetic term that allows electrons to hop between neighboring sites, and a potential term reflecting on-site interactions between electrons. +The Hamiltonian consists of two terms that describe competing forces of each electron: a kinetic term that allows electrons to hop between neighboring sites, and a potential term reflecting on-site interactions between electrons. Often, a third term is included which serves as a chemical potential to control the number of electrons in the system. ```math -H = -t \sum_{\langle i, j \rangle, \sigma} c^{\dagger}_{i,\sigma} c_{j,\sigma} + U \sum_i n_{i,\uparrow} n_{i,\downarrow} - \mu \sum_{i,\sigma} n_{i,\sigma} +H = -t ∑_{⟨i, j⟩, σ} c^{†}_{i,σ} c_{j,σ} + U ∑_i n_{i,↑} n_{i,↓} - μ ∑_{i,σ} n_{i,σ} ``` At half-filling, the system exhibits particle-hole symmetry, which can be made explicit by rewriting the Hamiltonian slightly. @@ -17,13 +16,13 @@ First, we fix the overall energy scale by setting `t = 1`, and then shift the to This results in the following Hamiltonian: ```math -H = - \sum_{\langle i, j \rangle, \sigma} c^{\dagger}_{i,\sigma} c_{j,\sigma} + U / 4 \sum_i (1 - 2 n_{i,\uparrow}) (1 - 2 n_{i,\downarrow}) - \mu \sum_{i,\sigma} n_{i,\sigma} +H = - ∑_{⟨i, j⟩, σ} c^{†}_{i,σ} c_{j,σ} + U / 4 ∑_i (1 - 2 n_{i,↑}) (1 - 2 n_{i,↓}) - μ ∑_{i,σ} n_{i,σ} ``` -Finally, setting `\mu = 0` and defining `u = U / 4` we obtain the Hubbard model at half-filling. +Finally, setting `μ = 0` and defining `u = U / 4` we obtain the Hubbard model at half-filling. ```math -H = - \sum_{\langle i, j \rangle, \sigma} c^{\dagger}_{i,\sigma} c_{j,\sigma} + u \sum_i (1 - 2 n_{i,\uparrow}) (1 - 2 n_{i,\downarrow}) +H = - ∑_{⟨i, j⟩, σ} c^{†}_{i,σ} c_{j,σ} + u ∑_i (1 - 2 n_{i,↑}) (1 - 2 n_{i,↓}) ``` """ @@ -37,9 +36,10 @@ using Plots using Interpolations using Optim -#src # for reproducibility: -#src using Random -#src Random.seed!(123) +# For reproducibility of this page, we fix the seed of the random number generator: + +using Random +Random.seed!(123); const t = 1.0 const mu = 0.0 @@ -47,10 +47,10 @@ const U = 3.0 md""" For this case, the ground state energy has an analytic solution, which can be used to benchmark the numerical results. -It follows from Eq. (6.82) in [](). +It follows from Eq. (6.82) in [Essler, Frahm, Göhmann, Klümper & Korepin, The One-Dimensional Hubbard Model](https://doi.org/10.1017/CBO9780511534843). ```math -e(u) = - u - 4 \int_0^{\infty} \frac{d\omega}{\omega} \frac{J_0(\omega) J_1(\omega)}{1 + \exp(2u \omega)} +e(u) = - u - 4 ∫₀^{∞} \frac{dω}{ω} \frac{J₀(ω) J₁(ω)}{1 + \exp(2u ω)} ``` We can easily verify this by comparing the numerical results to the analytic solution. @@ -58,7 +58,7 @@ We can easily verify this by comparing the numerical results to the analytic sol function hubbard_energy(u; rtol = 1.0e-12) integrandum(ω) = besselj0(ω) * besselj1(ω) / (1 + exp(2u * ω)) / ω - int, err = quadgk(integrandum, 0, Inf; rtol = rtol) + int, err = quadgk(integrandum, 0, Inf; rtol) return -u - 4 * int end @@ -68,7 +68,7 @@ function compute_groundstate( expansionfactor = (1 / 10), expansioniter = 20 ) - verbosity = 2 + verbosity = 0 psi, = find_groundstate(psi, H; tol = svalue * 10, verbosity) for _ in 1:expansioniter D = maximum(x -> dim(left_virtualspace(psi, x)), 1:length(psi)) @@ -87,7 +87,7 @@ function compute_groundstate( psi, = find_groundstate( psi, H, VUMPS(; tol = svalue / 100, verbosity, maxiter = 100) & - GradientGrassmann(; tol = svalue / 1000) + GradientGrassmann(; tol = svalue / 1000, verbosity) ) return psi @@ -108,7 +108,7 @@ md""" ## Symmetries The Hubbard model has a rich symmetry structure, which can be exploited to speed up simulations. -Apart from the fermionic parity, the model also has a $U(1)$ particle number symmetry, along with a $SU(2)$ spin symmetry. +Apart from the fermionic parity, the model also has a ``U(1)`` particle number symmetry, along with a ``SU(2)`` spin symmetry. Explicitly imposing these symmetries on the tensors can greatly reduce the computational cost of the simulation. Naively imposing these symmetries however, is not compatible with our desire to work at half-filling. @@ -140,7 +140,7 @@ The elementary excitations are known as spinons and holons, which are domain wal The fact that the spin and charge sectors are separate is a phenomenon known as spin-charge separation. The domain walls can be constructed by noticing that there are two equivalent groundstates, which differ by a translation over a single site. -In other words, the groundstates are ``\psi_{AB}` and ``\psi_{BA}``, where ``A`` and ``B`` are the two sites. +In other words, the groundstates are ``ψ_{AB}`` and ``ψ_{BA}``, where ``A`` and ``B`` are the two sites. These excitations can be constructed as follows: """ @@ -154,13 +154,13 @@ envs_BA = environments(psi_BA, H_u1_su2, psi_BA); spinon_charge = FermionParity(0) ⊠ U1Irrep(0) ⊠ SU2Irrep(1 // 2) E_spinon, ϕ_spinon = excitations( H_u1_su2, alg, momenta, psi_AB, envs_AB, psi_BA, envs_BA; - sector = spinon_charge, num = 1 + sector = spinon_charge, num = 1, verbosity = 0 ); holon_charge = FermionParity(1) ⊠ U1Irrep(-1) ⊠ SU2Irrep(0) E_holon, ϕ_holon = excitations( H_u1_su2, alg, momenta, psi_AB, envs_AB, psi_BA, envs_BA; - sector = holon_charge, num = 1 + sector = holon_charge, num = 1, verbosity = 0 ); md""" @@ -216,7 +216,7 @@ md""" The plot shows some discrepancies between the numerical and analytic results. First and foremost, we must realize that in the thermodynamic limit, the momentum of a domain wall is actually not well-defined. Concretely, only the difference in momentum between the two groundstates is well-defined, as we can always shift the momentum by multiplying one of the groundstates by a phase. -Here, we can fix this shift by realizing that our choice of shifting the groundstates by a single site, differs from the formula by a factor ``\pi/2``. +Here, we can fix this shift by realizing that our choice of shifting the groundstates by a single site, differs from the formula by a factor ``π/2``. """ momenta_shifted = rem2pi.(momenta .- π / 2, RoundNearest) diff --git a/examples/quantum1d/1.ising-cft/main.jl b/examples/groundstates/ising-cft/main.jl similarity index 69% rename from examples/quantum1d/1.ising-cft/main.jl rename to examples/groundstates/ising-cft/main.jl index c6fc12487..b2acd743c 100644 --- a/examples/quantum1d/1.ising-cft/main.jl +++ b/examples/groundstates/ising-cft/main.jl @@ -1,9 +1,8 @@ md""" # The Ising CFT spectrum -This tutorial is meant to show the finite size CFT spectrum for the quantum Ising model. We -do this by first employing an exact diagonalization technique, and then extending the -analysis to larger system sizes through the use of MPS techniques. +This tutorial is meant to show the finite size CFT spectrum for the quantum Ising model. +We do this by first employing an exact diagonalization technique, and then extending the analysis to larger system sizes through the use of MPS techniques. """ using MPSKit, TensorKit, Plots, KrylovKit @@ -12,8 +11,7 @@ using .ExampleModels using LinearAlgebra: eigvals, diagm, Hermitian md""" -The Hamiltonian is defined on a finite lattice with periodic boundary conditions, -which can be implemented as follows: +The Hamiltonian is defined on a finite lattice with periodic boundary conditions, which can be implemented as follows: """ L = 12 @@ -22,11 +20,8 @@ H = periodic_boundary_conditions(transverse_field_ising(), L) md""" ## Exact diagonalisation -In MPSKit, there is support for exact diagonalisation by leveraging the fact that applying -the Hamiltonian to an untruncated MPS will result in an effective Hamiltonian on the center -site which implements the action of the entire Hamiltonian. Thus, optimizing the middle -tensor is equivalent to optimixing a state in the entire Hilbert space, as all other tensors -are just unitary matrices that mix the basis. +In MPSKit, there is support for exact diagonalisation by leveraging the fact that applying the Hamiltonian to an untruncated MPS will result in an effective Hamiltonian on the center site which implements the action of the entire Hamiltonian. +Thus, optimizing the middle tensor is equivalent to optimizing a state in the entire Hilbert space, as all other tensors are just unitary matrices that mix the basis. """ energies, states = exact_diagonalization(H; num = 18, alg = Lanczos(; krylovdim = 200)); @@ -44,9 +39,8 @@ md""" md""" ## Extracting momentum -Given a state, it is possible to assign a momentum label -through the use of the translation operator. This operator can be defined in MPO language -either diagramatically as +Given a state, it is possible to assign a momentum label through the use of the translation operator. +This operator can be defined in MPO language either diagrammatically as ```@raw html translation operator @@ -62,13 +56,11 @@ function O_shift(L) end md""" -We can then calculate the momentum of the ground state as the expectation value of this -operator. However, there is a subtlety because of the degeneracies in the energy -eigenvalues. The eigensolver will find an orthonormal basis within each energy subspace, but -this basis is not necessarily a basis of eigenstates of the translation operator. In order -to fix this, we diagonalize the translation operator within each energy subspace. -The resulting energy levels have one-to-one correspondence to the operators in CFT, where -the momentum is related to their conformal spin as $P_n = \frac{2\pi}{L}S_n$. +We can then calculate the momentum of the ground state as the expectation value of this operator. +However, there is a subtlety because of the degeneracies in the energy eigenvalues. +The eigensolver will find an orthonormal basis within each energy subspace, but this basis is not necessarily a basis of eigenstates of the translation operator. +In order to fix this, we diagonalize the translation operator within each energy subspace. +The resulting energy levels have one-to-one correspondence to the operators in CFT, where the momentum is related to their conformal spin as $P_n = \frac{2π}{L}S_n$. """ function fix_degeneracies(basis) @@ -95,10 +87,8 @@ append!(momenta, fix_degeneracies(states[13:16])) append!(momenta, fix_degeneracies(states[17:18])) md""" -We can compute the scaling dimensions $\Delta_n$ of the operators in the CFT from the -energy gap of the corresponding excitations as $E_n - E_0 = \frac{2\pi v}{L} \Delta_n$, -where $v = 2$. If we plot these scaling dimensions against the conformal spin $S_n$ from -above, we retrieve the familiar spectrum of the Ising CFT. +We can compute the scaling dimensions $Δ_n$ of the operators in the CFT from the energy gap of the corresponding excitations as $E_n - E_0 = \frac{2π v}{L} Δ_n$, where $v = 2$. +If we plot these scaling dimensions against the conformal spin $S_n$ from above, we retrieve the familiar spectrum of the Ising CFT. """ v = 2.0 @@ -117,19 +107,17 @@ p md""" ## Finite bond dimension -If we limit the maximum bond dimension of the MPS, we get an approximate solution, but we -can reach higher system sizes. +If we limit the maximum bond dimension of the MPS, we get an approximate solution, but we can reach higher system sizes. """ L_mps = 20 H_mps = periodic_boundary_conditions(transverse_field_ising(), L_mps) D = 64 -ψ, envs, info = find_groundstate(FiniteMPS(L_mps, ℂ^2, ℂ^D), H_mps, DMRG()); +ψ, envs, info = find_groundstate(FiniteMPS(L_mps, ℂ^2, ℂ^D), H_mps, DMRG(; verbosity = 0)); md""" -Excitations on top of the ground state can be found through the use of the quasiparticle -ansatz. This returns quasiparticle states, which can be converted to regular `FiniteMPS` -objects. +Excitations on top of the ground state can be found through the use of the quasiparticle ansatz. +This returns quasiparticle states, which can be converted to regular `FiniteMPS` objects. """ E_ex, qps = excitations(H_mps, QuasiparticleAnsatz(), ψ, envs; num = 18) diff --git a/examples/quantum1d/1.ising-cft/translation_mpo.svg b/examples/groundstates/ising-cft/translation_mpo.svg similarity index 100% rename from examples/quantum1d/1.ising-cft/translation_mpo.svg rename to examples/groundstates/ising-cft/translation_mpo.svg diff --git a/examples/quantum1d/4.xxz-heisenberg/main.jl b/examples/groundstates/xxz-heisenberg/main.jl similarity index 88% rename from examples/quantum1d/4.xxz-heisenberg/main.jl rename to examples/groundstates/xxz-heisenberg/main.jl index 51c8ba027..1ca9716c5 100644 --- a/examples/quantum1d/4.xxz-heisenberg/main.jl +++ b/examples/groundstates/xxz-heisenberg/main.jl @@ -9,9 +9,10 @@ using MPSKit, TensorKit, Plots include(joinpath(@__DIR__, "..", "..", "..", "docs", "src", "assets", "models.jl")) #hide using .ExampleModels -#src # for reproducibility: -#src using Random -#src Random.seed!(123) +# For reproducibility of this page, we fix the seed of the random number generator: + +using Random +Random.seed!(123); md""" ## Failure @@ -24,7 +25,8 @@ Working directly in the thermodynamic limit, this is achieved as follows: H = heisenberg_XXX(; spin = 1 // 2) md""" -We then need an initial state, which we shall later optimize. In this example we work directly in the thermodynamic limit. +We then need an initial state, which we shall later optimize. +In this example we work directly in the thermodynamic limit. """ state = InfiniteMPS(2, 20) @@ -33,7 +35,7 @@ md""" The ground state can then be found by calling `find_groundstate`. """ -groundstate, cache, info = find_groundstate(state, H, VUMPS()); +groundstate, cache, info = find_groundstate(state, H, VUMPS(; verbosity = 1)); info md""" @@ -42,7 +44,7 @@ On its own, that is already quite curious. Maybe we can do better using another algorithm, such as gradient descent. """ -groundstate, cache, info = find_groundstate(state, H, GradientGrassmann(; maxiter = 20)); +groundstate, cache, info = find_groundstate(state, H, GradientGrassmann(; maxiter = 20, verbosity = 1)); info md""" @@ -76,7 +78,7 @@ Alternatively, our model helper can construct the Hamiltonian directly on a two- ## H2 = repeat(H, 2); -- copies the one-site version H2 = heisenberg_XXX(ComplexF64, Trivial; unitcell = 2, spin = 1 // 2) groundstate, envs, info = find_groundstate( - state, H2, VUMPS(; maxiter = 100, tol = 1.0e-12) + state, H2, VUMPS(; maxiter = 100, tol = 1.0e-12, verbosity = 1) ); md""" @@ -85,7 +87,7 @@ The reason behind this becomes more obvious at higher bond dimensions: """ groundstate, envs, info = find_groundstate( - state, H2, IDMRG2(; trunc = truncrank(50), maxiter = 20, tol = 1.0e-12) + state, H2, IDMRG2(; trunc = truncrank(50), maxiter = 20, tol = 1.0e-12, verbosity = 1) ); entanglementplot(groundstate) @@ -129,4 +131,4 @@ Even though the bond dimension is higher than in the example without symmetry, c println(dim(V1)) println(dim(V2)) -groundstate, cache, info = find_groundstate(state, H2, VUMPS(; maxiter = 400, tol = 1.0e-12)); +groundstate, cache, info = find_groundstate(state, H2, VUMPS(; maxiter = 400, tol = 1.0e-12, verbosity = 1)); diff --git a/examples/make.jl b/examples/make.jl index de842a665..e925df768 100644 --- a/examples/make.jl +++ b/examples/make.jl @@ -80,16 +80,92 @@ function attach_notebook_badge(root, name, str) return join(map(markdown_only, (mybinder, nbviewer, download)), "\n") * "\n\n" * str end +# Log messages captured from an executed example carry the absolute source location of +# whatever emitted them, e.g. +# +# └ @ MPSKit /home/someone/checkout/src/algorithms/groundstate/vumps.jl:87 +# └ @ OptimKit /home/someone/.julia/packages/OptimKit/K7Ujj/src/cg.jl:188 +# +# Both depend on who ran the pipeline — the checkout path for a dev'ed package, and the +# depot slug for an installed one — so committing them makes the rendered pages differ +# per machine. The module name is already printed, so keep only the in-package path. +normalize_log_locations(content::AbstractString) = + replace(content, r"(@ [A-Za-z_][A-Za-z0-9_]* )\S*?/(src/\S*\.jl:\d+)" => s"\1\2") + +# A `using` block emits precompilation progress whenever the pipeline happens to run +# against a cold depot, e.g. +# +# Precompiling packages... +# 14306.2 ms ✓ MPSKitModels +# 1 dependency successfully precompiled in 16 seconds. 74 already precompiled. +# +# That says nothing about the example and its timings differ per machine, so drop any +# captured-output block whose every line is precompilation progress. Blocks that mix +# precompilation with real output are left alone. +function strip_precompilation_output(content::AbstractString) + is_precompilation_line(line) = !isnothing( + match( + r"""^\s*(?: + Precompiling\ .* # the header + | [\d.]+\s*ms\s*[✓✗].* # per-package timings + | \d+\ dependenc(?:y|ies)\ successfully\ precompiled.* + | \d+\ already\ precompiled\..* + )?\s*$"""x, line + ) + ) + + lines = collect(eachsplit(content, '\n')) + kept = similar(lines, 0) + i = firstindex(lines) + while i <= lastindex(lines) + # Walk fenced blocks as blocks. Both ````julia (code) and bare ```` (captured + # output) open one and a bare ```` closes it, so a closing fence must never be + # mistaken for the start of the next block. + if startswith(lines[i], "````") + close = findnext(l -> rstrip(l) == "````", lines, i + 1) + if !isnothing(close) + body = @view lines[(i + 1):(close - 1)] + if rstrip(lines[i]) == "````" && + any(contains("Precompiling"), body) && + all(is_precompilation_line, body) + # drop the block, and the blank line that followed it + i = close + 1 + i <= lastindex(lines) && isempty(rstrip(lines[i])) && (i += 1) + continue + end + append!(kept, @view lines[i:close]) + i = close + 1 + continue + end + end + push!(kept, lines[i]) + i += 1 + end + return join(kept, '\n') +end + +# All examples run in this one process, and Plots keeps `theme`/`default` settings as global +# state, so an example that styles its plots would otherwise restyle every later example. +const PLOTS_PKGID = Base.PkgId(Base.UUID("91a5bcdd-55d7-5caf-9e0b-520d859cae80"), "Plots") +function reset_plot_defaults() + plots = get(Base.loaded_modules, PLOTS_PKGID, nothing) + isnothing(plots) || Base.invokelatest(plots.reset_defaults) + return nothing +end + function build_example(root, name) source_dir = joinpath(@__DIR__, "..", "examples", root, name) source_file = joinpath(source_dir, "main.jl") target_dir = joinpath(@__DIR__, "..", "docs", "src", "examples", root, name) return if !iscached(root, name) + reset_plot_defaults() Literate.markdown( source_file, target_dir; execute = true, name = "index", preprocess = str -> attach_notebook_badge(root, name, inline_models(str; hide = true)), - postprocess = content -> externalize_figures(content, target_dir), + postprocess = content -> strip_precompilation_output( + normalize_log_locations(externalize_figures(content, target_dir)) + ), mdstrings = true, nbviewer_root_url = "https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev", binder_root_url = "https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev", @@ -118,5 +194,9 @@ end # Scripts # ---------------------------------------------------------------------------------------- # -build("classic2d") -build("quantum1d") +# build every topic group: each subdirectory of examples/ is one group +for group in readdir(@__DIR__) + startswith(group, '.') && continue + isdir(joinpath(@__DIR__, group)) || continue + build(group) +end diff --git a/examples/quantum1d/5.haldane-spt/main.jl b/examples/quantum1d/5.haldane-spt/main.jl deleted file mode 100644 index 2fd4b7294..000000000 --- a/examples/quantum1d/5.haldane-spt/main.jl +++ /dev/null @@ -1,157 +0,0 @@ -md""" -# Spin 1 Heisenberg model - -The quantum Heisenberg model is a model often used in the study of critical points and phase -transitions of magnetic systems, in which the spins are treated quantum mechanically. It -models magnetic interactions between neighbouring spins through the so-called Heisenberg -interaction term, which causes the spins to either align ($J > 0$) or anti-align ($J < 0$), -thus modeling a (anti-) ferromagnetic system. Here, we will focus on the case of $S = 1$, -with anti-ferromagnetic interactions. - -```math -H = -J \sum_{\langle i, j \rangle} \vec{S}_i \cdot \vec{S}_j -``` - -Importantly, the Hamiltonian of the isotropic model is invariant under $SU(2)$ rotations, -which can be exploited to increase efficiency, as well as interpretability of the MPS -simulations. To see this, we can make use of the following derivation for the interaction -term: - -```math -(\vec{S}_i + \vec{S}_j)^2 = \vec{S}_i^2 + 2 \vec{S}_i \cdot \vec{S}_j + \vec{S}_j^2 -\implies \vec{S}_i \cdot \vec{S}_j = \frac{1}{2} \left( (\vec{S}_i + \vec{S}_j)^2 - \vec{S}_i^2 - \vec{S}_j^2 \right) -``` - -Here, we recognize the quadratic -[Casimir element](https://en.wikipedia.org/wiki/Casimir_element) $\vec{S}^2$, which commutes -with the elements of $SU(2)$. Consequently, the Hamiltonian also commutes with all elements -of $SU(2)$. -""" - -using TensorKit -using MPSKit -using Plots - -casimir(s::SU2Irrep) = s.j * (s.j + 1) - -function heisenberg_hamiltonian(; J = -1.0) - s = SU2Irrep(1) - ℋ = SU2Space(1 => 1) - SS = zeros(ComplexF64, ℋ ⊗ ℋ ← ℋ ⊗ ℋ) - for (S, data) in blocks(SS) - data .= -0.5J * (casimir(S) - casimir(s) - casimir(s)) - end - return InfiniteMPOHamiltonian(SS) -end -H = heisenberg_hamiltonian() - -md""" -## Symmetry-Protected Topological Order - -The representations of $SU(2)$ possess additional structure, known as a -$\mathbb{Z}_2$-grading. This means, that they can be partitioned in integer $(+)$ and -half-integer $(-)$ spins, and the fusion rules will respect this grading. In other words, -the following table holds: - -| $s_1$ | $s_2$ | $s_1 \otimes s_2$ | -| --- | --- | --- | -| $+$ | $+$ | $+$ | -| $+$ | $-$ | $-$ | -| $-$ | $+$ | $-$ | -| $-$ | $-$ | $+$ | - -This has important consequences for the MPS representation of an $SU(2)$-symmetric state. If -the physical spin consists of only integer representations, this means that the left and -right virtual spaces of the MPS tensor belong to the same grading, i.e. are either both -integer, or both half-integer. Thus, naively constructing a MPS tensor which contains spins -from both classes, will necessarily be the direct sum of the two, which yields a -non-injective MPS. - -```math -\ket{\psi} = \ket{\psi_+} \oplus \ket{\psi_-} -``` - -Because of this direct sum, many of the usual MPS algorithms will fail, as they typically -cannot deal with non-injective MPS. The resulting MPS will have multiple values of the -transfer matrix spectrum that have a magnitude close to 1, which is a clear sign of a -non-injective MPS. -""" - -ℋ = SU2Space(1 => 1) -V_wrong = SU2Space(0 => 8, 1 // 2 => 8, 1 => 3, 3 // 2 => 3) -ψ = InfiniteMPS(ℋ, V_wrong) -ψ, environments, info = find_groundstate(ψ, H, VUMPS(; maxiter = 10)) -sectors = SU2Irrep[0, 1 // 2, 1, 3 // 2] -transferplot(ψ; sectors, title = "Transfer matrix spectrum", legend = :outertop) - -md""" -Nevertheless, using the symmetry, this can be remedied rather easily, by imposing the -ground state to belong to a single class, and comparing the results. We can readily obtain 3 -different criteria for determining the SPT phase of the ground state. - -Firstly, we can compare variational energies for states of similar bond dimensions. As we -expect the state of the wrong SPT phase to have to expend some of its expressiveness in -correcting the SPT, it should have a harder time reaching lower energies. - -Secondly, when inspecting the spectrum of the transfer matrix, we should see that the wrong -SPT phase has a dominant value that is not in the trivial sector, which leads to a -non-injective MPS. - -Finally, the entanglement spectrum of the wrong SPT phase will show degeneracies of all -singular values, which can again be attributed to an attempt to mimic the spectrum of the -right SPT phase. -""" - -V_plus = SU2Space(0 => 10, 1 => 5, 2 => 3) -ψ_plus = InfiniteMPS(ℋ, V_plus) -ψ_plus, = find_groundstate(ψ_plus, H, VUMPS(; maxiter = 100)) -E_plus = expectation_value(ψ_plus, H) - -#+ - -V_minus = SU2Space(1 // 2 => 10, 3 // 2 => 5, 5 // 2 => 3) -ψ_minus = InfiniteMPS(ℋ, V_minus) -ψ_minus, = find_groundstate(ψ_minus, H, VUMPS(; maxiter = 100)) -E_minus = expectation_value(ψ_minus, H) - -#+ - -transferp_plus = transferplot( - ψ_plus; - sectors = SU2Irrep[0, 1, 2], title = "ψ_plus", legend = :outertop -) -transferp_minus = transferplot( - ψ_minus; - sectors = SU2Irrep[0, 1, 2], title = "ψ_minus", legend = :outertop -) -plot(transferp_plus, transferp_minus; layout = (1, 2), size = (800, 400)) - -#+ - -entanglementp_plus = entanglementplot(ψ_plus; title = "ψ_plus", legend = :outertop) -entanglementp_minus = entanglementplot(ψ_minus; title = "ψ_minus", legend = :outertop) -plot(entanglementp_plus, entanglementp_minus; layout = (1, 2), size = (800, 400)) - -md""" - -As we can see, the ground state can be found in the non-trivial SPT phase, $\ket{\psi_-}$. We -can obtain an intuitive understanding of $\ket{\psi_+}$ by considering the following -diagram. If we denote the MPS tensors that make up the ground state as $A_-$, we can -construct a state in the trivial SPT phase that approximates the ground state as follows: - -```@raw html -SPT tensors -``` - -In other words, we can factorize a purely virtual isomorphism of $S = 1/2$ in order to -obtain the ground state. This then also explains the degeneracies in the entanglement -spectrum as well as in the transfer matrix spectrum. Finally, we can further confirm this -intuition by looking at the entanglement entropy of the ground state. As we can see, the -entanglement entropy of the state in the wrong SPT phase is exactly $log(2)$ higher than the -one in the right SPT phase, which is exactly what we would expect from the diagram above. -""" - -S_minus = sum(real, entropy(ψ_minus)) -S_plus = sum(real, entropy(ψ_plus)) -println("S_minus + log(2) = $(S_minus + log(2))") -println("S_plus = $S_plus") diff --git a/examples/quantum1d/8.bose-hubbard/main.jl b/examples/quantum1d/8.bose-hubbard/main.jl deleted file mode 100644 index 51f7b67a6..000000000 --- a/examples/quantum1d/8.bose-hubbard/main.jl +++ /dev/null @@ -1,398 +0,0 @@ -using Markdown -using TensorKitTensors.BosonOperators: b_min, b_plus_b_min, b_min_b_plus, b_num -using MPSKit, TensorKit -include(joinpath(@__DIR__, "..", "..", "..", "docs", "src", "assets", "models.jl")) #hide -using .ExampleModels -using Plots, LaTeXStrings - -#src # for reproducibility: -#src using Random -#src Random.seed!(123) - -theme(:wong) -default(fontfamily = "Computer Modern", label = nothing, dpi = 100, framestyle = :box) - -md""" -# 1D Bose-Hubbard model - -In this tutorial, we will explore the physics of the one-dimensional Bose–Hubbard model -using matrix product states. For the most part, we replicate the results presented in -[**Phys. Rev. B 105, -134502**](https://journals.aps.org/prb/abstract/10.1103/PhysRevB.105.134502), which can be -consulted for any statements in this tutorial that are not otherwise cited. The Hamiltonian -under study is defined as follows: - -$$H = -t \sum_{i} (\hat{a}_i^{\dagger} \hat{a}_{i+1} + \hat{a}_{i+1}^{\dagger} \hat{a}_i) + \frac{U}{2} \sum_i \hat{n}_i(\hat{n}_i - 1) - \mu \sum_i \hat{n}_i$$ - -where the bosonic creation and annihilation operators satisfy the canonical commutation -relations (CCR): - -$$[\hat{a}_i, \hat{a}_j^{\dagger}] = \delta_{ij}.$$ - -Each lattice site hosts a local Hilbert space corresponding to bosonic occupation states -$|n\rangle$, where $(n = 0, 1, 2, \ldots)$. Since this space is formally -infinite-dimensional, numerical simulations typically impose a truncation at some maximum -occupation number $(n_{\text{max}})$. Such a treatment is justified since it can be observed -that the simulation results quickly converge with the cutoff if the filling fraction is kept -sufficiently low. - -Within this truncated space, the local creation and annihilation operators are represented -by finite-dimensional matrices. For example, with cutoff $n_{\text{max}}$, the annihilation -operator takes the form - -```math -\hat{a} = -\begin{bmatrix} -0 & \sqrt{1} & 0 & 0 & \cdots & 0 \\ -0 & 0 & \sqrt{2} & 0 & \cdots & 0 \\ -0 & 0 & 0 & \sqrt{3} & \cdots & 0 \\ -\vdots & & & \ddots & \ddots & \vdots \\ -0 & 0 & 0 & \cdots & 0 & \sqrt{n_{\text{max}}} \\ -0 & 0 & 0 & \cdots & 0 & 0 -\end{bmatrix} -``` - -and the creation operator is simply its Hermitian conjugate, - -```math -\hat{a}^\dagger = -\begin{bmatrix} -0 & 0 & 0 & \cdots & 0 & 0 \\ -\sqrt{1} & 0 & 0 & \cdots & 0 & 0 \\ -0 & \sqrt{2} & 0 & \cdots & 0 & 0 \\ -\vdots & & \ddots & \ddots & & \vdots \\ -0 & 0 & 0 & \cdots & 0 & 0 \\ -0 & 0 & 0 & \cdots & \sqrt{n_{\text{max}}} & 0 -\end{bmatrix} -``` - -The number operator is then given by - -$$\hat{n} = \hat{a}^\dagger \hat{a} = \mathrm{diag}(0, 1, 2, \ldots, n_{\text{max}}).$$ - -Before moving on, notice that the Hamiltonian is uniform and translationally invariant. -Typically, such models are studied on a finite chain of $N$ sites with periodic boundary -conditions, but this introduces finite-size effects that are rather annoying to deal with. -In contrast, the MPS framework allows us to work directly in the thermodynamic limit, -avoiding such artifacts. We will follow this line of exploration in this tutorial and leave -finite systems for another example. - -In order to work in the thermodynamic limit, we will have to create an -[`InfiniteMPS`](@ref). A complete specification of the MPS requires us to define the -physical space and the virtual space of the constituent tensors. At this point is it useful -to note that `MPSKit.jl` is powered by -[`TensorKit.jl`](https://github.com/QuantumKitHub/TensorKit.jl) under the hood and has some -very generic interfaces in order to allow imposing symmetries of all kinds. As a result, it -is sometimes necessary to be a bit more explicit about what we want to do in terms of the -vector spaces involved. In this case, we will not consider any symmetries and simply take -the most naive approach of working within the [`Trivial`](@extref TensorKitSectors.Trivial) -sector. The physical space is then `ℂ^(nmax+1)`, and the virtual space is `ℂ^D` where $D$ is -some integer chosen to be the bond dimension of the MPS, and `ℂ` is an alias for -[`ComplexSpace`](@extref TensorKit.ComplexSpace) (typeset as `\bbC`). As $D$ is increased, -one increases the amount of entanglement, i.e, quantum correlations that can be captured by -the state. -""" - -cutoff, D = 4, 5 -initial_state = InfiniteMPS(ℂ^(cutoff + 1), ℂ^D) - -md""" -This simply initializes a tensor filled with random entries (check out the documentation for -other useful constructors). Next, we need the creation and annihilation operators. While we -could construct them from scratch, here we will use -[TensorKitTensors.jl](https://github.com/QuantumKitHub/TensorKitTensors.jl), which provides -predefined operators. In particular, `BosonOperators.b_min` creates the bosonic annihilation -operator. -""" - -a_op = b_min(cutoff = cutoff) # creates a bosonic annihilation operator without any symmetries -display(a_op[]) -display((a_op' * a_op)[]) - -md""" - -The [] accessor lets us see the underlying array, and indeed the operators are exactly what -we require. The shared [model helpers](https://github.com/QuantumKitHub/MPSKit.jl/blob/main/docs/src/assets/models.jl) define -`bose_hubbard_model` using these operators, although we will construct our own variant -later on. - -""" - -hamiltonian = bose_hubbard_model(; cutoff = cutoff, U = 1, mu = 0.5, t = 0.2) # Infinite chain by default; pass L = N for an open finite chain - -md""" -This has created the Hamiltonian operator as a [matrix product operator](@ref -InfiniteMPOHamiltonian) (MPO) which is a convenient form to use in conjunction with MPS. -Finally, the ground state optimization may be performed with either [`iDMRG`](@ref IDMRG) or -[`VUMPS`](@ref). Both should take similar arguments but it is known that VUMPS is typically -more efficient for these systems so we proceed with that. -""" - -ground_state, _, _ = find_groundstate(initial_state, hamiltonian, VUMPS(tol = 1.0e-6, verbosity = 2, maxiter = 200)) -println("Energy: ", expectation_value(ground_state, hamiltonian)) - -md""" -This automatically runs the algorithm until a certain [error measure](@ref -MPSKit.calc_galerkin) falls below the specified tolerance or the maximum iterations is -reached. Let us wrap all this into a convenient function. -""" - -function get_ground_state(mu, t, cutoff, D; kwargs...) - hamiltonian = bose_hubbard_model(; cutoff = cutoff, U = 1, mu = mu, t = t) - state = InfiniteMPS(ℂ^(cutoff + 1), ℂ^D) - state, _, _ = find_groundstate(state, hamiltonian, VUMPS(; kwargs...)) - - return state -end - -ground_state = get_ground_state(0.5, 0.01, cutoff, D; tol = 1.0e-6, verbosity = 2, maxiter = 500) - -md""" - -Now that we have the state, we may compute observables using the [`expectation_value`](@ref) -function. It typically expects a `Pair`, `(i1, i2, .., ik) => op` where `op` is a -`TensorMap` or `InfiniteMPO` acting over `k` sites. In case of the Hamiltonian, it is not -necessary to specify the indices as it spans the whole lattice. We can now plot the -correlation function $\langle \hat{a}^{\dagger}_i \hat{a}_j\rangle$. -""" - -plot(map(i -> real.(expectation_value(ground_state, (0, i) => a_op' ⊗ a_op)), 1:50), lw = 2, xlabel = "Site index", ylabel = "Correlation function", yscale = :log10) -hline!([abs2(expectation_value(ground_state, (0,) => a_op))], ls = :dash, c = :black) - -md""" -We see that the correlations drop off exponentially, indicating the existence of a gapped -Mott insulating phase. Let us now shift our parameters to probe other phases. -""" - -ground_state = get_ground_state(0.5, 0.2, cutoff, D; tol = 1.0e-6, verbosity = 2, maxiter = 500) - -plot(map(i -> real.(expectation_value(ground_state, (0, i) => a_op' ⊗ a_op)), 1:100), lw = 2, xlabel = "Site index", ylabel = "Correlation function", yscale = :log10, xscale = :log10) -hline!([abs2(expectation_value(ground_state, (0,) => a_op))], ls = :dash, c = :black) - -md""" -In this case, the correlation function drops off algebraically and eventually saturates as -$\lim_{i \to \infty}\langle\hat{a}_i^{\dagger} \hat{a}_j\rangle ≈ \langle \hat{a}_i^{\dagger}\rangle \langle \hat{a}_j \rangle = |\langle a_i \rangle|^2 \neq 0$. -This is a signature of long-range order and suggests the existence of a Bose-Einstein -condensate. However, this is a bit odd since at zero temperature, the Bose Hubbard model is -not expected to break any continuous symmetries ($U(1)$ in this case, corresponding to -particle number conservation) due to the -[Mermin-Wagner theorem](https://en.wikipedia.org/wiki/Mermin%E2%80%93Wagner_theorem). The -source of this contradiction lies in the fact that the true 1D superfluid ground state is an -extended critical phase exhibiting algebraic decay, however, a finite bond-dimension MPS can -only capture exponentially decaying correlations. As a result, the finite bond dimension -effectively introduces a length scale into the system in a similar manner as finite-size -effects. We can see this clearly by increasing the bond dimension. We also see that the -correlation length seems to depend algebraically on the bond dimension as expected from -finite-entanglement scaling arguments. -""" - -cutoff = 4 -Ds = 20:5:50 -mu, t = 0.5, 0.2 -states = Vector{InfiniteMPS}(undef, length(Ds)) - -Threads.@threads for idx in eachindex(Ds) - states[idx] = get_ground_state(mu, t, cutoff, Ds[idx]; tol = 1.0e-7, verbosity = 1, maxiter = 500) -end - -npoints = 400 -two_point_correlation = zeros(length(Ds), npoints) -a_op = b_min(cutoff = cutoff) - -Threads.@threads for idx in eachindex(Ds) - two_point_correlation[idx, :] .= real.(expectation_value(states[idx], (1, i) => a_op' ⊗ a_op) for i in 1:npoints) -end - -p = plot( - framestyle = :box, ylabel = "Correlation function " * L"\langle a_i^{\dagger}a_j \rangle", - xlabel = "Distance " * L"|i-j|", xscale = :log10, yscale = :log10, - xticks = ([10, 100], ["10", "100"]), - yticks = ([0.25, 0.5, 1.0], ["0.25", "0.5", "1.0"]) -) - -plot!( - p, 2:npoints, two_point_correlation[:, 2:end]', - lab = "D = " .* string.(permutedims(Ds)), lw = 2 -) - -scatter!( - p, Ds, map(ψ -> correlation_length(ψ; sector = leftunit(ψ)), states), - ylabel = "Correlation length", xlabel = "Bond dimension", - xscale = :log10, yscale = :log10, - inset = bbox(0.2, 0.51, 0.25, 0.25), - subplot = 2, - xticks = (20:10:50, string.(20:10:50)), - yticks = ([50, 100], string.([50, 100])), - xlabelfontsize = 8, - ylabelfontsize = 8, - ylims = [20, 130], - xlims = [15, 60] -) - -md""" -This shows that any finite bond dimension MPS necessarily breaks the symmetry of the system, -forming a Bose-Einstein condensate which introduces erroneous long-distance behaviour of -correlation functions. In case of finite bond dimension, it is thus reasonable to associate -the finite expectation value of the field operator to the 'quasicondensate' density of the -system which vanishes as $D \to \infty$. -""" - -quasicondensate_density = map(state -> abs2(expectation_value(state, (0,) => a_op)), states) - -md""" -We may now also visualize the momentum distribution function, which is obtained as the -Fourier transform of the single-particle density matrix. Starting from the definition of the -momentum occupation operators: - -```math -\hat{a}_k = \frac{1}{\sqrt{L}} \sum_j e^{-ikj} \hat{a}_j, \qquad -\hat{a}_k^\dagger = \frac{1}{\sqrt{L}} \sum_{j'} e^{ikj'} \hat{a}_{j'}^\dagger -``` - -the momentum distribution is - -```math -\langle \hat{n}_k \rangle = \langle \hat{a}_k^\dagger \hat{a}_k \rangle -= \frac{1}{L} \sum_{j',j} e^{ik(j'-j)} \langle \hat{a}_{j'}^\dagger \hat{a}_j \rangle. -``` - -For a translationally invariant system, the correlation depends only on the distance -$r = j' - j$: - -$$\langle \hat{a}_{j'}^\dagger \hat{a}_j \rangle = C(r) = \langle \hat{a}_r^\dagger \hat{a}_0 \rangle.$$ - -Changing variables ($j' = j + r$) gives - -$$\langle \hat{n}_k \rangle = \frac{1}{L} \sum_j \sum_r e^{ikr} C(r).$$ - -The sum over $j$ yields a factor of $L$, which cancels the prefactor, leading to - -$$\langle \hat{n}_k \rangle = \sum_{r \in \mathbb{Z}} e^{ikr} \langle \hat{a}_r^\dagger \hat{a}_0 \rangle$$ - -However, we know that a finite bond dimension MPS introduces a non-zero quasi-condensate -density which would give rise to an $\mathcal{O}(N)$ divergence in the momentum distribution -that is not indicative of the true physics of the system. Since we know this contribution -vanishes in the infinite bond dimension limit, we instead work with -$\langle \hat{a}_r^{\dagger} \hat{a}_0 \rangle_c = \langle \hat{a}_r^{\dagger} \hat{a}_0 \rangle - |\langle \hat{a}\rangle|^2$. -""" - -ks = range(-0.05, 0.15, 500) -momentum_distribution = map( - ((corr, qc),) -> sum( - 2 .* cos.(ks' .* (2:npoints)) .* (corr[2:end] .- qc), dims = 1 - ) .+ (corr[1] .- qc), - zip( - eachrow(two_point_correlation), - quasicondensate_density - ) -) -momentum_distribution = vcat(momentum_distribution...)' -plot(ks, momentum_distribution, lab = "D = " .* string.(permutedims(Ds)), lw = 1.5, xlabel = "Momentum k", ylabel = L"\langle n_k \rangle", ylim = [0, 50]) - -md""" -We see that the density seems to peak around $k=0$, this time seemingly becoming more -prominent as $D \to \infty$ which seems to suggest again that there is a condensate. -However, going by the Penrose-Onsager criterion, the existence of a condensate can be -quantified by requiring the leading eigenvalue of the single particle density matrix (i.e, -$\langle \hat{n}_{k=0}\rangle = \sum_j \langle \hat{a}_j^{\dagger} \hat{a}_0\rangle$) to -diverge as $O(N)$ in the thermodynamic limit. In this case, since the correlations decay as -a power law, there is naturally a divergence at low momenta. But this does not imply the -existence of a condensate since the order of divergence is much weaker. However, this does -indicate the remnants of some kind of condensation in the 1D model despite the quantum -fluctuations, leading to the practical utility of defining the concept of a quasicondensate -where there is still a notion of phase coherence over short distances. - -What this means for us is that, as far as MPS simulations go, we may still utilize the -quasicondensate density as an effective order parameter, although it will be less robust as -the bond dimension is increased. Alternatively, we realize that the true phase is -characterized as being a superfluid (a concept distinct from Bose-Einstein condensation) and -can be identified by a non-zero value of the superfluid stiffness (also known as helicity -modulus, $\Upsilon$) as defined by Leggett. Upon applying a phase twist $\Phi$ to the -boundaries of the system, a superfluid phase would suffer an increase in energy whereas an -insulating phase would not. In the thermodynamic limit, one could show that the boundary -conditions may be considered as periodic and instead uniformly distribute the phase across -the chain as $\hat{a}_i \to \hat{a}_i e^{i\Phi/L}$. Concretely, in the limit of -$\Phi/L \to 0$, we have: - -$$\frac{E[\Phi] - E[0]}{L} \approx \frac{1}{2} \Upsilon(L) \bigg (\frac{\Phi}{L}\bigg)^2 + \cdots$$ - -In order to find the ground state under these twisted boundary conditions, we must construct -our own variant of the Bose-Hubbard Hamiltonian. Typically you would want to take a peek at -the -[model helper source](https://github.com/QuantumKitHub/MPSKit.jl/blob/main/docs/src/assets/models.jl) to see how these models are defined and tweak it as per your needs. -Here we see that applying twisted boundary conditions is equivalent to adding a prefactor of -$e^{\pm i\phi}$ in front of the hopping amplitudes. -""" - -function bose_hubbard_model_twisted_bc( - elt::Type{<:Number} = ComplexF64, symmetry::Type{<:Sector} = Trivial; - cutoff::Integer = 5, t = 1.0, U = 1.0, mu = 0.0, phi = 0 - ) - - a_pm = b_plus_b_min(elt, symmetry; cutoff = cutoff) - a_mp = b_min_b_plus(elt, symmetry; cutoff = cutoff) - N = b_num(elt, symmetry; cutoff = cutoff) - - interaction_term = N * (N - id(domain(N))) - - hopping = -t * (exp(1im * phi) * a_pm + exp(-1im * phi) * a_mp) - onsite = U / 2 * interaction_term - mu * N - return chain_hamiltonian(hopping, onsite) -end - -function superfluid_stiffness_profile(t, mu, D, cutoff, ϵ = 1.0e-4, npoints = 11) - phis = range(-ϵ, ϵ, npoints) - energies = zeros(length(phis)) - - Threads.@threads for idx in eachindex(phis) - hamiltonian_twisted = bose_hubbard_model_twisted_bc(; - cutoff = cutoff, t = t, mu = mu, U = 1, phi = phis[idx] - ) - state = InfiniteMPS(ℂ^(cutoff + 1), ℂ^D) - state_twisted, _, _ = find_groundstate( - state, hamiltonian_twisted, VUMPS(; tol = 1.0e-8, verbosity = 0) - ) - energies[idx] = real(expectation_value(state_twisted, hamiltonian_twisted)) - end - - return plot(phis, energies, lw = 2, xlabel = "Phase twist per site" * L"(\phi)", ylabel = "Ground state energy", title = "t = $t | μ = $mu | D = $D | cutoff = $cutoff") -end - -superfluid_stiffness_profile(0.2, 0.3, 5, 4) # superfluid - -superfluid_stiffness_profile(0.01, 0.3, 5, 4) # mott insulator - -md""" -Now that we know what phases to expect, we can plot the phase diagram by scanning over a -range of parameters. In general, one could do better by performing a bisection algorithm for -each chemical potential to determine the value of the hopping parameter at the transition -point, however the 1D Bose-Hubbard model may have two transition points at the same chemical -potential which makes this a bit cumbersome to implement robustly. Furthermore, we stick to -using the quasi-condensate density as an order parameter since extracting the superfluid -density accurately requires a more robust scheme to compute second derivatives which takes -us away from the focus of this tutorial. -""" - -cutoff, D = 4, 10 -mus = range(0, 0.75, 40) -ts = range(0, 0.3, 40) - -a_op = b_min(cutoff = cutoff) -order_parameters = zeros(length(ts), length(mus)) - -Threads.@threads for (i, j) in collect(Iterators.product(eachindex(mus), eachindex(ts))) - hamiltonian = bose_hubbard_model(; cutoff = cutoff, U = 1, mu = mus[i], t = ts[j]) - init_state = InfiniteMPS(ℂ^(cutoff + 1), ℂ^D) - state, _, _ = find_groundstate(init_state, hamiltonian, VUMPS(; tol = 1.0e-8, verbosity = 0)) - order_parameters[i, j] = abs(expectation_value(state, 0 => a_op)) -end - -heatmap(ts, mus, order_parameters, xlabel = L"t/U", ylabel = L"\mu/U", title = L"\langle \hat{a}_i \rangle") - -md""" -Although the bond dimension here is quite low, we already see the deformation of the Mott -insulator lobes to give way to the well known BKT transition that happens at commensurate -density. One can go further and estimate the critical exponents using finite-entanglement -scaling procedures on the correlation functions, but these may now be performed with ease -using what we have learnt in this tutorial. -""" diff --git a/examples/classic2d/1.hard-hexagon/hexagon.svg b/examples/statmech/hard-hexagon/hexagon.svg similarity index 100% rename from examples/classic2d/1.hard-hexagon/hexagon.svg rename to examples/statmech/hard-hexagon/hexagon.svg diff --git a/examples/classic2d/1.hard-hexagon/main.jl b/examples/statmech/hard-hexagon/main.jl similarity index 98% rename from examples/classic2d/1.hard-hexagon/main.jl rename to examples/statmech/hard-hexagon/main.jl index 864ad1785..5972d088f 100644 --- a/examples/classic2d/1.hard-hexagon/main.jl +++ b/examples/statmech/hard-hexagon/main.jl @@ -64,7 +64,8 @@ This can be exploited to formulate a scaling hypothesis [pollmann2009](@cite), w First we need to know the entropy and correlation length at several different bond dimensions. Our approach will be to re-use the previous approximated dominant eigenvector, and then expanding its bond dimension and re-running VUMPS. -According to the scaling hypothesis we should have ``S ∝ \frac{c}{6} log(ξ)``. Therefore we should find ``c`` using +According to the scaling hypothesis we should have ``S ∝ \frac{c}{6} log(ξ)``. +Therefore we should find ``c`` using """ function scaling_simulations(