diff --git a/docs/make.jl b/docs/make.jl
index 2549e0060..29dd97a5c 100644
--- a/docs/make.jl
+++ b/docs/make.jl
@@ -12,13 +12,19 @@ using DocumenterVitepress
using DocumenterCitations
using DocumenterInterLinks
-# 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",
+]
+example_pages = map(example_groups) do (title, group)
+ pages = map(readdir(joinpath(example_dir, group))) do dir
+ return joinpath("examples", group, dir, "index.md")
+ end
+ return title => pages
end
# contributing guide: `CONTRIBUTING.md` in the repository root is canonical, since that is the
@@ -70,7 +76,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 78d2bc359..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 60%
rename from docs/src/examples/quantum1d/3.ising-dqpt/index.md
rename to docs/src/examples/dynamics/ising-dqpt/index.md
index c60dc700d..e0c8d74c6 100644
--- a/docs/src/examples/quantum1d/3.ising-dqpt/index.md
+++ b/docs/src/examples/dynamics/ising-dqpt/index.md
@@ -1,32 +1,33 @@
```@meta
-EditURL = "../../../../../examples/quantum1d/3.ising-dqpt/main.jl"
+EditURL = "../../../../../examples/dynamics/ising-dqpt/main.jl"
```
-[](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/quantum1d/3.ising-dqpt/main.ipynb)
-[](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/quantum1d/3.ising-dqpt/main.ipynb)
-[](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/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/dynamics/ising-dqpt/main.ipynb)
+[](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/dynamics/ising-dqpt/main.ipynb)
+[](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, MPSKitModels, TensorKit
````
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:
@@ -35,30 +36,20 @@ First we construct the Hamiltonian in MPO form, and obtain the pre-quenched grou
L = 20
H₀ = transverse_field_ising(FiniteChain(L); g = -0.5)
ψ₀ = FiniteMPS(L, ℂ^2, ℂ^10)
-ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG());
-````
-
-````
-[ Info: DMRG init: obj = +9.979013604153e+00 err = 1.4988e-01
-[ Info: DMRG 1: obj = -2.040021714911e+01 err = 6.6274818897e-04 time = 4.21 sec
-[ Info: DMRG 2: obj = -2.040021715179e+01 err = 4.7025708686e-07 time = 0.30 sec
-[ Info: DMRG 3: obj = -2.040021786572e+01 err = 3.1050733385e-05 time = 0.09 sec
-[ Info: DMRG 4: obj = -2.040021786702e+01 err = 1.7208246127e-06 time = 0.04 sec
-[ Info: DMRG 5: obj = -2.040021786703e+01 err = 3.5080300899e-08 time = 0.04 sec
-[ Info: DMRG conv 6: obj = -2.040021786703e+01 err = 3.6868374475e-11 time = 4.71 sec
-
+ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG(; verbosity = 0));
````
## Finite MPS quenching
-We can define a helper function that measures the loschmith echo
+We can define a helper function that measures the Loschmidt echo
````julia
echo(ψ₀::FiniteMPS, ψₜ::FiniteMPS) = -2 * log(abs(dot(ψ₀, ψₜ))) / length(ψ₀)
@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(FiniteChain(L); g = -2.0)
@@ -67,7 +58,8 @@ dt = 0.01
ψₜ, envs = 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
@@ -75,7 +67,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(FiniteChain(L); g = -0.5)
- ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG())
+ ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG(; verbosity = 0))
H₁ = transverse_field_ising(FiniteChain(L); g = -2.0)
ψₜ = deepcopy(ψ₀)
@@ -107,22 +99,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.970192050239e-01 err = 3.8858e-01
-[ Info: VUMPS 1: obj = -1.049521519045e+00 err = 9.6762771022e-02 time = 1.62 sec
-[ Info: VUMPS 2: obj = -1.063544398670e+00 err = 1.0462983506e-04 time = 0.02 sec
-[ Info: VUMPS 3: obj = -1.063544409966e+00 err = 3.0128180222e-06 time = 0.01 sec
-[ Info: VUMPS 4: obj = -1.063544409973e+00 err = 5.4785900416e-08 time = 0.01 sec
-[ Info: VUMPS 5: obj = -1.063544409973e+00 err = 3.5329191510e-09 time = 0.01 sec
-[ Info: VUMPS 6: obj = -1.063544409973e+00 err = 3.7796484550e-10 time = 0.01 sec
-[ Info: VUMPS conv 7: obj = -1.063544409973e+00 err = 2.9001138645e-11 time = 1.69 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
@@ -130,7 +110,7 @@ dot(ψ₀, ψ₀)
````
````
-0.9999999999999996 + 3.8955006105253705e-16im
+1.0000000000000047 + 1.040736567930811e-16im
````
so the Loschmidt echo takes on the pleasant form
@@ -140,7 +120,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:
@@ -163,7 +144,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 82%
rename from docs/src/examples/quantum1d/3.ising-dqpt/main.ipynb
rename to docs/src/examples/dynamics/ising-dqpt/main.ipynb
index e3418901c..2d8f90416 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"
]
},
{
@@ -24,17 +24,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:"
@@ -49,7 +50,7 @@
"L = 20\n",
"H₀ = transverse_field_ising(FiniteChain(L); g = -0.5)\n",
"ψ₀ = FiniteMPS(L, ℂ^2, ℂ^10)\n",
- "ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG());"
+ "ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG(; verbosity = 0));"
]
},
{
@@ -58,7 +59,7 @@
"source": [
"## Finite MPS quenching\n",
"\n",
- "We can define a helper function that measures the loschmith echo"
+ "We can define a helper function that measures the Loschmidt echo"
]
},
{
@@ -75,7 +76,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"
]
},
{
@@ -94,7 +96,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"
]
@@ -108,7 +111,7 @@
"function finite_sim(L; dt = 0.05, finaltime = 5.0)\n",
" ψ₀ = FiniteMPS(L, ℂ^2, ℂ^10)\n",
" H₀ = transverse_field_ising(FiniteChain(L); g = -0.5)\n",
- " ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG())\n",
+ " ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG(; verbosity = 0))\n",
"\n",
" H₁ = transverse_field_ising(FiniteChain(L); g = -2.0)\n",
" ψₜ = deepcopy(ψ₀)\n",
@@ -151,14 +154,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"
]
},
@@ -192,7 +195,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:"
]
@@ -241,7 +245,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/dynamics/xy-finiteT/figure-1.png b/docs/src/examples/dynamics/xy-finiteT/figure-1.png
new file mode 100644
index 000000000..be692709a
Binary files /dev/null and b/docs/src/examples/dynamics/xy-finiteT/figure-1.png differ
diff --git a/docs/src/examples/dynamics/xy-finiteT/figure-2.png b/docs/src/examples/dynamics/xy-finiteT/figure-2.png
new file mode 100644
index 000000000..508b94015
Binary files /dev/null and b/docs/src/examples/dynamics/xy-finiteT/figure-2.png differ
diff --git a/docs/src/examples/dynamics/xy-finiteT/figure-3.png b/docs/src/examples/dynamics/xy-finiteT/figure-3.png
new file mode 100644
index 000000000..9df578602
Binary files /dev/null and b/docs/src/examples/dynamics/xy-finiteT/figure-3.png differ
diff --git a/docs/src/examples/dynamics/xy-finiteT/figure-4.png b/docs/src/examples/dynamics/xy-finiteT/figure-4.png
new file mode 100644
index 000000000..701d842e0
Binary files /dev/null and b/docs/src/examples/dynamics/xy-finiteT/figure-4.png differ
diff --git a/docs/src/examples/dynamics/xy-finiteT/figure-5.png b/docs/src/examples/dynamics/xy-finiteT/figure-5.png
new file mode 100644
index 000000000..cfd09fdbc
Binary files /dev/null and b/docs/src/examples/dynamics/xy-finiteT/figure-5.png differ
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..16501b948
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 a897a94b2..17baaf505 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/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/quantum1d/7.xy-finiteT/main.ipynb)
-[](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/quantum1d/7.xy-finiteT/main.ipynb)
-[](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/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/dynamics/xy-finiteT/main.ipynb)
+[](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/dynamics/xy-finiteT/main.ipynb)
+[](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
@@ -27,7 +27,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$.
@@ -59,15 +59,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)
@@ -116,7 +114,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))
@@ -125,11 +123,7 @@ println("Exact (N=Inf):\t", groundstate_energy(J, Inf))
````
````
-[ Info: DMRG2 1: obj = -5.004084801485e+00 err = 9.7485774328e-01 time = 1.43 min
-[ Info: DMRG2 2: obj = -5.004096940647e+00 err = 1.1899230994e-06 time = 1.27 sec
-[ Info: DMRG2 3: obj = -5.004096975044e+00 err = 2.2262868216e-09 time = 0.80 sec
-[ Info: DMRG2 conv 4: obj = -5.004096975044e+00 err = 1.1612932838e-13 time = 1.47 min
-Numerical: -0.15637803047010942
+Numerical: -0.1563780304701162
Exact (N=32): -0.15637803047254015
Exact (N=Inf): -0.15915494309189535
@@ -141,40 +135,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)
@@ -197,10 +190,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
@@ -242,11 +235,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
@@ -257,9 +250,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}
```
@@ -293,23 +286,20 @@ end

-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(ρ₁)
@@ -349,15 +339,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}
```
@@ -424,10 +414,10 @@ end

-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.
@@ -435,23 +425,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)
@@ -504,7 +494,7 @@ end

-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
@@ -516,14 +506,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))
@@ -575,7 +565,7 @@ end

!!! 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 81%
rename from docs/src/examples/quantum1d/7.xy-finiteT/main.ipynb
rename to docs/src/examples/dynamics/xy-finiteT/main.ipynb
index 26798bd0e..306e6edd8 100644
--- a/docs/src/examples/quantum1d/7.xy-finiteT/main.ipynb
+++ b/docs/src/examples/dynamics/xy-finiteT/main.ipynb
@@ -1,8 +1,10 @@
{
"cells": [
{
- "outputs": [],
"cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"using Markdown\n",
"using TensorKit\n",
@@ -14,12 +16,11 @@
"using Plots\n",
"using LinearAlgebra\n",
"using BenchmarkFreeFermions"
- ],
- "metadata": {},
- "execution_count": null
+ ]
},
{
"cell_type": "markdown",
+ "metadata": {},
"source": [
"# Finite temperature XY model\n",
"\n",
@@ -29,23 +30,24 @@
"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$."
- ],
- "metadata": {}
+ ]
},
{
"cell_type": "markdown",
+ "metadata": {},
"source": [
"Parameters"
- ],
- "metadata": {}
+ ]
},
{
- "outputs": [],
"cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"J = 1 / 2\n",
"T = ComplexF64\n",
@@ -63,31 +65,28 @@
" end\n",
" end\n",
"end"
- ],
- "metadata": {},
- "execution_count": null
+ ]
},
{
"cell_type": "markdown",
+ "metadata": {},
"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."
- ],
- "metadata": {}
+ "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)."
+ ]
},
{
- "outputs": [],
"cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"function groundstate_energy(J, N)\n",
" isfinite(N) || return -J / π\n",
@@ -95,22 +94,22 @@
" ϵ = SingleParticleSpectrum(T)\n",
" return Energy(ϵ, Inf, 0) / N\n",
"end"
- ],
- "metadata": {},
- "execution_count": null
+ ]
},
{
"cell_type": "markdown",
+ "metadata": {},
"source": [
"### Exact diagonalization\n",
"\n",
"We can check our results by comparing them to the exact diagonalization of the Hamiltonian."
- ],
- "metadata": {}
+ ]
},
{
- "outputs": [],
"cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"N_exact = 6\n",
"H = open_boundary_conditions(XY_hamiltonian(T, symmetry; J, N = Inf), N_exact)\n",
@@ -121,22 +120,22 @@
"println(\"Numerical:\\t\", minimum(real(vals)))\n",
"println(\"Exact (N=$(N_exact)):\\t\", groundstate_energy(J, N_exact))\n",
"println(\"Exact (N=Inf):\\t\", groundstate_energy(J, Inf))"
- ],
- "metadata": {},
- "execution_count": null
+ ]
},
{
"cell_type": "markdown",
+ "metadata": {},
"source": [
"### Finite MPS\n",
"\n",
"If we wish to increase the system size, we can use the finite MPS representation."
- ],
- "metadata": {}
+ ]
},
{
- "outputs": [],
"cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"N = 32\n",
"H = XY_hamiltonian(T, symmetry; J, N)\n",
@@ -144,18 +143,17 @@
"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",
"println(\"Exact (N=$N):\\t\", groundstate_energy(J, N))\n",
"println(\"Exact (N=Inf):\\t\", groundstate_energy(J, Inf))"
- ],
- "metadata": {},
- "execution_count": null
+ ]
},
{
"cell_type": "markdown",
+ "metadata": {},
"source": [
"## Finite temperature properties\n",
"\n",
@@ -163,47 +161,46 @@
"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."
- ],
- "metadata": {}
+ "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))."
+ ]
},
{
- "outputs": [],
"cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"function partition_function(β::Number, J::Number, N::Number)\n",
" T = diagm(1 => J / 2 * ones(N - 1), -1 => J / 2 * ones(N - 1))\n",
@@ -220,27 +217,27 @@
"\n",
"Z_analytic = partition_function.(βs, J, N);\n",
"F_analytic = free_energy.(βs, J, N);"
- ],
- "metadata": {},
- "execution_count": null
+ ]
},
{
"cell_type": "markdown",
+ "metadata": {},
"source": [
"### 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."
- ],
- "metadata": {}
+ ]
},
{
- "outputs": [],
"cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"expansion_orders = 1:3\n",
"\n",
@@ -274,25 +271,24 @@
" plot!(p2, βs, F_taylor; label = labels)\n",
" plot(p1, p2)\n",
"end"
- ],
- "metadata": {},
- "execution_count": null
+ ]
},
{
"cell_type": "markdown",
+ "metadata": {},
"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."
- ],
- "metadata": {}
+ "- 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."
+ ]
},
{
"cell_type": "markdown",
+ "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",
@@ -303,21 +299,22 @@
" 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",
"\n",
"Therefore, we will exclude the first order approximation from now on.\n",
"Zooming in on the differences with the analytical result, we find:"
- ],
- "metadata": {}
+ ]
},
{
- "outputs": [],
"cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"expansion_orders = 2:3\n",
"Z_taylor = Z_taylor[:, 2:end]\n",
@@ -340,37 +337,33 @@
" )\n",
" plot(p1, p2)\n",
"end"
- ],
- "metadata": {},
- "execution_count": null
+ ]
},
{
"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."
- ],
- "metadata": {}
+ "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^†$."
+ ]
},
{
- "outputs": [],
"cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"double_logpartition(ρ₁, ρ₂ = ρ₁) = log(real(dot(ρ₁, ρ₂))) / length(ρ₁)\n",
"\n",
@@ -403,24 +396,23 @@
" )\n",
" plot(p1, p2)\n",
"end"
- ],
- "metadata": {},
- "execution_count": null
+ ]
},
{
"cell_type": "markdown",
+ "metadata": {},
"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",
@@ -436,12 +428,13 @@
"> In particular, the truncation of the MPO is now happening in the Frobenius norm, rather than the operator norm.\n",
"> While for small truncations this might still work, this is not guaranteed to be the case for larger truncations.\n",
"> As a result, the truncated object might not be positive semidefinite, spoiling its interpretation as a density matrix."
- ],
- "metadata": {}
+ ]
},
{
- "outputs": [],
"cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"Z_mpo_mul = zeros(length(βs))\n",
"D_max = 64\n",
@@ -489,17 +482,16 @@
" )\n",
" plot(p1, p2)\n",
"end"
- ],
- "metadata": {},
- "execution_count": null
+ ]
},
{
"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",
@@ -507,34 +499,35 @@
"\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."
- ],
- "metadata": {}
+ ]
},
{
"cell_type": "markdown",
+ "metadata": {},
"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."
- ],
- "metadata": {}
+ "In other words, we can scan a range of exponentially increasing $β$ values by squaring the density matrix at each step."
+ ]
},
{
- "outputs": [],
"cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"βs_exp = 2.0 .^ (-3:3)\n",
"Z_analytic_exp = partition_function.(βs_exp, J, N)\n",
@@ -582,45 +575,45 @@
" plot!(p2, βs_exp, abs.(F_mpo_mul_exp .- F_analytic_exp); label = \"MPO multiplication exp\")\n",
" plot(p1, p2)\n",
"end"
- ],
- "metadata": {},
- "execution_count": null
+ ]
},
{
"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",
">\n",
"> Again, using MPS techniques to approximate the multiplication of density matrices might lead to unphysical truncated density matrices.\n",
"> Increasing the stepsize could make this happen sooner, so we need to be careful with the maximal bond dimension."
- ],
- "metadata": {}
+ ]
},
{
"cell_type": "markdown",
+ "metadata": {},
"source": [
"### Time evolution approach\n",
"\n",
"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."
- ],
- "metadata": {}
+ "In particular, this state is given by the identity MPO, and we can evolve this state to compute the partition function at any $β$ value."
+ ]
},
{
- "outputs": [],
"cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"Z_tdvp = zeros(length(βs))\n",
"\n",
@@ -666,43 +659,41 @@
"\n",
" plot(p1, p2)\n",
"end"
- ],
- "metadata": {},
- "execution_count": null
+ ]
},
{
"cell_type": "markdown",
+ "metadata": {},
"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."
- ],
- "metadata": {}
+ ]
},
{
"cell_type": "markdown",
+ "metadata": {},
"source": [
"---\n",
"\n",
"*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*"
- ],
- "metadata": {}
+ ]
}
],
- "nbformat_minor": 3,
"metadata": {
+ "kernelspec": {
+ "display_name": "Julia 1.12.6",
+ "language": "julia",
+ "name": "julia-1.12"
+ },
"language_info": {
"file_extension": ".jl",
"mimetype": "application/julia",
"name": "julia",
- "version": "1.12.4"
- },
- "kernelspec": {
- "name": "julia-1.12",
- "display_name": "Julia 1.12.4",
- "language": "julia"
+ "version": "1.12.6"
}
},
- "nbformat": 4
+ "nbformat": 4,
+ "nbformat_minor": 3
}
\ No newline at end of file
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 71bcad85e..af8cd0401 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/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/quantum1d/2.haldane/main.ipynb)
-[](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/quantum1d/2.haldane/main.ipynb)
-[](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/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/excitations/haldane/main.ipynb)
+[](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/excitations/haldane/main.ipynb)
+[](https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/QuantumKitHub/MPSKit.jl/examples/tree/gh-pages/dev/examples/excitations/haldane)
# The Haldane gap
@@ -61,7 +61,7 @@ En_2, st_2 = excitations(H, QuasiparticleAnsatz(), ψ, envs; sector = SU2Irrep(2
````
````
-0.7989253589480472
+0.7989253589480387
````
We can go even further and doublecheck the claim that ``S = 1`` is an edge excitation, by plotting the energy density.
@@ -100,7 +100,7 @@ f = fit(Ls .^ (-2), ΔEs, 1)
````
````
-0.4517340158583749
+0.4517340158585316
````
````julia
@@ -129,30 +129,14 @@ virtual_space_inf = Rep[SU₂](1 // 2 => 16, 3 // 2 => 16, 5 // 2 => 8, 7 // 2 =
ψ_inf, envs_inf, delta_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 = 1.4660765716752369
-[ Info: Found excitations for momentum = 1.2566370614359172
-[ Info: Found excitations for momentum = 0.8377580409572781
-[ Info: Found excitations for momentum = 1.0471975511965976
-[ 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.9321531433504737
-[ Info: Found excitations for momentum = 2.722713633111154
-[ Info: Found excitations for momentum = 3.141592653589793
-minimum @k = 3.141592653589793: ΔE = 0.41047924848831047
+minimum @k = 3.141592653589793: ΔE = 0.41047924870059116
````
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 2084d50d9..a2c095d53 100644
--- a/docs/src/examples/quantum1d/2.haldane/main.ipynb
+++ b/docs/src/examples/excitations/haldane/main.ipynb
@@ -164,7 +164,7 @@
"ψ_inf, envs_inf, delta_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..d57ee39ea
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..b767a8f0a
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..5befe8bb3
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..03e3e7ee3
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..a770a1995
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..9570db4e5
--- /dev/null
+++ b/docs/src/examples/groundstates/bose-hubbard/index.md
@@ -0,0 +1,405 @@
+```@meta
+EditURL = "../../../../../examples/groundstates/bose-hubbard/main.jl"
+```
+
+[](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/groundstates/bose-hubbard/main.ipynb)
+[](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/groundstates/bose-hubbard/main.ipynb)
+[](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 MPSKit, MPSKitModels, TensorKit
+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 [`MPSKitModels.jl`](https://github.com/QuantumKitHub/MPSKitModels.jl) instead that has predefined operators and models for most well-known lattice models.
+In particular, we can use [`MPSKitModels.a_min`](@extref) to create the bosonic annihilation operator.
+
+````julia
+a_op = a_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.
+Similarly, the Bose Hubbard model is also predefined in [`MPSKitModels.bose_hubbard_model`](@extref) (although we will construct our own variant later on).
+
+````julia
+hamiltonian = bose_hubbard_model(InfiniteChain(1); cutoff = cutoff, U = 1, mu = 0.5, t = 0.2) # It is not strictly required to pass InfiniteChain() and is only included for clarity; one may instead pass FiniteChain(N) as well
+````
+
+````
+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.857458547301e-01 err = 6.0721e-01
+[ Info: VUMPS conv 60: obj = -6.756981551605e-01 err = 9.2085211730e-07 time = 2.08 sec
+Energy: -0.675698155160498 - 6.668144181512403e-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(InfiniteChain(); 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)
+````
+
+
+
+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)
+````
+
+
+
+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 = a_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]
+)
+````
+
+
+
+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.30974277430265373
+ 0.28814775946586596
+ 0.27020005963868154
+ 0.25712729075508733
+ 0.24685385782407282
+ 0.23539787855732608
+ 0.22799752254820496
+````
+
+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])
+````
+
+
+
+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 [source code](https://github.com/QuantumKitHub/MPSKitModels.jl/blob/f4c36d9660a9eab05fa253ffd5c20dc6b7df44cc/src/models/hamiltonians.jl#L379-L409) of `MPSKitModels.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,
+ lattice::AbstractLattice = InfiniteChain(1);
+ cutoff::Integer = 5, t = 1.0, U = 1.0, mu = 0.0, phi = 0
+ )
+
+ a_pm = a_plusmin(elt, symmetry; cutoff = cutoff)
+ a_mp = a_minplus(elt, symmetry; cutoff = cutoff)
+ N = a_number(elt, symmetry; cutoff = cutoff)
+
+ interaction_term = N * (N - id(domain(N)))
+
+ return H = @mpoham begin
+ sum(nearest_neighbours(lattice)) do (i, j)
+ return -t * (exp(1im * phi) * a_pm{i, j} + exp(1im * -phi) * a_mp{i, j})
+ end +
+ sum(vertices(lattice)) do i
+ return U / 2 * interaction_term{i} - mu * N{i}
+ end
+ end
+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
+````
+
+
+
+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 = a_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(InfiniteChain(); 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")
+````
+
+
+
+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 51%
rename from docs/src/examples/quantum1d/8.bose-hubbard/main.ipynb
rename to docs/src/examples/groundstates/bose-hubbard/main.ipynb
index 615cea4a7..5fbec9399 100644
--- a/docs/src/examples/quantum1d/8.bose-hubbard/main.ipynb
+++ b/docs/src/examples/groundstates/bose-hubbard/main.ipynb
@@ -21,82 +21,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."
]
},
{
@@ -113,12 +96,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",
- "[`MPSKitModels.jl`](https://github.com/QuantumKitHub/MPSKitModels.jl) instead that has\n",
- "predefined operators and models for most well-known lattice models. In particular, we can\n",
- "use `MPSKitModels.a_min` to create the bosonic annihilation 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 [`MPSKitModels.jl`](https://github.com/QuantumKitHub/MPSKitModels.jl) instead that has predefined operators and models for most well-known lattice models.\n",
+ "In particular, we can use `MPSKitModels.a_min` to create the bosonic annihilation operator."
]
},
{
@@ -136,10 +117,8 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "The [] accessor lets us see the underlying array, and indeed the operators are exactly what\n",
- "we require. Similarly, the Bose Hubbard model is also predefined in\n",
- "`MPSKitModels.bose_hubbard_model` (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",
+ "Similarly, the Bose Hubbard model is also predefined in `MPSKitModels.bose_hubbard_model` (although we will construct our own variant later on)."
]
},
{
@@ -155,11 +134,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."
]
},
{
@@ -176,9 +153,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."
]
},
{
@@ -202,11 +178,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⟩$."
]
},
{
@@ -223,8 +198,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."
]
},
{
@@ -243,20 +218,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."
]
},
{
@@ -313,11 +281,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 → ∞$."
]
},
{
@@ -333,40 +298,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$."
]
},
{
@@ -393,39 +353,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",
- "[source code](https://github.com/QuantumKitHub/MPSKitModels.jl/blob/f4c36d9660a9eab05fa253ffd5c20dc6b7df44cc/src/models/hamiltonians.jl#L379-L409)\n",
- "of `MPSKitModels.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 [source code](https://github.com/QuantumKitHub/MPSKitModels.jl/blob/f4c36d9660a9eab05fa253ffd5c20dc6b7df44cc/src/models/hamiltonians.jl#L379-L409) of `MPSKitModels.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."
]
},
{
@@ -483,14 +427,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."
]
},
{
@@ -520,11 +459,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..31124b5a5
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..f9d22360f
Binary files /dev/null and b/docs/src/examples/groundstates/haldane-spt/figure-2.png differ
diff --git a/docs/src/examples/quantum1d/5.haldane-spt/figure-3.png b/docs/src/examples/groundstates/haldane-spt/figure-3.png
similarity index 51%
rename from docs/src/examples/quantum1d/5.haldane-spt/figure-3.png
rename to docs/src/examples/groundstates/haldane-spt/figure-3.png
index 8dfdfe224..42c179c9f 100644
Binary files a/docs/src/examples/quantum1d/5.haldane-spt/figure-3.png 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..764fdf398
--- /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/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/groundstates/haldane-spt/main.ipynb)
+[](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/groundstates/haldane-spt/main.ipynb)
+[](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, δ = find_groundstate(ψ, H, VUMPS(; maxiter = 10))
+sectors = SU2Irrep[0, 1 // 2, 1, 3 // 2]
+transferplot(ψ; sectors, title = "Transfer matrix spectrum", legend = :outertop)
+````
+
+
+
+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.4014193313393004 - 2.2233521403023605e-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.4014839739630827 + 6.744598315147384e-17im
+````
+
+````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))
+````
+
+
+
+````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))
+````
+
+
+
+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
+
+```
+
+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.5486227235423025
+S_plus = 1.545032353055433
+
+````
+
+---
+
+*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 f85c243c3..3f02a0b95 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",
"
\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/quantum1d/6.hubbard/figure-1.png b/docs/src/examples/groundstates/hubbard/figure-1.png
similarity index 99%
rename from docs/src/examples/quantum1d/6.hubbard/figure-1.png
rename to docs/src/examples/groundstates/hubbard/figure-1.png
index 87a4f10f8..73f21dcec 100644
Binary files a/docs/src/examples/quantum1d/6.hubbard/figure-1.png and b/docs/src/examples/groundstates/hubbard/figure-1.png differ
diff --git a/docs/src/examples/quantum1d/6.hubbard/figure-2.png b/docs/src/examples/groundstates/hubbard/figure-2.png
similarity index 99%
rename from docs/src/examples/quantum1d/6.hubbard/figure-2.png
rename to docs/src/examples/groundstates/hubbard/figure-2.png
index f28bef941..f6487a68d 100644
Binary files a/docs/src/examples/quantum1d/6.hubbard/figure-2.png 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..625511c00
--- /dev/null
+++ b/docs/src/examples/groundstates/hubbard/index.md
@@ -0,0 +1,335 @@
+```@meta
+EditURL = "../../../../../examples/groundstates/hubbard/main.jl"
+```
+
+[](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/groundstates/hubbard/main.ipynb)
+[](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/groundstates/hubbard/main.ipynb)
+[](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 MPSKitModels
+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(InfiniteChain(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.189996060974577
+└ * 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, InfiniteChain(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 = [oneunit(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.190015347514472
+└ * 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
+````
+
+
+
+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
+````
+
+
+
+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
+````
+
+
+
+---
+
+*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 89%
rename from docs/src/examples/quantum1d/6.hubbard/main.ipynb
rename to docs/src/examples/groundstates/hubbard/main.ipynb
index 9b64a7960..62597f93c 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",
"$$"
]
},
@@ -52,8 +51,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",
@@ -65,10 +80,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."
@@ -82,7 +97,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",
@@ -92,7 +107,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",
@@ -111,7 +126,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",
@@ -178,7 +193,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:"
]
},
@@ -198,13 +213,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",
");"
]
},
@@ -274,7 +289,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$."
]
},
{
diff --git a/docs/src/examples/groundstates/ising-cft/figure-1.png b/docs/src/examples/groundstates/ising-cft/figure-1.png
new file mode 100644
index 000000000..dc4c4239a
Binary files /dev/null and b/docs/src/examples/groundstates/ising-cft/figure-1.png differ
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..8b55ec3c9
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..8162b37f8
--- /dev/null
+++ b/docs/src/examples/groundstates/ising-cft/index.md
@@ -0,0 +1,210 @@
+```@meta
+EditURL = "../../../../../examples/groundstates/ising-cft/main.jl"
+```
+
+[](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/groundstates/ising-cft/main.ipynb)
+[](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/groundstates/ising-cft/main.ipynb)
+[](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, MPSKitModels, TensorKit, Plots, KrylovKit
+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"
+)
+````
+
+
+
+!!! 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
+
+```
+
+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}:
+ -6.835457747908734e-17
+ -1.5823223400041525e-17
+ -2.117254204162227e-17
+ -0.5235987755982994
+ 0.5235987755982988
+ -1.047197551196598
+ 1.0471975511965976
+ 0.5235987755982993
+ -0.5235987755982985
+ -1.0471975511965976
+ 1.0471975511965976
+ -1.3705449793358471e-17
+ 1.570796326794897
+ -1.5707963267948966
+ -1.0471975511965979
+ 1.0471975511965976
+ 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
+````
+
+
+
+## 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, δ = 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
+````
+
+
+
+---
+
+*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 77%
rename from docs/src/examples/quantum1d/1.ising-cft/main.ipynb
rename to docs/src/examples/groundstates/ising-cft/main.ipynb
index 54e1b0ed7..0e6d15199 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."
]
},
{
@@ -25,8 +24,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:"
]
},
{
@@ -45,11 +43,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."
]
},
{
@@ -81,9 +76,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",
"
\n",
@@ -109,13 +103,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$."
]
},
{
@@ -152,10 +144,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."
]
},
{
@@ -184,8 +174,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."
]
},
{
@@ -197,16 +186,15 @@
"L_mps = 20\n",
"H_mps = periodic_boundary_conditions(transverse_field_ising(), L_mps)\n",
"D = 64\n",
- "ψ, envs, δ = find_groundstate(FiniteMPS(L_mps, ℂ^2, ℂ^D), H_mps, DMRG());"
+ "ψ, envs, δ = 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..5cb23a3c0
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..f487be24a
--- /dev/null
+++ b/docs/src/examples/groundstates/xxz-heisenberg/index.md
@@ -0,0 +1,209 @@
+```@meta
+EditURL = "../../../../../examples/groundstates/xxz-heisenberg/main.jl"
+```
+
+[](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/groundstates/xxz-heisenberg/main.ipynb)
+[](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/groundstates/xxz-heisenberg/main.ipynb)
+[](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, MPSKitModels, TensorKit, Plots
+````
+
+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, delta = find_groundstate(state, H, VUMPS(; verbosity = 1));
+````
+
+````
+┌ Warning: VUMPS cancel 200: obj = -1.878034423426e-01 err = 3.7915687051e-01 time = 6.59 sec
+└ @ MPSKit src/algorithms/groundstate/vumps.jl:87
+
+````
+
+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, delta = find_groundstate(state, H, GradientGrassmann(; maxiter = 20, verbosity = 1));
+````
+
+````
+┌ Warning: resorting to η
+└ @ OptimKit src/cg.jl:225
+┌ Warning: CG: not converged to requested tol after 20 iterations and time 5.39 s: f = -4.427115230754e-01, ‖∇f‖ = 5.6573e-03
+└ @ OptimKit src/cg.jl:188
+
+````
+
+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)
+````
+
+
+
+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, the Hamiltonian can be constructed directly on a two-site unit cell by making use of MPSKitModels.jl's `@mpoham`.
+
+````julia
+# H2 = repeat(H, 2); -- copies the one-site version
+H2 = heisenberg_XXX(ComplexF64, Trivial, InfiniteChain(2); spin = 1 // 2)
+groundstate, envs, delta = find_groundstate(
+ state, H2, VUMPS(; maxiter = 100, tol = 1.0e-12, verbosity = 1)
+);
+````
+
+````
+┌ Warning: VUMPS cancel 100: obj = -8.862417624752e-01 err = 4.2010390727e-06 time = 3.79 sec
+└ @ MPSKit src/algorithms/groundstate/vumps.jl:87
+
+````
+
+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, delta = find_groundstate(
+ state, H2, IDMRG2(; trunc = truncrank(50), maxiter = 20, tol = 1.0e-12, verbosity = 1)
+);
+entanglementplot(groundstate)
+````
+
+
+
+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, InfiniteChain(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, delta = 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 80%
rename from docs/src/examples/quantum1d/4.xxz-heisenberg/main.ipynb
rename to docs/src/examples/groundstates/xxz-heisenberg/main.ipynb
index ce74387d6..a1bda9266 100644
--- a/docs/src/examples/quantum1d/4.xxz-heisenberg/main.ipynb
+++ b/docs/src/examples/groundstates/xxz-heisenberg/main.ipynb
@@ -2,182 +2,201 @@
"cells": [
{
"cell_type": "markdown",
+ "metadata": {},
"source": [
"# The XXZ model\n",
"\n",
"In this file we will give step by step instructions on how to analyze the spin 1/2 XXZ model.\n",
"The necessary packages to follow this tutorial are:"
- ],
- "metadata": {}
+ ]
},
{
- "outputs": [],
"cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"using MPSKit, MPSKitModels, TensorKit, Plots"
- ],
+ ]
+ },
+ {
+ "cell_type": "markdown",
"metadata": {},
- "execution_count": null
+ "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": {},
"source": [
"## Failure\n",
"\n",
"First we should define the Hamiltonian we want to work with.\n",
"Then we specify an initial guess, which we then further optimize.\n",
"Working directly in the thermodynamic limit, this is achieved as follows:"
- ],
- "metadata": {}
+ ]
},
{
- "outputs": [],
"cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"H = heisenberg_XXX(; spin = 1 // 2)"
- ],
- "metadata": {},
- "execution_count": null
+ ]
},
{
"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."
- ],
- "metadata": {}
+ "We then need an initial state, which we shall later optimize.\n",
+ "In this example we work directly in the thermodynamic limit."
+ ]
},
{
- "outputs": [],
"cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"state = InfiniteMPS(2, 20)"
- ],
- "metadata": {},
- "execution_count": null
+ ]
},
{
"cell_type": "markdown",
+ "metadata": {},
"source": [
"The ground state can then be found by calling `find_groundstate`."
- ],
- "metadata": {}
+ ]
},
{
- "outputs": [],
"cell_type": "code",
- "source": [
- "groundstate, cache, delta = find_groundstate(state, H, VUMPS());"
- ],
+ "execution_count": null,
"metadata": {},
- "execution_count": null
+ "outputs": [],
+ "source": [
+ "groundstate, cache, delta = find_groundstate(state, H, VUMPS(; verbosity = 1));"
+ ]
},
{
"cell_type": "markdown",
+ "metadata": {},
"source": [
"As you can see, VUMPS struggles to converge.\n",
"On its own, that is already quite curious.\n",
"Maybe we can do better using another algorithm, such as gradient descent."
- ],
- "metadata": {}
+ ]
},
{
- "outputs": [],
"cell_type": "code",
- "source": [
- "groundstate, cache, delta = find_groundstate(state, H, GradientGrassmann(; maxiter = 20));"
- ],
+ "execution_count": null,
"metadata": {},
- "execution_count": null
+ "outputs": [],
+ "source": [
+ "groundstate, cache, delta = find_groundstate(state, H, GradientGrassmann(; maxiter = 20, verbosity = 1));"
+ ]
},
{
"cell_type": "markdown",
+ "metadata": {},
"source": [
"Convergence is quite slow and even fails after sufficiently many iterations.\n",
"To understand why, we can look at the transfer matrix spectrum."
- ],
- "metadata": {}
+ ]
},
{
- "outputs": [],
"cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"transferplot(groundstate, groundstate)"
- ],
- "metadata": {},
- "execution_count": null
+ ]
},
{
"cell_type": "markdown",
+ "metadata": {},
"source": [
"We can clearly see multiple eigenvalues close to the unit circle.\n",
"Our state is close to being non-injective, and represents the sum of multiple injective states.\n",
"This is numerically very problematic, but also indicates that we used an incorrect ansatz to approximate the groundstate.\n",
"We should retry with a larger unit cell."
- ],
- "metadata": {}
+ ]
},
{
"cell_type": "markdown",
+ "metadata": {},
"source": [
"## Success\n",
"\n",
"Let's initialize a different initial state, this time with a 2-site unit cell:"
- ],
- "metadata": {}
+ ]
},
{
- "outputs": [],
"cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"state = InfiniteMPS(fill(2, 2), fill(20, 2))"
- ],
- "metadata": {},
- "execution_count": null
+ ]
},
{
"cell_type": "markdown",
+ "metadata": {},
"source": [
"In MPSKit, we require that the periodicity of the Hamiltonian equals that of the state it is applied to.\n",
"This is not a big obstacle, you can simply repeat the original Hamiltonian.\n",
"Alternatively, the Hamiltonian can be constructed directly on a two-site unit cell by making use of MPSKitModels.jl's `@mpoham`."
- ],
- "metadata": {}
+ ]
},
{
- "outputs": [],
"cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"# H2 = repeat(H, 2); -- copies the one-site version\n",
"H2 = heisenberg_XXX(ComplexF64, Trivial, InfiniteChain(2); spin = 1 // 2)\n",
"groundstate, envs, delta = 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",
");"
- ],
- "metadata": {},
- "execution_count": null
+ ]
},
{
"cell_type": "markdown",
+ "metadata": {},
"source": [
"We get convergence, but it takes an enormous amount of iterations.\n",
"The reason behind this becomes more obvious at higher bond dimensions:"
- ],
- "metadata": {}
+ ]
},
{
- "outputs": [],
"cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"groundstate, envs, delta = 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)"
- ],
- "metadata": {},
- "execution_count": null
+ ]
},
{
"cell_type": "markdown",
+ "metadata": {},
"source": [
"We see that some eigenvalues clearly belong to a group, and are almost degenerate.\n",
"This implies 2 things:\n",
@@ -185,31 +204,31 @@
"- poor convergence if we cut off within such a subspace\n",
"\n",
"It are precisely those problems that we can solve by using symmetries."
- ],
- "metadata": {}
+ ]
},
{
"cell_type": "markdown",
+ "metadata": {},
"source": [
"## Symmetries\n",
"\n",
"The XXZ Heisenberg Hamiltonian is SU(2) symmetric and we can exploit this to greatly speed up the simulation.\n",
"\n",
"It is cumbersome to construct symmetric Hamiltonians, but luckily SU(2) symmetric XXZ is already implemented:"
- ],
- "metadata": {}
+ ]
},
{
- "outputs": [],
"cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"H2 = heisenberg_XXX(ComplexF64, SU2Irrep, InfiniteChain(2); spin = 1 // 2);"
- ],
- "metadata": {},
- "execution_count": null
+ ]
},
{
"cell_type": "markdown",
+ "metadata": {},
"source": [
"Our initial state should also be SU(2) symmetric.\n",
"It now becomes apparent why we have to use a two-site periodic state.\n",
@@ -218,62 +237,61 @@
"The staggering thus happens on the virtual level.\n",
"\n",
"An alternative constructor for the initial state is"
- ],
- "metadata": {}
+ ]
},
{
- "outputs": [],
"cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"P = Rep[SU₂](1 // 2 => 1)\n",
"V1 = Rep[SU₂](1 // 2 => 10, 3 // 2 => 5, 5 // 2 => 2)\n",
"V2 = Rep[SU₂](0 => 15, 1 => 10, 2 => 5)\n",
"state = InfiniteMPS([P, P], [V1, V2]);"
- ],
- "metadata": {},
- "execution_count": null
+ ]
},
{
"cell_type": "markdown",
+ "metadata": {},
"source": [
"Even though the bond dimension is higher than in the example without symmetry, convergence is reached much faster:"
- ],
- "metadata": {}
+ ]
},
{
- "outputs": [],
"cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"println(dim(V1))\n",
"println(dim(V2))\n",
- "groundstate, cache, delta = find_groundstate(state, H2, VUMPS(; maxiter = 400, tol = 1.0e-12));"
- ],
- "metadata": {},
- "execution_count": null
+ "groundstate, cache, delta = find_groundstate(state, H2, VUMPS(; maxiter = 400, tol = 1.0e-12, verbosity = 1));"
+ ]
},
{
"cell_type": "markdown",
+ "metadata": {},
"source": [
"---\n",
"\n",
"*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*"
- ],
- "metadata": {}
+ ]
}
],
- "nbformat_minor": 3,
"metadata": {
+ "kernelspec": {
+ "display_name": "Julia 1.12.6",
+ "language": "julia",
+ "name": "julia-1.12"
+ },
"language_info": {
"file_extension": ".jl",
"mimetype": "application/julia",
"name": "julia",
- "version": "1.12.4"
- },
- "kernelspec": {
- "name": "julia-1.12",
- "display_name": "Julia 1.12.4",
- "language": "julia"
+ "version": "1.12.6"
}
},
- "nbformat": 4
+ "nbformat": 4,
+ "nbformat_minor": 3
}
\ No newline at end of file
diff --git a/docs/src/examples/index.md b/docs/src/examples/index.md
index d56cc7397..8a36ee4fb 100644
--- a/docs/src/examples/index.md
+++ b/docs/src/examples/index.md
@@ -1,15 +1,92 @@
-# Examples
+# [Examples](@id examples_index)
-## Quantum (1+1)d
+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.
-```@contents
-Pages = map(file -> joinpath("quantum1d", file, "index.md"), readdir("quantum1d"))
-Depth = 1
-```
+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 alphabetically.
-## Classical (2+0)d
+## Ground states
-```@contents
-Pages = map(file -> joinpath("classic2d", file, "index.md"), readdir("classic2d"))
-Depth = 1
-```
\ No newline at end of file
+### [The Ising CFT spectrum](groundstates/ising-cft/index.md)
+
+
+
+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)
+
+
+
+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)
+
+
+
+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)
+
+
+
+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)
+
+
+
+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)
+
+
+
+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)
+
+
+
+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)
+
+
+
+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)
+
+
+
+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 non-abelian anyonic symmetries in MPSKit.
+**Complexity: advanced.**
diff --git a/docs/src/examples/quantum1d/1.ising-cft/figure-1.png b/docs/src/examples/quantum1d/1.ising-cft/figure-1.png
deleted file mode 100644
index ec004e5a0..000000000
Binary files a/docs/src/examples/quantum1d/1.ising-cft/figure-1.png and /dev/null differ
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 8f76b9208..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 f634da5ff..000000000
--- a/docs/src/examples/quantum1d/1.ising-cft/index.md
+++ /dev/null
@@ -1,268 +0,0 @@
-```@meta
-EditURL = "../../../../../examples/quantum1d/1.ising-cft/main.jl"
-```
-
-[](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/quantum1d/1.ising-cft/main.ipynb)
-[](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/quantum1d/1.ising-cft/main.ipynb)
-[](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, MPSKitModels, TensorKit, Plots, KrylovKit
-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"
-)
-````
-
-
-
-!!! 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
-
-```
-
-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}:
- 1.0963150642957372e-17
- -2.4157081442786943e-17
- 9.150251499481629e-18
- -0.523598775598299
- 0.5235987755982987
- -1.0471975511965979
- 1.0471975511965976
- 0.5235987755982989
- -0.5235987755982993
- 1.047197551196598
- -1.0471975511965976
- 1.4597368636872088e-17
- -1.5707963267948966
- 1.5707963267948963
- 1.0471975511965976
- -1.0471975511965976
- -1.570796326794897
- 1.5707963267948963
-````
-
-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
-````
-
-
-
-## 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, δ = find_groundstate(FiniteMPS(L_mps, ℂ^2, ℂ^D), H_mps, DMRG());
-````
-
-````
-[ Info: DMRG init: obj = -1.946908612087e+01 err = 7.7434e-02
-[ Info: DMRG 1: obj = -2.549098951719e+01 err = 8.0439536934e-03 time = 2.57 sec
-[ Info: DMRG 2: obj = -2.549098968635e+01 err = 1.0703227324e-06 time = 0.80 sec
-[ Info: DMRG 3: obj = -2.549098968636e+01 err = 1.4373447563e-07 time = 0.98 sec
-[ Info: DMRG 4: obj = -2.549098968636e+01 err = 1.4665972881e-08 time = 0.42 sec
-[ Info: DMRG 5: obj = -2.549098968636e+01 err = 6.8081026722e-09 time = 0.44 sec
-[ Info: DMRG 6: obj = -2.549098968636e+01 err = 3.7573810815e-09 time = 0.43 sec
-[ Info: DMRG 7: obj = -2.549098968636e+01 err = 2.5698292651e-09 time = 0.43 sec
-[ Info: DMRG 8: obj = -2.549098968636e+01 err = 2.0113551709e-09 time = 0.43 sec
-[ Info: DMRG 9: obj = -2.549098968636e+01 err = 1.6427286008e-09 time = 0.89 sec
-[ Info: DMRG 10: obj = -2.549098968636e+01 err = 1.3479784013e-09 time = 0.55 sec
-[ Info: DMRG 11: obj = -2.549098968636e+01 err = 1.2769471445e-09 time = 0.44 sec
-[ Info: DMRG 12: obj = -2.549098968636e+01 err = 1.4168057275e-09 time = 0.48 sec
-[ Info: DMRG 13: obj = -2.549098968636e+01 err = 1.5595217750e-09 time = 0.42 sec
-[ Info: DMRG 14: obj = -2.549098968636e+01 err = 1.6950091915e-09 time = 0.41 sec
-[ Info: DMRG 15: obj = -2.549098968636e+01 err = 1.8105744613e-09 time = 0.40 sec
-[ Info: DMRG 16: obj = -2.549098968636e+01 err = 1.8924908787e-09 time = 0.96 sec
-[ Info: DMRG 17: obj = -2.549098968636e+01 err = 1.9288336151e-09 time = 0.49 sec
-[ Info: DMRG 18: obj = -2.549098968636e+01 err = 1.9133807885e-09 time = 0.31 sec
-[ Info: DMRG 19: obj = -2.549098968636e+01 err = 1.8713994972e-09 time = 0.38 sec
-[ Info: DMRG 20: obj = -2.549098968636e+01 err = 1.7813737815e-09 time = 0.41 sec
-[ Info: DMRG 21: obj = -2.549098968636e+01 err = 1.6542099689e-09 time = 0.40 sec
-[ Info: DMRG 22: obj = -2.549098968636e+01 err = 1.5039369007e-09 time = 0.40 sec
-[ Info: DMRG 23: obj = -2.549098968636e+01 err = 1.3441838671e-09 time = 0.90 sec
-[ Info: DMRG 24: obj = -2.549098968636e+01 err = 1.1858446625e-09 time = 0.28 sec
-[ Info: DMRG 25: obj = -2.549098968636e+01 err = 1.0362811206e-09 time = 0.34 sec
-[ Info: DMRG 26: obj = -2.549098968636e+01 err = 8.9963099646e-10 time = 0.35 sec
-[ Info: DMRG 27: obj = -2.549098968636e+01 err = 7.7760121034e-10 time = 0.41 sec
-[ Info: DMRG 28: obj = -2.549098968636e+01 err = 6.7030150822e-10 time = 0.40 sec
-[ Info: DMRG 29: obj = -2.549098968636e+01 err = 5.7691780289e-10 time = 0.41 sec
-[ Info: DMRG 30: obj = -2.549098968636e+01 err = 4.9618146296e-10 time = 0.91 sec
-[ Info: DMRG 31: obj = -2.549098968636e+01 err = 4.2666435281e-10 time = 0.36 sec
-[ Info: DMRG 32: obj = -2.549098968636e+01 err = 3.6694816435e-10 time = 0.40 sec
-[ Info: DMRG 33: obj = -2.549098968636e+01 err = 3.1571200436e-10 time = 0.36 sec
-[ Info: DMRG 34: obj = -2.549098968636e+01 err = 2.7176974363e-10 time = 0.38 sec
-[ Info: DMRG 35: obj = -2.549098968636e+01 err = 2.3407977700e-10 time = 0.40 sec
-[ Info: DMRG 36: obj = -2.549098968636e+01 err = 2.0173966270e-10 time = 0.40 sec
-[ Info: DMRG 37: obj = -2.549098968636e+01 err = 1.7397391951e-10 time = 0.40 sec
-[ Info: DMRG 38: obj = -2.549098968636e+01 err = 1.5011934910e-10 time = 0.87 sec
-[ Info: DMRG 39: obj = -2.549098968636e+01 err = 1.2961022917e-10 time = 0.35 sec
-[ Info: DMRG 40: obj = -2.549098968636e+01 err = 1.1196457020e-10 time = 0.38 sec
-[ Info: DMRG conv 41: obj = -2.549098968636e+01 err = 9.6771723038e-11 time = 22.14 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
-````
-
-
-
----
-
-*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 f5e4a5ec2..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 bd8dee700..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 d1b18bb75..000000000
--- a/docs/src/examples/quantum1d/4.xxz-heisenberg/index.md
+++ /dev/null
@@ -1,568 +0,0 @@
-```@meta
-EditURL = "../../../../../examples/quantum1d/4.xxz-heisenberg/main.jl"
-```
-
-[](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/quantum1d/4.xxz-heisenberg/main.ipynb)
-[](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/quantum1d/4.xxz-heisenberg/main.ipynb)
-[](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, MPSKitModels, TensorKit, Plots
-````
-
-## 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, delta = find_groundstate(state, H, VUMPS());
-````
-
-````
-[ Info: VUMPS init: obj = +2.499992657736e-01 err = 2.3659e-03
-[ Info: VUMPS 1: obj = -1.113720211054e-01 err = 3.4044919149e-01 time = 7.00 sec
-[ Info: VUMPS 2: obj = -7.257521722207e-02 err = 3.7501747094e-01 time = 0.29 sec
-[ Info: VUMPS 3: obj = -4.083234386194e-02 err = 4.0906387666e-01 time = 0.08 sec
-[ Info: VUMPS 4: obj = -1.575841447054e-01 err = 3.6917648883e-01 time = 0.03 sec
-[ Info: VUMPS 5: obj = -1.988657390936e-01 err = 3.6791056420e-01 time = 0.03 sec
-[ Info: VUMPS 6: obj = -3.197413863086e-01 err = 3.3258645953e-01 time = 0.04 sec
-[ Info: VUMPS 7: obj = -7.087435895207e-02 err = 3.8002416604e-01 time = 0.03 sec
-[ Info: VUMPS 8: obj = -1.207743137267e-01 err = 3.8655168695e-01 time = 0.02 sec
-[ Info: VUMPS 9: obj = -2.086746007253e-01 err = 3.8866997285e-01 time = 0.08 sec
-[ Info: VUMPS 10: obj = -2.135149067167e-02 err = 3.9998107520e-01 time = 0.02 sec
-[ Info: VUMPS 11: obj = +7.890476682466e-02 err = 3.9662516712e-01 time = 0.02 sec
-[ Info: VUMPS 12: obj = +8.179378380698e-02 err = 3.6865902322e-01 time = 0.04 sec
-[ Info: VUMPS 13: obj = -3.389058286591e-01 err = 3.3592003962e-01 time = 0.07 sec
-[ Info: VUMPS 14: obj = -6.936331877678e-02 err = 4.1384132520e-01 time = 0.02 sec
-[ Info: VUMPS 15: obj = -2.095570913077e-01 err = 3.7672687907e-01 time = 0.03 sec
-[ Info: VUMPS 16: obj = -8.547010074293e-02 err = 4.0253817589e-01 time = 0.02 sec
-[ Info: VUMPS 17: obj = -1.633131236641e-01 err = 3.7085752764e-01 time = 0.01 sec
-[ Info: VUMPS 18: obj = -2.440483461804e-01 err = 3.5634709811e-01 time = 0.02 sec
-[ Info: VUMPS 19: obj = -2.227348354782e-01 err = 3.6670163212e-01 time = 0.07 sec
-[ Info: VUMPS 20: obj = -1.647291995688e-01 err = 3.9547116130e-01 time = 0.03 sec
-[ Info: VUMPS 21: obj = -2.272417561286e-01 err = 3.6276621476e-01 time = 0.02 sec
-[ Info: VUMPS 22: obj = -3.388919076523e-01 err = 3.1579827529e-01 time = 0.02 sec
-[ Info: VUMPS 23: obj = -3.915604574764e-01 err = 2.5208735300e-01 time = 0.07 sec
-[ Info: VUMPS 24: obj = -2.716952795096e-01 err = 3.4825332817e-01 time = 0.04 sec
-[ Info: VUMPS 25: obj = -2.511746372453e-01 err = 3.6715895791e-01 time = 0.03 sec
-[ Info: VUMPS 26: obj = -2.731262108428e-02 err = 3.8195721702e-01 time = 0.04 sec
-[ Info: VUMPS 27: obj = -8.401004965257e-02 err = 4.1219830163e-01 time = 0.03 sec
-[ Info: VUMPS 28: obj = -5.593112558648e-02 err = 3.7991678397e-01 time = 0.07 sec
-[ Info: VUMPS 29: obj = -1.039939899261e-01 err = 4.1791684367e-01 time = 0.04 sec
-[ Info: VUMPS 30: obj = +3.646055386088e-02 err = 3.6884640674e-01 time = 0.02 sec
-[ Info: VUMPS 31: obj = -1.745961021141e-01 err = 3.7601676764e-01 time = 0.02 sec
-[ Info: VUMPS 32: obj = +6.528773402406e-03 err = 3.9555076806e-01 time = 0.02 sec
-[ Info: VUMPS 33: obj = -6.925592993288e-02 err = 3.7990669074e-01 time = 0.08 sec
-[ Info: VUMPS 34: obj = -1.493711283015e-01 err = 3.9233769652e-01 time = 0.02 sec
-[ Info: VUMPS 35: obj = -8.418199500312e-02 err = 3.6340380458e-01 time = 0.02 sec
-[ Info: VUMPS 36: obj = -2.276410296255e-02 err = 3.7714183538e-01 time = 0.02 sec
-[ Info: VUMPS 37: obj = -1.746306414275e-02 err = 4.3071643087e-01 time = 0.03 sec
-[ Info: VUMPS 38: obj = -1.078530946206e-01 err = 4.0554246549e-01 time = 0.08 sec
-[ Info: VUMPS 39: obj = -4.201824689181e-03 err = 3.8332228055e-01 time = 0.03 sec
-[ Info: VUMPS 40: obj = -1.695196954544e-01 err = 4.0646048881e-01 time = 0.03 sec
-[ Info: VUMPS 41: obj = -3.991123357010e-01 err = 2.6404691766e-01 time = 0.03 sec
-[ Info: VUMPS 42: obj = -3.731927178774e-04 err = 4.0618023904e-01 time = 0.02 sec
-[ Info: VUMPS 43: obj = -1.361859934319e-01 err = 3.8083035475e-01 time = 0.07 sec
-[ Info: VUMPS 44: obj = -4.351467677687e-02 err = 3.6067072767e-01 time = 0.01 sec
-[ Info: VUMPS 45: obj = +2.036960334903e-02 err = 4.0236374802e-01 time = 0.02 sec
-[ Info: VUMPS 46: obj = -2.564786609196e-01 err = 3.6550194687e-01 time = 0.02 sec
-[ Info: VUMPS 47: obj = -2.794914053758e-02 err = 3.8554065921e-01 time = 0.03 sec
-[ Info: VUMPS 48: obj = -1.456926586076e-01 err = 3.8815324507e-01 time = 0.02 sec
-[ Info: VUMPS 49: obj = -1.424477861718e-02 err = 3.7358256943e-01 time = 0.06 sec
-[ Info: VUMPS 50: obj = -4.121215827673e-02 err = 3.7267277980e-01 time = 0.02 sec
-[ Info: VUMPS 51: obj = -3.082552784588e-01 err = 3.3162188490e-01 time = 0.02 sec
-[ Info: VUMPS 52: obj = -2.583717562047e-01 err = 3.6414628453e-01 time = 0.03 sec
-[ Info: VUMPS 53: obj = +1.080952855580e-01 err = 3.5691712848e-01 time = 0.02 sec
-[ Info: VUMPS 54: obj = -2.388241333550e-01 err = 3.6671504144e-01 time = 0.06 sec
-[ Info: VUMPS 55: obj = -2.158989432818e-01 err = 3.5252639302e-01 time = 0.02 sec
-[ Info: VUMPS 56: obj = -2.846028253281e-01 err = 3.4280944784e-01 time = 0.02 sec
-[ Info: VUMPS 57: obj = -2.844144756572e-01 err = 3.5771158117e-01 time = 0.04 sec
-[ Info: VUMPS 58: obj = -3.296915296031e-01 err = 3.4124297389e-01 time = 0.07 sec
-[ Info: VUMPS 59: obj = -3.428588110821e-01 err = 3.1055543978e-01 time = 0.05 sec
-[ Info: VUMPS 60: obj = -3.689645262669e-01 err = 2.6709254795e-01 time = 0.04 sec
-[ Info: VUMPS 61: obj = -1.926296113920e-01 err = 3.9051008708e-01 time = 0.04 sec
-[ Info: VUMPS 62: obj = -2.083013559580e-01 err = 3.8360309501e-01 time = 0.09 sec
-[ Info: VUMPS 63: obj = -2.330751889106e-01 err = 3.5874442855e-01 time = 0.04 sec
-[ Info: VUMPS 64: obj = -1.170346482791e-01 err = 3.8356655434e-01 time = 0.03 sec
-[ Info: VUMPS 65: obj = -5.200963578316e-02 err = 3.6833174183e-01 time = 0.03 sec
-[ Info: VUMPS 66: obj = -2.197891431095e-02 err = 4.0124794245e-01 time = 0.02 sec
-[ Info: VUMPS 67: obj = -1.169084572818e-01 err = 3.9681625944e-01 time = 0.07 sec
-[ Info: VUMPS 68: obj = -3.187497224181e-01 err = 3.5389927614e-01 time = 0.03 sec
-[ Info: VUMPS 69: obj = -5.774837074080e-02 err = 4.0555259098e-01 time = 0.05 sec
-[ Info: VUMPS 70: obj = -1.385925677140e-01 err = 3.5902899521e-01 time = 0.02 sec
-[ Info: VUMPS 71: obj = -1.618893406115e-01 err = 3.7624779484e-01 time = 0.02 sec
-[ Info: VUMPS 72: obj = -1.501259513086e-01 err = 3.9017543257e-01 time = 0.08 sec
-[ Info: VUMPS 73: obj = -2.344578406890e-01 err = 3.7629861390e-01 time = 0.02 sec
-[ Info: VUMPS 74: obj = -9.559035249277e-02 err = 3.7722338357e-01 time = 0.02 sec
-[ Info: VUMPS 75: obj = -3.559605422071e-01 err = 3.0031220801e-01 time = 0.02 sec
-[ Info: VUMPS 76: obj = -1.954426659903e-01 err = 3.7999614851e-01 time = 0.02 sec
-[ Info: VUMPS 77: obj = -2.469742715213e-01 err = 3.5932526830e-01 time = 0.07 sec
-[ Info: VUMPS 78: obj = +1.378130557437e-02 err = 3.7598886038e-01 time = 0.02 sec
-[ Info: VUMPS 79: obj = -1.211793491665e-02 err = 3.6307721624e-01 time = 0.02 sec
-[ Info: VUMPS 80: obj = -1.280208560452e-01 err = 4.0740026676e-01 time = 0.03 sec
-[ Info: VUMPS 81: obj = -2.377846434769e-01 err = 3.7798079849e-01 time = 0.02 sec
-[ Info: VUMPS 82: obj = -3.480513403032e-01 err = 3.1094392423e-01 time = 0.06 sec
-[ Info: VUMPS 83: obj = -3.231666531453e-01 err = 3.5795237816e-01 time = 0.03 sec
-[ Info: VUMPS 84: obj = -3.230617723979e-01 err = 3.5338137372e-01 time = 0.08 sec
-[ Info: VUMPS 85: obj = -1.391043781959e-01 err = 3.7699469945e-01 time = 0.02 sec
-[ Info: VUMPS 86: obj = -3.434600465182e-01 err = 3.1162050408e-01 time = 0.08 sec
-[ Info: VUMPS 87: obj = -2.896970610423e-01 err = 3.4715325464e-01 time = 0.03 sec
-[ Info: VUMPS 88: obj = -3.473500689819e-01 err = 3.0203627103e-01 time = 0.03 sec
-[ Info: VUMPS 89: obj = -3.920852749773e-01 err = 2.4261851491e-01 time = 0.04 sec
-[ Info: VUMPS 90: obj = -2.323120265885e-03 err = 3.9363699208e-01 time = 0.08 sec
-[ Info: VUMPS 91: obj = +6.597719659994e-03 err = 3.6444377682e-01 time = 0.02 sec
-[ Info: VUMPS 92: obj = +2.842708237132e-02 err = 3.8036913669e-01 time = 0.02 sec
-[ Info: VUMPS 93: obj = +8.866987029294e-02 err = 3.6998433554e-01 time = 0.03 sec
-[ Info: VUMPS 94: obj = -5.099903063388e-02 err = 3.6527413192e-01 time = 0.02 sec
-[ Info: VUMPS 95: obj = -1.724607412056e-01 err = 3.6271311572e-01 time = 0.06 sec
-[ Info: VUMPS 96: obj = -1.359620364521e-01 err = 3.7163521224e-01 time = 0.03 sec
-[ Info: VUMPS 97: obj = -2.487229212367e-01 err = 3.5559482532e-01 time = 0.03 sec
-[ Info: VUMPS 98: obj = -3.363768011366e-01 err = 3.0691942472e-01 time = 0.02 sec
-[ Info: VUMPS 99: obj = -2.349190816289e-01 err = 3.8040603712e-01 time = 0.07 sec
-[ Info: VUMPS 100: obj = -2.501766365990e-01 err = 3.3899797906e-01 time = 0.02 sec
-[ Info: VUMPS 101: obj = -3.944006951029e-01 err = 2.5365404140e-01 time = 0.02 sec
-[ Info: VUMPS 102: obj = -2.995031953737e-02 err = 3.9078970987e-01 time = 0.03 sec
-[ Info: VUMPS 103: obj = -1.496727054580e-01 err = 3.8887820541e-01 time = 0.03 sec
-[ Info: VUMPS 104: obj = -1.651964515037e-01 err = 3.8482074585e-01 time = 0.06 sec
-[ Info: VUMPS 105: obj = -2.136080092702e-01 err = 3.7086717496e-01 time = 0.02 sec
-[ Info: VUMPS 106: obj = +2.076780492509e-03 err = 3.7971063787e-01 time = 0.02 sec
-[ Info: VUMPS 107: obj = -1.801409094443e-01 err = 3.7203262354e-01 time = 0.02 sec
-[ Info: VUMPS 108: obj = -2.824910448641e-01 err = 3.5387251865e-01 time = 0.02 sec
-[ Info: VUMPS 109: obj = -1.603436420431e-01 err = 3.9841222680e-01 time = 0.07 sec
-[ Info: VUMPS 110: obj = +2.717881863352e-02 err = 3.9583169447e-01 time = 0.02 sec
-[ Info: VUMPS 111: obj = +3.649162587405e-02 err = 3.2986973402e-01 time = 0.03 sec
-[ Info: VUMPS 112: obj = -1.931191472770e-01 err = 3.8832602682e-01 time = 0.02 sec
-[ Info: VUMPS 113: obj = -8.595612263509e-02 err = 3.9456614625e-01 time = 0.02 sec
-[ Info: VUMPS 114: obj = -2.052894924418e-01 err = 3.7916182480e-01 time = 0.08 sec
-[ Info: VUMPS 115: obj = -1.566623408265e-01 err = 3.7572169212e-01 time = 0.02 sec
-[ Info: VUMPS 116: obj = -2.697925193842e-01 err = 3.5224872666e-01 time = 0.03 sec
-[ Info: VUMPS 117: obj = -1.653547727749e-01 err = 3.6580445508e-01 time = 0.02 sec
-[ Info: VUMPS 118: obj = -3.099498644207e-01 err = 3.3813063847e-01 time = 0.02 sec
-[ Info: VUMPS 119: obj = -1.249709134848e-01 err = 3.9297526101e-01 time = 0.07 sec
-[ Info: VUMPS 120: obj = -1.759792403391e-01 err = 3.9286117967e-01 time = 0.03 sec
-[ Info: VUMPS 121: obj = -1.531238685880e-01 err = 3.7741746395e-01 time = 0.02 sec
-[ Info: VUMPS 122: obj = -2.607981777457e-02 err = 4.0319496740e-01 time = 0.03 sec
-[ Info: VUMPS 123: obj = -6.592234489034e-02 err = 4.0591425677e-01 time = 0.06 sec
-[ Info: VUMPS 124: obj = -6.634520512325e-02 err = 3.5661784954e-01 time = 0.02 sec
-[ Info: VUMPS 125: obj = -8.713375234031e-02 err = 3.8753642064e-01 time = 0.03 sec
-[ Info: VUMPS 126: obj = -1.125202000113e-01 err = 3.9947548666e-01 time = 0.02 sec
-[ Info: VUMPS 127: obj = -7.695667111350e-02 err = 3.8370863324e-01 time = 0.02 sec
-[ Info: VUMPS 128: obj = -1.626394492619e-01 err = 3.6111268034e-01 time = 0.06 sec
-[ Info: VUMPS 129: obj = -3.049948899402e-01 err = 3.4403324168e-01 time = 0.02 sec
-[ Info: VUMPS 130: obj = -1.258602194339e-01 err = 3.9449464476e-01 time = 0.03 sec
-[ Info: VUMPS 131: obj = -1.104762253188e-01 err = 3.7940891591e-01 time = 0.03 sec
-[ Info: VUMPS 132: obj = +1.961617781097e-01 err = 3.5498814846e-01 time = 0.04 sec
-[ Info: VUMPS 133: obj = -5.045117087438e-02 err = 3.9852508192e-01 time = 0.06 sec
-[ Info: VUMPS 134: obj = -1.456650620925e-01 err = 3.7944737703e-01 time = 0.02 sec
-[ Info: VUMPS 135: obj = -8.263613090518e-02 err = 3.9519353274e-01 time = 0.02 sec
-[ Info: VUMPS 136: obj = -1.765342573210e-01 err = 3.8095369610e-01 time = 0.02 sec
-[ Info: VUMPS 137: obj = -1.324964043868e-01 err = 3.9563235756e-01 time = 0.02 sec
-[ Info: VUMPS 138: obj = -5.801528053563e-03 err = 3.9895803654e-01 time = 0.07 sec
-[ Info: VUMPS 139: obj = +3.411995954756e-02 err = 3.7915180484e-01 time = 0.02 sec
-[ Info: VUMPS 140: obj = -5.243883424994e-02 err = 3.6892061143e-01 time = 0.02 sec
-[ Info: VUMPS 141: obj = -2.240214467858e-01 err = 3.6441762460e-01 time = 0.02 sec
-[ Info: VUMPS 142: obj = -2.299190776747e-01 err = 3.6831318301e-01 time = 0.03 sec
-[ Info: VUMPS 143: obj = -1.957936901383e-01 err = 3.9355611293e-01 time = 0.06 sec
-[ Info: VUMPS 144: obj = -3.444003971032e-01 err = 3.1223261647e-01 time = 0.02 sec
-[ Info: VUMPS 145: obj = -4.206622251723e-01 err = 1.7712997133e-01 time = 0.03 sec
-[ Info: VUMPS 146: obj = -3.152511465401e-01 err = 3.4248220688e-01 time = 0.04 sec
-[ Info: VUMPS 147: obj = -2.523905778375e-01 err = 3.7740870895e-01 time = 0.07 sec
-[ Info: VUMPS 148: obj = -4.278492688890e-02 err = 3.5715350579e-01 time = 0.02 sec
-[ Info: VUMPS 149: obj = -1.324003340205e-01 err = 4.0013050853e-01 time = 0.02 sec
-[ Info: VUMPS 150: obj = -1.068985749883e-01 err = 4.0722208139e-01 time = 0.02 sec
-[ Info: VUMPS 151: obj = -2.083862397081e-01 err = 3.6731256501e-01 time = 0.02 sec
-[ Info: VUMPS 152: obj = -1.440078983653e-01 err = 3.7426441705e-01 time = 0.06 sec
-[ Info: VUMPS 153: obj = -2.520133076021e-01 err = 3.7507045206e-01 time = 0.02 sec
-[ Info: VUMPS 154: obj = -1.034831359402e-01 err = 3.3452358556e-01 time = 0.02 sec
-[ Info: VUMPS 155: obj = -6.618164126260e-02 err = 3.5135937268e-01 time = 0.03 sec
-[ Info: VUMPS 156: obj = -8.742999239375e-02 err = 3.8534304068e-01 time = 0.02 sec
-[ Info: VUMPS 157: obj = -1.259714263689e-01 err = 3.9638882368e-01 time = 0.07 sec
-[ Info: VUMPS 158: obj = -9.181298201303e-02 err = 3.6918229891e-01 time = 0.02 sec
-[ Info: VUMPS 159: obj = -3.681891440506e-01 err = 2.7888162226e-01 time = 0.02 sec
-[ Info: VUMPS 160: obj = -3.357053644322e-01 err = 3.2800086170e-01 time = 0.03 sec
-[ Info: VUMPS 161: obj = -3.215096951929e-03 err = 3.6664749423e-01 time = 0.07 sec
-[ Info: VUMPS 162: obj = +2.646903240101e-03 err = 3.8676335475e-01 time = 0.02 sec
-[ Info: VUMPS 163: obj = +7.709386278480e-02 err = 3.5056948614e-01 time = 0.01 sec
-[ Info: VUMPS 164: obj = -1.446953476068e-01 err = 3.4180912676e-01 time = 0.02 sec
-[ Info: VUMPS 165: obj = -3.581100223225e-01 err = 3.1025051513e-01 time = 0.02 sec
-[ Info: VUMPS 166: obj = -2.406814929481e-01 err = 3.8072841015e-01 time = 0.07 sec
-[ Info: VUMPS 167: obj = -3.531848910098e-01 err = 3.1053005603e-01 time = 0.02 sec
-[ Info: VUMPS 168: obj = -3.297669874782e-01 err = 3.2648804299e-01 time = 0.03 sec
-[ Info: VUMPS 169: obj = -1.800883981792e-01 err = 3.7239998435e-01 time = 0.03 sec
-[ Info: VUMPS 170: obj = -4.056383454871e-02 err = 3.8301203586e-01 time = 0.03 sec
-[ Info: VUMPS 171: obj = -4.448126315315e-02 err = 3.9634205142e-01 time = 0.07 sec
-[ Info: VUMPS 172: obj = -3.109186702795e-01 err = 3.4936775901e-01 time = 0.02 sec
-[ Info: VUMPS 173: obj = -1.687932549901e-01 err = 3.9713516798e-01 time = 0.02 sec
-[ Info: VUMPS 174: obj = -1.703811557590e-01 err = 3.7182890670e-01 time = 0.02 sec
-[ Info: VUMPS 175: obj = -1.360221590893e-01 err = 3.7750383659e-01 time = 0.02 sec
-[ Info: VUMPS 176: obj = -1.746982448560e-01 err = 3.8728329904e-01 time = 0.07 sec
-[ Info: VUMPS 177: obj = -9.378022642433e-02 err = 3.9508912967e-01 time = 0.02 sec
-[ Info: VUMPS 178: obj = -1.283340267182e-01 err = 3.8629991015e-01 time = 0.02 sec
-[ Info: VUMPS 179: obj = -8.976213980219e-02 err = 4.0495071985e-01 time = 0.02 sec
-[ Info: VUMPS 180: obj = -2.669351107647e-01 err = 3.3526515364e-01 time = 0.07 sec
-[ Info: VUMPS 181: obj = -3.341709529585e-02 err = 3.8011147844e-01 time = 0.02 sec
-[ Info: VUMPS 182: obj = -3.109125460864e-01 err = 3.4567156611e-01 time = 0.03 sec
-[ Info: VUMPS 183: obj = -2.183171915980e-01 err = 3.7747243244e-01 time = 0.02 sec
-[ Info: VUMPS 184: obj = -2.372842132147e-01 err = 3.7374609557e-01 time = 0.03 sec
-[ Info: VUMPS 185: obj = -2.543753578738e-01 err = 3.5816792692e-01 time = 0.07 sec
-[ Info: VUMPS 186: obj = -2.469096804733e-01 err = 3.7131984627e-01 time = 0.03 sec
-[ Info: VUMPS 187: obj = -1.830902139527e-01 err = 3.9043241343e-01 time = 0.02 sec
-[ Info: VUMPS 188: obj = -1.574766929467e-01 err = 3.7319600051e-01 time = 0.03 sec
-[ Info: VUMPS 189: obj = -6.991335970984e-02 err = 3.9222107611e-01 time = 0.07 sec
-[ Info: VUMPS 190: obj = -1.792846142089e-01 err = 3.6356547438e-01 time = 0.02 sec
-[ Info: VUMPS 191: obj = -1.225042448135e-01 err = 3.7360289247e-01 time = 0.02 sec
-[ Info: VUMPS 192: obj = -2.129551247072e-01 err = 3.8299381445e-01 time = 0.02 sec
-[ Info: VUMPS 193: obj = -8.283800637018e-02 err = 3.8500403241e-01 time = 0.02 sec
-[ Info: VUMPS 194: obj = -2.897858859525e-01 err = 3.5007431093e-01 time = 0.06 sec
-[ Info: VUMPS 195: obj = -5.369820439974e-02 err = 4.1837283102e-01 time = 0.02 sec
-[ Info: VUMPS 196: obj = -2.800880298694e-01 err = 3.7189978262e-01 time = 0.03 sec
-[ Info: VUMPS 197: obj = -1.669323399699e-01 err = 3.9481332800e-01 time = 0.03 sec
-[ Info: VUMPS 198: obj = -6.212304176188e-02 err = 4.2526435790e-01 time = 0.03 sec
-[ Info: VUMPS 199: obj = -4.204616169557e-02 err = 3.7541303170e-01 time = 0.07 sec
-┌ Warning: VUMPS cancel 200: obj = -1.383333715979e-01 err = 3.7699700566e-01 time = 14.12 sec
-└ @ MPSKit /home/ldevos/LocalProjects/MPSKit.jl/src/algorithms/groundstate/vumps.jl:76
-
-````
-
-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, delta = find_groundstate(state, H, GradientGrassmann(; maxiter = 20));
-````
-
-````
-[ Info: CG: initializing with f = 2.499992657736e-01, ‖∇f‖ = 1.6729e-03
-┌ Warning: CG: not converged to requested tol after 20 iterations and time 8.29 s: f = -4.426111048892e-01, ‖∇f‖ = 5.5480e-03
-└ @ OptimKit ~/.julia/packages/OptimKit/OEwMx/src/cg.jl:174
-
-````
-
-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)
-````
-
-
-
-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, the Hamiltonian can be constructed directly on a two-site unit cell by making use of MPSKitModels.jl's `@mpoham`.
-
-````julia
-# H2 = repeat(H, 2); -- copies the one-site version
-H2 = heisenberg_XXX(ComplexF64, Trivial, InfiniteChain(2); spin = 1 // 2)
-groundstate, envs, delta = find_groundstate(
- state, H2, VUMPS(; maxiter = 100, tol = 1.0e-12)
-);
-````
-
-````
-[ Info: VUMPS init: obj = +4.987085387825e-01 err = 7.4815e-02
-[ Info: VUMPS 1: obj = -4.070483296483e-02 err = 3.8421207912e-01 time = 0.42 sec
-[ Info: VUMPS 2: obj = -8.588956105873e-01 err = 1.4311389089e-01 time = 0.04 sec
-[ Info: VUMPS 3: obj = -8.846913958875e-01 err = 1.6288696714e-02 time = 0.03 sec
-[ Info: VUMPS 4: obj = -8.858742904256e-01 err = 6.3271162473e-03 time = 0.03 sec
-[ Info: VUMPS 5: obj = -8.860994898111e-01 err = 4.7506122055e-03 time = 0.07 sec
-[ Info: VUMPS 6: obj = -8.861800643367e-01 err = 2.9027017342e-03 time = 0.04 sec
-[ Info: VUMPS 7: obj = -8.862112267309e-01 err = 2.2025348252e-03 time = 0.05 sec
-[ Info: VUMPS 8: obj = -8.862256494201e-01 err = 1.5437281695e-03 time = 0.08 sec
-[ Info: VUMPS 9: obj = -8.862322270149e-01 err = 1.2285599184e-03 time = 0.05 sec
-[ Info: VUMPS 10: obj = -8.862354505034e-01 err = 9.5416020728e-04 time = 0.09 sec
-[ Info: VUMPS 11: obj = -8.862369964136e-01 err = 7.5140552439e-04 time = 0.06 sec
-[ Info: VUMPS 12: obj = -8.862377649153e-01 err = 6.2947047843e-04 time = 0.06 sec
-[ Info: VUMPS 13: obj = -8.862381482190e-01 err = 5.3567155770e-04 time = 0.08 sec
-[ Info: VUMPS 14: obj = -8.862383501305e-01 err = 4.9726432030e-04 time = 0.05 sec
-[ Info: VUMPS 15: obj = -8.862384635741e-01 err = 4.7373205998e-04 time = 0.04 sec
-[ Info: VUMPS 16: obj = -8.862385365416e-01 err = 4.7834381662e-04 time = 0.08 sec
-[ Info: VUMPS 17: obj = -8.862385914623e-01 err = 4.9010937476e-04 time = 0.06 sec
-[ Info: VUMPS 18: obj = -8.862386397747e-01 err = 5.1562261789e-04 time = 0.09 sec
-[ Info: VUMPS 19: obj = -8.862386887080e-01 err = 5.4370412689e-04 time = 0.07 sec
-[ Info: VUMPS 20: obj = -8.862387403517e-01 err = 5.8200261169e-04 time = 0.06 sec
-[ Info: VUMPS 21: obj = -8.862388009252e-01 err = 6.1775277631e-04 time = 0.11 sec
-[ Info: VUMPS 22: obj = -8.862388688746e-01 err = 6.6580030857e-04 time = 0.06 sec
-[ Info: VUMPS 23: obj = -8.862389551333e-01 err = 7.0184437921e-04 time = 0.06 sec
-[ Info: VUMPS 24: obj = -8.862390538865e-01 err = 7.5533855251e-04 time = 0.09 sec
-[ Info: VUMPS 25: obj = -8.862391870753e-01 err = 7.7812725098e-04 time = 0.08 sec
-[ Info: VUMPS 26: obj = -8.862393378986e-01 err = 8.2535209132e-04 time = 0.10 sec
-[ Info: VUMPS 27: obj = -8.862395466874e-01 err = 8.0853992627e-04 time = 0.05 sec
-[ Info: VUMPS 28: obj = -8.862397673707e-01 err = 8.2479835919e-04 time = 0.06 sec
-[ Info: VUMPS 29: obj = -8.862400528237e-01 err = 7.3793510189e-04 time = 0.07 sec
-[ Info: VUMPS 30: obj = -8.862403146804e-01 err = 6.9781267895e-04 time = 0.05 sec
-[ Info: VUMPS 31: obj = -8.862405935152e-01 err = 5.6033545341e-04 time = 0.06 sec
-[ Info: VUMPS 32: obj = -8.862408144890e-01 err = 4.7977846041e-04 time = 0.09 sec
-[ Info: VUMPS 33: obj = -8.862410054255e-01 err = 3.7069947399e-04 time = 0.06 sec
-[ Info: VUMPS 34: obj = -8.862411519178e-01 err = 3.0069863398e-04 time = 0.08 sec
-[ Info: VUMPS 35: obj = -8.862412707892e-01 err = 2.4560744504e-04 time = 0.06 sec
-[ Info: VUMPS 36: obj = -8.862413665434e-01 err = 2.0272830003e-04 time = 0.06 sec
-[ Info: VUMPS 37: obj = -8.862414453558e-01 err = 1.7669114346e-04 time = 0.09 sec
-[ Info: VUMPS 38: obj = -8.862415101392e-01 err = 1.4966363352e-04 time = 0.06 sec
-[ Info: VUMPS 39: obj = -8.862415632632e-01 err = 1.3429888391e-04 time = 0.09 sec
-[ Info: VUMPS 40: obj = -8.862416064083e-01 err = 1.1453939487e-04 time = 0.06 sec
-[ Info: VUMPS 41: obj = -8.862416411106e-01 err = 1.0327940645e-04 time = 0.09 sec
-[ Info: VUMPS 42: obj = -8.862416687158e-01 err = 8.8267214320e-05 time = 0.06 sec
-[ Info: VUMPS 43: obj = -8.862416904652e-01 err = 7.8860063320e-05 time = 0.06 sec
-[ Info: VUMPS 44: obj = -8.862417074438e-01 err = 6.7541649023e-05 time = 0.08 sec
-[ Info: VUMPS 45: obj = -8.862417205990e-01 err = 5.9561454565e-05 time = 0.04 sec
-[ Info: VUMPS 46: obj = -8.862417307234e-01 err = 5.0969707803e-05 time = 0.08 sec
-[ Info: VUMPS 47: obj = -8.862417384749e-01 err = 4.4565392480e-05 time = 0.05 sec
-[ Info: VUMPS 48: obj = -8.862417443833e-01 err = 3.8093480996e-05 time = 0.06 sec
-[ Info: VUMPS 49: obj = -8.862417488723e-01 err = 3.3125644419e-05 time = 0.07 sec
-[ Info: VUMPS 50: obj = -8.862417522738e-01 err = 2.8298971982e-05 time = 0.06 sec
-[ Info: VUMPS 51: obj = -8.862417548472e-01 err = 2.4528466837e-05 time = 0.10 sec
-[ Info: VUMPS 52: obj = -8.862417567915e-01 err = 2.0959047274e-05 time = 0.04 sec
-[ Info: VUMPS 53: obj = -8.862417582599e-01 err = 1.8137306177e-05 time = 0.03 sec
-[ Info: VUMPS 54: obj = -8.862417593686e-01 err = 1.5513404897e-05 time = 0.06 sec
-[ Info: VUMPS 55: obj = -8.862417602060e-01 err = 1.3420069417e-05 time = 0.05 sec
-[ Info: VUMPS 56: obj = -8.862417608389e-01 err = 1.1498110058e-05 time = 0.08 sec
-[ Info: VUMPS 57: obj = -8.862417613176e-01 err = 9.9527379200e-06 time = 0.05 sec
-[ Info: VUMPS 58: obj = -8.862417616801e-01 err = 8.5468069101e-06 time = 0.09 sec
-[ Info: VUMPS 59: obj = -8.862417619550e-01 err = 7.4079343001e-06 time = 0.04 sec
-[ Info: VUMPS 60: obj = -8.862417621636e-01 err = 6.3789249797e-06 time = 0.05 sec
-[ Info: VUMPS 61: obj = -8.862417623223e-01 err = 5.5392243963e-06 time = 0.08 sec
-[ Info: VUMPS 62: obj = -8.862417624431e-01 err = 4.7843861025e-06 time = 0.05 sec
-[ Info: VUMPS 63: obj = -8.862417625353e-01 err = 4.1638877981e-06 time = 0.09 sec
-[ Info: VUMPS 64: obj = -8.862417626056e-01 err = 3.6081493661e-06 time = 0.06 sec
-[ Info: VUMPS 65: obj = -8.862417626595e-01 err = 3.1478809644e-06 time = 0.06 sec
-[ Info: VUMPS 66: obj = -8.862417627008e-01 err = 2.7367561575e-06 time = 0.09 sec
-[ Info: VUMPS 67: obj = -8.862417627324e-01 err = 2.3936907479e-06 time = 0.06 sec
-[ Info: VUMPS 68: obj = -8.862417627568e-01 err = 2.0878342869e-06 time = 0.09 sec
-[ Info: VUMPS 69: obj = -8.862417627755e-01 err = 1.8307028023e-06 time = 0.07 sec
-[ Info: VUMPS 70: obj = -8.862417627899e-01 err = 1.6017548673e-06 time = 0.04 sec
-[ Info: VUMPS 71: obj = -8.862417628011e-01 err = 1.4078813339e-06 time = 0.08 sec
-[ Info: VUMPS 72: obj = -8.862417628097e-01 err = 1.2353984722e-06 time = 0.05 sec
-[ Info: VUMPS 73: obj = -8.862417628163e-01 err = 1.0883299189e-06 time = 0.09 sec
-[ Info: VUMPS 74: obj = -8.862417628215e-01 err = 9.5754695615e-07 time = 0.06 sec
-[ Info: VUMPS 75: obj = -8.862417628254e-01 err = 8.4531618087e-07 time = 0.06 sec
-[ Info: VUMPS 76: obj = -8.862417628285e-01 err = 7.4553090808e-07 time = 0.08 sec
-[ Info: VUMPS 77: obj = -8.862417628309e-01 err = 6.5939726315e-07 time = 0.05 sec
-[ Info: VUMPS 78: obj = -8.862417628328e-01 err = 5.8281301292e-07 time = 0.06 sec
-[ Info: VUMPS 79: obj = -8.862417628343e-01 err = 5.1635751307e-07 time = 0.05 sec
-[ Info: VUMPS 80: obj = -8.862417628354e-01 err = 4.5725918727e-07 time = 0.05 sec
-[ Info: VUMPS 81: obj = -8.862417628363e-01 err = 4.0573779114e-07 time = 0.10 sec
-[ Info: VUMPS 82: obj = -8.862417628370e-01 err = 3.5990703959e-07 time = 0.05 sec
-[ Info: VUMPS 83: obj = -8.862417628375e-01 err = 3.1978939846e-07 time = 0.09 sec
-[ Info: VUMPS 84: obj = -8.862417628379e-01 err = 2.8408974871e-07 time = 0.06 sec
-[ Info: VUMPS 85: obj = -8.862417628382e-01 err = 2.5273008172e-07 time = 0.06 sec
-[ Info: VUMPS 86: obj = -8.862417628385e-01 err = 2.2481213666e-07 time = 0.08 sec
-[ Info: VUMPS 87: obj = -8.862417628387e-01 err = 2.0021375141e-07 time = 0.06 sec
-[ Info: VUMPS 88: obj = -8.862417628389e-01 err = 1.7830492076e-07 time = 0.08 sec
-[ Info: VUMPS 89: obj = -8.862417628390e-01 err = 1.5895093159e-07 time = 0.06 sec
-[ Info: VUMPS 90: obj = -8.862417628391e-01 err = 1.4170464909e-07 time = 0.05 sec
-[ Info: VUMPS 91: obj = -8.862417628391e-01 err = 1.2643555469e-07 time = 0.08 sec
-[ Info: VUMPS 92: obj = -8.862417628392e-01 err = 1.1282240133e-07 time = 0.06 sec
-[ Info: VUMPS 93: obj = -8.862417628393e-01 err = 1.0074688630e-07 time = 0.08 sec
-[ Info: VUMPS 94: obj = -8.862417628393e-01 err = 8.9975370500e-08 time = 0.04 sec
-[ Info: VUMPS 95: obj = -8.862417628393e-01 err = 8.0404798877e-08 time = 0.06 sec
-[ Info: VUMPS 96: obj = -8.862417628393e-01 err = 7.1863230951e-08 time = 0.08 sec
-[ Info: VUMPS 97: obj = -8.862417628394e-01 err = 6.4263171589e-08 time = 0.05 sec
-[ Info: VUMPS 98: obj = -8.862417628394e-01 err = 5.7476665744e-08 time = 0.07 sec
-[ Info: VUMPS 99: obj = -8.862417628394e-01 err = 5.1430713814e-08 time = 0.04 sec
-┌ Warning: VUMPS cancel 100: obj = -8.862417628394e-01 err = 4.6029104045e-08 time = 6.81 sec
-└ @ MPSKit /home/ldevos/LocalProjects/MPSKit.jl/src/algorithms/groundstate/vumps.jl:76
-
-````
-
-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, delta = find_groundstate(
- state, H2, IDMRG2(; trunc = truncrank(50), maxiter = 20, tol = 1.0e-12)
-);
-entanglementplot(groundstate)
-````
-
-
-
-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, InfiniteChain(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 /home/ldevos/LocalProjects/MPSKit.jl/src/states/infinitemps.jl:160
-
-````
-
-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, delta = find_groundstate(state, H2, VUMPS(; maxiter = 400, tol = 1.0e-12));
-````
-
-````
-52
-70
-[ Info: VUMPS init: obj = +8.454690130663e-02 err = 3.6812e-01
-[ Info: VUMPS 1: obj = -8.807747096663e-01 err = 7.4524923622e-02 time = 3.77 sec
-[ Info: VUMPS 2: obj = -8.858788324414e-01 err = 6.9171953600e-03 time = 0.05 sec
-[ Info: VUMPS 3: obj = -8.861621536444e-01 err = 2.6767683452e-03 time = 0.04 sec
-[ Info: VUMPS 4: obj = -8.862392626495e-01 err = 1.6032192901e-03 time = 0.04 sec
-[ Info: VUMPS 5: obj = -8.862672547653e-01 err = 9.5323528320e-04 time = 0.05 sec
-[ Info: VUMPS 6: obj = -8.862784830480e-01 err = 7.0061763044e-04 time = 0.05 sec
-[ Info: VUMPS 7: obj = -8.862834114803e-01 err = 5.7030493713e-04 time = 0.06 sec
-[ Info: VUMPS 8: obj = -8.862857129161e-01 err = 4.5154675114e-04 time = 0.15 sec
-[ Info: VUMPS 9: obj = -8.862868209497e-01 err = 3.5140725914e-04 time = 0.05 sec
-[ Info: VUMPS 10: obj = -8.862873648329e-01 err = 2.6806862728e-04 time = 0.13 sec
-[ Info: VUMPS 11: obj = -8.862876338388e-01 err = 2.0070574932e-04 time = 0.11 sec
-[ Info: VUMPS 12: obj = -8.862877672196e-01 err = 1.4816530119e-04 time = 0.07 sec
-[ Info: VUMPS 13: obj = -8.862878333408e-01 err = 1.0821757653e-04 time = 0.15 sec
-[ Info: VUMPS 14: obj = -8.862878660733e-01 err = 7.8417469213e-05 time = 0.06 sec
-[ Info: VUMPS 15: obj = -8.862878822601e-01 err = 5.6494124158e-05 time = 0.07 sec
-[ Info: VUMPS 16: obj = -8.862878902612e-01 err = 4.0537829957e-05 time = 0.06 sec
-[ Info: VUMPS 17: obj = -8.862878942156e-01 err = 2.9004225089e-05 time = 0.07 sec
-[ Info: VUMPS 18: obj = -8.862878961706e-01 err = 2.0708366147e-05 time = 0.07 sec
-[ Info: VUMPS 19: obj = -8.862878971378e-01 err = 1.4762413368e-05 time = 0.13 sec
-[ Info: VUMPS 20: obj = -8.862878976166e-01 err = 1.0511055800e-05 time = 0.03 sec
-[ Info: VUMPS 21: obj = -8.862878978539e-01 err = 7.4778223881e-06 time = 0.03 sec
-[ Info: VUMPS 22: obj = -8.862878979715e-01 err = 5.3158051331e-06 time = 0.07 sec
-[ Info: VUMPS 23: obj = -8.862878980299e-01 err = 3.7764425487e-06 time = 0.07 sec
-[ Info: VUMPS 24: obj = -8.862878980589e-01 err = 2.6814072095e-06 time = 0.14 sec
-[ Info: VUMPS 25: obj = -8.862878980733e-01 err = 1.9030014616e-06 time = 0.06 sec
-[ Info: VUMPS 26: obj = -8.862878980805e-01 err = 1.3500577199e-06 time = 0.06 sec
-[ Info: VUMPS 27: obj = -8.862878980841e-01 err = 9.5735794398e-07 time = 0.06 sec
-[ Info: VUMPS 28: obj = -8.862878980859e-01 err = 6.7863772480e-07 time = 0.06 sec
-[ Info: VUMPS 29: obj = -8.862878980867e-01 err = 4.8090393886e-07 time = 0.14 sec
-[ Info: VUMPS 30: obj = -8.862878980872e-01 err = 3.4067956729e-07 time = 0.06 sec
-[ Info: VUMPS 31: obj = -8.862878980874e-01 err = 2.4127441738e-07 time = 0.05 sec
-[ Info: VUMPS 32: obj = -8.862878980875e-01 err = 1.7082422697e-07 time = 0.05 sec
-[ Info: VUMPS 33: obj = -8.862878980876e-01 err = 1.2091935762e-07 time = 0.06 sec
-[ Info: VUMPS 34: obj = -8.862878980876e-01 err = 8.5574898934e-08 time = 0.05 sec
-[ Info: VUMPS 35: obj = -8.862878980876e-01 err = 6.0549094354e-08 time = 0.13 sec
-[ Info: VUMPS 36: obj = -8.862878980877e-01 err = 4.2833729305e-08 time = 0.05 sec
-[ Info: VUMPS 37: obj = -8.862878980877e-01 err = 3.0296142094e-08 time = 0.06 sec
-[ Info: VUMPS 38: obj = -8.862878980877e-01 err = 2.1424846144e-08 time = 0.06 sec
-[ Info: VUMPS 39: obj = -8.862878980877e-01 err = 1.5148957750e-08 time = 0.06 sec
-[ Info: VUMPS 40: obj = -8.862878980877e-01 err = 1.0709953737e-08 time = 0.12 sec
-[ Info: VUMPS 41: obj = -8.862878980877e-01 err = 7.5707891268e-09 time = 0.03 sec
-[ Info: VUMPS 42: obj = -8.862878980877e-01 err = 5.3510570418e-09 time = 0.04 sec
-[ Info: VUMPS 43: obj = -8.862878980877e-01 err = 3.7817943502e-09 time = 0.07 sec
-[ Info: VUMPS 44: obj = -8.862878980877e-01 err = 2.6724193960e-09 time = 0.06 sec
-[ Info: VUMPS 45: obj = -8.862878980877e-01 err = 1.8882939261e-09 time = 0.07 sec
-[ Info: VUMPS 46: obj = -8.862878980877e-01 err = 1.3341314150e-09 time = 0.11 sec
-[ Info: VUMPS 47: obj = -8.862878980877e-01 err = 9.4252990942e-10 time = 0.06 sec
-[ Info: VUMPS 48: obj = -8.862878980877e-01 err = 6.6582278922e-10 time = 0.06 sec
-[ Info: VUMPS 49: obj = -8.862878980877e-01 err = 4.7032251942e-10 time = 0.06 sec
-[ Info: VUMPS 50: obj = -8.862878980877e-01 err = 3.3220639408e-10 time = 0.06 sec
-[ Info: VUMPS 51: obj = -8.862878980878e-01 err = 2.3463502230e-10 time = 0.12 sec
-[ Info: VUMPS 52: obj = -8.862878980878e-01 err = 1.6571486183e-10 time = 0.05 sec
-[ Info: VUMPS 53: obj = -8.862878980878e-01 err = 1.1703442495e-10 time = 0.05 sec
-[ Info: VUMPS 54: obj = -8.862878980878e-01 err = 8.2650109655e-11 time = 0.06 sec
-[ Info: VUMPS 55: obj = -8.862878980878e-01 err = 5.8367474734e-11 time = 0.05 sec
-[ Info: VUMPS 56: obj = -8.862878980878e-01 err = 4.1213417017e-11 time = 0.12 sec
-[ Info: VUMPS 57: obj = -8.862878980878e-01 err = 2.9101697547e-11 time = 0.04 sec
-[ Info: VUMPS 58: obj = -8.862878980878e-01 err = 2.0551179926e-11 time = 0.06 sec
-[ Info: VUMPS 59: obj = -8.862878980878e-01 err = 1.4510549999e-11 time = 0.06 sec
-[ Info: VUMPS 60: obj = -8.862878980878e-01 err = 1.0245548104e-11 time = 0.05 sec
-[ Info: VUMPS 61: obj = -8.862878980878e-01 err = 7.2325909689e-12 time = 0.06 sec
-[ Info: VUMPS 62: obj = -8.862878980878e-01 err = 5.1092588216e-12 time = 0.12 sec
-[ Info: VUMPS 63: obj = -8.862878980878e-01 err = 3.6043616497e-12 time = 0.02 sec
-[ Info: VUMPS 64: obj = -8.862878980878e-01 err = 2.5462748087e-12 time = 0.04 sec
-[ Info: VUMPS 65: obj = -8.862878980878e-01 err = 1.7984804673e-12 time = 0.03 sec
-[ Info: VUMPS 66: obj = -8.862878980878e-01 err = 1.2696913652e-12 time = 0.02 sec
-[ Info: VUMPS conv 67: obj = -8.862878980879e-01 err = 8.9456922075e-13 time = 8.25 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 5d8edbbce..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 1df134303..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/index.md b/docs/src/examples/quantum1d/5.haldane-spt/index.md
deleted file mode 100644
index 8fb8dee4d..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/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/quantum1d/5.haldane-spt/main.ipynb)
-[](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/quantum1d/5.haldane-spt/main.ipynb)
-[](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, δ = find_groundstate(ψ, H, VUMPS(; maxiter = 10))
-sectors = SU2Irrep[0, 1 // 2, 1, 3 // 2]
-transferplot(ψ; sectors, title = "Transfer matrix spectrum", legend = :outertop)
-````
-
-
-
-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.4014193313393009 - 3.851708855717825e-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.4014839739630844 - 5.800167584873572e-17im
-````
-
-````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))
-````
-
-
-
-````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))
-````
-
-
-
-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
-
-```
-
-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.548622723541372
-S_plus = 1.5450323530299226
-
-````
-
----
-
-*This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*
-
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 60a81e622..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/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/quantum1d/6.hubbard/main.ipynb)
-[](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/quantum1d/6.hubbard/main.ipynb)
-[](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 MPSKitModels
-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(InfiniteChain(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.450454615857e+00 err = 5.5193e-01
-[ Info: VUMPS conv 7: obj = -4.377048688339e+00 err = 8.8092806195e-03 time = 4.49 sec
-[ Info: VUMPS init: obj = -4.377048688339e+00 err = 1.6440e-02
-[ Info: VUMPS conv 6: obj = -4.378747269347e+00 err = 1.3129004135e-04 time = 0.25 sec
-[ Info: VUMPS init: obj = -4.378747269347e+00 err = 7.9951e-03
-[ Info: VUMPS conv 6: obj = -4.379161081627e+00 err = 1.5539751336e-04 time = 0.37 sec
-[ Info: VUMPS init: obj = -4.379161081627e+00 err = 6.1111e-03
-[ Info: VUMPS conv 5: obj = -4.379452169384e+00 err = 1.6927654674e-04 time = 0.27 sec
-[ Info: VUMPS init: obj = -4.379452169384e+00 err = 5.6959e-03
-[ Info: VUMPS conv 4: obj = -4.379651733231e+00 err = 1.8162083040e-04 time = 0.26 sec
-[ Info: VUMPS init: obj = -4.379651733231e+00 err = 4.1039e-03
-[ Info: VUMPS conv 4: obj = -4.379735601762e+00 err = 1.3801495045e-04 time = 0.42 sec
-[ Info: VUMPS init: obj = -4.379735601762e+00 err = 3.5769e-03
-[ Info: VUMPS conv 3: obj = -4.379797886653e+00 err = 1.3472741143e-04 time = 0.39 sec
-[ Info: VUMPS init: obj = -4.379797886653e+00 err = 2.7707e-03
-[ Info: VUMPS conv 2: obj = -4.379838526805e+00 err = 1.7752552389e-04 time = 0.33 sec
-[ Info: VUMPS init: obj = -4.379838526805e+00 err = 2.7291e-03
-[ Info: VUMPS conv 3: obj = -4.379878849406e+00 err = 1.9781894590e-04 time = 0.74 sec
-[ Info: VUMPS init: obj = -4.379878849406e+00 err = 2.6911e-03
-[ Info: VUMPS conv 3: obj = -4.379929229387e+00 err = 1.7761427615e-04 time = 0.82 sec
-[ Info: VUMPS init: obj = -4.379929229387e+00 err = 2.5553e-03
-[ Info: VUMPS conv 3: obj = -4.379968040382e+00 err = 1.8461546636e-04 time = 2.21 sec
-[ Info: VUMPS init: obj = -4.379968040382e+00 err = 1.7682e-03
-[ Info: VUMPS conv 2: obj = -4.379986877757e+00 err = 1.9131369028e-04 time = 0.98 sec
-[ Info: VUMPS init: obj = -4.379986877757e+00 err = 1.5838e-03
-[ Info: VUMPS conv 2: obj = -4.380001005486e+00 err = 1.9231335759e-04 time = 1.05 sec
-[ Info: VUMPS init: obj = -4.380001005486e+00 err = 1.5109e-03
-[ Info: VUMPS conv 2: obj = -4.380013169634e+00 err = 1.5225084116e-04 time = 1.32 sec
-[ Info: VUMPS init: obj = -4.380013169634e+00 err = 1.4234e-03
-[ Info: VUMPS conv 2: obj = -4.380024401012e+00 err = 1.7737882775e-04 time = 1.59 sec
-[ Info: VUMPS init: obj = -4.380024401012e+00 err = 1.3330e-03
-[ Info: VUMPS conv 2: obj = -4.380038158990e+00 err = 1.5757417636e-04 time = 2.60 sec
-[ Info: VUMPS init: obj = -4.380038158990e+00 err = 1.0032e-03
-[ Info: VUMPS conv 1: obj = -4.380043682260e+00 err = 1.6736593859e-04 time = 0.89 sec
-[ Info: VUMPS init: obj = -4.380043682260e+00 err = 9.0999e-04
-[ Info: VUMPS conv 1: obj = -4.380048641018e+00 err = 1.8573996574e-04 time = 1.19 sec
-[ Info: VUMPS init: obj = -4.380048641018e+00 err = 8.3081e-04
-[ Info: VUMPS conv 1: obj = -4.380053199895e+00 err = 1.8060836975e-04 time = 2.30 sec
-[ Info: VUMPS init: obj = -4.380053199895e+00 err = 6.8144e-04
-[ Info: VUMPS conv 1: obj = -4.380057143242e+00 err = 1.8854132138e-04 time = 1.71 sec
-[ Info: VUMPS init: obj = -4.380057143242e+00 err = 6.0293e-04
-[ Info: VUMPS conv 1: obj = -4.380060551312e+00 err = 1.8083344266e-04 time = 2.45 sec
-[ Info: VUMPS init: obj = -4.379609468445e+00 err = 4.0958e-03
-[ Info: VUMPS conv 19: obj = -4.379763157256e+00 err = 9.9415625365e-06 time = 8.41 sec
-[ Info: CG: initializing with f = -4.379763156901e+00, ‖∇f‖ = 3.1520e-05
-[ Info: CG: converged after 158 iterations and time 1.36 m: f = -4.379763361376e+00, ‖∇f‖ = 9.9957e-07
-┌ Info: Groundstate energy:
-│ * numerical: -2.1899960609769664
-└ * 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, InfiniteChain(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 = [oneunit(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.092499297284e-01 err = 8.6283e-01
-[ Info: VUMPS conv 1: obj = -4.000000000000e+00 err = 1.4030299342e-15 time = 2.40 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.09 sec
-[ Info: VUMPS init: obj = -4.289650419749e+00 err = 1.1203e-01
-[ Info: VUMPS conv 6: obj = -4.359865567620e+00 err = 1.0046942911e-04 time = 0.29 sec
-[ Info: VUMPS init: obj = -4.359865567619e+00 err = 4.3643e-02
-[ Info: VUMPS conv 6: obj = -4.372880928482e+00 err = 1.3025843115e-04 time = 2.61 sec
-[ Info: VUMPS init: obj = -4.372880928482e+00 err = 3.2693e-02
-[ Info: VUMPS conv 4: obj = -4.375236954488e+00 err = 1.1814239608e-04 time = 0.20 sec
-[ Info: VUMPS init: obj = -4.375236954488e+00 err = 2.9487e-02
-[ Info: VUMPS conv 7: obj = -4.378159084364e+00 err = 1.1896740056e-04 time = 0.60 sec
-[ Info: VUMPS init: obj = -4.378159084364e+00 err = 1.9312e-02
-[ Info: VUMPS conv 5: obj = -4.379272966040e+00 err = 1.5785413165e-04 time = 0.50 sec
-[ Info: VUMPS init: obj = -4.379272966040e+00 err = 9.9128e-03
-[ Info: VUMPS conv 4: obj = -4.379592229143e+00 err = 1.5550378745e-04 time = 0.51 sec
-[ Info: VUMPS init: obj = -4.379592229143e+00 err = 6.4841e-03
-[ Info: VUMPS conv 4: obj = -4.379819377264e+00 err = 1.7492038571e-04 time = 0.56 sec
-[ Info: VUMPS init: obj = -4.379819377264e+00 err = 3.8754e-03
-┌ Warning: VUMPS cancel 10: obj = -4.379964033305e+00 err = 2.1228930049e-04 time = 1.76 sec
-└ @ MPSKit ~/Projects/MPSKit.jl/docs/src/algorithms/groundstate/vumps.jl:83
-[ Info: VUMPS init: obj = -4.379964033305e+00 err = 2.8978e-03
-[ Info: VUMPS conv 3: obj = -4.380010384710e+00 err = 1.4775284542e-04 time = 0.88 sec
-[ Info: VUMPS init: obj = -4.380010384710e+00 err = 2.0609e-03
-[ Info: VUMPS conv 3: obj = -4.380041751503e+00 err = 1.6327798118e-04 time = 1.81 sec
-[ Info: VUMPS init: obj = -4.380041751502e+00 err = 1.2364e-03
-[ Info: VUMPS conv 2: obj = -4.380055778759e+00 err = 1.8366845284e-04 time = 0.83 sec
-[ Info: VUMPS init: obj = -4.380055778759e+00 err = 8.5857e-04
-[ Info: VUMPS conv 2: obj = -4.380064749427e+00 err = 1.3905442267e-04 time = 1.14 sec
-[ Info: VUMPS init: obj = -4.380064749427e+00 err = 5.2502e-04
-[ Info: VUMPS conv 1: obj = -4.380067974777e+00 err = 1.5646700070e-04 time = 0.79 sec
-[ Info: VUMPS init: obj = -4.380067974777e+00 err = 3.3275e-04
-[ Info: VUMPS conv 1: obj = -4.380070351418e+00 err = 1.3123916502e-04 time = 1.05 sec
-[ Info: VUMPS init: obj = -4.380070351418e+00 err = 2.0348e-04
-[ Info: VUMPS conv 1: obj = -4.380072125256e+00 err = 1.1119707628e-04 time = 2.15 sec
-[ Info: VUMPS init: obj = -4.380072125256e+00 err = 1.3635e-04
-[ Info: VUMPS conv 1: obj = -4.380073467831e+00 err = 8.5045032311e-05 time = 2.22 sec
-[ Info: VUMPS init: obj = -4.380073467830e+00 err = 9.7226e-05
-[ Info: VUMPS conv 1: obj = -4.380074455763e+00 err = 6.4430026631e-05 time = 3.60 sec
-[ Info: VUMPS init: obj = -4.380074455763e+00 err = 7.3787e-05
-[ Info: VUMPS conv 1: obj = -4.380075159887e+00 err = 6.2144398833e-05 time = 8.05 sec
-[ Info: VUMPS init: obj = -4.380075159887e+00 err = 5.9899e-05
-[ Info: VUMPS conv 1: obj = -4.380075661721e+00 err = 4.2515939994e-05 time = 12.11 sec
-[ Info: VUMPS init: obj = -4.379308795201e+00 err = 7.9930e-03
-┌ Warning: VUMPS cancel 100: obj = -4.379692711472e+00 err = 1.5979764572e-05 time = 27.91 sec
-└ @ MPSKit ~/Projects/MPSKit.jl/docs/src/algorithms/groundstate/vumps.jl:83
-[ Info: CG: initializing with f = -4.379692711472e+00, ‖∇f‖ = 5.7923e-05
-[ Info: CG: converged after 13 iterations and time 7.22 s: f = -4.379692712393e+00, ‖∇f‖ = 6.2087e-07
-┌ Info: Groundstate energy:
-│ * numerical: -2.1900153475144695
-└ * 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.7671458676442586
-[ Info: Found excitations for momentum = -1.9634954084936207
-[ Info: Found excitations for momentum = -1.5707963267948966
-[ Info: Found excitations for momentum = -1.3744467859455345
-[ Info: Found excitations for momentum = -0.9817477042468103
-[ Info: Found excitations for momentum = -1.1780972450961724
-[ Info: Found excitations for momentum = -0.7853981633974483
-[ Info: Found excitations for momentum = -0.5890486225480862
-[ Info: Found excitations for momentum = -0.19634954084936207
-[ Info: Found excitations for momentum = -0.39269908169872414
-[ 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.3744467859455345
-[ Info: Found excitations for momentum = 1.1780972450961724
-[ Info: Found excitations for momentum = 1.5707963267948966
-[ Info: Found excitations for momentum = 1.7671458676442586
-[ Info: Found excitations for momentum = 2.356194490192345
-[ Info: Found excitations for momentum = 1.9634954084936207
-[ Info: Found excitations for momentum = 2.1598449493429825
-[ 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.748893571891069
-[ Info: Found excitations for momentum = -2.552544031041707
-[ Info: Found excitations for momentum = -2.945243112740431
-[ 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 = 3.141592653589793
-[ Info: Found excitations for momentum = 2.945243112740431
-
-````
-
-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
-````
-
-
-
-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
-````
-
-
-
-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
-````
-
-
-
----
-
-*This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*
-
diff --git a/docs/src/examples/quantum1d/7.xy-finiteT/figure-1.png b/docs/src/examples/quantum1d/7.xy-finiteT/figure-1.png
deleted file mode 100644
index da451a216..000000000
Binary files a/docs/src/examples/quantum1d/7.xy-finiteT/figure-1.png and /dev/null differ
diff --git a/docs/src/examples/quantum1d/7.xy-finiteT/figure-2.png b/docs/src/examples/quantum1d/7.xy-finiteT/figure-2.png
deleted file mode 100644
index 94ec71854..000000000
Binary files a/docs/src/examples/quantum1d/7.xy-finiteT/figure-2.png and /dev/null differ
diff --git a/docs/src/examples/quantum1d/7.xy-finiteT/figure-3.png b/docs/src/examples/quantum1d/7.xy-finiteT/figure-3.png
deleted file mode 100644
index 9daa46501..000000000
Binary files a/docs/src/examples/quantum1d/7.xy-finiteT/figure-3.png and /dev/null differ
diff --git a/docs/src/examples/quantum1d/7.xy-finiteT/figure-4.png b/docs/src/examples/quantum1d/7.xy-finiteT/figure-4.png
deleted file mode 100644
index e15988a40..000000000
Binary files a/docs/src/examples/quantum1d/7.xy-finiteT/figure-4.png and /dev/null differ
diff --git a/docs/src/examples/quantum1d/7.xy-finiteT/figure-5.png b/docs/src/examples/quantum1d/7.xy-finiteT/figure-5.png
deleted file mode 100644
index 733496a0c..000000000
Binary files a/docs/src/examples/quantum1d/7.xy-finiteT/figure-5.png and /dev/null differ
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 c2680aed3..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 2ab23add4..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 523496c7a..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 addf62c5a..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 a33028f2e..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 805386dc7..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 0472fcdcb..000000000
--- a/docs/src/examples/quantum1d/8.bose-hubbard/index.md
+++ /dev/null
@@ -1,469 +0,0 @@
-```@meta
-EditURL = "../../../../../examples/quantum1d/8.bose-hubbard/main.jl"
-```
-
-[](https://mybinder.org/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/quantum1d/8.bose-hubbard/main.ipynb)
-[](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/quantum1d/8.bose-hubbard/main.ipynb)
-[](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 MPSKit, MPSKitModels, TensorKit
-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
-[`MPSKitModels.jl`](https://github.com/QuantumKitHub/MPSKitModels.jl) instead that has
-predefined operators and models for most well-known lattice models. In particular, we can
-use [`MPSKitModels.a_min`](@extref) to create the bosonic annihilation operator.
-
-````julia
-a_op = a_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. Similarly, the Bose Hubbard model is also predefined in
-[`MPSKitModels.bose_hubbard_model`](@extref) (although we will construct our own variant
-later on).
-
-````julia
-hamiltonian = bose_hubbard_model(InfiniteChain(1); cutoff = cutoff, U = 1, mu = 0.5, t = 0.2) # It is not strictly required to pass InfiniteChain() and is only included for clarity; one may instead pass FiniteChain(N) as well
-````
-
-````
-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.450895756898e-01 err = 6.6224e-01
-[ Info: VUMPS conv 72: obj = -6.756981551609e-01 err = 9.5394492271e-07 time = 2.91 sec
-Energy: -0.6756981551608794 - 1.967768893425733e-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(InfiniteChain(); 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)
-````
-
-
-
-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)
-````
-
-
-
-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 = a_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]
-)
-````
-
-
-
-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.31098779070601257
- 0.2881478589434881
- 0.2702000230423913
- 0.25712728516508654
- 0.24685385948652017
- 0.23539753899204166
- 0.22799654088348645
-````
-
-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])
-````
-
-
-
-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
-[source code](https://github.com/QuantumKitHub/MPSKitModels.jl/blob/f4c36d9660a9eab05fa253ffd5c20dc6b7df44cc/src/models/hamiltonians.jl#L379-L409)
-of `MPSKitModels.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,
- lattice::AbstractLattice = InfiniteChain(1);
- cutoff::Integer = 5, t = 1.0, U = 1.0, mu = 0.0, phi = 0
- )
-
- a_pm = a_plusmin(elt, symmetry; cutoff = cutoff)
- a_mp = a_minplus(elt, symmetry; cutoff = cutoff)
- N = a_number(elt, symmetry; cutoff = cutoff)
-
- interaction_term = N * (N - id(domain(N)))
-
- return H = @mpoham begin
- sum(nearest_neighbours(lattice)) do (i, j)
- return -t * (exp(1im * phi) * a_pm{i, j} + exp(1im * -phi) * a_mp{i, j})
- end +
- sum(vertices(lattice)) do i
- return U / 2 * interaction_term{i} - mu * N{i}
- end
- end
-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
-````
-
-
-
-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 = a_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(InfiniteChain(); 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")
-````
-
-
-
-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..57317a051
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 84%
rename from docs/src/examples/classic2d/1.hard-hexagon/index.md
rename to docs/src/examples/statmech/hard-hexagon/index.md
index 6f103a911..da3e0ab23 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/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/classic2d/1.hard-hexagon/main.ipynb)
-[](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/classic2d/1.hard-hexagon/main.ipynb)
-[](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/v2/gh/QuantumKitHub/MPSKit.jl/gh-pages?filepath=dev/examples/statmech/hard-hexagon/main.ipynb)
+[](https://nbviewer.jupyter.org/github/QuantumKitHub/MPSKit.jl/blob/gh-pages/dev/examples/statmech/hard-hexagon/main.ipynb)
+[](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
@@ -24,7 +24,7 @@ The [hard hexagon model](https://en.wikipedia.org/wiki/Hard_hexagon_model) is a
This can be encoded in a transfer matrix with a local MPO tensor using anyonic symmetries, and the resulting MPO has been implemented in MPSKitModels.
In order to use these anyonic symmetries, we need to generalise the notion of the bond dimension and define how it interacts with the symmetry.
-Thus, we implement away of converting integers to symmetric spaces of the given dimension, which provides a crude guess for how the final MPS would distribute its Schmidt spectrum.
+Thus, we implement a way of converting integers to symmetric spaces of the given dimension, which provides a crude guess for how the final MPS would distribute its Schmidt spectrum.
````julia
mpo = hard_hexagon()
@@ -59,7 +59,7 @@ println("F = $F\tS = $S\tξ = $ξ")
````
````
-F = 0.8839037051703852 S = 1.2807829621826905 ξ = 13.849682581482702
+F = 0.8839037051703854 S = 1.2807829621910287 ξ = 13.849682581985
````
@@ -69,9 +69,10 @@ The dominant eigenvector is of course only an approximation.
The finite bond dimension enforces a finite correlation length, which effectively introduces a length scale in the system.
This can be exploited to formulate a scaling hypothesis [pollmann2009](@cite), which in turn allows to extract the central charge.
-First we need to know the entropy and correlation length at a bunch of different bond dimensions.
+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(
@@ -81,7 +82,7 @@ function scaling_simulations(
entropies = similar(Ds, Float64)
correlations = similar(Ds, Float64)
alg = VUMPS(; verbosity, tol, alg_eigsolve)
- sector = unit(sectortype(mpo)) # dominant correlation functions are in the trivial sector
+ sector = unit(sectortype(mpo)) # in this example the dominant correlation functions are in the trivial sector
ψ, envs, = leading_boundary(ψ₀, mpo, alg)
entropies[1] = real(entropy(ψ)[1])
@@ -105,7 +106,7 @@ c = f.coeffs[2]
````
````
-0.802524639544328
+0.8025361167011884
````
````julia
diff --git a/docs/src/examples/classic2d/1.hard-hexagon/main.ipynb b/docs/src/examples/statmech/hard-hexagon/main.ipynb
similarity index 93%
rename from docs/src/examples/classic2d/1.hard-hexagon/main.ipynb
rename to docs/src/examples/statmech/hard-hexagon/main.ipynb
index 129e5589a..d8576e2ec 100644
--- a/docs/src/examples/classic2d/1.hard-hexagon/main.ipynb
+++ b/docs/src/examples/statmech/hard-hexagon/main.ipynb
@@ -32,7 +32,7 @@
"This can be encoded in a transfer matrix with a local MPO tensor using anyonic symmetries, and the resulting MPO has been implemented in MPSKitModels.\n",
"\n",
"In order to use these anyonic symmetries, we need to generalise the notion of the bond dimension and define how it interacts with the symmetry.\n",
- "Thus, we implement away of converting integers to symmetric spaces of the given dimension, which provides a crude guess for how the final MPS would distribute its Schmidt spectrum."
+ "Thus, we implement a way of converting integers to symmetric spaces of the given dimension, which provides a crude guess for how the final MPS would distribute its Schmidt spectrum."
]
},
{
@@ -92,9 +92,10 @@
"The finite bond dimension enforces a finite correlation length, which effectively introduces a length scale in the system.\n",
"This can be exploited to formulate a scaling hypothesis [pollmann2009](@cite), which in turn allows to extract the central charge.\n",
"\n",
- "First we need to know the entropy and correlation length at a bunch of different bond dimensions.\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"
]
},
{
@@ -110,7 +111,7 @@
" entropies = similar(Ds, Float64)\n",
" correlations = similar(Ds, Float64)\n",
" alg = VUMPS(; verbosity, tol, alg_eigsolve)\n",
- " sector = unit(sectortype(mpo)) # dominant correlation functions are in the trivial sector\n",
+ " sector = unit(sectortype(mpo)) # in this example the dominant correlation functions are in the trivial sector\n",
"\n",
" ψ, envs, = leading_boundary(ψ₀, mpo, alg)\n",
" entropies[1] = real(entropy(ψ)[1])\n",
diff --git a/docs/src/index.md b/docs/src/index.md
index 2b9265209..267699dcb 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)
@@ -229,7 +229,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 1b15ac166..16e6368d8 100644
--- a/examples/Cache.toml
+++ b/examples/Cache.toml
@@ -1,12 +1,16 @@
-[classic2d]
-"1.hard-hexagon" = "059f9a5162d75323c7104a8fb178458f965db7bda1481021ae51d4fb8ec9cde9"
+[excitations]
+"haldane" = "c09df36c3be5cd452bec564d22f6e2815474a9dab71dd5528013a3fb46b2f7c0"
-[quantum1d]
-"2.haldane" = "c5a0eb70f0930d38053535c659ab39a87121b6ebda040ceadd9deb5f30921315"
-"6.hubbard" = "cef140a6224350345735aac889ee7e33724fc0af9ce94f68be47a7e40107c09c"
-"7.xy-finiteT" = "0f330a157bea739a43a82a937791c680b2fa6e5e479171ee5ef318d1fdae7bcf"
-"8.bose-hubbard" = "cf0d9a543e784dc6053e413780d19e1b59bf956dd4598752fdf664804dd68ce6"
-"3.ising-dqpt" = "a2900eed23de7655f600943fae72d1dc35f87f33d11947f707d302ce245399fe"
-"5.haldane-spt" = "c8fd3a8d406b9ff3ea9a34b596c5910d81e4eb710b665d7fa04e30f795a46086"
-"4.xxz-heisenberg" = "9033fb104c3f658ccb1b8e335b986b7abda59e0b5359be086d3b62e1cc32ebd6"
-"1.ising-cft" = "3d34757eee7b95120b746c5e30e713203a876a87353ecd9937798f29183c9916"
+[dynamics]
+"ising-dqpt" = "dec15a8c3187b1d0f92577e48e1ac2c719711414eb3e5e57bbb878f8d2621580"
+"xy-finiteT" = "ab355f357560061f9aaba461e0aa3d7686929d7ba610ee0d91fb97f6708db55b"
+
+[statmech]
+"hard-hexagon" = "6ef3f8f094c60d0562b209aa0541f30bccf0e593a095550d539b083b416ba30c"
+
+[groundstates]
+"haldane-spt" = "f29ae05d4f2b6c60e0e9d283444acb6d22b106d0b7c92f360642cc0c1d5ba1c1"
+"hubbard" = "6cd857ca7bf28c5524254cdc49d14ba02153f22d8437872c56e96baa4ea7950f"
+"bose-hubbard" = "439cf4165079bd64b2e9420f7be8def0dac9beba72f083e04c3d48097c3c19bb"
+"xxz-heisenberg" = "c6c200059f14bd05a8c4d7e65fe670231d22715ed500f1f5baf164993c15f840"
+"ising-cft" = "387d4986e2925f1ee227f6c7fa088418e605aaab6a7d7e5f52037c739063ce06"
diff --git a/examples/README.md b/examples/README.md
index 0aea9a7d9..74daf002d 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. Examples are listed
+alphabetically in the sidebar; the reading order for newcomers lives in the summaries on the
+gallery index page (`docs/src/examples/index.md`), which is also where a new example should be
+described.
\ 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 73%
rename from examples/quantum1d/3.ising-dqpt/main.jl
rename to examples/dynamics/ising-dqpt/main.jl
index 50f7a3a23..dc1081ada 100644
--- a/examples/quantum1d/3.ising-dqpt/main.jl
+++ b/examples/dynamics/ising-dqpt/main.jl
@@ -1,25 +1,26 @@
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, MPSKitModels, TensorKit
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:
@@ -28,19 +29,20 @@ First we construct the Hamiltonian in MPO form, and obtain the pre-quenched grou
L = 20
H₀ = transverse_field_ising(FiniteChain(L); g = -0.5)
ψ₀ = FiniteMPS(L, ℂ^2, ℂ^10)
-ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG());
+ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG(; verbosity = 0));
md"""
## Finite MPS quenching
-We can define a helper function that measures the loschmith echo
+We can define a helper function that measures the Loschmidt echo
"""
echo(ψ₀::FiniteMPS, ψₜ::FiniteMPS) = -2 * log(abs(dot(ψ₀, ψₜ))) / length(ψ₀)
@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(FiniteChain(L); g = -2.0)
@@ -49,7 +51,8 @@ dt = 0.01
ψₜ, envs = 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
"""
@@ -57,7 +60,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(FiniteChain(L); g = -0.5)
- ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG())
+ ψ₀, _ = find_groundstate(ψ₀, H₀, DMRG(; verbosity = 0))
H₁ = transverse_field_ising(FiniteChain(L); g = -2.0)
ψₜ = deepcopy(ψ₀)
@@ -85,10 +88,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
"""
@@ -102,7 +105,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:
"""
@@ -123,7 +127,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 473006179..f3ed23a75 100644
--- a/examples/quantum1d/7.xy-finiteT/main.jl
+++ b/examples/dynamics/xy-finiteT/main.jl
@@ -22,7 +22,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$.
@@ -49,15 +49,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)
@@ -95,7 +93,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))
@@ -109,40 +107,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)
@@ -165,10 +162,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.
"""
@@ -208,13 +205,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
@@ -225,9 +222,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}
```
@@ -259,23 +256,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(ρ₁)
@@ -313,15 +308,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}
```
@@ -386,10 +381,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.
@@ -397,25 +392,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)
@@ -466,7 +461,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
@@ -480,14 +475,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))
@@ -537,6 +532,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 98ac72a0e..8ce58e3cf 100644
--- a/examples/quantum1d/2.haldane/main.jl
+++ b/examples/excitations/haldane/main.jl
@@ -106,7 +106,7 @@ virtual_space_inf = Rep[SU₂](1 // 2 => 16, 3 // 2 => 16, 5 // 2 => 8, 7 // 2 =
ψ_inf, envs_inf, delta_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..0cfa88eff
--- /dev/null
+++ b/examples/groundstates/bose-hubbard/main.jl
@@ -0,0 +1,338 @@
+using Markdown
+using MPSKit, MPSKitModels, TensorKit
+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 [`MPSKitModels.jl`](https://github.com/QuantumKitHub/MPSKitModels.jl) instead that has predefined operators and models for most well-known lattice models.
+In particular, we can use [`MPSKitModels.a_min`](@extref) to create the bosonic annihilation operator.
+"""
+
+a_op = a_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.
+Similarly, the Bose Hubbard model is also predefined in [`MPSKitModels.bose_hubbard_model`](@extref) (although we will construct our own variant later on).
+
+"""
+
+hamiltonian = bose_hubbard_model(InfiniteChain(1); cutoff = cutoff, U = 1, mu = 0.5, t = 0.2) # It is not strictly required to pass InfiniteChain() and is only included for clarity; one may instead pass FiniteChain(N) as well
+
+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(InfiniteChain(); 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 = a_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 [source code](https://github.com/QuantumKitHub/MPSKitModels.jl/blob/f4c36d9660a9eab05fa253ffd5c20dc6b7df44cc/src/models/hamiltonians.jl#L379-L409) of `MPSKitModels.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,
+ lattice::AbstractLattice = InfiniteChain(1);
+ cutoff::Integer = 5, t = 1.0, U = 1.0, mu = 0.0, phi = 0
+ )
+
+ a_pm = a_plusmin(elt, symmetry; cutoff = cutoff)
+ a_mp = a_minplus(elt, symmetry; cutoff = cutoff)
+ N = a_number(elt, symmetry; cutoff = cutoff)
+
+ interaction_term = N * (N - id(domain(N)))
+
+ return H = @mpoham begin
+ sum(nearest_neighbours(lattice)) do (i, j)
+ return -t * (exp(1im * phi) * a_pm{i, j} + exp(1im * -phi) * a_mp{i, j})
+ end +
+ sum(vertices(lattice)) do i
+ return U / 2 * interaction_term{i} - mu * N{i}
+ end
+ end
+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 = a_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(InfiniteChain(); 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..e04bad1bb
--- /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, δ = 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
+
+```
+
+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 0c4057484..c138966f8 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,↓})
```
"""
@@ -36,9 +35,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
@@ -46,10 +46,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.
@@ -57,7 +57,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
@@ -67,7 +67,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))
@@ -86,7 +86,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
@@ -107,7 +107,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.
@@ -139,7 +139,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:
"""
@@ -153,13 +153,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"""
@@ -215,7 +215,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 a3f45887e..4f52e8448 100644
--- a/examples/quantum1d/1.ising-cft/main.jl
+++ b/examples/groundstates/ising-cft/main.jl
@@ -1,17 +1,15 @@
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, MPSKitModels, TensorKit, Plots, KrylovKit
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
@@ -20,11 +18,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));
@@ -42,9 +37,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
@@ -60,13 +54,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)
@@ -93,10 +85,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
@@ -115,19 +105,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, δ = find_groundstate(FiniteMPS(L_mps, ℂ^2, ℂ^D), H_mps, DMRG());
+ψ, envs, δ = 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 36ce04d77..677f00ecd 100644
--- a/examples/quantum1d/4.xxz-heisenberg/main.jl
+++ b/examples/groundstates/xxz-heisenberg/main.jl
@@ -7,9 +7,10 @@ The necessary packages to follow this tutorial are:
using MPSKit, MPSKitModels, TensorKit, Plots
-#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
@@ -22,7 +23,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)
@@ -31,7 +33,7 @@ md"""
The ground state can then be found by calling `find_groundstate`.
"""
-groundstate, cache, delta = find_groundstate(state, H, VUMPS());
+groundstate, cache, delta = find_groundstate(state, H, VUMPS(; verbosity = 1));
md"""
As you can see, VUMPS struggles to converge.
@@ -39,7 +41,7 @@ On its own, that is already quite curious.
Maybe we can do better using another algorithm, such as gradient descent.
"""
-groundstate, cache, delta = find_groundstate(state, H, GradientGrassmann(; maxiter = 20));
+groundstate, cache, delta = find_groundstate(state, H, GradientGrassmann(; maxiter = 20, verbosity = 1));
md"""
Convergence is quite slow and even fails after sufficiently many iterations.
@@ -72,7 +74,7 @@ Alternatively, the Hamiltonian can be constructed directly on a two-site unit ce
## H2 = repeat(H, 2); -- copies the one-site version
H2 = heisenberg_XXX(ComplexF64, Trivial, InfiniteChain(2); spin = 1 // 2)
groundstate, envs, delta = find_groundstate(
- state, H2, VUMPS(; maxiter = 100, tol = 1.0e-12)
+ state, H2, VUMPS(; maxiter = 100, tol = 1.0e-12, verbosity = 1)
);
md"""
@@ -81,7 +83,7 @@ The reason behind this becomes more obvious at higher bond dimensions:
"""
groundstate, envs, delta = 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)
@@ -125,4 +127,4 @@ Even though the bond dimension is higher than in the example without symmetry, c
println(dim(V1))
println(dim(V2))
-groundstate, cache, delta = find_groundstate(state, H2, VUMPS(; maxiter = 400, tol = 1.0e-12));
+groundstate, cache, delta = find_groundstate(state, H2, VUMPS(; maxiter = 400, tol = 1.0e-12, verbosity = 1));
diff --git a/examples/make.jl b/examples/make.jl
index c6c9cfd72..a2e7b0e29 100644
--- a/examples/make.jl
+++ b/examples/make.jl
@@ -65,6 +65,70 @@ 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
+
function build_example(root, name)
source_dir = joinpath(@__DIR__, "..", "examples", root, name)
source_file = joinpath(source_dir, "main.jl")
@@ -74,7 +138,9 @@ function build_example(root, name)
Literate.markdown(
source_file, target_dir; execute = true, name = "index",
preprocess = attach_notebook_badge(root, name),
- 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",
@@ -103,5 +169,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 937228e6d..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, δ = 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
-
-```
-
-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 86edb774d..000000000
--- a/examples/quantum1d/8.bose-hubbard/main.jl
+++ /dev/null
@@ -1,402 +0,0 @@
-using Markdown
-using MPSKit, MPSKitModels, TensorKit
-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
-[`MPSKitModels.jl`](https://github.com/QuantumKitHub/MPSKitModels.jl) instead that has
-predefined operators and models for most well-known lattice models. In particular, we can
-use [`MPSKitModels.a_min`](@extref) to create the bosonic annihilation operator.
-"""
-
-a_op = a_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. Similarly, the Bose Hubbard model is also predefined in
-[`MPSKitModels.bose_hubbard_model`](@extref) (although we will construct our own variant
-later on).
-
-"""
-
-hamiltonian = bose_hubbard_model(InfiniteChain(1); cutoff = cutoff, U = 1, mu = 0.5, t = 0.2) # It is not strictly required to pass InfiniteChain() and is only included for clarity; one may instead pass FiniteChain(N) as well
-
-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(InfiniteChain(); 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 = a_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
-[source code](https://github.com/QuantumKitHub/MPSKitModels.jl/blob/f4c36d9660a9eab05fa253ffd5c20dc6b7df44cc/src/models/hamiltonians.jl#L379-L409)
-of `MPSKitModels.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,
- lattice::AbstractLattice = InfiniteChain(1);
- cutoff::Integer = 5, t = 1.0, U = 1.0, mu = 0.0, phi = 0
- )
-
- a_pm = a_plusmin(elt, symmetry; cutoff = cutoff)
- a_mp = a_minplus(elt, symmetry; cutoff = cutoff)
- N = a_number(elt, symmetry; cutoff = cutoff)
-
- interaction_term = N * (N - id(domain(N)))
-
- return H = @mpoham begin
- sum(nearest_neighbours(lattice)) do (i, j)
- return -t * (exp(1im * phi) * a_pm{i, j} + exp(1im * -phi) * a_mp{i, j})
- end +
- sum(vertices(lattice)) do i
- return U / 2 * interaction_term{i} - mu * N{i}
- end
- end
-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 = a_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(InfiniteChain(); 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 c7b3531b1..0af7f76e0 100644
--- a/examples/classic2d/1.hard-hexagon/main.jl
+++ b/examples/statmech/hard-hexagon/main.jl
@@ -62,7 +62,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(