MPSKit.jl
Efficient and versatile tools for working with matrix product states
Table of contents
Installation
MPSKit.jl is a part of the general registry, and can be installed via the package manager as:
pkg> add MPSKit
Key Features
- Construction and manipulation of Matrix Product States (MPS)
- Calculation of observables and expectation values
- Various optimization methods for obtaining MPS fixed points
- Support for both finite and infinite MPS
- Support for wide variety of symmetries, including Abelian, non-Abelian, fermionic and anyonic symmetries
Usage
To get started with MPSKit, we recommend also including TensorKit.jl and MPSKitModels.jl. The former defines the tensor backend which is used throughout MPSKit, while the latter includes some common operators and models.
using TensorOperations
using TensorKit
using MPSKit
using LinearAlgebra: norm
Finite Matrix Product States
Finite MPS are characterised by a set of tensors, one for each site, which each have 3 legs. They can be constructed by specifying the virtual spaces and the physical spaces, i.e. the dimensions of each of the legs. These are then contracted to form the MPS. In MPSKit, they are represented by FiniteMPS
, which can be constructed either by passing in the tensors directly, or by specifying the dimensions of the legs.
d = 2 # physical dimension
D = 5 # virtual dimension
L = 10 # number of sites
mps = FiniteMPS(L, ComplexSpace(d), ComplexSpace(D)) # random MPS with maximal bond dimension D
10-site FiniteMPS (ComplexF64, TensorKit.ComplexSpace):
┌ C[10]: TensorMap(ℂ^1 ← ℂ^1)
├── AL[10]: TensorMap((ℂ^2 ⊗ ℂ^2) ← ℂ^1)
├── AL[9]: TensorMap((ℂ^4 ⊗ ℂ^2) ← ℂ^2)
├── AL[8]: TensorMap((ℂ^5 ⊗ ℂ^2) ← ℂ^4)
├── AL[7]: TensorMap((ℂ^5 ⊗ ℂ^2) ← ℂ^5)
├── AL[6]: TensorMap((ℂ^5 ⊗ ℂ^2) ← ℂ^5)
├── AL[5]: TensorMap((ℂ^5 ⊗ ℂ^2) ← ℂ^5)
├── AL[4]: TensorMap((ℂ^5 ⊗ ℂ^2) ← ℂ^5)
├── AL[3]: TensorMap((ℂ^4 ⊗ ℂ^2) ← ℂ^5)
├── AL[2]: TensorMap((ℂ^2 ⊗ ℂ^2) ← ℂ^4)
└── AL[1]: TensorMap((ℂ^1 ⊗ ℂ^2) ← ℂ^2)
The FiniteMPS
object then handles the gauging of the MPS, which is necessary for many of the algorithms. This is done automatically when needed, and the user can access the gauged tensors by getting and setting the AL
, AR
, CR
/CL
and AC
fields, which each represent a vector of these tensors.
al = mps.AL[3] # left gauged tensor of the third site
@tensor E[a; b] := al[c, d, b] * conj(al[c, d, a])
@show isapprox(E, id(right_virtualspace(mps, 3)))
true
ar = mps.AR[3] # right gauged tensor of the third site
@tensor E[a; b] := ar[a, d, c] * conj(ar[b, d, c])
@show isapprox(E, id(left_virtualspace(mps, 3)))
true
As the mps will be kept in a gauged form, updating a tensor will also update the gauged tensors. For example, we can set the tensor of the third site to the identity, and the gauged tensors will be updated accordingly.
mps.C[3] = id(domain(mps.C[3]))
mps
10-site FiniteMPS (ComplexF64, TensorKit.ComplexSpace):
┌── AR[10]: TensorMap((ℂ^2 ⊗ ℂ^2) ← ℂ^1)
├── AR[9]: TensorMap((ℂ^4 ⊗ ℂ^2) ← ℂ^2)
├── AR[8]: TensorMap((ℂ^5 ⊗ ℂ^2) ← ℂ^4)
├── AR[7]: TensorMap((ℂ^5 ⊗ ℂ^2) ← ℂ^5)
├── AR[6]: TensorMap((ℂ^5 ⊗ ℂ^2) ← ℂ^5)
├── AR[5]: TensorMap((ℂ^5 ⊗ ℂ^2) ← ℂ^5)
├── AR[4]: TensorMap((ℂ^5 ⊗ ℂ^2) ← ℂ^5)
│ C[3]: TensorMap(ℂ^5 ← ℂ^5)
├── AL[3]: TensorMap((ℂ^4 ⊗ ℂ^2) ← ℂ^5)
├── AL[2]: TensorMap((ℂ^2 ⊗ ℂ^2) ← ℂ^4)
└── AL[1]: TensorMap((ℂ^1 ⊗ ℂ^2) ← ℂ^2)
These objects can then be used to compute observables and expectation values. For example, the expectation value of the identity operator at the third site, which is equal to the norm of the MPS, can be computed as:
N1 = LinearAlgebra.norm(mps)
N2 = expectation_value(mps, 3 => id(physicalspace(mps, 3)))
println("‖mps‖ = $N1")
println("<mps|𝕀₃|mps> = $N2")
‖mps‖ = 2.23606797749979
<mps|𝕀₃|mps> = 0.9999999999999997 + 0.0im
Finally, the MPS can be optimized in order to determine groundstates of given Hamiltonians. Using the pre-defined models in MPSKitModels
, we can construct the groundstate for the transverse field Ising model:
J = 1.0
g = 0.5
lattice = fill(ComplexSpace(2), 10)
X = TensorMap(ComplexF64[0 1; 1 0], ComplexSpace(2), ComplexSpace(2))
Z = TensorMap(ComplexF64[1 0; 0 -1], space(X))
H = FiniteMPOHamiltonian(lattice, (i, i+1) => -J * X ⊗ X for i in 1:length(lattice)-1) +
FiniteMPOHamiltonian(lattice, (i,) => - g * Z for i in 1:length(lattice))
find_groundstate!(mps, H, DMRG(; maxiter=10))
E0 = expectation_value(mps, H)
println("<mps|H|mps> = $real(E0)")
[ Info: DMRG init: obj = -3.975371577051e+00 err = 4.8823e-01
[ Info: DMRG 1: obj = -9.765248516883e+00 err = 1.5748047765e-02 time = 3.76 sec
[ Info: DMRG 2: obj = -9.765503495202e+00 err = 7.5739395288e-04 time = 0.36 sec
[ Info: DMRG 3: obj = -9.765503497223e+00 err = 4.5322374913e-06 time = 0.01 sec
[ Info: DMRG 4: obj = -9.765503497524e+00 err = 1.9047150867e-06 time = 0.01 sec
[ Info: DMRG 5: obj = -9.765503497586e+00 err = 8.3047301910e-07 time = 0.01 sec
[ Info: DMRG 6: obj = -9.765503497599e+00 err = 4.0766920243e-07 time = 0.01 sec
[ Info: DMRG 7: obj = -9.765503497602e+00 err = 2.0209053735e-07 time = 0.01 sec
[ Info: DMRG 8: obj = -9.765503497602e+00 err = 1.0064232643e-07 time = 0.01 sec
[ Info: DMRG 9: obj = -9.765503497603e+00 err = 5.0216875410e-08 time = 0.01 sec
┌ Warning: DMRG cancel 10: obj = -9.765503497603e+00 err = 2.5071210904e-08 time = 4.22 sec
└ @ MPSKit ~/work/MPSKit.jl/MPSKit.jl/src/algorithms/groundstate/dmrg.jl:55
<mps|H|mps> = real(E0)
Infinite Matrix Product States
Similarly, an infinite MPS can be constructed by specifying the tensors for the unit cell, characterised by the spaces (dimensions) thereof.
d = 2 # physical dimension
D = 5 # virtual dimension
mps = InfiniteMPS(d, D) # random MPS
single site InfiniteMPS:
│ ⋮
│ C[1]: TensorMap(ℂ^5 ← ℂ^5)
├── AL[1]: TensorMap((ℂ^5 ⊗ ℂ^2) ← ℂ^5)
│ ⋮
The InfiniteMPS
object then handles the gauging of the MPS, which is necessary for many of the algorithms. This is done automatically upon creation of the object, and the user can access the gauged tensors by getting and setting the AL
, AR
, C
and AC
fields, which each represent a (periodic) vector of these tensors.
al = mps.AL[1] # left gauged tensor of the first site
@tensor E[a; b] := al[c, d, b] * conj(al[c, d, a])
@show isapprox(E, id(left_virtualspace(mps, 1)))
true
ar = mps.AR[1] # right gauged tensor of the first site
@tensor E[a; b] := ar[a, d, c] * conj(ar[b, d, c])
@show isapprox(E, id(right_virtualspace(mps, 2)))
true
As regauging the MPS is not possible without recomputing all the tensors, setting a single tensor is not supported. Instead, the user should construct a new mps object with the desired tensor, which will then be gauged upon construction.
als = 3 .* mps.AL
mps = InfiniteMPS(als)
single site InfiniteMPS:
│ ⋮
│ C[1]: TensorMap(ℂ^5 ← ℂ^5)
├── AL[1]: TensorMap((ℂ^5 ⊗ ℂ^2) ← ℂ^5)
│ ⋮
These objects can then be used to compute observables and expectation values. For example, the norm of the MPS, which is equal to the expectation value of the identity operator can be computed by:
N1 = norm(mps)
N2 = expectation_value(mps, 1 => id(physicalspace(mps, 1)))
println("‖mps‖ = $N1")
println("<mps|𝕀₁|mps> = $N2")
‖mps‖ = 0.9999999999999998
<mps|𝕀₁|mps> = 1.0 + 0.0im
Because infinite MPS cannot sensibly be normalized to anything but $1$, the norm
of an infinite MPS is always set to be $1$ at construction. If this were not the case, any observable computed from the MPS would either blow up to infinity or vanish to zero.
Finally, the MPS can be optimized in order to determine groundstates of given Hamiltonians. There are plenty of pre-defined models in MPSKitModels
, but we can also manually construct the groundstate for the transverse field Ising model:
J = 1.0
g = 0.5
lattice = PeriodicVector([ComplexSpace(2)])
X = TensorMap(ComplexF64[0 1; 1 0], ComplexSpace(2), ComplexSpace(2))
Z = TensorMap(ComplexF64[1 0; 0 -1], space(X))
H = InfiniteMPOHamiltonian(lattice, (1, 2) => -J * X ⊗ X, (1,) => - g * Z)
mps, = find_groundstate(mps, H, VUMPS(; maxiter=10))
E0 = expectation_value(mps, H)
println("<mps|H|mps> = $(sum(real(E0)) / length(mps))")
[ Info: VUMPS init: obj = -9.796941087370e-01 err = 2.3945e-01
[ Info: VUMPS 1: obj = -1.063544059558e+00 err = 5.4358624879e-04 time = 6.87 sec
[ Info: VUMPS 2: obj = -1.063544409684e+00 err = 1.4440841528e-05 time = 0.00 sec
[ Info: VUMPS 3: obj = -1.063544409973e+00 err = 8.5673401039e-07 time = 0.00 sec
[ Info: VUMPS 4: obj = -1.063544409973e+00 err = 8.2204690434e-08 time = 0.00 sec
[ Info: VUMPS 5: obj = -1.063544409973e+00 err = 8.1002465883e-09 time = 0.00 sec
[ Info: VUMPS 6: obj = -1.063544409973e+00 err = 8.5720949477e-10 time = 0.00 sec
[ Info: VUMPS conv 7: obj = -1.063544409973e+00 err = 9.1804114612e-11 time = 6.88 sec
<mps|H|mps> = -1.0635444099732503
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 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 library's issue tracker on the GitHub repository and open a new issue.