diff --git a/.gitignore b/.gitignore
index ab455f5..03dadbf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,3 @@
-.ipynb_checkpoints/*
-*-checkpoint.ipynb
+.ipynb_checkpoints
*~
*.pyc
diff --git a/README.md b/README.md
index c260cbc..a843074 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,14 @@
-# BadHonnefTutorial
+# uniformMpsTutorial
-This repository contains the files for the tutorial sessions on tangent space methods for uniform matrix product stats, given at the 2020 school on Tensor Network based approaches to Quantum Many-Body Systems, held in Bad Honnef.
+This repository contains a tutorial on tangent space methods for uniform matrix product states, originally written to be taught at the [2020 school on Tensor Network based approaches to Quantum Many-Body Systems](http://quantumtensor.pks.mpg.de/index.php/schools/2020-school/) held in Bad Honnef, Germany. The tutorials are based on the lecture notes ['Tangent-space methods for uniform matrix product states'](https://doi.org/10.21468/SciPostPhysLectNotes.7) by Laurens Vanderstraeten, Jutho Haegeman and Frank Verstraete.
## Setup and installation
-The tutorial is given in the form of Python notebooks, which provide a detailed guide through the relevant concepts and algorithms, interspersed with checks and demonstrations. Reference implementations are also given in MATLAB.
+The tutorial is given in the form of IPython Notebooks, which provide a detailed guide through the relevant concepts and algorithms, interspersed with checks and demonstrations. Reference implementations are also given in MATLAB.
The easiest way to get all of the tools needed to open and run the notebooks is to install Python via the anaconda distribution: https://docs.anaconda.com/anaconda/install/. This automatically comes with all packages needed for this tutorial.
-If you already have a python version installed, you may install the jupyter notebook package seperately through pip by running
+If you already have a python version installed, you may [install Jupyter](https://jupyter.org/install) seperately via the [Python Package Index](https://pypi.org/) by running
```console
pip install notebook
```
@@ -18,7 +18,7 @@ Once jupyter notebook is installed, clone this repository to a local folder of y
jupyter notebook
```
-For performing contractions of tensor networks, we have opted to use the Python implementation of the ncon contractor (https://arxiv.org/abs/1402.0939), which can be found at https://github.com/mhauru/ncon. There are undoubtedly many contraction tools that work equally well and any of them may of course be used, but this one has a particularly intuituve and handy syntax. To install ncon, you may run
+For performing contractions of tensor networks, we have opted to use the Python implementation of the [ncon contractor](https://arxiv.org/abs/1402.0939), which can be found at https://github.com/mhauru/ncon. There are undoubtedly many contraction tools that work equally well and any of them may of course be used, but this one has a particularly intuituve and handy syntax. To install ncon, you may run
```console
pip install ncon
```
@@ -33,13 +33,11 @@ The tutorial consists of three parts:
This part is concerned with the basics of MPS in the thermodynamic limit: normalization, fixed points, algorithms for gauging an MPS, and computing expectation values.
#### 2. Finding ground states of local Hamiltonians
-This part describes how to perform a variational search in the space of MPS to find the ground state of a local Hamiltonian. We start by considering a simple gradient based approach, and then introduce the vumps algorithm. We also briefly touch upon how excited states may be constructed using the result of the vumps ground state search.
+This part describes how to perform a variational search in the space of MPS to find the ground state of a local Hamiltonian. We start by considering a simple gradient based approach, and then introduce the [VUMPS algorithm](https://journals.aps.org/prb/abstract/10.1103/PhysRevB.97.045145). We also briefly touch upon how excited states may be constructed using the result of the VUMPS ground state search. The algorithms are demonstrated for the case of the one-dimensional quantum spin-1 Heisenberg model.
#### 3. Transfer matrices and fixed points
-In this part the vumps algorithm is extended to matrix product operators (MPOs), and it is then used to compute the partition function and magnetization for the 2d classical Ising model.
+In this part the VUMPS algorithm is extended to transfer matrices in the form of matrix product operators (MPOs), which can then be used to contract infinite two-dimensional tensor networks. The algorithm is demonstrated for the case of the classical two-dimensional Ising model, and is for example used to compute its partition function and evaluate the expectation value of the magnetization.
## Use of this tutorial
-Each chapter provides a jupyter notebook (.ipynb) file with guided exercices on implementing the algorithms, as well as a solution notebook. Similar files are available for MATLAB.
-
-The approach itself is very basic, where all algorithms are broken down and illustrated in digestible steps. The implementations are very simple: there are no black boxes or fancy tricks involved, everything is built from the ground up. As a result, implementing all these steps from the start would most likely take longer than two tutorial sessions. The contents of the tutorial should therefore not just be seen as a task to complete, but just as much as a reference on how one would go about implementing these concepts. Please feel free to use this in any way you see fit.
+Each chapter provides a notebook (.ipynb) file with guided exercices on implementing the algorithms, as well as a solution notebook. Similar files are available for MATLAB. The approach itself is very basic, where all algorithms are broken down and illustrated in digestible steps. The implementations are very simple: there are no black boxes or fancy tricks involved, everything is built from the ground up. While these tutorials were originally intended to be taught at a school on tensor networks, they now serve as a general reference on uniform MPS and how one would go about implementing these concepts.
diff --git a/matlab/chapter1.m b/matlab/chapter1.m
index ad35959..06d6c2e 100644
--- a/matlab/chapter1.m
+++ b/matlab/chapter1.m
@@ -32,8 +32,8 @@
assert(~isreal(A), 'MPS tensor should have complex values')
-% normalising an MPS through naive diagonalization of the transfer matrix:
-A = normaliseMPSNaive(A);
+% normalizing an MPS through naive diagonalization of the transfer matrix:
+A = normalizeMPSNaive(A);
[l, r] = fixedPointsNaive(A);
assert(ArrayIsEqual(l, l', 1e-12), 'left fixed point should be hermitian!')
@@ -41,15 +41,15 @@
assert(ArrayIsEqual(l, ncon({A, l, conj(A)}, {[1, 2, -2], [3, 1], [3, 2, -1]}), 1e-12), 'l should be a left fixed point!')
assert(ArrayIsEqual(r, ncon({A, r, conj(A)}, {[-1, 2, 1], [1, 3], [-2, 2, 3]}), 1e-12), 'r should be a right fixed point!')
-assert(abs(trace(l*r) - 1) < 1e-12, 'Left and right fixed points should be trace normalised!')
+assert(abs(trace(l*r) - 1) < 1e-12, 'Left and right fixed points should be trace normalized!')
%% 1.2 Gauge fixing
% left and right orthonormalisation through taking square root of fixed points
-[L, Al] = leftOrthonormaliseNaive(A, l);
-[R, Ar] = rightOrthonormaliseNaive(A, r);
+[L, Al] = leftOrthonormalizeNaive(A, l);
+[R, Ar] = rightOrthonormalizeNaive(A, r);
assert(ArrayIsEqual(R * R', r, 1e-12), 'Right gauge does not square to r')
assert(ArrayIsEqual(L' * L, l, 1e-12), 'Left gauge does not sqaure to l')
@@ -77,8 +77,8 @@
A = createMPS(D, d);
-% normalising an MPS through action of transfer matrix on a left and right matrix as a function handle:
-A = normaliseMPS(A);
+% normalizing an MPS through action of transfer matrix on a left and right matrix as a function handle:
+A = normalizeMPS(A);
[l, r] = fixedPoints(A);
assert(ArrayIsEqual(l, l', 1e-12), 'left fixed point should be hermitian!')
@@ -86,13 +86,13 @@
assert(ArrayIsEqual(l, ncon({A, l, conj(A)}, {[1, 2, -2], [3, 1], [3, 2, -1]}), 1e-12), 'l should be a left fixed point!')
assert(ArrayIsEqual(r, ncon({A, r, conj(A)}, {[-1, 2, 1], [1, 3], [-2, 2, 3]}), 1e-12), 'r should be a right fixed point!')
-assert(abs(trace(l*r) - 1) < 1e-12, 'Left and right fixed points should be trace normalised!')
+assert(abs(trace(l*r) - 1) < 1e-12, 'Left and right fixed points should be trace normalized!')
% gauging an MPS through iterative QR decompositions:
[Al, Ac, Ar, C] = mixedCanonical(A);
-% [R, Ar] = rightOrthonormalise(A);
-% [L, Al] = leftOrthonormalise(A);
+% [R, Ar] = rightOrthonormalize(A);
+% [L, Al] = leftOrthonormalize(A);
assert(ArrayIsEqual(eye(D), ncon({Ar, conj(Ar)}, {[-1 1 2], [-2 1 2]}), 1e-12), 'Ar not in right-orthonormal form')
assert(ArrayIsEqual(eye(D), ncon({Al, conj(Al)}, {[1 2 -2], [1 2 -1]}), 1e-12), 'Al not in left-orthonormal form')
@@ -103,7 +103,7 @@
%% 1.5 Computing expectation values
A = createMPS(D, d);
-A = normaliseMPS(A);
+A = normalizeMPS(A);
[l, r] = fixedPoints(A);
[Al, Ac, Ar, C] = mixedCanonical(A);
@@ -167,8 +167,8 @@
end
-function Anew = normaliseMPSNaive(A)
- % Normalise an MPS tensor.
+function Anew = normalizeMPSNaive(A)
+ % Normalize an MPS tensor.
%
% Parameters
% ----------
@@ -185,7 +185,7 @@
% Complexity
% ----------
% O(D ** 6) algorithm,
- % directly diagonalising (D ** 2, D ** 2) matrix.
+ % directly diagonalizing (D ** 2, D ** 2) matrix.
end
@@ -208,7 +208,7 @@
% Complexity
% ----------
% O(D ** 6) algorithm,
- % diagonalising (D ** 2, D ** 2) matrix.
+ % diagonalizing (D ** 2, D ** 2) matrix.
end
@@ -231,13 +231,13 @@
% Complexity
% ----------
% O(D ** 6) algorithm,
- % diagonalising (D ** 2, D ** 2) matrix.
+ % diagonalizing (D ** 2, D ** 2) matrix.
end
function [l, r] = fixedPointsNaive(A)
- % Find normalised fixed points.
+ % Find normalized fixed points.
%
% Parameters
% ----------
@@ -257,7 +257,7 @@
% Complexity
% ----------
% O(D ** 6) algorithm,
- % diagonalising (D ** 2, D ** 2) matrix
+ % diagonalizing (D ** 2, D ** 2) matrix
end
@@ -265,7 +265,7 @@
%% 1.2 Gauge fixing
-function [L, Al] = leftOrthonormaliseNaive(A, l)
+function [L, Al] = leftOrthonormalizeNaive(A, l)
% Transform A to left-orthonormal gauge.
%
% Parameters
@@ -280,19 +280,19 @@
% left gauge with 2 legs,
% ordered left-right.
% Al : array(D, d, D)
- % MPS tensor zith 3 legs,
+ % MPS tensor with 3 legs,
% ordered left-bottom-right,
% left orthonormal
%
% Complexity
% ----------
% O(D ** 6) algorithm,
- % diagonalising (D ** 2, D ** 2) matrix
+ % diagonalizing (D ** 2, D ** 2) matrix
end
-function [R, Ar] = rightOrthonormaliseNaive(A, r)
+function [R, Ar] = rightOrthonormalizeNaive(A, r)
% Transform A to right-orthonormal gauge.
%
% Parameters
@@ -307,14 +307,14 @@
% right gauge with 2 legs,
% ordered left-right.
% Ar : array(D, d, D)
- % MPS tensor zith 3 legs,
+ % MPS tensor with 3 legs,
% ordered left-bottom-right,
% left orthonormal
%
% Complexity
% ----------
% O(D ** 6) algorithm,
- % diagonalising (D ** 2, D ** 2) dmatrix
+ % diagonalizing (D ** 2, D ** 2) dmatrix
end
@@ -331,15 +331,15 @@
% Returns
% -------
% Al : array(D, d, D)
- % MPS tensor zith 3 legs,
+ % MPS tensor with 3 legs,
% ordered left-bottom-right,
% left orthonormal.
% Ac : array(D, d, D)
- % MPS tensor zith 3 legs,
+ % MPS tensor with 3 legs,
% ordered left-bottom-right,
% center gauge.
% Ar : array(D, d, D)
- % MPS tensor zith 3 legs,
+ % MPS tensor with 3 legs,
% ordered left-bottom-right,
% right orthonormal.
% C : array(D, D)
@@ -370,15 +370,15 @@
% Returns
% -------
% AlTilde : array(Dtrunc, d, Dtrunc)
- % MPS tensor zith 3 legs,
+ % MPS tensor with 3 legs,
% ordered left-bottom-right,
% left orthonormal.
% AcTilde : array(Dtrunc, d, Dtrunc)
- % MPS tensor zith 3 legs,
+ % MPS tensor with 3 legs,
% ordered left-bottom-right,
% center gauge.
% ArTilde : array(Dtrunc, d, Dtrunc)
- % MPS tensor zith 3 legs,
+ % MPS tensor with 3 legs,
% ordered left-bottom-right,
% right orthonormal.
% CTilde : array(Dtrunc, Dtrunc)
@@ -392,8 +392,8 @@
%% 1.4 Algorithms for finding canonical forms
-function Anew = normaliseMPS(A)
- % Normalise an MPS tensor.
+function Anew = normalizeMPS(A)
+ % Normalize an MPS tensor.
%
% Parameters
% ----------
@@ -462,7 +462,7 @@
function [l, r] = fixedPoints(A)
- % Find normalised fixed points.
+ % Find normalized fixed points.
%
% Parameters
% ----------
@@ -490,7 +490,7 @@
% rqPos: already implemented as function 'lq' in the folder 'AuxiliaryFunctions'
-function [R, Ar] = rightOrthonormalise(A, R0, tol, maxIter)
+function [R, Ar] = rightOrthonormalize(A, R0, tol, maxIter)
% Transform A to right-orthonormal gauge.
%
% Parameters
@@ -513,7 +513,7 @@
% right gauge with 2 legs,
% ordered left-right.
% Ar : array(D, d, D)
- % MPS tensor zith 3 legs,
+ % MPS tensor with 3 legs,
% ordered left-bottom-right,
% right-orthonormal
@@ -523,7 +523,7 @@
% qrPos: already implemented as function 'qrpos' in the folder 'AuxiliaryFunctions'
-function [L, Al] = leftOrthonormalise(A, L0, tol, maxIter)
+function [L, Al] = leftOrthonormalize(A, L0, tol, maxIter)
% Transform A to left-orthonormal gauge.
%
% Parameters
@@ -546,7 +546,7 @@
% left gauge with 2 legs,
% ordered left-right.
% Al : array(D, d, D)
- % MPS tensor zith 3 legs,
+ % MPS tensor with 3 legs,
% ordered left-bottom-right,
% left-orthonormal
@@ -565,15 +565,15 @@
% Returns
% -------
% Al : array(D, d, D)
- % MPS tensor zith 3 legs,
+ % MPS tensor with 3 legs,
% ordered left-bottom-right,
% left orthonormal.
% Ac : array(D, d, D)
- % MPS tensor zith 3 legs,
+ % MPS tensor with 3 legs,
% ordered left-bottom-right,
% center gauge.
% Ar : array(D, d, D)
- % MPS tensor zith 3 legs,
+ % MPS tensor with 3 legs,
% ordered left-bottom-right,
% right orthonormal.
% C : array(D, D)
@@ -602,10 +602,10 @@
% ordered left-bottom-right.
% l : array(D, D), optional
% left fixed point of transfermatrix,
- % normalised.
+ % normalized.
% r : array(D, D), optional
% right fixed point of transfermatrix,
- % normalised.
+ % normalized.
%
% Returns
% -------
@@ -654,10 +654,10 @@
% ordered left-bottom-right.
% l : array(D, D), optional
% left fixed point of transfermatrix,
- % normalised.
+ % normalized.
% r : array(D, D), optional
% right fixed point of transfermatrix,
- % normalised.
+ % normalized.
%
% Returns
% -------
diff --git a/matlab/chapter1Solution.m b/matlab/chapter1Solution.m
index ac27fb7..d0a40f7 100644
--- a/matlab/chapter1Solution.m
+++ b/matlab/chapter1Solution.m
@@ -20,7 +20,7 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% 1.1 Normalisation
+%% 1.1 Normalization
d = 3;
D = 5;
@@ -32,8 +32,8 @@
assert(~isreal(A), 'MPS tensor should have complex values')
-% normalising an MPS through naive diagonalization of the transfer matrix:
-A = normaliseMPSNaive(A);
+% normalizing an MPS through naive diagonalization of the transfer matrix:
+A = normalizeMPSNaive(A);
[l, r] = fixedPointsNaive(A);
assert(ArrayIsEqual(l, l', 1e-12), 'left fixed point should be hermitian!')
@@ -41,15 +41,15 @@
assert(ArrayIsEqual(l, ncon({A, l, conj(A)}, {[1, 2, -2], [3, 1], [3, 2, -1]}), 1e-12), 'l should be a left fixed point!')
assert(ArrayIsEqual(r, ncon({A, r, conj(A)}, {[-1, 2, 1], [1, 3], [-2, 2, 3]}), 1e-12), 'r should be a right fixed point!')
-assert(abs(trace(l*r) - 1) < 1e-12, 'Left and right fixed points should be trace normalised!')
+assert(abs(trace(l*r) - 1) < 1e-12, 'Left and right fixed points should be trace normalized!')
%% 1.2 Gauge fixing
% left and right orthonormalisation through taking square root of fixed points
-[L, Al] = leftOrthonormaliseNaive(A, l);
-[R, Ar] = rightOrthonormaliseNaive(A, r);
+[L, Al] = leftOrthonormalizeNaive(A, l);
+[R, Ar] = rightOrthonormalizeNaive(A, r);
assert(ArrayIsEqual(R * R', r, 1e-12), 'Right gauge does not square to r')
assert(ArrayIsEqual(L' * L, l, 1e-12), 'Left gauge does not sqaure to l')
@@ -77,8 +77,8 @@
A = createMPS(D, d);
-% normalising an MPS through action of transfer matrix on a left and right matrix as a function handle:
-A = normaliseMPS(A);
+% normalizing an MPS through action of transfer matrix on a left and right matrix as a function handle:
+A = normalizeMPS(A);
[l, r] = fixedPoints(A);
assert(ArrayIsEqual(l, l', 1e-12), 'left fixed point should be hermitian!')
@@ -86,13 +86,13 @@
assert(ArrayIsEqual(l, ncon({A, l, conj(A)}, {[1, 2, -2], [3, 1], [3, 2, -1]}), 1e-12), 'l should be a left fixed point!')
assert(ArrayIsEqual(r, ncon({A, r, conj(A)}, {[-1, 2, 1], [1, 3], [-2, 2, 3]}), 1e-12), 'r should be a right fixed point!')
-assert(abs(trace(l*r) - 1) < 1e-12, 'Left and right fixed points should be trace normalised!')
+assert(abs(trace(l*r) - 1) < 1e-12, 'Left and right fixed points should be trace normalized!')
% gauging an MPS through iterative QR decompositions:
[Al, Ac, Ar, C] = mixedCanonical(A);
-% [R, Ar] = rightOrthonormalise(A);
-% [L, Al] = leftOrthonormalise(A);
+% [R, Ar] = rightOrthonormalize(A);
+% [L, Al] = leftOrthonormalize(A);
assert(ArrayIsEqual(eye(D), ncon({Ar, conj(Ar)}, {[-1 1 2], [-2 1 2]}), 1e-12), 'Ar not in right-orthonormal form')
assert(ArrayIsEqual(eye(D), ncon({Al, conj(Al)}, {[1 2 -2], [1 2 -1]}), 1e-12), 'Al not in left-orthonormal form')
@@ -103,7 +103,7 @@
%% 1.5 Computing expectation values
A = createMPS(D, d);
-A = normaliseMPS(A);
+A = normalizeMPS(A);
[l, r] = fixedPoints(A);
[Al, Ac, Ar, C] = mixedCanonical(A);
@@ -166,8 +166,8 @@
end
-function Anew = normaliseMPSNaive(A)
- % Normalise an MPS tensor.
+function Anew = normalizeMPSNaive(A)
+ % Normalize an MPS tensor.
%
% Parameters
% ----------
@@ -184,7 +184,7 @@
% Complexity
% ----------
% O(D ** 6) algorithm,
- % directly diagonalising (D ** 2, D ** 2) matrix.
+ % directly diagonalizing (D ** 2, D ** 2) matrix.
D = size(A, 1);
E = createTransfermatrix(A);
@@ -211,7 +211,7 @@
% Complexity
% ----------
% O(D ** 6) algorithm,
- % diagonalising (D ** 2, D ** 2) matrix.
+ % diagonalizing (D ** 2, D ** 2) matrix.
D = size(A, 1);
E = createTransfermatrix(A);
@@ -242,7 +242,7 @@
% Complexity
% ----------
% O(D ** 6) algorithm,
- % diagonalising (D ** 2, D ** 2) matrix.
+ % diagonalizing (D ** 2, D ** 2) matrix.
D = size(A, 1);
E = createTransfermatrix(A);
@@ -256,7 +256,7 @@
function [l, r] = fixedPointsNaive(A)
- % Find normalised fixed points.
+ % Find normalized fixed points.
%
% Parameters
% ----------
@@ -276,18 +276,18 @@
% Complexity
% ----------
% O(D ** 6) algorithm,
- % diagonalising (D ** 2, D ** 2) matrix
+ % diagonalizing (D ** 2, D ** 2) matrix
l = leftFixedPointNaive(A);
r = rightFixedPointNaive(A);
- l = l / trace(l*r); % normalise
+ l = l / trace(l*r); % normalize
end
%% 1.2 Gauge fixing
-function [L, Al] = leftOrthonormaliseNaive(A, l)
+function [L, Al] = leftOrthonormalizeNaive(A, l)
% Transform A to left-orthonormal gauge.
%
% Parameters
@@ -302,14 +302,14 @@
% left gauge with 2 legs,
% ordered left-right.
% Al : array(D, d, D)
- % MPS tensor zith 3 legs,
+ % MPS tensor with 3 legs,
% ordered left-bottom-right,
% left orthonormal
%
% Complexity
% ----------
% O(D ** 6) algorithm,
- % diagonalising (D ** 2, D ** 2) matrix
+ % diagonalizing (D ** 2, D ** 2) matrix
if nargin < 2
l = leftFixedPointNaive(A);
@@ -320,7 +320,7 @@
end
-function [R, Ar] = rightOrthonormaliseNaive(A, r)
+function [R, Ar] = rightOrthonormalizeNaive(A, r)
% Transform A to right-orthonormal gauge.
%
% Parameters
@@ -335,14 +335,14 @@
% right gauge with 2 legs,
% ordered left-right.
% Ar : array(D, d, D)
- % MPS tensor zith 3 legs,
+ % MPS tensor with 3 legs,
% ordered left-bottom-right,
% left orthonormal
%
% Complexity
% ----------
% O(D ** 6) algorithm,
- % diagonalising (D ** 2, D ** 2) dmatrix
+ % diagonalizing (D ** 2, D ** 2) dmatrix
if nargin < 2
r = rightFixedPointNaive(A);
@@ -365,15 +365,15 @@
% Returns
% -------
% Al : array(D, d, D)
- % MPS tensor zith 3 legs,
+ % MPS tensor with 3 legs,
% ordered left-bottom-right,
% left orthonormal.
% Ac : array(D, d, D)
- % MPS tensor zith 3 legs,
+ % MPS tensor with 3 legs,
% ordered left-bottom-right,
% center gauge.
% Ar : array(D, d, D)
- % MPS tensor zith 3 legs,
+ % MPS tensor with 3 legs,
% ordered left-bottom-right,
% right orthonormal.
% C : array(D, D)
@@ -386,8 +386,8 @@
% O(D ** 6) algorithm,
% diagonalisation of (D ** 2, D ** 2) matrix
- [L, Al] = leftOrthonormaliseNaive(A);
- [R, Ar] = rightOrthonormaliseNaive(A);
+ [L, Al] = leftOrthonormalizeNaive(A);
+ [R, Ar] = rightOrthonormalizeNaive(A);
C = L * R;
[U, C, V] = svd(C);
% normalize center matrix
@@ -415,15 +415,15 @@
% Returns
% -------
% AlTilde : array(Dtrunc, d, Dtrunc)
- % MPS tensor zith 3 legs,
+ % MPS tensor with 3 legs,
% ordered left-bottom-right,
% left orthonormal.
% AcTilde : array(Dtrunc, d, Dtrunc)
- % MPS tensor zith 3 legs,
+ % MPS tensor with 3 legs,
% ordered left-bottom-right,
% center gauge.
% ArTilde : array(Dtrunc, d, Dtrunc)
- % MPS tensor zith 3 legs,
+ % MPS tensor with 3 legs,
% ordered left-bottom-right,
% right orthonormal.
% CTilde : array(Dtrunc, Dtrunc)
@@ -450,8 +450,8 @@
%% 1.4 Algorithms for finding canonical forms
-function Anew = normaliseMPS(A)
- % Normalise an MPS tensor.
+function Anew = normalizeMPS(A)
+ % Normalize an MPS tensor.
%
% Parameters
% ----------
@@ -540,7 +540,7 @@
function [l, r] = fixedPoints(A)
- % Find normalised fixed points.
+ % Find normalized fixed points.
%
% Parameters
% ----------
@@ -564,14 +564,14 @@
l = leftFixedPoint(A);
r = rightFixedPoint(A);
- l = l / trace(l*r); % normalise
+ l = l / trace(l*r); % normalize
end
% rqPos: already implemented as function 'lq' in the folder 'AuxiliaryFunctions'
-function [R, Ar] = rightOrthonormalise(A, R0, tol, maxIter)
+function [R, Ar] = rightOrthonormalize(A, R0, tol, maxIter)
% Transform A to right-orthonormal gauge.
%
% Parameters
@@ -594,7 +594,7 @@
% right gauge with 2 legs,
% ordered left-right.
% Ar : array(D, d, D)
- % MPS tensor zith 3 legs,
+ % MPS tensor with 3 legs,
% ordered left-bottom-right,
% right-orthonormal
@@ -633,7 +633,7 @@
% qrPos: already implemented as function 'qrpos' in the folder 'AuxiliaryFunctions'
-function [L, Al] = leftOrthonormalise(A, L0, tol, maxIter)
+function [L, Al] = leftOrthonormalize(A, L0, tol, maxIter)
% Transform A to left-orthonormal gauge.
%
% Parameters
@@ -656,7 +656,7 @@
% left gauge with 2 legs,
% ordered left-right.
% Al : array(D, d, D)
- % MPS tensor zith 3 legs,
+ % MPS tensor with 3 legs,
% ordered left-bottom-right,
% left-orthonormal
@@ -705,15 +705,15 @@
% Returns
% -------
% Al : array(D, d, D)
- % MPS tensor zith 3 legs,
+ % MPS tensor with 3 legs,
% ordered left-bottom-right,
% left orthonormal.
% Ac : array(D, d, D)
- % MPS tensor zith 3 legs,
+ % MPS tensor with 3 legs,
% ordered left-bottom-right,
% center gauge.
% Ar : array(D, d, D)
- % MPS tensor zith 3 legs,
+ % MPS tensor with 3 legs,
% ordered left-bottom-right,
% right orthonormal.
% C : array(D, D)
@@ -730,8 +730,8 @@
end
D = size(A, 1);
R0 = randcomplex(D, D); L0 = randcomplex(D, D); % initialize random matrices
- [L, Al] = leftOrthonormalise(A, L0, tol);
- [R, Ar] = rightOrthonormalise(A, R0, tol);
+ [L, Al] = leftOrthonormalize(A, L0, tol);
+ [R, Ar] = rightOrthonormalize(A, R0, tol);
[U, C, V] = svd(L * R);
% normalize center matrix
nrm = trace(C * C');
@@ -757,10 +757,10 @@
% ordered left-bottom-right.
% l : array(D, D), optional
% left fixed point of transfermatrix,
- % normalised.
+ % normalized.
% r : array(D, D), optional
% right fixed point of transfermatrix,
- % normalised.
+ % normalized.
%
% Returns
% -------
@@ -807,10 +807,10 @@
% ordered left-bottom-right.
% l : array(D, D), optional
% left fixed point of transfermatrix,
- % normalised.
+ % normalized.
% r : array(D, D), optional
% right fixed point of transfermatrix,
- % normalised.
+ % normalized.
%
% Returns
% -------
diff --git a/matlab/chapter2Solution.m b/matlab/chapter2Solution.m
index 3722726..03b3d33 100644
--- a/matlab/chapter2Solution.m
+++ b/matlab/chapter2Solution.m
@@ -1,66 +1,66 @@
-% Matlab script for chapter 1 of Bad Honnef tutorial on "Tangent space
-% methods for Tangent-space methods for uniform matrix product states",
-% based on the lecture notes: https://arxiv.org/abs/1810.07006
+% % Matlab script for chapter 1 of Bad Honnef tutorial on "Tangent space
+% % methods for Tangent-space methods for uniform matrix product states",
+% % based on the lecture notes: https://arxiv.org/abs/1810.07006
+% %
+% % Detailed explanations of all the different steps can be found in the
+% % python notebooks for the different chapters. These files provide a canvas
+% % for a MATLAB implementation that mirrors the contents of the python
+% % notebooks
+%
+% %% 2. Finding ground states of local Hamiltonians
+%
+% % Unlike the notebooks, where function definitions and corresponding checks
+% % are constructed in sequence, here all checks and demonstrations are
+% % placed at the start of the script, while all function definitions must
+% % be given at the bottom of the script
+%
+%
+% %%
+%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% % DEMONSTRATIONS
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+%
+% %% 2.2 Gradient descent algorithms
+%
+%
+% % Variational optimization of spin-1 Heisenberg Hamiltonian through
+% % minimization of the gradient in uniform gauge
+%
+% % coupling strengths
+% Jx = -1; Jy = -1; Jz = -1; hz = 0; % Heisenberg antiferromagnet
+% % Heisenberg Hamiltonian
+% h = Heisenberg(Jx, Jy, Jz, hz);
+%
+% % initialize bond dimension, physical dimension
+% D = 12;
+% d = 3;
+%
+% % initialize random MPS
+% A = createMPS(D, d);
+% A = normaliseMPS(A);
+%
+%
+% % Minimization of the energy through naive gradient descent
+% tol = 1e-4; % tolerance for norm of gradient
+% fprintf('\n\nGradient descent optimization:\n\n')
+% tic;
+% [E1, A1] = groundStateGradDescent(h, D, 0.1, A, tol, 5e2);
+% t1 = toc;
+% fprintf('\nTime until convergence: %fs\n', t1)
+% fprintf('Computed energy: %.14f\n\n', E1)
+%
+%
+% % Minimization of the energy using the fminunc minimizer:
+% tol = 1e-6; % tolerance for fminunc
+% fprintf('\n\nOptimization using fminunc:\n\n')
+% tic
+% [E2, A2] = groundStateMinimise(h, D, A, tol);
+% t2 = toc;
+% fprintf('\nTime until convergence: %fs\n', t2)
+% fprintf('Computed energy: %.14f\n', E2)
%
-% Detailed explanations of all the different steps can be found in the
-% python notebooks for the different chapters. These files provide a canvas
-% for a MATLAB implementation that mirrors the contents of the python
-% notebooks
-
-%% 2. Finding ground states of local Hamiltonians
-
-% Unlike the notebooks, where function definitions and corresponding checks
-% are constructed in sequence, here all checks and demonstrations are
-% placed at the start of the script, while all function definitions must
-% be given at the bottom of the script
-
-
-%%
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-% DEMONSTRATIONS
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-
-%% 2.2 Gradient descent algorithms
-
-
-% Variational optimization of spin-1 Heisenberg Hamiltonian through
-% minimization of the gradient in uniform gauge
-
-% coupling strengths
-Jx = -1; Jy = -1; Jz = -1; hz = 0; % Heisenberg antiferromagnet
-% Heisenberg Hamiltonian
-h = Heisenberg(Jx, Jy, Jz, hz);
-
-% initialize bond dimension, physical dimension
-D = 12;
-d = 3;
-
-% initialize random MPS
-A = createMPS(D, d);
-A = normaliseMPS(A);
-
-
-% Minimization of the energy through naive gradient descent
-tol = 1e-4; % tolerance for norm of gradient
-fprintf('\n\nGradient descent optimization:\n\n')
-tic;
-[E1, A1] = groundStateGradDescent(h, D, 0.1, A, tol, 5e2);
-t1 = toc;
-fprintf('\nTime until convergence: %fs\n', t1)
-fprintf('Computed energy: %.14f\n\n', E1)
-
-
-% Minimization of the energy using the fminunc minimizer:
-tol = 1e-6; % tolerance for fminunc
-fprintf('\n\nOptimization using fminunc:\n\n')
-tic
-[E2, A2] = groundStateMinimise(h, D, A, tol);
-t2 = toc;
-fprintf('\nTime until convergence: %fs\n', t2)
-fprintf('Computed energy: %.14f\n', E2)
-
%% 2.3 VUMPS
@@ -1047,14 +1047,16 @@
[Al, Ac, Ar, C] = mixedCanonical(A0);
flag = true;
delta = 1e-5;
+ i = 0;
while flag
+ i = i + 1;
% regularise H
hTilde = reducedHamMixed(h, Ac, Ar);
% calculate environments
- Lh = LhMixed(hTilde, Al, C, delta/10);
- Rh = RhMixed(hTilde, Ar, C, delta/10);
+ Lh = LhMixed(hTilde, Al, C, delta/100);
+ Rh = RhMixed(hTilde, Ar, C, delta/100);
% calculate new center
- [AcTilde, CTilde] = calcNewCenter(hTilde, Al, Ac, Ar, C, Lh, Rh, tol);
+ [AcTilde, CTilde] = calcNewCenter(hTilde, Al, Ac, Ar, C, Lh, Rh, delta/100);
% find Al, Ar from new Ac, C
[AlTilde, AcTilde, ArTilde, CTilde] = minAcC(AcTilde, CTilde);
% calculate norm
@@ -1067,47 +1069,47 @@
Al = AlTilde; Ac = AcTilde; Ar = ArTilde; C = CTilde;
% print current energy, optional...
E = real(expVal2Mixed(h, Ac, Ar));
- fprintf('Current energy: %.14f\n', E);
+ fprintf('iteration:\t%d,\tenergy:\t%.12f\tgradient norm\t%.4e\n', i, E, delta)
end
end
%% 2.4 Elementary excitations
-function [x,e]=quasiParticle(h,Al,Ar,Ac,C,p,num)
+function [x,e] = quasiParticle(h,Al,Ar,Ac,C,p,num)
-tol=1e-12; D=size(Al,1); d=size(Al,2);
+tol = 1e-12; D = size(Al,1); d = size(Al,2);
% renormalize hamiltonian and find left and right environments
hTilde = reducedHamMixed(h, Ac, Ar);
Lh = LhMixed(hTilde, Al, C, tol);
Rh = RhMixed(hTilde, Ar, C, tol);
% find reduced parametrization
-L=reshape(permute(conj(Al),[3 1 2]),[D D*d]);
+L = reshape(permute(conj(Al),[3 1 2]),[D D*d]);
VL = reshape(null(L),[D d D*(d-1)]);
[x,e] = eigs(@(x)ApplyHeff(x),D^2*(d-1),num,'sr');
-function y=ApplyHeff(x)
+function y = ApplyHeff(x)
- x=reshape(x,[D*(d-1) D]);
- B=ncon({VL,x},{[-1,-2,1],[1,-3]},1);
+ x = reshape(x,[D*(d-1) D]);
+ B = ncon({VL,x},{[-1,-2,1],[1,-3]},1);
% right disconnected
- right=ncon({B,conj(Ar)},{[-1,2,1],[-2,2,1]});
+ right = ncon({B,conj(Ar)},{[-1,2,1],[-2,2,1]});
[right, ~] = gmres(@(v)ApplyELR(v,p), reshape(right, [], 1), [], tol);
right = reshape(right, [D D]);
% left disconnected
- left=...
+ left = ...
1*ncon({Lh,B,conj(Al)},{[1,2],[2,3,-2],[1,3,-1]})+...
1*ncon({Al,B,conj(Al),conj(Al),hTilde},{[1,2,4],[4,5,-2],[1,3,6],[6,7,-1],[3,7,2,5]})+...
exp(-1i*p)*ncon({B,Ar,conj(Al),conj(Al),hTilde},{[1,2,4],[4,5,-2],[1,3,6],[6,7,-1],[3,7,2,5]});
[left, ~] = gmres(@(v)ApplyERL(v,-p), reshape(left, [], 1), [], tol);
left = reshape(left, [D D]);
- y=...
+ y = ...
1*ncon({B,Ar,conj(Ar),hTilde},{[-1,2,1],[1,3,4],[-3,5,4],[-2,5,2,3]})+...
exp(1i*p)*ncon({Al,B,conj(Ar),hTilde},{[-1,2,1],[1,3,4],[-3,5,4],[-2,5,2,3]})+...
exp(-1i*p)*ncon({B,Ar,conj(Al),hTilde},{[4,3,1],[1,2,-3],[4,5,-1],[5,-2,3,2]})+...
@@ -1119,24 +1121,24 @@
exp(-1i*p)*ncon({left,Ar},{[-1,1],[1,-2,-3]})+...
exp(+1i*p)*ncon({Lh,Al,right},{[-1,1],[1,-2,2],[2,-3]});
- y=ncon({y,conj(VL)},{[1,2,-2],[1,2,-1]});
- y=reshape(y,[],1);
+ y = ncon({y,conj(VL)},{[1,2,-2],[1,2,-1]});
+ y = reshape(y,[],1);
end
- function y=ApplyELR(x,p)
- x=reshape(x,[D D]);
- overlap=ncon({conj(C),x},{[1,2],[1,2]});
- y=ncon({Al,conj(Ar),x},{[-1,3,1],[-2,3,2],[1,2]});
- y=x-exp(1i*p)*(y-overlap*C);
- y=reshape(y,[D^2 1]);
+ function y = ApplyELR(x,p)
+ x = reshape(x,[D D]);
+ overlap = ncon({conj(C),x},{[1,2],[1,2]});
+ y = ncon({Al,conj(Ar),x},{[-1,3,1],[-2,3,2],[1,2]});
+ y = x-exp(1i*p)*(y-overlap*C);
+ y = reshape(y,[D^2 1]);
end
- function y=ApplyERL(x,p)
- x=reshape(x,[D D]);
- overlap=ncon({conj(C),x},{[1,2],[1,2]});
- y=ncon({x,Ar,conj(Al)},{[1,2],[2,3,-2],[1,3,-1]});
- y=x-exp(1i*p)*(y-overlap*C);
- y=reshape(y,[D^2 1]);
+ function y = ApplyERL(x,p)
+ x = reshape(x,[D D]);
+ overlap = ncon({conj(C),x},{[1,2],[1,2]});
+ y = ncon({x,Ar,conj(Al)},{[1,2],[2,3,-2],[1,3,-1]});
+ y = x-exp(1i*p)*(y-overlap*C);
+ y = reshape(y,[D^2 1]);
end
end
diff --git a/python/chapter1.ipynb b/python/chapter1.ipynb
index 7ca4f40..5923902 100644
--- a/python/chapter1.ipynb
+++ b/python/chapter1.ipynb
@@ -15,22 +15,22 @@
},
{
"cell_type": "markdown",
- "metadata": {},
+ "metadata": {
+ "tags": []
+ },
"source": [
- "# Tangent-space methods for uniform matrix product states\n",
- "\n",
- "https://arxiv.org/abs/1810.07006\n",
+ "# [Tangent-space methods for uniform matrix product states](https://doi.org/10.21468/SciPostPhysLectNotes.7)\n",
"\n",
"## 1. Matrix product states in the thermodynamic limit\n",
- "### 1.1 Normalisation\n",
- "We start by considering a uniform MPS in the thermodynamic limit, which is defined by \n",
- "$$ |\\Psi(A)> = \\sum_i^d \\nu_L^\\dagger \\left[ \\prod_{m\\in Z} A_i \\right]\\nu_R |i>. $$\n",
+ "### 1.1 Normalization\n",
+ "We start by considering a uniform MPS in the thermodynamic limit, which is defined as \n",
+ "$$\\left | \\Psi(A) \\right \\rangle = \\sum_{\\{i\\}} \\boldsymbol{v}_L^\\dagger \\left[ \\prod_{m\\in\\mathbb{Z}} A^{i_m} \\right] \\boldsymbol{v}_R \\left | \\{i\\} \\right \\rangle.$$\n",
"\n",
- "Here, $A_i$ are complex matrices of size $D \\times D$, for every entry of the index $d$. This allows for the interpretation of the object $A$ as a three-legged tensor of dimensions $D\\times d \\times D$, where we will refer to $D$ as the bond dimension and $d$ as the physical dimension. With this object and the diagrammatic language of tensor networks, we can represent the state as\n",
+ "Here, $\\boldsymbol{v}_L^\\dagger$ and $\\boldsymbol{v}_R$ represent boundary vectors at infinity and the $A^i$ are complex matrices of size $D \\times D$ for every entry of the index $i$. This allows for the interpretation of the object $A$ as a three-legged tensor of dimensions $D\\times d \\times D$, where we will refer to $D$ as the bond dimension and $d$ as the physical dimension. With this object and the diagrammatic language of tensor networks, we can represent the state as\n",
"\n",
- "\n",
+ "
\n",
"\n",
- "Thus, we initialise and represent a uniform MPS state as follows:"
+ "Thus, we initialize and represent a uniform MPS state as follows:"
]
},
{
@@ -77,28 +77,29 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "One of the most relevant objects in all our calculations will be the transfer matrix, defined as\n",
+ "One of the central objects in any MPS calculation is the transfer matrix, defined in our case as\n",
"\n",
- "\n",
+ "
\n",
"\n",
- "where we will be using the convention of ordering the legs as\n",
+ "This object corresponds to an operator acting on the space of $D \\times D$ matrices, and can be interpreted as a 4-leg tensor. We will use the following convention for ordering the legs:\n",
"1. top left\n",
"2. bottom left\n",
"3. top right\n",
"4. bottom right\n",
"\n",
- "The transfer matrix can be shown to be a completely positive map, such that the leading eigenvalue is a positive number, which we conveniently rescale to unity for a proper normalization of the state in the thermodynamic limit. This means solving the (left and right) eigenvalue equation:\n",
+ "The transfer matrix can be shown to be a completely positive map, such that its leading eigenvalue is a positive number. This eigenvalue should be rescaled to one to ensure a proper normalization of the state in the thermodynamic limit. To perform this normalization, we must therefore find the left and right fixed points $l$ and $r$ which correspond to the largest eigenvalues of the eigenvalue equations\n",
"\n",
- "\n",
+ "
\n",
"\n",
- "Additionally, we may fix the normalisation of the eigenvectors by requiring that their trace is equal to one:\n",
+ "Normalizing the state then means rescaling the MPS tensor $A \\leftarrow A / \\sqrt{\\lambda}$. Additionally, we may fix the normalization of the eigenvectors by requiring that their trace is equal to one:\n",
"\n",
- "\n",
+ "
\n",
"\n",
+ "With these properties in place, the norm of an MPS can be evaluated as\n",
"\n",
- "If we now require that there is no influence of the boundary on the bulk, we require the overlap of the boundary vectors with the fixed points to equal unity. In this case, we will have properly normalised the MPS:\n",
+ "
\n",
"\n",
- "\n"
+ "It can be readily seen that the infinite product of transfer matrices reduces to a projector onto the fixed points, so that the norm reduces to the overlap between the boundary vectors and the fixed points. Since there is no effect of the boundary vectors on the bulk properties of the MPS, we can always choose these such that MPS is properly normalized as $ \\left \\langle \\Psi(\\bar{A})\\middle | \\Psi(A) \\right \\rangle = 1$."
]
},
{
@@ -113,13 +114,13 @@
" \n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
" \n",
" Returns\n",
" -------\n",
- " E : np.array(D, D, D, D)\n",
+ " E : np.array (D, D, D, D)\n",
" Transfermatrix with 4 legs,\n",
" ordered topLeft-bottomLeft-topRight-bottomRight.\n",
" \"\"\"\n",
@@ -137,26 +138,26 @@
"metadata": {},
"outputs": [],
"source": [
- "def normaliseMPS(A):\n",
+ "def normalizeMPS(A):\n",
" \"\"\"\n",
- " Normalise an MPS tensor.\n",
+ " Normalize an MPS tensor.\n",
" \n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
" \n",
" Returns\n",
" -------\n",
- " Anew : np.array(D, d, D)\n",
+ " Anew : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
" \n",
" Complexity\n",
" ----------\n",
" O(D ** 6) algorithm,\n",
- " diagonalising (D ** 2, D ** 2) matrix.\n",
+ " diagonalizing (D ** 2, D ** 2) matrix.\n",
" \"\"\"\n",
"\n",
" return Anew"
@@ -174,20 +175,20 @@
" \n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
" \n",
" Returns\n",
" -------\n",
- " l : np.array(D, D)\n",
+ " l : np.array (D, D)\n",
" left fixed point with 2 legs,\n",
" ordered bottom-top.\n",
" \n",
" Complexity\n",
" ----------\n",
" O(D ** 6) algorithm,\n",
- " diagonalising (D ** 2, D ** 2) matrix.\n",
+ " diagonalizing (D ** 2, D ** 2) matrix.\n",
" \"\"\"\n",
" \n",
" return l"
@@ -205,20 +206,20 @@
" \n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
" \n",
" Returns\n",
" -------\n",
- " r : np.array(D, D)\n",
+ " r : np.array (D, D)\n",
" right fixed point with 2 legs,\n",
" ordered top-bottom.\n",
" \n",
" Complexity\n",
" ----------\n",
" O(D ** 6) algorithm,\n",
- " diagonalising (D ** 2, D ** 2) matrix.\n",
+ " diagonalizing (D ** 2, D ** 2) matrix.\n",
" \"\"\"\n",
" \n",
" return r"
@@ -232,27 +233,27 @@
"source": [
"def fixedPoints(A):\n",
" \"\"\"\n",
- " Find normalised fixed points.\n",
+ " Find normalized fixed points.\n",
" \n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
" \n",
" Returns\n",
" -------\n",
- " l : np.array(D, D)\n",
+ " l : np.array (D, D)\n",
" left fixed point with 2 legs,\n",
" ordered bottom-top.\n",
- " r : np.array(D, D)\n",
+ " r : np.array (D, D)\n",
" right fixed point with 2 legs,\n",
" ordered top-bottom.\n",
" \n",
" Complexity\n",
" ----------\n",
" O(D ** 6) algorithm,\n",
- " diagonalising (D ** 2, D ** 2) matrix\n",
+ " diagonalizing (D ** 2, D ** 2) matrix\n",
" \"\"\"\n",
" \n",
" return l, r"
@@ -266,7 +267,7 @@
},
"outputs": [],
"source": [
- "A = normaliseMPS(A)\n",
+ "A = normalizeMPS(A)\n",
"l, r = fixedPoints(A)\n",
"\n",
"assert np.allclose(l, np.conj(l).T, 1e-12), \"left fixed point should be hermitian!\"\n",
@@ -274,27 +275,47 @@
"\n",
"assert np.allclose(l, ncon((A, l, np.conj(A)), ([1, 2, -2], [3, 1], [3, 2, -1])), 1e-12), \"l should be a left fixed point!\"\n",
"assert np.allclose(r, ncon((A, r, np.conj(A)), ([-1, 2, 1], [1, 3], [-2, 2, 3])), 1e-12), \"r should be a right fixed point!\"\n",
- "assert np.abs(ncon((l, r), ([1, 2], [2, 1])) - 1) < 1e-12, \"Left and right fixed points should be trace normalised!\""
+ "assert np.abs(ncon((l, r), ([1, 2], [2, 1])) - 1) < 1e-12, \"Left and right fixed points should be trace normalized!\""
]
},
{
"cell_type": "markdown",
- "metadata": {},
+ "metadata": {
+ "tags": []
+ },
"source": [
- "### 1.2 Gauge fixing\n",
- "While a single $A$ corresponds to a unique state $|\\Psi(A)>$, the converse is not true, as different tensors may give rise to the same state. This is easily seen by noting that the gauge transform\n",
- "\n",
+ "### 1.2 Gauge freedom\n",
+ "While a given MPS tensor $A$ corresponds to a unique state $\\left | \\Psi(A) \\right \\rangle$, the converse is not true, as different tensors may give rise to the same state. This is easily seen by noting that the gauge transform\n",
+ "\n",
+ "
\n",
"\n",
"leaves the physical state invariant. We may use this freedom in parametrization to impose canonical forms on the MPS tensor $A$.\n",
"\n",
- "We start by considering the left-orthonormal form $A_L$ of an MPS, which has the property that\n",
- "\n",
+ "We start by considering the *left-orthonormal form* of an MPS, which is defined in terms of a tensor $A_L$ that satisfies the condition\n",
+ "\n",
+ "
\n",
+ "\n",
+ "We can find the gauge transform $L$ that brings $A$ into this form\n",
+ "\n",
+ "
\n",
+ "\n",
+ "by decomposing the fixed point $l$ as $l = L^\\dagger L$, such that\n",
+ "\n",
+ "
\n",
+ "\n",
+ "Note that this gauge choice still leaves room for unitary gauge transformations\n",
+ "\n",
+ "
\n",
"\n",
- "We can find the matrix $L$ that brings $A$ into this form by decomposing the fixed point $l$ as $l = L^\\dagger L$, such that\n",
- "\n",
+ "which can be used to bring the right fixed point $r$ into diagonal form. Similarly, we can find the gauge transform that brings $A$ into *right-orthonormal form*\n",
"\n",
- "Furthermore, there is still room for unitary gauge transformations such that we can also bring the right fixed point in diagonal form. Similarly, we can consider the right-orthonormal form $A_R$, where we have\n",
- ""
+ "
\n",
+ "\n",
+ "such that\n",
+ "\n",
+ "
\n",
+ "\n",
+ "and the left fixed point $l$ is diagonal. The routines that bring a given MPS into canonical form by decomposing the corresponding transfer matrix fixed points can be defined as follows:"
]
},
{
@@ -303,34 +324,30 @@
"metadata": {},
"outputs": [],
"source": [
- "\"\"\"As transfer matrix is positive definite, fixed points are also positive definite, thus allowing for a\n",
- "square root algorithm of the eigenvalues. Note that taking square roots is typically less favourable,\n",
- "as this increases the error\"\"\"\n",
- "\n",
- "def leftOrthonormalise(A, l=None):\n",
+ "def leftOrthonormalize(A, l=None):\n",
" \"\"\"\n",
" Transform A to left-orthonormal gauge.\n",
" \n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
" \n",
" Returns\n",
" -------\n",
- " L : np.array(D, D)\n",
+ " L : np.array (D, D)\n",
" left gauge with 2 legs,\n",
" ordered left-right.\n",
- " Al : np.array(D, d, D)\n",
- " MPS tensor zith 3 legs,\n",
+ " Al : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" left orthonormal\n",
" \n",
" Complexity\n",
" ----------\n",
" O(D ** 6) algorithm,\n",
- " diagonalising (D ** 2, D ** 2) matrix\n",
+ " diagonalizing (D ** 2, D ** 2) matrix\n",
" \"\"\"\n",
" \n",
" return L, Al"
@@ -342,30 +359,30 @@
"metadata": {},
"outputs": [],
"source": [
- "def rightOrthonormalise(A, r=None):\n",
+ "def rightOrthonormalize(A, r=None):\n",
" \"\"\"\n",
" Transform A to right-orthonormal gauge.\n",
" \n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
" \n",
" Returns\n",
" -------\n",
- " R : np.array(D, D)\n",
+ " R : np.array (D, D)\n",
" right gauge with 2 legs,\n",
" ordered left-right.\n",
- " Ar : np.array(D, d, D)\n",
- " MPS tensor zith 3 legs,\n",
+ " Ar : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" left orthonormal\n",
" \n",
" Complexity\n",
" ----------\n",
" O(D ** 6) algorithm,\n",
- " diagonalising (D ** 2, D ** 2) dmatrix\n",
+ " diagonalizing (D ** 2, D ** 2) dmatrix\n",
" \"\"\"\n",
" \n",
" return R, Ar"
@@ -377,8 +394,8 @@
"metadata": {},
"outputs": [],
"source": [
- "L, Al = leftOrthonormalise(A, l)\n",
- "R, Ar = rightOrthonormalise(A, r)\n",
+ "L, Al = leftOrthonormalize(A, l)\n",
+ "R, Ar = rightOrthonormalize(A, r)\n",
"\n",
"assert np.allclose(R @ np.conj(R).T, r), \"Right gauge doesn't square to r\"\n",
"assert np.allclose(np.conj(L).T @ L, l), \"Left gauge doesn't sqaure to l\"\n",
@@ -390,22 +407,27 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "Finally, there is the notion of a mixed gauge for the uniform MPS by choosing a 'center site', bringing the left tensors in left-orthonormal form and the right tensors in right-orthonormal form, and redefining the center tensor as follows:\n",
- "\n",
+ "Finally, we can define a *mixed gauge* for the uniform MPS by choosing one site, the 'center site', and bringing all tensors to the left of it in the left-orthonormal form and all the tensors to the right of it in the right-orthonormal form. Defining a new tensor $A_C$ on the center site, we obtain the form\n",
+ "\n",
+ "
\n",
+ "\n",
+ "By contrast, the original representation using the same tensor at every site is commonly referred to as the *uniform gauge*. The mixed gauge has an intuitive interpretation. Defining $C = LR$, this tensor then implements the gauge transform that maps the left-orthonormal tensor to the right-orthonormal one, thereby defining the center-site tensor $A_C$:\n",
"\n",
+ "
\n",
"\n",
- "The mixed gauge has an intuitive interpretation. Defining $C = LR$, this implements the gauge transform that maps the left-orthonormal tensor to the right-orthonormal one, hence defining the center-site tensor $A_C$:\n",
- "\n",
- "The above is called the mixed gauge condition and allows us to freely move the center tensor $A_C$ around in the MPS.\n",
+ "This relation is called the mixed gauge condition and allows us to freely move the center tensor $A_C$ through the MPS, linking the left- and right orthonormal tensors.\n",
"\n",
+ "Finally we may bring $C$ into diagonal form by performing a singular value decomposition $C = USV^\\dagger$ and absorbing $U$ and $V^\\dagger$ into the definition of $A_L$ and $A_R$ using the residual unitary gauge freedom\n",
"\n",
- "Finally we may perform an SVD of $C = USV^\\dagger$, and taking up $U$ and $V^\\dagger$ in the definition of $A_L$ and $A_R$, such that we are left with a diagonal $C$ on the virtual bonds.\n",
- "\n",
+ "
\n",
"\n",
- "In fact, this means that we can write down a Schmidt decomposition of the state across an arbitrary bond in the chain, and the diagonal elements $C_l$ are exactly the Schmidt numbers of any bipartition of the MPS. \n",
- "\n",
- "Hence, we can calculate the bipartite entanglement entropy by \n",
- "$$ S = -\\sum_l C_l^2 \\log(C_l^2) $$\n"
+ "The mixed canonical form with a diagonal $C$ now allows to straightforwardly write down a Schmidt decomposition of the state across an arbitrary bond in the chain\n",
+ "\n",
+ "$$ \\left | \\Psi(A) \\right \\rangle = \\sum_{i=1}^{D} C_i \\left | \\Psi^i_L(A_L) \\right \\rangle \\otimes \\left | \\Psi^i_R(A_R) \\right \\rangle,$$\n",
+ "\n",
+ "where the states $\\left | \\Psi^i_L(A_L) \\right \\rangle$ and $\\left | \\Psi^i_R(A_R) \\right \\rangle$ are orthogonal states on half the lattice. The diagonal elements $C_i$ are exactly the Schmidt numbers of any bipartition of the MPS, and as such determine its bipartite entanglement entropy\n",
+ "\n",
+ "$$ S = -\\sum_i C_i^2 \\log(C_i^2) .$$"
]
},
{
@@ -420,25 +442,25 @@
" \n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
" \n",
" Returns\n",
" -------\n",
- " Al : np.array(D, d, D)\n",
- " MPS tensor zith 3 legs,\n",
+ " Al : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" left orthonormal.\n",
- " Ac : np.array(D, d, D)\n",
- " MPS tensor zith 3 legs,\n",
+ " Ac : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauge.\n",
- " Ar : np.array(D, d, D)\n",
- " MPS tensor zith 3 legs,\n",
+ " Ar : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" right orthonormal.\n",
- " C : np.array(D, D)\n",
+ " C : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right,\n",
" diagonal.\n",
@@ -446,7 +468,7 @@
" Complexity\n",
" ----------\n",
" O(D ** 6) algorithm,\n",
- " diagonalisation of (D ** 2, D ** 2) matrix\n",
+ " diagonalization of (D ** 2, D ** 2) matrix\n",
" \"\"\"\n",
"\n",
" return Al, Ac, Ar, C"
@@ -464,7 +486,7 @@
" \n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
" \n",
@@ -499,15 +521,19 @@
},
{
"cell_type": "markdown",
- "metadata": {},
+ "metadata": {
+ "tags": []
+ },
"source": [
"### 1.3 Truncation of a uniform MPS\n",
- "The mixed canonical form is not only useful for finding the entanglement entropy, it also allows for a way to truncate MPS states efficiently. This is done by truncating the Schmidt decomposition, such that the new MPS has a reduced bond dimension for that ond. This truncation can be shown to be optimal in the sense that the norm between the original and the truncated MPS is minimised. The truncated MPS in the mixed gauge is thus:\n",
- "\n",
"\n",
- "We mention that this is a local optimization, in the sense that it maximizes the local overlap, however not the global overlap. This would require a variational optimization of the following cost function:\n",
- "$$ || ~|\\Psi(A)> - |\\Psi(\\tilde{A})> ||^2 $$\n",
- "We postpone the detailed discussion hereof until later."
+ "The mixed canonical form also enables efficient truncatation an MPS. The sum in the above Schmidt decomposition can be truncated, giving rise to a new MPS that has a reduced bond dimension for that bond. This truncation is optimal in the sense that the norm between the original and the truncated MPS is maximized. To arrive at a translation invariant truncated MPS, we can truncate the columns of the absorbed isometries $U$ and $V^\\dagger$ correspondingly, thereby transforming *every* tensor $A_L$ or $A_R$. The truncated MPS in the mixed gauge is then given by\n",
+ "\n",
+ "
\n",
+ "\n",
+ "We note that the resulting state based on this local truncation is not guaranteed to correspond to the MPS with a lower bond dimension that is globally optimal. This would require a variational optimization of the cost function.\n",
+ "\n",
+ "$$ \\left | \\left | ~\\left | \\Psi(A) \\right \\rangle - \\left | \\Psi(\\tilde{A}) \\right \\rangle ~\\right | \\right |^2.$$"
]
},
{
@@ -522,7 +548,7 @@
" \n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
" Dtrunc : int\n",
@@ -530,19 +556,19 @@
" \n",
" Returns\n",
" -------\n",
- " AlTilde : np.array(Dtrunc, d, Dtrunc)\n",
- " MPS tensor zith 3 legs,\n",
+ " AlTilde : np.array (Dtrunc, d, Dtrunc)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" left orthonormal.\n",
- " AcTilde : np.array(Dtrunc, d, Dtrunc)\n",
- " MPS tensor zith 3 legs,\n",
+ " AcTilde : np.array (Dtrunc, d, Dtrunc)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauge.\n",
- " ArTilde : np.array(Dtrunc, d, Dtrunc)\n",
- " MPS tensor zith 3 legs,\n",
+ " ArTilde : np.array (Dtrunc, d, Dtrunc)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" right orthonormal.\n",
- " CTilde : np.array(Dtrunc, Dtrunc)\n",
+ " CTilde : np.array (Dtrunc, Dtrunc)\n",
" Center gauge with 2 legs,\n",
" ordered left-right,\n",
" diagonal.\n",
@@ -557,8 +583,9 @@
"metadata": {},
"outputs": [],
"source": [
- "AlTilde, AcTilde, ArTilde, CTilde = truncateMPS(A, 3)\n",
- "assert AlTilde.shape[0] == 3 and AlTilde.shape[2] == 3, \"Something went wrong in truncating the MPS\""
+ "Dtrunc = 3\n",
+ "AlTilde, AcTilde, ArTilde, CTilde = truncateMPS(A, Dtrunc)\n",
+ "assert AlTilde.shape[0] == Dtrunc and AlTilde.shape[2] == Dtrunc, \"Something went wrong in truncating the MPS\""
]
},
{
@@ -566,11 +593,9 @@
"metadata": {},
"source": [
"### 1.4 Algorithms for finding canonical forms\n",
- "One of the great things of MPS is that they provide efficient approximations of physical states, and for gapped systems they are expected to be exact in the limit $D \\rightarrow \\infty$. The idea is that if we increase the bond dimension enough, we can get to arbitrary precision. However, increasing the bond dimension comes at a numerical cost, as the MPS algorithms scale in $D$. It is possible to ensure that the complexity of all MPS algorithms scales as $O(D^3)$, however this means we need to be a little smarter with the previously encountered algorithms.\n",
- "\n",
- "As a first step, we can refrain from explicitly building the matrices that are used in the eigenvalue solvers, but this will easily extend to all following algorithms. We can circumvent explicitly building the matrix by implementing a function that corresponds to the action of the operator, usually called a handle, and passing this to the eigenvalue solver.\n",
+ "The success of using MPS for describing physical systems stems from the fact that they provide efficient approximations to a large class of physically relevant states. In one dimension, they have been shown to approximate low-energy states of gapped systems arbitrarily well at only a polynomial cost in the bond dimension $D$. This means that in principle we can push MPS results for these systems to arbitrary precision as long as we increase the bond dimension enough. However, increasing the bond dimension comes at a numerical cost, as the complexity of any MPS algorithm scales with $D$. As opposed to the naive routines given above, it is possible to ensure that the complexity of all MPS algorithms scales as $O(D^3)$, so long as we are a bit careful when implementing them.\n",
"\n",
- "For example, a better way of fixing the normalisation of an MPS tensor, as well as determining left and right fixed points is by implementing the handles and using the optimal contraction sequences:"
+ "As a first example, we can refrain from explicitly contructing the matrices that are used in the eigenvalue problems, and instead pass a function that implements the action of the corresponding operator on a vector to the eigenvalue solver. We demonstrate this for the problem of normalizing an MPS, where instead of explicitly constructing the transfer matrix we now define a function handle which implements its action on the right and left fixed points using an optimal contraction sequence:"
]
},
{
@@ -579,19 +604,19 @@
"metadata": {},
"outputs": [],
"source": [
- "def normaliseMPS(A):\n",
+ "def normalizeMPS(A):\n",
" \"\"\"\n",
- " Normalise an MPS tensor.\n",
+ " Normalize an MPS tensor.\n",
"\n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
"\n",
" Returns\n",
" -------\n",
- " Anew : np.array(D, d, D)\n",
+ " Anew : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
"\n",
@@ -616,13 +641,13 @@
"\n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
"\n",
" Returns\n",
" -------\n",
- " l : np.array(D, D)\n",
+ " l : np.array (D, D)\n",
" left fixed point with 2 legs,\n",
" ordered bottom-top.\n",
"\n",
@@ -647,13 +672,13 @@
"\n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
"\n",
" Returns\n",
" -------\n",
- " r : np.array(D, D)\n",
+ " r : np.array (D, D)\n",
" right fixed point with 2 legs,\n",
" ordered top-bottom.\n",
"\n",
@@ -674,20 +699,20 @@
"source": [
"def fixedPoints(A):\n",
" \"\"\"\n",
- " Find normalised fixed points.\n",
+ " Find normalized fixed points.\n",
"\n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
"\n",
" Returns\n",
" -------\n",
- " l : np.array(D, D)\n",
+ " l : np.array (D, D)\n",
" left fixed point with 2 legs,\n",
" ordered bottom-top.\n",
- " r : np.array(D, D)\n",
+ " r : np.array (D, D)\n",
" right fixed point with 2 legs,\n",
" ordered top-bottom.\n",
"\n",
@@ -697,7 +722,7 @@
" D ** 3 contraction for transfer matrix handle.\n",
" \"\"\"\n",
"\n",
- " return l / trace, r"
+ " return l, r"
]
},
{
@@ -709,7 +734,7 @@
"outputs": [],
"source": [
"A = createMPS(D, d)\n",
- "A = normaliseMPS(A)\n",
+ "A = normalizeMPS(A)\n",
"l, r = fixedPoints(A)\n",
"\n",
"assert np.allclose(l, np.conj(l).T, 1e-12), \"left fixed point should be hermitian!\"\n",
@@ -717,14 +742,31 @@
"\n",
"assert np.allclose(l, ncon((A, l, np.conj(A)), ([1, 2, -2], [3, 1], [3, 2, -1])), 1e-12), \"l should be a left fixed point!\"\n",
"assert np.allclose(r, ncon((A, r, np.conj(A)), ([-1, 2, 1], [1, 3], [-2, 2, 3])), 1e-12), \"r should be a right fixed point!\"\n",
- "assert abs(ncon((l, r), ([1, 2], [2, 1])) - 1) < 1e-12, \"Left and right fixed points should be trace normalised!\""
+ "assert abs(ncon((l, r), ([1, 2], [2, 1])) - 1) < 1e-12, \"Left and right fixed points should be trace normalized!\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "Furthermore, taking the square root of such a fixed point to find the left and right gauge is also a way to introduce larger errors than neccesary. This can be circumvented by using an iterative scheme that makes use of a unique decomposition, such as a QR or polar decomposition. Note however that for a QR decomposition to be unique we need the diagonal elements of R to have a positive sign."
+ "We can similarly improve both the efficiency and accuracy of the routines bringing a given MPS into its mixed canonical form. While plugging in the more efficient ways of finding the left and right fixed point into the above `mixedCanonical` routine would reduce its complexity to $O(D^3)$, this algorithm would still be suboptimal in terms of numerical accuracy. This arises from the fact that, while $l$ and $r$ are theoretically known to be positive hermitian matrices, at least one of them will nevertheless have small eigenvalues, say of order $\\eta$, if the MPS is supposed to provide a good approximation to an actual state. In practice, $l$ and $r$ are determined using an iterative eigensolver and will only be accurate up to a specified tolerance $\\epsilon$. Upon taking the 'square roots' $L$ and $R$, the numerical precision will then decrease to $\\text{min}(\\sqrt{\\epsilon}, \\epsilon/\\sqrt{\\eta})$. Furthermore, gauge transforming $A$ with $L$ or $R$ requires the potentially ill-conditioned inversions of $L$ and $R$, and will typically yield $A_L$ and $A_R$ which violate the orthonormalization condition in the same order $\\epsilon/\\sqrt{\\eta}$. We can circumvent both these probelems by resorting to so-called *single-layer algorithms*. These are algorithms that only work on the level of the MPS tensors in the ket layer, and never consider operations for which contractions with the bra layer are needed. We now demonstrate such a single-layer algorithm for finding canonical forms.\n",
+ "\n",
+ "Suppose we are given an MPS tensor $A$, then from the above discussion we know that bringing it into left canonical form means finding a left-orthonormal tensor $A_L$ and a matrix $L$ such that $L A=A_L L$. The idea is then to solve this equation iteratively, where in every iteration\n",
+ "\n",
+ "1. we start from a matrix $L^{i}$\n",
+ "2. we construct the tensor $L^{i}A$\n",
+ "3. we take a QR decomposition to obtain $A_L^{i+1} L^{i+1} = L^{i}A$, and\n",
+ "4. we take $L^{i+1}$ to the next iteration\n",
+ "\n",
+ "The QR decomposition is represented diagrammatically as\n",
+ "\n",
+ "
\n",
+ "\n",
+ "This iterative procedure is bound to converge to a fixed point for which $L^{(i+1)}=L^{(i)}=L$ and $A_L$ is left orthonormal by construction:\n",
+ "\n",
+ "
\n",
+ "\n",
+ "A similar procedure can be used to find a right-orthonormal tensor $A_R$ and a matrix $R$ such that $A R = R A_R$. It is important to note that the convergence of this procedure relies on the fact that the QR decomposition is unique, which is not actually the case in general. However, it can be made unique by imposing that the diagonal elements of the triangular matrix $R$ must be positive. This extra condition is imposed in the routines `qrPos` and `rqPos` defined below."
]
},
{
@@ -739,15 +781,15 @@
" \n",
" Parameters\n",
" ----------\n",
- " A : np.array(M, N)\n",
+ " A : np.array (M, N)\n",
" Matrix to decompose.\n",
" \n",
" Returns\n",
" -------\n",
- " R : np.array(M, M)\n",
+ " R : np.array (M, M)\n",
" Upper triangular matrix,\n",
" positive diagonal elements.\n",
- " Q : np.array(M, N)\n",
+ " Q : np.array (M, N)\n",
" Orthogonal matrix.\n",
" \n",
" Complexity\n",
@@ -755,7 +797,6 @@
" ~O(max(M, N) ** 3) algorithm.\n",
" \"\"\"\n",
"\n",
- " \n",
" M, N = A.shape\n",
" \n",
" # LQ decomposition: scipy conventions: Q.shape = (N, N), L.shape = (M, N)\n",
@@ -773,16 +814,16 @@
" return R, Q\n",
"\n",
"\n",
- "def rightOrthonormalise(A, R0=None, tol=1e-14, maxIter=1e5):\n",
+ "def rightOrthonormalize(A, R0=None, tol=1e-14, maxIter=1e5):\n",
" \"\"\"\n",
" Transform A to right-orthonormal gauge.\n",
"\n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
- " R0 : np.array(D, D), optional\n",
+ " R0 : np.array (D, D), optional\n",
" Right gauge matrix,\n",
" initial guess.\n",
" tol : float, optional\n",
@@ -793,11 +834,11 @@
"\n",
" Returns\n",
" -------\n",
- " R : np.array(D, D)\n",
+ " R : np.array (D, D)\n",
" right gauge with 2 legs,\n",
" ordered left-right.\n",
- " Ar : np.array(D, d, D)\n",
- " MPS tensor zith 3 legs,\n",
+ " Ar : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" right-orthonormal\n",
" \"\"\"\n",
@@ -817,14 +858,14 @@
" \n",
" Parameters\n",
" ----------\n",
- " A : np.array(M, N)\n",
+ " A : np.array (M, N)\n",
" Matrix to decompose.\n",
" \n",
" Returns\n",
" -------\n",
- " Q : np.array(M, N)\n",
+ " Q : np.array (M, N)\n",
" Orthogonal matrix.\n",
- " R : np.array(N, N)\n",
+ " R : np.array (N, N)\n",
" Upper triangular matrix,\n",
" positive diagonal elements.\n",
" \n",
@@ -850,16 +891,16 @@
" return Q, R\n",
"\n",
"\n",
- "def leftOrthonormalise(A, L0=None, tol=1e-14, maxIter=1e5):\n",
+ "def leftOrthonormalize(A, L0=None, tol=1e-14, maxIter=1e5):\n",
" \"\"\"\n",
" Transform A to left-orthonormal gauge.\n",
"\n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
- " L0 : np.array(D, D), optional\n",
+ " L0 : np.array (D, D), optional\n",
" Left gauge matrix,\n",
" initial guess.\n",
" tol : float, optional\n",
@@ -870,11 +911,11 @@
"\n",
" Returns\n",
" -------\n",
- " L : np.array(D, D)\n",
+ " L : np.array (D, D)\n",
" left gauge with 2 legs,\n",
" ordered left-right.\n",
- " Al : np.array(D, d, D)\n",
- " MPS tensor zith 3 legs,\n",
+ " Al : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" left-orthonormal\n",
" \"\"\"\n",
@@ -894,25 +935,25 @@
"\n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
"\n",
" Returns\n",
" -------\n",
- " Al : np.array(D, d, D)\n",
- " MPS tensor zith 3 legs,\n",
+ " Al : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" left orthonormal.\n",
- " Ac : np.array(D, d, D)\n",
- " MPS tensor zith 3 legs,\n",
+ " Ac : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauge.\n",
- " Ar : np.array(D, d, D)\n",
- " MPS tensor zith 3 legs,\n",
+ " Ar : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" right orthonormal.\n",
- " C : np.array(D, D)\n",
+ " C : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right,\n",
" diagonal.\n",
@@ -954,17 +995,20 @@
"metadata": {},
"source": [
"### 1.5 Computing expectation values \n",
- "Having described the states in different gauges, we want to use these to determine the expectation values of extensive operators:\n",
- "$$ O = \\frac{1}{Z} \\sum_{n\\in Z} O_n $$\n",
+ "Now that we have seen the different ways to parametrize a given MPS, namely the uniform gauge and the mixed gauge, we wish to use these to compute expectation values of an extensive operator:\n",
+ "$$ O = \\frac{1}{\\mathbb{Z}} \\sum_{n \\in \\mathbb{Z}} O_n. $$\n",
+ "\n",
+ "If we assume that each $O_n$ acts on a single site and we are working with a properly normalized MPS, translation invariance dictates that the expectation value of $O$ is given by the contraction\n",
+ "\n",
+ "
\n",
"\n",
- "If we work with properly normalized MPS, the expectation value per site of a one-body operator (such as an order parameter e.g. $X_i$) is then found by considering the following contraction:\n",
- "\n",
+ "In the uniform gauge, we can use the fixed points of the transfer matrix to contract everything to the left and to the right of the operator, such that we are left with the contraction\n",
"\n",
- "If we use the uniform gauge, we can use the fixed points of the transfer matrix to collapse everything on the left and right, such that we are left with the contraction:\n",
- "\n",
+ "
\n",
"\n",
- "However, in the mixed gauge, we can locate the center site where the operator is acting, and then contract everything to the left and right to the identity, such that we find\n",
- ""
+ "In the mixed gauge, we can locate the center site where the operator is acting, and then contract everything to the left and right to the identity to arrive at the particularly simple expression\n",
+ "\n",
+ "
"
]
},
{
@@ -979,24 +1023,24 @@
"\n",
" Parameters\n",
" ----------\n",
- " O : np.array(d, d)\n",
+ " O : np.array (d, d)\n",
" single-site operator.\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
- " l : np.array(D, D), optional\n",
+ " l : np.array (D, D), optional\n",
" left fixed point of transfermatrix,\n",
- " normalised.\n",
- " r : np.array(D, D), optional\n",
+ " normalized.\n",
+ " r : np.array (D, D), optional\n",
" right fixed point of transfermatrix,\n",
- " normalised.\n",
+ " normalized.\n",
"\n",
" Returns\n",
" -------\n",
" o : complex float\n",
" expectation value of O.\n",
" \"\"\"\n",
- " \n",
+ "\n",
" # given as an example:\n",
"\n",
" # calculate fixed points if not given\n",
@@ -1021,9 +1065,9 @@
"\n",
" Parameters\n",
" ----------\n",
- " O : np.array(d, d)\n",
+ " O : np.array (d, d)\n",
" single-site operator.\n",
- " Ac : np.array(D, d, D)\n",
+ " Ac : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauged.\n",
@@ -1045,7 +1089,7 @@
"source": [
"O = np.random.rand(d,d) + 1.0j * np.random.rand(d,d)\n",
"A = createMPS(D, d)\n",
- "A = normaliseMPS(A)\n",
+ "A = normalizeMPS(A)\n",
"Al, Ac, Ar, C = mixedCanonical(A)\n",
"expVal = expVal1Uniform(O, A)\n",
"expValMix = expVal1Mixed(O, Ac)\n",
@@ -1057,8 +1101,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "This procedure generalises easily to operators that act on multiple sites. In particular, the Hamiltonian $h$ can be evaluated as\n",
- ""
+ "This procedure can be readily generalized to operators that act on multiple sites. In particular, a two-site operator such as a Hamiltonian term $h$ can be evaluated as\n",
+ "\n",
+ "
"
]
},
{
@@ -1073,18 +1118,18 @@
"\n",
" Parameters\n",
" ----------\n",
- " O : np.array(d, d, d, d)\n",
+ " O : np.array (d, d, d, d)\n",
" two-site operator,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight.\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
- " l : np.array(D, D), optional\n",
+ " l : np.array (D, D), optional\n",
" left fixed point of transfermatrix,\n",
- " normalised.\n",
- " r : np.array(D, D), optional\n",
+ " normalized.\n",
+ " r : np.array (D, D), optional\n",
" right fixed point of transfermatrix,\n",
- " normalised.\n",
+ " normalized.\n",
"\n",
" Returns\n",
" -------\n",
@@ -1107,14 +1152,14 @@
"\n",
" Parameters\n",
" ----------\n",
- " O : np.array(d, d, d, d)\n",
+ " O : np.array (d, d, d, d)\n",
" two-site operator,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight.\n",
- " Ac : np.array(D, d, D)\n",
+ " Ac : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauged.\n",
- " Ar : np.array(D, d, D)\n",
+ " Ar : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" right gauged.\n",
@@ -1149,7 +1194,7 @@
"metadata": {
"celltoolbar": "Attachments",
"kernelspec": {
- "display_name": "Python 3",
+ "display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
@@ -1163,7 +1208,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.7.9"
+ "version": "3.8.3"
}
},
"nbformat": 4,
diff --git a/python/chapter1Solution.ipynb b/python/chapter1Solution.ipynb
index 7ce4509..2410090 100644
--- a/python/chapter1Solution.ipynb
+++ b/python/chapter1Solution.ipynb
@@ -15,22 +15,23 @@
},
{
"cell_type": "markdown",
- "metadata": {},
+ "metadata": {
+ "jp-MarkdownHeadingCollapsed": true,
+ "tags": []
+ },
"source": [
- "# Tangent-space methods for uniform matrix product states\n",
- "\n",
- "https://arxiv.org/abs/1810.07006\n",
+ "# [Tangent-space methods for uniform matrix product states](https://doi.org/10.21468/SciPostPhysLectNotes.7)\n",
"\n",
"## 1. Matrix product states in the thermodynamic limit\n",
- "### 1.1 Normalisation\n",
- "We start by considering a uniform MPS in the thermodynamic limit, which is defined by \n",
- "$$ |\\Psi(A)> = \\sum_i^d \\nu_L^\\dagger \\left[ \\prod_{m\\in Z} A_i \\right]\\nu_R |i>. $$\n",
+ "### 1.1 Normalization\n",
+ "We start by considering a uniform MPS in the thermodynamic limit, which is defined as \n",
+ "$$\\left | \\Psi(A) \\right \\rangle = \\sum_{\\{i\\}} \\boldsymbol{v}_L^\\dagger \\left[ \\prod_{m\\in\\mathbb{Z}} A^{i_m} \\right] \\boldsymbol{v}_R \\left | \\{i\\} \\right \\rangle.$$\n",
"\n",
- "Here, $A_i$ are complex matrices of size $D \\times D$, for every entry of the index $d$. This allows for the interpretation of the object $A$ as a three-legged tensor of dimensions $D\\times d \\times D$, where we will refer to $D$ as the bond dimension and $d$ as the physical dimension. With this object and the diagrammatic language of tensor networks, we can represent the state as\n",
+ "Here, $\\boldsymbol{v}_L^\\dagger$ and $\\boldsymbol{v}_R$ represent boundary vectors at infinity and the $A^i$ are complex matrices of size $D \\times D$ for every entry of the index $i$. This allows for the interpretation of the object $A$ as a three-legged tensor of dimensions $D\\times d \\times D$, where we will refer to $D$ as the bond dimension and $d$ as the physical dimension. With this object and the diagrammatic language of tensor networks, we can represent the state as\n",
"\n",
- "\n",
+ "
\n",
"\n",
- "Thus, we initialise and represent a uniform MPS state as follows:"
+ "Thus, we initialize and represent a uniform MPS state as follows:"
]
},
{
@@ -79,28 +80,29 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "One of the most relevant objects in all our calculations will be the transfer matrix, defined as\n",
+ "One of the central objects in any MPS calculation is the transfer matrix, defined in our case as\n",
"\n",
- "\n",
+ "
\n",
"\n",
- "where we will be using the convention of ordering the legs as\n",
+ "This object corresponds to an operator acting on the space of $D \\times D$ matrices, and can be interpreted as a 4-leg tensor. We will use the following convention for ordering the legs:\n",
"1. top left\n",
"2. bottom left\n",
"3. top right\n",
"4. bottom right\n",
"\n",
- "The transfer matrix can be shown to be a completely positive map, such that the leading eigenvalue is a positive number, which we conveniently rescale to unity for a proper normalization of the state in the thermodynamic limit. This means solving the (left and right) eigenvalue equation:\n",
+ "The transfer matrix can be shown to be a completely positive map, such that its leading eigenvalue is a positive number. This eigenvalue should be rescaled to one to ensure a proper normalization of the state in the thermodynamic limit. To perform this normalization, we must therefore find the left and right fixed points $l$ and $r$ which correspond to the largest eigenvalues of the eigenvalue equations\n",
"\n",
- "\n",
+ "
\n",
"\n",
- "Additionally, we may fix the normalisation of the eigenvectors by requiring that their trace is equal to one:\n",
+ "Normalizing the state then means rescaling the MPS tensor $A \\leftarrow A / \\sqrt{\\lambda}$. Additionally, we may fix the normalization of the eigenvectors by requiring that their trace is equal to one:\n",
"\n",
- "\n",
+ "
\n",
"\n",
+ "With these properties in place, the norm of an MPS can be evaluated as\n",
"\n",
- "If we now require that there is no influence of the boundary on the bulk, we require the overlap of the boundary vectors with the fixed points to equal unity. In this case, we will have properly normalised the MPS:\n",
+ "
\n",
"\n",
- "\n"
+ "It can be readily seen that the infinite product of transfer matrices reduces to a projector onto the fixed points, so that the norm reduces to the overlap between the boundary vectors and the fixed points. Since there is no effect of the boundary vectors on the bulk properties of the MPS, we can always choose these such that MPS is properly normalized as $ \\left \\langle \\Psi(\\bar{A})\\middle | \\Psi(A) \\right \\rangle = 1$."
]
},
{
@@ -115,13 +117,13 @@
" \n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
" \n",
" Returns\n",
" -------\n",
- " E : np.array(D, D, D, D)\n",
+ " E : np.array (D, D, D, D)\n",
" Transfermatrix with 4 legs,\n",
" ordered topLeft-bottomLeft-topRight-bottomRight.\n",
" \"\"\"\n",
@@ -137,26 +139,26 @@
"metadata": {},
"outputs": [],
"source": [
- "def normaliseMPS(A):\n",
+ "def normalizeMPS(A):\n",
" \"\"\"\n",
- " Normalise an MPS tensor.\n",
+ " Normalize an MPS tensor.\n",
" \n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
" \n",
" Returns\n",
" -------\n",
- " Anew : np.array(D, d, D)\n",
+ " Anew : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
" \n",
" Complexity\n",
" ----------\n",
" O(D ** 6) algorithm,\n",
- " diagonalising (D ** 2, D ** 2) matrix.\n",
+ " diagonalizing (D ** 2, D ** 2) matrix.\n",
" \"\"\"\n",
"\n",
" D = A.shape[0]\n",
@@ -181,20 +183,20 @@
" \n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
" \n",
" Returns\n",
" -------\n",
- " l : np.array(D, D)\n",
+ " l : np.array (D, D)\n",
" left fixed point with 2 legs,\n",
" ordered bottom-top.\n",
" \n",
" Complexity\n",
" ----------\n",
" O(D ** 6) algorithm,\n",
- " diagonalising (D ** 2, D ** 2) matrix.\n",
+ " diagonalizing (D ** 2, D ** 2) matrix.\n",
" \"\"\"\n",
" \n",
" D = A.shape[0]\n",
@@ -226,20 +228,20 @@
" \n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
" \n",
" Returns\n",
" -------\n",
- " r : np.array(D, D)\n",
+ " r : np.array (D, D)\n",
" right fixed point with 2 legs,\n",
" ordered top-bottom.\n",
" \n",
" Complexity\n",
" ----------\n",
" O(D ** 6) algorithm,\n",
- " diagonalising (D ** 2, D ** 2) matrix.\n",
+ " diagonalizing (D ** 2, D ** 2) matrix.\n",
" \"\"\"\n",
" \n",
" D = A.shape[0]\n",
@@ -267,27 +269,27 @@
"source": [
"def fixedPoints(A):\n",
" \"\"\"\n",
- " Find normalised fixed points.\n",
+ " Find normalized fixed points.\n",
" \n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
" \n",
" Returns\n",
" -------\n",
- " l : np.array(D, D)\n",
+ " l : np.array (D, D)\n",
" left fixed point with 2 legs,\n",
" ordered bottom-top.\n",
- " r : np.array(D, D)\n",
+ " r : np.array (D, D)\n",
" right fixed point with 2 legs,\n",
" ordered top-bottom.\n",
" \n",
" Complexity\n",
" ----------\n",
" O(D ** 6) algorithm,\n",
- " diagonalising (D ** 2, D ** 2) matrix\n",
+ " diagonalizing (D ** 2, D ** 2) matrix\n",
" \"\"\"\n",
" \n",
" # find fixed points\n",
@@ -307,7 +309,7 @@
},
"outputs": [],
"source": [
- "A = normaliseMPS(A)\n",
+ "A = normalizeMPS(A)\n",
"l, r = fixedPoints(A)\n",
"\n",
"assert np.allclose(l, np.conj(l).T, 1e-12), \"left fixed point should be hermitian!\"\n",
@@ -315,27 +317,47 @@
"\n",
"assert np.allclose(l, ncon((A, l, np.conj(A)), ([1, 2, -2], [3, 1], [3, 2, -1])), 1e-12), \"l should be a left fixed point!\"\n",
"assert np.allclose(r, ncon((A, r, np.conj(A)), ([-1, 2, 1], [1, 3], [-2, 2, 3])), 1e-12), \"r should be a right fixed point!\"\n",
- "assert np.abs(ncon((l, r), ([1, 2], [2, 1])) - 1) < 1e-12, \"Left and right fixed points should be trace normalised!\""
+ "assert np.abs(ncon((l, r), ([1, 2], [2, 1])) - 1) < 1e-12, \"Left and right fixed points should be trace normalized!\""
]
},
{
"cell_type": "markdown",
- "metadata": {},
+ "metadata": {
+ "tags": []
+ },
"source": [
- "### 1.2 Gauge fixing\n",
- "While a single $A$ corresponds to a unique state $|\\Psi(A)>$, the converse is not true, as different tensors may give rise to the same state. This is easily seen by noting that the gauge transform\n",
- "\n",
+ "### 1.2 Gauge freedom\n",
+ "While a given MPS tensor $A$ corresponds to a unique state $\\left | \\Psi(A) \\right \\rangle$, the converse is not true, as different tensors may give rise to the same state. This is easily seen by noting that the gauge transform\n",
+ "\n",
+ "
\n",
"\n",
"leaves the physical state invariant. We may use this freedom in parametrization to impose canonical forms on the MPS tensor $A$.\n",
"\n",
- "We start by considering the left-orthonormal form $A_L$ of an MPS, which has the property that\n",
- "\n",
+ "We start by considering the *left-orthonormal form* of an MPS, which is defined in terms of a tensor $A_L$ that satisfies the condition\n",
+ "\n",
+ "
\n",
+ "\n",
+ "We can find the gauge transform $L$ that brings $A$ into this form\n",
+ "\n",
+ "
\n",
+ "\n",
+ "by decomposing the fixed point $l$ as $l = L^\\dagger L$, such that\n",
+ "\n",
+ "
\n",
+ "\n",
+ "Note that this gauge choice still leaves room for unitary gauge transformations\n",
"\n",
- "We can find the matrix $L$ that brings $A$ into this form by decomposing the fixed point $l$ as $l = L^\\dagger L$, such that\n",
- "\n",
+ "
\n",
"\n",
- "Furthermore, there is still room for unitary gauge transformations such that we can also bring the right fixed point in diagonal form. Similarly, we can consider the right-orthonormal form $A_R$, where we have\n",
- ""
+ "which can be used to bring the right fixed point $r$ into diagonal form. Similarly, we can find the gauge transform that brings $A$ into *right-orthonormal form*\n",
+ "\n",
+ "
\n",
+ "\n",
+ "such that\n",
+ "\n",
+ "
\n",
+ "\n",
+ "and the left fixed point $l$ is diagonal. The routines that bring a given MPS into canonical form by decomposing the corresponding transfer matrix fixed points can be defined as follows:"
]
},
{
@@ -344,34 +366,30 @@
"metadata": {},
"outputs": [],
"source": [
- "\"\"\"As transfer matrix is positive definite, fixed points are also positive definite, thus allowing for a\n",
- "square root algorithm of the eigenvalues. Note that taking square roots is typically less favourable,\n",
- "as this increases the error\"\"\"\n",
- "\n",
- "def leftOrthonormalise(A, l=None):\n",
+ "def leftOrthonormalize(A, l=None):\n",
" \"\"\"\n",
" Transform A to left-orthonormal gauge.\n",
" \n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
" \n",
" Returns\n",
" -------\n",
- " L : np.array(D, D)\n",
+ " L : np.array (D, D)\n",
" left gauge with 2 legs,\n",
" ordered left-right.\n",
- " Al : np.array(D, d, D)\n",
- " MPS tensor zith 3 legs,\n",
+ " Al : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" left orthonormal\n",
" \n",
" Complexity\n",
" ----------\n",
" O(D ** 6) algorithm,\n",
- " diagonalising (D ** 2, D ** 2) matrix\n",
+ " diagonalizing (D ** 2, D ** 2) matrix\n",
" \"\"\"\n",
" \n",
" # find left fixed point\n",
@@ -394,30 +412,30 @@
"metadata": {},
"outputs": [],
"source": [
- "def rightOrthonormalise(A, r=None):\n",
+ "def rightOrthonormalize(A, r=None):\n",
" \"\"\"\n",
" Transform A to right-orthonormal gauge.\n",
" \n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
" \n",
" Returns\n",
" -------\n",
- " R : np.array(D, D)\n",
+ " R : np.array (D, D)\n",
" right gauge with 2 legs,\n",
" ordered left-right.\n",
- " Ar : np.array(D, d, D)\n",
- " MPS tensor zith 3 legs,\n",
+ " Ar : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" left orthonormal\n",
" \n",
" Complexity\n",
" ----------\n",
" O(D ** 6) algorithm,\n",
- " diagonalising (D ** 2, D ** 2) dmatrix\n",
+ " diagonalizing (D ** 2, D ** 2) dmatrix\n",
" \"\"\"\n",
" \n",
" # find right fixed point\n",
@@ -440,8 +458,8 @@
"metadata": {},
"outputs": [],
"source": [
- "L, Al = leftOrthonormalise(A, l)\n",
- "R, Ar = rightOrthonormalise(A, r)\n",
+ "L, Al = leftOrthonormalize(A, l)\n",
+ "R, Ar = rightOrthonormalize(A, r)\n",
"\n",
"assert np.allclose(R @ np.conj(R).T, r), \"Right gauge doesn't square to r\"\n",
"assert np.allclose(np.conj(L).T @ L, l), \"Left gauge doesn't sqaure to l\"\n",
@@ -453,22 +471,27 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "Finally, there is the notion of a mixed gauge for the uniform MPS by choosing a 'center site', bringing the left tensors in left-orthonormal form and the right tensors in right-orthonormal form, and redefining the center tensor as follows:\n",
- "\n",
+ "Finally, we can define a *mixed gauge* for the uniform MPS by choosing one site, the 'center site', and bringing all tensors to the left of it in the left-orthonormal form and all the tensors to the right of it in the right-orthonormal form. Defining a new tensor $A_C$ on the center site, we obtain the form\n",
"\n",
+ "
\n",
"\n",
- "The mixed gauge has an intuitive interpretation. Defining $C = LR$, this implements the gauge transform that maps the left-orthonormal tensor to the right-orthonormal one, hence defining the center-site tensor $A_C$:\n",
- "\n",
- "The above is called the mixed gauge condition and allows us to freely move the center tensor $A_C$ around in the MPS.\n",
+ "By contrast, the original representation using the same tensor at every site is commonly referred to as the *uniform gauge*. The mixed gauge has an intuitive interpretation. Defining $C = LR$, this tensor then implements the gauge transform that maps the left-orthonormal tensor to the right-orthonormal one, thereby defining the center-site tensor $A_C$:\n",
"\n",
+ "
\n",
"\n",
- "Finally we may perform an SVD of $C = USV^\\dagger$, and taking up $U$ and $V^\\dagger$ in the definition of $A_L$ and $A_R$, such that we are left with a diagonal $C$ on the virtual bonds.\n",
- "\n",
+ "This relation is called the mixed gauge condition and allows us to freely move the center tensor $A_C$ through the MPS, linking the left- and right orthonormal tensors.\n",
"\n",
- "In fact, this means that we can write down a Schmidt decomposition of the state across an arbitrary bond in the chain, and the diagonal elements $C_l$ are exactly the Schmidt numbers of any bipartition of the MPS. \n",
- "\n",
- "Hence, we can calculate the bipartite entanglement entropy by \n",
- "$$ S = -\\sum_l C_l^2 \\log(C_l^2) $$\n"
+ "Finally we may bring $C$ into diagonal form by performing a singular value decomposition $C = USV^\\dagger$ and absorbing $U$ and $V^\\dagger$ into the definition of $A_L$ and $A_R$ using the residual unitary gauge freedom\n",
+ "\n",
+ "
\n",
+ "\n",
+ "The mixed canonical form with a diagonal $C$ now allows to straightforwardly write down a Schmidt decomposition of the state across an arbitrary bond in the chain\n",
+ "\n",
+ "$$ \\left | \\Psi(A) \\right \\rangle = \\sum_{i=1}^{D} C_i \\left | \\Psi^i_L(A_L) \\right \\rangle \\otimes \\left | \\Psi^i_R(A_R) \\right \\rangle,$$\n",
+ "\n",
+ "where the states $\\left | \\Psi^i_L(A_L) \\right \\rangle$ and $\\left | \\Psi^i_R(A_R) \\right \\rangle$ are orthogonal states on half the lattice. The diagonal elements $C_i$ are exactly the Schmidt numbers of any bipartition of the MPS, and as such determine its bipartite entanglement entropy\n",
+ "\n",
+ "$$ S = -\\sum_i C_i^2 \\log(C_i^2) .$$"
]
},
{
@@ -483,25 +506,25 @@
" \n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
" \n",
" Returns\n",
" -------\n",
- " Al : np.array(D, d, D)\n",
- " MPS tensor zith 3 legs,\n",
+ " Al : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" left orthonormal.\n",
- " Ac : np.array(D, d, D)\n",
- " MPS tensor zith 3 legs,\n",
+ " Ac : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauge.\n",
- " Ar : np.array(D, d, D)\n",
- " MPS tensor zith 3 legs,\n",
+ " Ar : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" right orthonormal.\n",
- " C : np.array(D, D)\n",
+ " C : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right,\n",
" diagonal.\n",
@@ -509,19 +532,19 @@
" Complexity\n",
" ----------\n",
" O(D ** 6) algorithm,\n",
- " diagonalisation of (D ** 2, D ** 2) matrix\n",
+ " diagonalization of (D ** 2, D ** 2) matrix\n",
" \"\"\"\n",
"\n",
" D = A.shape[0]\n",
"\n",
" # Compute left and right orthonormal forms\n",
- " L, Al = leftOrthonormalise(A)\n",
- " R, Ar = rightOrthonormalise(A)\n",
+ " L, Al = leftOrthonormalize(A)\n",
+ " R, Ar = rightOrthonormalize(A)\n",
" \n",
" # center matrix C is matrix multiplication of L and R\n",
" C = L @ R\n",
" \n",
- " # singular value decomposition to diagonalise C\n",
+ " # singular value decomposition to diagonalize C\n",
" U, S, Vdag = svd(C)\n",
" C = np.diag(S)\n",
"\n",
@@ -529,7 +552,7 @@
" Al = ncon((np.conj(U).T, Al, U), ([-1, 1], [1, -2, 2], [2, -3]))\n",
" Ar = ncon((Vdag, Ar, np.conj(Vdag).T), ([-1, 1], [1, -2, 2], [2, -3]))\n",
"\n",
- " # normalise center matrix\n",
+ " # normalize center matrix\n",
" norm = np.trace(C @ np.conj(C).T)\n",
" C /= np.sqrt(norm)\n",
"\n",
@@ -551,7 +574,7 @@
" \n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
" \n",
@@ -593,15 +616,19 @@
},
{
"cell_type": "markdown",
- "metadata": {},
+ "metadata": {
+ "tags": []
+ },
"source": [
"### 1.3 Truncation of a uniform MPS\n",
- "The mixed canonical form is not only useful for finding the entanglement entropy, it also allows for a way to truncate MPS states efficiently. This is done by truncating the Schmidt decomposition, such that the new MPS has a reduced bond dimension for that ond. This truncation can be shown to be optimal in the sense that the norm between the original and the truncated MPS is minimised. The truncated MPS in the mixed gauge is thus:\n",
- "\n",
"\n",
- "We mention that this is a local optimization, in the sense that it maximizes the local overlap, however not the global overlap. This would require a variational optimization of the following cost function:\n",
- "$$ || ~|\\Psi(A)> - |\\Psi(\\tilde{A})> ||^2 $$\n",
- "We postpone the detailed discussion hereof until later."
+ "The mixed canonical form also enables efficient truncatation an MPS. The sum in the above Schmidt decomposition can be truncated, giving rise to a new MPS that has a reduced bond dimension for that bond. This truncation is optimal in the sense that the norm between the original and the truncated MPS is maximized. To arrive at a translation invariant truncated MPS, we can truncate the columns of the absorbed isometries $U$ and $V^\\dagger$ correspondingly, thereby transforming *every* tensor $A_L$ or $A_R$. The truncated MPS in the mixed gauge is then given by\n",
+ "\n",
+ "
\n",
+ "\n",
+ "We note that the resulting state based on this local truncation is not guaranteed to correspond to the MPS with a lower bond dimension that is globally optimal. This would require a variational optimization of the cost function.\n",
+ "\n",
+ "$$ \\left | \\left | ~\\left | \\Psi(A) \\right \\rangle - \\left | \\Psi(\\tilde{A}) \\right \\rangle ~\\right | \\right |^2.$$"
]
},
{
@@ -616,7 +643,7 @@
" \n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
" Dtrunc : int\n",
@@ -624,19 +651,19 @@
" \n",
" Returns\n",
" -------\n",
- " AlTilde : np.array(Dtrunc, d, Dtrunc)\n",
- " MPS tensor zith 3 legs,\n",
+ " AlTilde : np.array (Dtrunc, d, Dtrunc)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" left orthonormal.\n",
- " AcTilde : np.array(Dtrunc, d, Dtrunc)\n",
- " MPS tensor zith 3 legs,\n",
+ " AcTilde : np.array (Dtrunc, d, Dtrunc)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauge.\n",
- " ArTilde : np.array(Dtrunc, d, Dtrunc)\n",
- " MPS tensor zith 3 legs,\n",
+ " ArTilde : np.array (Dtrunc, d, Dtrunc)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" right orthonormal.\n",
- " CTilde : np.array(Dtrunc, Dtrunc)\n",
+ " CTilde : np.array (Dtrunc, Dtrunc)\n",
" Center gauge with 2 legs,\n",
" ordered left-right,\n",
" diagonal.\n",
@@ -655,7 +682,7 @@
" ArTilde = ncon((Vdag, Ar, np.conj(Vdag).T), ([-1, 1], [1, -2, 2], [2, -3]))\n",
" CTilde = np.diag(S)\n",
" \n",
- " # normalise center matrix\n",
+ " # normalize center matrix\n",
" norm = np.trace(CTilde @ np.conj(CTilde).T)\n",
" CTilde /= np.sqrt(norm)\n",
"\n",
@@ -671,8 +698,9 @@
"metadata": {},
"outputs": [],
"source": [
- "AlTilde, AcTilde, ArTilde, CTilde = truncateMPS(A, 3)\n",
- "assert AlTilde.shape[0] == 3 and AlTilde.shape[2] == 3, \"Something went wrong in truncating the MPS\""
+ "Dtrunc = 3\n",
+ "AlTilde, AcTilde, ArTilde, CTilde = truncateMPS(A, Dtrunc)\n",
+ "assert AlTilde.shape[0] == Dtrunc and AlTilde.shape[2] == Dtrunc, \"Something went wrong in truncating the MPS\""
]
},
{
@@ -680,11 +708,9 @@
"metadata": {},
"source": [
"### 1.4 Algorithms for finding canonical forms\n",
- "One of the great things of MPS is that they provide efficient approximations of physical states, and for gapped systems they are expected to be exact in the limit $D \\rightarrow \\infty$. The idea is that if we increase the bond dimension enough, we can get to arbitrary precision. However, increasing the bond dimension comes at a numerical cost, as the MPS algorithms scale in $D$. It is possible to ensure that the complexity of all MPS algorithms scales as $O(D^3)$, however this means we need to be a little smarter with the previously encountered algorithms.\n",
- "\n",
- "As a first step, we can refrain from explicitly building the matrices that are used in the eigenvalue solvers, but this will easily extend to all following algorithms. We can circumvent explicitly building the matrix by implementing a function that corresponds to the action of the operator, usually called a handle, and passing this to the eigenvalue solver.\n",
+ "The success of using MPS for describing physical systems stems from the fact that they provide efficient approximations to a large class of physically relevant states. In one dimension, they have been shown to approximate low-energy states of gapped systems arbitrarily well at only a polynomial cost in the bond dimension $D$. This means that in principle we can push MPS results for these systems to arbitrary precision as long as we increase the bond dimension enough. However, increasing the bond dimension comes at a numerical cost, as the complexity of any MPS algorithm scales with $D$. As opposed to the naive routines given above, it is possible to ensure that the complexity of all MPS algorithms scales as $O(D^3)$, so long as we are a bit careful when implementing them.\n",
"\n",
- "For example, a better way of fixing the normalisation of an MPS tensor, as well as determining left and right fixed points is by implementing the handles and using the optimal contraction sequences:"
+ "As a first example, we can refrain from explicitly contructing the matrices that are used in the eigenvalue problems, and instead pass a function that implements the action of the corresponding operator on a vector to the eigenvalue solver. We demonstrate this for the problem of normalizing an MPS, where instead of explicitly constructing the transfer matrix we now define a function handle which implements its action on the right and left fixed points using an optimal contraction sequence:"
]
},
{
@@ -693,19 +719,19 @@
"metadata": {},
"outputs": [],
"source": [
- "def normaliseMPS(A):\n",
+ "def normalizeMPS(A):\n",
" \"\"\"\n",
- " Normalise an MPS tensor.\n",
+ " Normalize an MPS tensor.\n",
"\n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
"\n",
" Returns\n",
" -------\n",
- " Anew : np.array(D, d, D)\n",
+ " Anew : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
"\n",
@@ -742,13 +768,13 @@
"\n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
"\n",
" Returns\n",
" -------\n",
- " l : np.array(D, D)\n",
+ " l : np.array (D, D)\n",
" left fixed point with 2 legs,\n",
" ordered bottom-top.\n",
"\n",
@@ -790,13 +816,13 @@
"\n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
"\n",
" Returns\n",
" -------\n",
- " r : np.array(D, D)\n",
+ " r : np.array (D, D)\n",
" right fixed point with 2 legs,\n",
" ordered top-bottom.\n",
"\n",
@@ -834,20 +860,20 @@
"source": [
"def fixedPoints(A):\n",
" \"\"\"\n",
- " Find normalised fixed points.\n",
+ " Find normalized fixed points.\n",
"\n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
"\n",
" Returns\n",
" -------\n",
- " l : np.array(D, D)\n",
+ " l : np.array (D, D)\n",
" left fixed point with 2 legs,\n",
" ordered bottom-top.\n",
- " r : np.array(D, D)\n",
+ " r : np.array (D, D)\n",
" right fixed point with 2 legs,\n",
" ordered top-bottom.\n",
"\n",
@@ -875,7 +901,7 @@
"outputs": [],
"source": [
"A = createMPS(D, d)\n",
- "A = normaliseMPS(A)\n",
+ "A = normalizeMPS(A)\n",
"l, r = fixedPoints(A)\n",
"\n",
"assert np.allclose(l, np.conj(l).T, 1e-12), \"left fixed point should be hermitian!\"\n",
@@ -883,14 +909,31 @@
"\n",
"assert np.allclose(l, ncon((A, l, np.conj(A)), ([1, 2, -2], [3, 1], [3, 2, -1])), 1e-12), \"l should be a left fixed point!\"\n",
"assert np.allclose(r, ncon((A, r, np.conj(A)), ([-1, 2, 1], [1, 3], [-2, 2, 3])), 1e-12), \"r should be a right fixed point!\"\n",
- "assert abs(ncon((l, r), ([1, 2], [2, 1])) - 1) < 1e-12, \"Left and right fixed points should be trace normalised!\""
+ "assert abs(ncon((l, r), ([1, 2], [2, 1])) - 1) < 1e-12, \"Left and right fixed points should be trace normalized!\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "Furthermore, taking the square root of such a fixed point to find the left and right gauge is also a way to introduce larger errors than neccesary. This can be circumvented by using an iterative scheme that makes use of a unique decomposition, such as a QR or polar decomposition. Note however that for a QR decomposition to be unique we need the diagonal elements of R to have a positive sign."
+ "We can similarly improve both the efficiency and accuracy of the routines bringing a given MPS into its mixed canonical form. While plugging in the more efficient ways of finding the left and right fixed point into the above `mixedCanonical` routine would reduce its complexity to $O(D^3)$, this algorithm would still be suboptimal in terms of numerical accuracy. This arises from the fact that, while $l$ and $r$ are theoretically known to be positive hermitian matrices, at least one of them will nevertheless have small eigenvalues, say of order $\\eta$, if the MPS is supposed to provide a good approximation to an actual state. In practice, $l$ and $r$ are determined using an iterative eigensolver and will only be accurate up to a specified tolerance $\\epsilon$. Upon taking the 'square roots' $L$ and $R$, the numerical precision will then decrease to $\\text{min}(\\sqrt{\\epsilon}, \\epsilon/\\sqrt{\\eta})$. Furthermore, gauge transforming $A$ with $L$ or $R$ requires the potentially ill-conditioned inversions of $L$ and $R$, and will typically yield $A_L$ and $A_R$ which violate the orthonormalization condition in the same order $\\epsilon/\\sqrt{\\eta}$. We can circumvent both these probelems by resorting to so-called *single-layer algorithms*. These are algorithms that only work on the level of the MPS tensors in the ket layer, and never consider operations for which contractions with the bra layer are needed. We now demonstrate such a single-layer algorithm for finding canonical forms.\n",
+ "\n",
+ "Suppose we are given an MPS tensor $A$, then from the above discussion we know that bringing it into left canonical form means finding a left-orthonormal tensor $A_L$ and a matrix $L$ such that $L A=A_L L$. The idea is then to solve this equation iteratively, where in every iteration\n",
+ "\n",
+ "1. we start from a matrix $L^{i}$\n",
+ "2. we construct the tensor $L^{i}A$\n",
+ "3. we take a QR decomposition to obtain $A_L^{i+1} L^{i+1} = L^{i}A$, and\n",
+ "4. we take $L^{i+1}$ to the next iteration\n",
+ "\n",
+ "The QR decomposition is represented diagrammatically as\n",
+ "\n",
+ "
\n",
+ "\n",
+ "This iterative procedure is bound to converge to a fixed point for which $L^{(i+1)}=L^{(i)}=L$ and $A_L$ is left orthonormal by construction:\n",
+ "\n",
+ "
\n",
+ "\n",
+ "A similar procedure can be used to find a right-orthonormal tensor $A_R$ and a matrix $R$ such that $A R = R A_R$. It is important to note that the convergence of this procedure relies on the fact that the QR decomposition is unique, which is not actually the case in general. However, it can be made unique by imposing that the diagonal elements of the triangular matrix $R$ must be positive. This extra condition is imposed in the routines `qrPos` and `rqPos` defined below."
]
},
{
@@ -905,15 +948,15 @@
" \n",
" Parameters\n",
" ----------\n",
- " A : np.array(M, N)\n",
+ " A : np.array (M, N)\n",
" Matrix to decompose.\n",
" \n",
" Returns\n",
" -------\n",
- " R : np.array(M, M)\n",
+ " R : np.array (M, M)\n",
" Upper triangular matrix,\n",
" positive diagonal elements.\n",
- " Q : np.array(M, N)\n",
+ " Q : np.array (M, N)\n",
" Orthogonal matrix.\n",
" \n",
" Complexity\n",
@@ -939,16 +982,16 @@
" return R, Q\n",
"\n",
"\n",
- "def rightOrthonormalise(A, R0=None, tol=1e-14, maxIter=1e5):\n",
+ "def rightOrthonormalize(A, R0=None, tol=1e-14, maxIter=1e5):\n",
" \"\"\"\n",
" Transform A to right-orthonormal gauge.\n",
"\n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
- " R0 : np.array(D, D), optional\n",
+ " R0 : np.array (D, D), optional\n",
" Right gauge matrix,\n",
" initial guess.\n",
" tol : float, optional\n",
@@ -959,27 +1002,28 @@
"\n",
" Returns\n",
" -------\n",
- " R : np.array(D, D)\n",
+ " R : np.array (D, D)\n",
" right gauge with 2 legs,\n",
" ordered left-right.\n",
- " Ar : np.array(D, d, D)\n",
- " MPS tensor zith 3 legs,\n",
+ " Ar : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" right-orthonormal\n",
" \"\"\"\n",
"\n",
" D = A.shape[0]\n",
" d = A.shape[1]\n",
+ " tol = max(tol, 1e-14)\n",
" i = 1\n",
"\n",
" # Random guess for R0 if none specified\n",
" if R0 is None:\n",
" R0 = np.random.rand(D, D)\n",
"\n",
- " # Normalise R0\n",
+ " # Normalize R0\n",
" R0 = R0 / np.linalg.norm(R0)\n",
"\n",
- " # Initialise loop\n",
+ " # Initialize loop\n",
" R, Ar = rqPos(np.reshape(ncon((A, R0), ([-1, -2, 1], [1, -3])), (D, D * d)))\n",
" R = R / np.linalg.norm(R)\n",
" convergence = np.linalg.norm(R - R0)\n",
@@ -989,7 +1033,7 @@
" # calculate AR and decompose\n",
" Rnew, Ar = rqPos(np.reshape(ncon((A, R), ([-1, -2, 1], [1, -3])), (D, D * d)))\n",
"\n",
- " # normalise new R\n",
+ " # normalize new R\n",
" Rnew = Rnew / np.linalg.norm(Rnew)\n",
"\n",
" # calculate convergence criterium\n",
@@ -1017,14 +1061,14 @@
" \n",
" Parameters\n",
" ----------\n",
- " A : np.array(M, N)\n",
+ " A : np.array (M, N)\n",
" Matrix to decompose.\n",
" \n",
" Returns\n",
" -------\n",
- " Q : np.array(M, N)\n",
+ " Q : np.array (M, N)\n",
" Orthogonal matrix.\n",
- " R : np.array(N, N)\n",
+ " R : np.array (N, N)\n",
" Upper triangular matrix,\n",
" positive diagonal elements.\n",
" \n",
@@ -1050,16 +1094,16 @@
" return Q, R\n",
"\n",
"\n",
- "def leftOrthonormalise(A, L0=None, tol=1e-14, maxIter=1e5):\n",
+ "def leftOrthonormalize(A, L0=None, tol=1e-14, maxIter=1e5):\n",
" \"\"\"\n",
" Transform A to left-orthonormal gauge.\n",
"\n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
- " L0 : np.array(D, D), optional\n",
+ " L0 : np.array (D, D), optional\n",
" Left gauge matrix,\n",
" initial guess.\n",
" tol : float, optional\n",
@@ -1070,27 +1114,28 @@
"\n",
" Returns\n",
" -------\n",
- " L : np.array(D, D)\n",
+ " L : np.array (D, D)\n",
" left gauge with 2 legs,\n",
" ordered left-right.\n",
- " Al : np.array(D, d, D)\n",
- " MPS tensor zith 3 legs,\n",
+ " Al : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" left-orthonormal\n",
" \"\"\"\n",
"\n",
" D = A.shape[0]\n",
" d = A.shape[1]\n",
+ " tol = max(tol, 1e-14)\n",
" i = 1\n",
"\n",
" # Random guess for L0 if none specified\n",
" if L0 is None:\n",
" L0 = np.random.rand(D, D)\n",
"\n",
- " # Normalise L0\n",
+ " # Normalize L0\n",
" L0 = L0 / np.linalg.norm(L0)\n",
"\n",
- " # Initialise loop\n",
+ " # Initialize loop\n",
" Al, L = qrPos(np.reshape(ncon((L0, A), ([-1, 1], [1, -2, -3])), (D * d, D)))\n",
" L = L / np.linalg.norm(L)\n",
" convergence = np.linalg.norm(L - L0)\n",
@@ -1100,7 +1145,7 @@
" # calculate LA and decompose\n",
" Al, Lnew = qrPos(np.reshape(ncon((L, A), ([-1, 1], [1, -2, -3])), (D * d, D)))\n",
"\n",
- " # normalise new L\n",
+ " # normalize new L\n",
" Lnew = Lnew / np.linalg.norm(Lnew)\n",
"\n",
" # calculate convergence criterium\n",
@@ -1128,25 +1173,25 @@
"\n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
"\n",
" Returns\n",
" -------\n",
- " Al : np.array(D, d, D)\n",
- " MPS tensor zith 3 legs,\n",
+ " Al : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" left orthonormal.\n",
- " Ac : np.array(D, d, D)\n",
- " MPS tensor zith 3 legs,\n",
+ " Ac : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauge.\n",
- " Ar : np.array(D, d, D)\n",
- " MPS tensor zith 3 legs,\n",
+ " Ar : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" right orthonormal.\n",
- " C : np.array(D, D)\n",
+ " C : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right,\n",
" diagonal.\n",
@@ -1157,6 +1202,7 @@
" \"\"\"\n",
"\n",
" D = A.shape[0]\n",
+ " tol = max(tol, 1e-14)\n",
"\n",
" # Random guess for L0 if none specified\n",
" if L0 is None:\n",
@@ -1167,13 +1213,13 @@
" R0 = np.random.rand(D, D)\n",
"\n",
" # Compute left and right orthonormal forms\n",
- " L, Al = leftOrthonormalise(A, L0, tol, maxIter)\n",
- " R, Ar = rightOrthonormalise(A, R0, tol, maxIter)\n",
+ " L, Al = leftOrthonormalize(A, L0, tol, maxIter)\n",
+ " R, Ar = rightOrthonormalize(A, R0, tol, maxIter)\n",
"\n",
" # center matrix C is matrix multiplication of L and R\n",
" C = L @ R\n",
"\n",
- " # singular value decomposition to diagonalise C\n",
+ " # singular value decomposition to diagonalize C\n",
" U, S, Vdag = svd(C)\n",
" C = np.diag(S)\n",
"\n",
@@ -1181,7 +1227,7 @@
" Al = ncon((np.conj(U).T, Al, U), ([-1, 1], [1, -2, 2], [2, -3]))\n",
" Ar = ncon((Vdag, Ar, np.conj(Vdag).T), ([-1, 1], [1, -2, 2], [2, -3]))\n",
"\n",
- " # normalise center matrix\n",
+ " # normalize center matrix\n",
" norm = np.trace(C @ np.conj(C).T)\n",
" C /= np.sqrt(norm)\n",
"\n",
@@ -1220,17 +1266,20 @@
"metadata": {},
"source": [
"### 1.5 Computing expectation values \n",
- "Having described the states in different gauges, we want to use these to determine the expectation values of extensive operators:\n",
- "$$ O = \\frac{1}{Z} \\sum_{n\\in Z} O_n $$\n",
+ "Now that we have seen the different ways to parametrize a given MPS, namely the uniform gauge and the mixed gauge, we wish to use these to compute expectation values of an extensive operator:\n",
+ "$$ O = \\frac{1}{\\mathbb{Z}} \\sum_{n \\in \\mathbb{Z}} O_n. $$\n",
+ "\n",
+ "If we assume that each $O_n$ acts on a single site and we are working with a properly normalized MPS, translation invariance dictates that the expectation value of $O$ is given by the contraction\n",
+ "\n",
+ "
\n",
"\n",
- "If we work with properly normalized MPS, the expectation value per site of a one-body operator (such as an order parameter e.g. $X_i$) is then found by considering the following contraction:\n",
- "\n",
+ "In the uniform gauge, we can use the fixed points of the transfer matrix to contract everything to the left and to the right of the operator, such that we are left with the contraction\n",
"\n",
- "If we use the uniform gauge, we can use the fixed points of the transfer matrix to collapse everything on the left and right, such that we are left with the contraction:\n",
- "\n",
+ "
\n",
"\n",
- "However, in the mixed gauge, we can locate the center site where the operator is acting, and then contract everything to the left and right to the identity, such that we find\n",
- ""
+ "In the mixed gauge, we can locate the center site where the operator is acting, and then contract everything to the left and right to the identity to arrive at the particularly simple expression\n",
+ "\n",
+ "
"
]
},
{
@@ -1245,17 +1294,17 @@
"\n",
" Parameters\n",
" ----------\n",
- " O : np.array(d, d)\n",
+ " O : np.array (d, d)\n",
" single-site operator.\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
- " l : np.array(D, D), optional\n",
+ " l : np.array (D, D), optional\n",
" left fixed point of transfermatrix,\n",
- " normalised.\n",
- " r : np.array(D, D), optional\n",
+ " normalized.\n",
+ " r : np.array (D, D), optional\n",
" right fixed point of transfermatrix,\n",
- " normalised.\n",
+ " normalized.\n",
"\n",
" Returns\n",
" -------\n",
@@ -1285,9 +1334,9 @@
"\n",
" Parameters\n",
" ----------\n",
- " O : np.array(d, d)\n",
+ " O : np.array (d, d)\n",
" single-site operator.\n",
- " Ac : np.array(D, d, D)\n",
+ " Ac : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauged.\n",
@@ -1312,7 +1361,7 @@
"source": [
"O = np.random.rand(d,d) + 1.0j * np.random.rand(d,d)\n",
"A = createMPS(D, d)\n",
- "A = normaliseMPS(A)\n",
+ "A = normalizeMPS(A)\n",
"Al, Ac, Ar, C = mixedCanonical(A)\n",
"expVal = expVal1Uniform(O, A)\n",
"expValMix = expVal1Mixed(O, Ac)\n",
@@ -1324,8 +1373,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "This procedure generalises easily to operators that act on multiple sites. In particular, the Hamiltonian $h$ can be evaluated as\n",
- ""
+ "This procedure can be readily generalized to operators that act on multiple sites. In particular, a two-site operator such as a Hamiltonian term $h$ can be evaluated as\n",
+ "\n",
+ "
"
]
},
{
@@ -1340,18 +1390,18 @@
"\n",
" Parameters\n",
" ----------\n",
- " O : np.array(d, d, d, d)\n",
+ " O : np.array (d, d, d, d)\n",
" two-site operator,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight.\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
- " l : np.array(D, D), optional\n",
+ " l : np.array (D, D), optional\n",
" left fixed point of transfermatrix,\n",
- " normalised.\n",
- " r : np.array(D, D), optional\n",
+ " normalized.\n",
+ " r : np.array (D, D), optional\n",
" right fixed point of transfermatrix,\n",
- " normalised.\n",
+ " normalized.\n",
"\n",
" Returns\n",
" -------\n",
@@ -1381,14 +1431,14 @@
"\n",
" Parameters\n",
" ----------\n",
- " O : np.array(d, d, d, d)\n",
+ " O : np.array (d, d, d, d)\n",
" two-site operator,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight.\n",
- " Ac : np.array(D, d, D)\n",
+ " Ac : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauged.\n",
- " Ar : np.array(D, d, D)\n",
+ " Ar : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" right gauged.\n",
@@ -1426,7 +1476,7 @@
"metadata": {
"celltoolbar": "Attachments",
"kernelspec": {
- "display_name": "Python 3",
+ "display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
@@ -1440,7 +1490,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.7.9"
+ "version": "3.8.3"
}
},
"nbformat": 4,
diff --git a/python/chapter2.ipynb b/python/chapter2.ipynb
index 04f65d0..061cc1b 100644
--- a/python/chapter2.ipynb
+++ b/python/chapter2.ipynb
@@ -15,29 +15,30 @@
"from scipy.linalg import svd, polar, null_space\n",
"from functools import partial\n",
"from time import time\n",
- "from tutorialFunctions import createMPS, normaliseMPS, fixedPoints, rightOrthonormalise, mixedCanonical, expVal2Uniform, expVal2Mixed"
+ "from tutorialFunctions import createMPS, normalizeMPS, fixedPoints, rightOrthonormalize, mixedCanonical, expVal2Uniform, expVal2Mixed"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "# Tangent-space methods for uniform matrix product states\n",
- "\n",
- "https://arxiv.org/abs/1810.07006\n",
+ "# [Tangent-space methods for uniform matrix product states](https://doi.org/10.21468/SciPostPhysLectNotes.7)\n",
"\n",
"## 2. Finding ground states of local Hamiltonians\n",
"\n",
- "Having found a way of encoding the states, the next step is to implement a way of finding the ground state. To this end, we consider a nearest-neighbour Hamiltonian H, of the form\n",
- "$$H = \\sum_n h_{n, n+1}.$$\n",
- "Here $h_{n,n+1}$ is a hermitian operator acting non-trivially on the sites $n$ and $n+1$. As in any variational approach, the variational principle serves as a guide for finding ground-state approximations, we want to minimise the expectation value of the energy,\n",
- "$$ \\min_A \\frac{<\\Psi(\\bar{A})| H | \\Psi(A) >}{<\\Psi(\\bar{A})|\\Psi(A)>}. $$\n",
+ "In the previous chapter, we stated that uniform MPS can be used to efficiently approximate low-energy states of one-dimensional systems with gapped local Hamiltonians. Having defined ways of representing and manipulating MPS, the logical next step is therefore to have a look at how exactly they can be used to find ground states. To this end, we consider a nearest-neighbour Hamiltonian $H$ of the form\n",
+ "\n",
+ "$$H = \\sum_n h_{n, n+1}$$\n",
+ "\n",
+ "acting on an infinite one-dimensional system. Here, $h_{n,n+1}$ is a hermitian operator acting non-trivially on sites $n$ and $n+1$. As in any variational approach, the variational principle serves as a guide for finding ground-state approximations, dictating that the optimal MPS approximation of the ground state corresponds to the minimum of the expectation value of the energy,\n",
+ "\n",
+ "$$ \\min_A \\frac{\\left \\langle \\Psi(\\bar{A}) \\middle | H \\middle | \\Psi(A) \\right \\rangle}{\\left \\langle \\Psi(\\bar{A}) \\middle | \\Psi(A) \\right \\rangle}. $$\n",
"\n",
- "In the thermodynamic limit the energy diverges with system size, but, since we are working with translation-invariant states only, we should rather minimise the energy density. We also will restrict to properly normalised states. Diagrammatically, the minimization problem is recast as\n",
+ "In the thermodynamic limit the energy diverges with system size, but, since we are working with translation-invariant states only, we should rather minimize the energy density. In the following we will always restrict our discussion to preoperly normalized states. Diagrammatically, the minimization problem can then be recast as\n",
"\n",
- "\n",
+ "
\n",
"\n",
- "We now turn to some numerical optimization strategies for minimizing this energy density directly."
+ "In this notebook we illustratre numerical optimization strategies for minimizing this energy density directly."
]
},
{
@@ -46,20 +47,35 @@
"source": [
"### 2.1 The gradient\n",
"\n",
- "Any optimization problem relies on an efficient evaluation of the gradient, so the first thing to do is to compute this quantity (efficiently). The objective function $f$ that we want to minimize is a real function of the complex-valued $A$, or equivalently, the independent variables $A$ and $\\bar{A}$. The gradient $g$ is then obtained by differentiating $f(\\bar{A},A)$ with respect to $\\bar{A}$,\n",
+ "Any optimization problem relies on an efficient evaluation of the gradient, so the first thing to do is to compute this quantity. The objective function $f$ that we want to minimize is a real function of the complex-valued $A$, or equivalently, of the independent variables $A$ and $\\bar{A}$. The gradient $g$ is then obtained by differentiating $f(\\bar{A},A)$ with respect to $\\bar{A}$,\n",
"\n",
- "\n",
+ "$$\n",
+ "\\begin{align}\n",
+ "g &= 2 \\times \\frac{\\partial f(\\bar{A},A) }{ \\partial \\bar{A} } \\\\\n",
+ "&= 2\\times \\frac{\\partial_{\\bar{A}} \\left \\langle \\Psi(\\bar{A}) \\middle | h \\middle | \\Psi(A) \\right \\rangle } {\\left \\langle \\Psi(\\bar{A}) \\middle | \\Psi(A) \\right \\rangle} - 2\\times \\frac{\\left \\langle \\Psi(\\bar{A}) \\middle | h \\middle | \\Psi(A) \\right \\rangle} {\\left \\langle \\Psi(\\bar{A}) \\middle | \\Psi(A) \\right \\rangle^2} \\partial_{\\bar{A}} \\left \\langle \\Psi(\\bar{A}) \\middle | \\Psi(A) \\right \\rangle ,\\\\\n",
+ "&= 2\\times \\frac{\\partial_{\\bar{A}} \\left \\langle \\Psi(\\bar{A}) \\middle | h \\middle | \\Psi(A) \\right \\rangle - e \\partial_{\\bar{A}} \\left \\langle \\Psi(\\bar{A}) \\middle | \\Psi(A) \\right \\rangle } {\\left \\langle \\Psi(\\bar{A}) \\middle | \\Psi(A) \\right \\rangle},\\\\\n",
+ "\\end{align}\n",
+ "$$\n",
"\n",
- "If we make sure that the MPS is properly normalised, and subtract the current energy density from every term in the hamiltonian, the gradient takes on the simple form\n",
- "$$ g = 2 \\partial_{\\bar{A}} <\\Psi(\\bar{A})| h | \\Psi(A) >.$$\n",
+ "where we have clearly indicated $A$ and $\\bar{A}$ as independent variables and $e$ is the current energy density given by\n",
+ "\n",
+ "$$\n",
+ "e = \\frac{\\left \\langle \\Psi(\\bar{A}) \\middle | h \\middle | \\Psi(A) \\right \\rangle} {\\left \\langle \\Psi(\\bar{A}) \\middle | \\Psi(A) \\right \\rangle}.\n",
+ "$$\n",
+ "\n",
+ "If we make sure that the MPS is properly normalized and subtract the current energy density from every term in the hamiltonian, $h \\leftarrow h - e$, the gradient takes on the simple form\n",
+ "\n",
+ "$$ g = 2 \\times \\partial_{\\bar{A}} \\left \\langle \\Psi(\\bar{A}) \\middle | h \\middle | \\Psi(A) \\right \\rangle.$$\n",
"\n",
"Thus, the gradient is obtained by differentiating the expression\n",
"\n",
- "\n",
+ "
\n",
+ "\n",
+ "with respect to $\\bar{A}$. This gives rise to a sum over all sites, where in every term we differentiate with respect to one tensor $\\bar{A}$ in the bra layer. Differentiating with respect to one $\\bar{A}$ tensor amounts to leaving out that tensor, and interpreting the open legs as outgoing ones, i.e. each term looks like\n",
"\n",
- "with respect to $\\bar{A}$. Differentiating with respect to one $\\bar{A}$ tensor amounts to leaving out that tensor, and interpreting the open legs as outgoing ones, i.e. each term looks like\n",
+ "
\n",
"\n",
- ""
+ "The full gradient is then obtained as an infinite sum over these terms. By dividing the terms into three different classes and doing some bookkeeping as illustrated below, we can eventually write this sum in a relatively simple closed form."
]
},
{
@@ -67,9 +83,9 @@
"metadata": {},
"source": [
"#### Terms of the 'center' kind\n",
- "The first kind of terms that arise in the above expression for the gradient are obtained by removing one of the $\\bar{A}$ on the legs of the Hamiltonian term. This leads to\n",
+ "The first kind of terms that arise in the above expression for the gradient are obtained by differentiation with respect to an $\\bar{A}$ tensor on the legs of the Hamiltonian operator. This results in two 'center' terms\n",
"\n",
- "\n"
+ "
"
]
},
{
@@ -88,21 +104,21 @@
" reduced Hamiltonian,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight.\n",
" A : np.array (D, d, D)\n",
- " normalised MPS tensor with 3 legs,\n",
+ " normalized MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
- " l : np.array(D, D), optional\n",
+ " l : np.array (D, D), optional\n",
" left fixed point of transfermatrix,\n",
- " normalised.\n",
- " r : np.array(D, D), optional\n",
+ " normalized.\n",
+ " r : np.array (D, D), optional\n",
" right fixed point of transfermatrix,\n",
- " normalised.\n",
+ " normalized.\n",
" \n",
" Returns\n",
" -------\n",
- " term1 : np.array(D, d, D)\n",
+ " term1 : np.array (D, d, D)\n",
" first term of gradient,\n",
" ordered left-mid-right.\n",
- " term2 : np.array(D, d, D)\n",
+ " term2 : np.array (D, d, D)\n",
" second term of gradient,\n",
" ordered left-mid-right.\n",
" \"\"\"\n",
@@ -115,35 +131,31 @@
"metadata": {},
"source": [
"#### Terms of the 'left' kind\n",
- "For the terms where we omit an $\\bar{A}$ tensor to the left of the operator $h$, we can contract everything to the left of this missing $\\bar{A}$ tensor with the fixed point $l$, and everything to the right of the site containing the Hamiltonian is contracted with $r$.\n",
+ "For the terms where we leave out an $\\bar{A}$ tensor to the left of $h$, which we will call 'left' terms, we can contract everything to the left of this missing $\\bar{A}$ with the left fixed point $l$, while everything to the right of $h$ can be contracted with right fixed point $r$.\n",
"\n",
- "In between these two parts of the network, there is $E^n$, the transfermatrix multiplied $n$ times where $n$ is the separation between the two regions. Thus, summing all terms of the 'left' kind together means that we sum over $n$, and the relevant tensor becomes\n",
+ "In between these two outer parts of the network there remains a region where the regular MPS transfer matrix $E$ is applied a number of times. The action of this region is therefore captured by the operator $E^n$, where the power $n$ is determined by the seperation between the outer left and right parts for the specific term under consideration. When summing all left terms, the outer parts of the contraction always remain the same, while only the power $n$ differs for every term. Thus, summing all left terms corresponds to contracting the operator \n",
"\n",
- "$$E_\\text{sum} = 1 + E + E^2 + \\dots = \\frac{1}{1-E}.$$\n",
+ "$$E_\\text{sum} = 1 + E + E^2 + \\dots = \\frac{1}{1-E}$$\n",
"\n",
- "However, we must be careful when doing this, as the transfer matrix has leading eigenvalue $1$ (as defined by our normalization), this quantity will diverge. This can be solved by defining a regularized transfer matrix $\\tilde{E}$, substracting the divergent part:\n",
+ "between the left and right outer parts. Here, we have naively used the geometric series to write the sum in a closed form. However, since by our normalization the transfer matrix has leading eigenvalue $1$, this resulting expression will diverge and is therefore ill-defined. We can get around this by introducing a regularized transfer matrix $\\tilde{E}$ which is defined by subtracting the divergent part,\n",
"\n",
- "\n",
+ "
\n",
"\n",
- "and only then taking the inverse.\n",
+ "Since we have already shifted the energy density to have a zero expectation value, $h \\leftarrow h - e$, it can easily be verified that the contribution of the leading divergent part vanishes in every left term, meaning that we can simply replace the original transfer matrix by its regularized version without changing any of the terms, and only then take the infinite sum which now has a well defined expression in terms of an inverse,\n",
"\n",
- "$$ \\tilde{E}_\\text{sum} = \\frac{1}{1-\\tilde{E}} =: (1 - E)^p $$\n",
+ "$$ E_\\text{sum} \\rightarrow \\frac{1}{1-\\tilde{E}} \\equiv (1 - E)^P ,$$\n",
"\n",
- "To ensure that this substraction has no effect on the outcome, we will redefine the Hamiltonian to have expectation value $0$. Such a constant shift in energy has no effect when we calculate the gradient. The benefit is that the divergent part we substracted from the transfermatrix vanishes.\n",
+ "where we have introduced the pseudo-inverse defined as $(1 - E)^P = (1-\\tilde{E})^{-1}$.\n",
"\n",
- "$$ \\tilde{H} = H - e $$\n",
+ "Using this notation we can define the partial contraction\n",
"\n",
- "Using this, we define the partial contraction\n",
- "\n",
- "\n",
+ "
\n",
"\n",
"such that the sum of all left terms equals\n",
"\n",
- "\n",
- "\n",
- "Implementing this inverse naively would be an ill-defined problem, so we resort to other algorithms to find $R_h$. Multiplying both sides with $(1-\\tilde{E})$ results in an equation of the form $Ax = b$, which may be solved for $x$ by implementing a Generalized Minimal RESidual (GMRES) algorithm. \n",
+ "
\n",
"\n",
- "Note that again such an algorithm requires only the action of A on a vector, not the actual matrix, such that its construction should again be implemented using a function handle."
+ "If we would compute the partial contraction $R_h$ directly by explicitly computing the pseudo-inverse, this would entail a computational complexity $O(D^6)$. Instead, we can define $L_h$ as the solution of a linear system by multiplying both sides of the corresponding definition by $(1-\\tilde{E})$. This results in an equation of the form $Ax = b$, which may be solved for $x$ by using Krylov-based iterative methods such as a Generalized Minimal RESidual (GMRES) algorithm. Note that these methods only require the action of $A = (1-\\tilde{E})$ on a vector and not the full matrix $A$. This action can again be supplied to the linear solver using a function handle."
]
},
{
@@ -154,7 +166,7 @@
"source": [
"def reducedHamUniform(h, A, l=None, r=None):\n",
" \"\"\"\n",
- " Regularise Hamiltonian such that its expectation value is 0.\n",
+ " Regularize Hamiltonian such that its expectation value is 0.\n",
" \n",
" Parameters\n",
" ----------\n",
@@ -162,14 +174,14 @@
" Hamiltonian that needs to be reduced,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight.\n",
" A : np.array (D, d, D)\n",
- " normalised MPS tensor with 3 legs,\n",
+ " normalized MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
- " l : np.array(D, D), optional\n",
+ " l : np.array (D, D), optional\n",
" left fixed point of transfermatrix,\n",
- " normalised.\n",
- " r : np.array(D, D), optional\n",
+ " normalized.\n",
+ " r : np.array (D, D), optional\n",
" right fixed point of transfermatrix,\n",
- " normalised.\n",
+ " normalized.\n",
" \n",
" Returns\n",
" -------\n",
@@ -194,22 +206,22 @@
" Parameters\n",
" ----------\n",
" A : np.array (D, d, D)\n",
- " normalised MPS tensor with 3 legs,\n",
+ " normalized MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
- " l : np.array(D, D), optional\n",
+ " l : np.array (D, D), optional\n",
" left fixed point of transfermatrix,\n",
- " normalised.\n",
- " r : np.array(D, D), optional\n",
+ " normalized.\n",
+ " r : np.array (D, D), optional\n",
" right fixed point of transfermatrix,\n",
- " normalised.\n",
- " v : np.array(D**2)\n",
+ " normalized.\n",
+ " v : np.array (D**2)\n",
" right matrix of size (D, D) on which\n",
" (1 - Etilde) acts,\n",
" given as a vector of size (D**2,)\n",
" \n",
" Returns\n",
" -------\n",
- " vNew : np.array(D**2)\n",
+ " vNew : np.array (D**2)\n",
" result of action of (1 - Etilde)\n",
" on a right matrix,\n",
" given as a vector of size (D**2,)\n",
@@ -249,20 +261,20 @@
" hTilde : np.array (d, d, d, d)\n",
" reduced Hamiltonian,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight,\n",
- " renormalised.\n",
+ " renormalized.\n",
" A : np.array (D, d, D)\n",
- " normalised MPS tensor with 3 legs,\n",
+ " normalized MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
- " l : np.array(D, D), optional\n",
+ " l : np.array (D, D), optional\n",
" left fixed point of transfermatrix,\n",
- " normalised.\n",
- " r : np.array(D, D), optional\n",
+ " normalized.\n",
+ " r : np.array (D, D), optional\n",
" right fixed point of transfermatrix,\n",
- " normalised.\n",
+ " normalized.\n",
" \n",
" Returns\n",
" -------\n",
- " Rh : np.array(D, D)\n",
+ " Rh : np.array (D, D)\n",
" result of contraction,\n",
" ordered top-bottom.\n",
" \"\"\"\n",
@@ -300,20 +312,20 @@
" hTilde : np.array (d, d, d, d)\n",
" reduced Hamiltonian,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight,\n",
- " renormalised.\n",
+ " renormalized.\n",
" A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
- " l : np.array(D, D), optional\n",
+ " l : np.array (D, D), optional\n",
" left fixed point of transfermatrix,\n",
- " normalised.\n",
- " r : np.array(D, D), optional\n",
+ " normalized.\n",
+ " r : np.array (D, D), optional\n",
" right fixed point of transfermatrix,\n",
- " normalised.\n",
+ " normalized.\n",
" \n",
" Returns\n",
" -------\n",
- " leftTerms : np.array(D, d, D)\n",
+ " leftTerms : np.array (D, d, D)\n",
" left terms of gradient,\n",
" ordered left-mid-right.\n",
" \"\"\"\n",
@@ -326,13 +338,14 @@
"metadata": {},
"source": [
"#### Terms of the 'right' kind\n",
- "In a very similar way, the terms where we leave out an $\\bar{A}$ to the right of the operator $h$, can be evaluated with the following contractions:\n",
"\n",
- "\n",
+ "In a similar way, the terms where we leave out an $\\bar{A}$ to the right of $h$ can be evaluated by defining the partial contraction\n",
"\n",
- "such that the sum of all 'right' terms equals\n",
+ "
\n",
"\n",
- ""
+ "which can again be found by solving a linear system, such that the sum of all right terms can be written as\n",
+ "\n",
+ "
"
]
},
{
@@ -348,22 +361,22 @@
" Parameters\n",
" ----------\n",
" A : np.array (D, d, D)\n",
- " normalised MPS tensor with 3 legs,\n",
+ " normalized MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
- " l : np.array(D, D), optional\n",
+ " l : np.array (D, D), optional\n",
" left fixed point of transfermatrix,\n",
- " normalised.\n",
- " r : np.array(D, D), optional\n",
+ " normalized.\n",
+ " r : np.array (D, D), optional\n",
" right fixed point of transfermatrix,\n",
- " normalised.\n",
- " v : np.array(D**2)\n",
+ " normalized.\n",
+ " v : np.array (D**2)\n",
" right matrix of size (D, D) on which\n",
" (1 - Etilde) acts,\n",
" given as a vector of size (D**2,)\n",
" \n",
" Returns\n",
" -------\n",
- " vNew : np.array(D**2)\n",
+ " vNew : np.array (D**2)\n",
" result of action of (1 - Etilde)\n",
" on a left matrix,\n",
" given as a vector of size (D**2,)\n",
@@ -387,20 +400,20 @@
" hTilde : np.array (d, d, d, d)\n",
" reduced Hamiltonian,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight,\n",
- " renormalised.\n",
+ " renormalized.\n",
" A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
- " l : np.array(D, D), optional\n",
+ " l : np.array (D, D), optional\n",
" left fixed point of transfermatrix,\n",
- " normalised.\n",
- " r : np.array(D, D), optional\n",
+ " normalized.\n",
+ " r : np.array (D, D), optional\n",
" right fixed point of transfermatrix,\n",
- " normalised.\n",
+ " normalized.\n",
" \n",
" Returns\n",
" -------\n",
- " Lh : np.array(D, D)\n",
+ " Lh : np.array (D, D)\n",
" result of contraction,\n",
" ordered bottom-top.\n",
" \"\"\"\n",
@@ -423,20 +436,20 @@
" hTilde : np.array (d, d, d, d)\n",
" reduced Hamiltonian,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight,\n",
- " renormalised.\n",
+ " renormalized.\n",
" A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
- " l : np.array(D, D), optional\n",
+ " l : np.array (D, D), optional\n",
" left fixed point of transfermatrix,\n",
- " normalised.\n",
- " r : np.array(D, D), optional\n",
+ " normalized.\n",
+ " r : np.array (D, D), optional\n",
" right fixed point of transfermatrix,\n",
- " normalised.\n",
+ " normalized.\n",
" \n",
" Returns\n",
" -------\n",
- " rightTerms : np.array(D, d, D)\n",
+ " rightTerms : np.array (D, d, D)\n",
" right terms of gradient,\n",
" ordered left-mid-right.\n",
" \"\"\"\n",
@@ -448,9 +461,11 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "The gradient is then found by summing all these contributions:\n",
+ "#### The gradient\n",
+ "\n",
+ "The full gradient is then found by summing the contributions of all three types of terms,\n",
"\n",
- "\n"
+ "
"
]
},
{
@@ -468,20 +483,20 @@
" h : np.array (d, d, d, d)\n",
" Hamiltonian,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight,\n",
- " renormalised.\n",
+ " renormalized.\n",
" A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
- " l : np.array(D, D), optional\n",
+ " l : np.array (D, D), optional\n",
" left fixed point of transfermatrix,\n",
- " normalised.\n",
- " r : np.array(D, D), optional\n",
+ " normalized.\n",
+ " r : np.array (D, D), optional\n",
" right fixed point of transfermatrix,\n",
- " normalised.\n",
+ " normalized.\n",
" \n",
" Returns\n",
" -------\n",
- " grad : np.array(D, d, D)\n",
+ " grad : np.array (D, d, D)\n",
" Gradient,\n",
" ordered left-mid-right.\n",
" \"\"\"\n",
@@ -494,11 +509,12 @@
"metadata": {},
"source": [
"### 2.2 Gradient descent algorithms\n",
- "The simplest way to use this information to find the ground state of a Hamiltonian is then to use a method of gradient descent, or just by iterating, for a small step $\\epsilon$,\n",
"\n",
- "$$ A_{i+1} = A_i - \\epsilon g $$\n",
+ "The most straightforward way to use this expression for the gradient to find the ground state of a Hamiltonian is to implement a gradient-search method for minimizing the energy expecation value. The simplest such method is a steepest-descent search, where in every iteration the tensor $A$ is updated in the direction opposite to the gradient along a small step $\\varepsilon$,\n",
"\n",
- "in order to find the optimal MPS tensor $A^*$ for which the gradient vanishes. However, this can be further improved upon by using more advanced algorithms, as for example those already implemented by the scipy package scipy.optimize."
+ "$$ A_{i+1} = A_i - \\varepsilon g .$$\n",
+ "\n",
+ "This procedure is repeated until we find the optimal MPS tensor $A^*$ for which the gradient vanishes. This approach can be improved upon by resorting to other optimization schemes such a conjugate-gradient or quasi-Newton methods. Below we demonstrate both a simple steepest-descent with a fixed step size, as well as an approach using routines supplied by the scipy package in `scipy.optimize`."
]
},
{
@@ -507,21 +523,21 @@
"metadata": {},
"outputs": [],
"source": [
- "def groundStateGradDescent(h, D, eps=1e-1, A0=None, tol=1e-4, maxIter=1e4):\n",
+ "def groundStateGradDescent(h, D, eps=1e-1, A0=None, tol=1e-4, maxIter=1e4, verbose=True):\n",
" \"\"\"\n",
" Find the ground state using gradient descent.\n",
" \n",
" Parameters\n",
" ----------\n",
" h : np.array (d, d, d, d)\n",
- " Hamiltonian to minimise,\n",
+ " Hamiltonian to minimize,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight.\n",
" D : int\n",
" Bond dimension\n",
" eps : float\n",
" Stepsize.\n",
" A0 : np.array (D, d, D)\n",
- " normalised MPS tensor with 3 legs,\n",
+ " normalized MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" initial guess.\n",
" tol : float\n",
@@ -531,7 +547,7 @@
" -------\n",
" E : float\n",
" expectation value @ minimum\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" ground state MPS,\n",
" ordered left-mid-right.\n",
" \"\"\"\n",
@@ -541,7 +557,7 @@
" # if no initial value, choose random\n",
" if A0 is None:\n",
" A0 = createMPS(D, d)\n",
- " A0 = normaliseMPS(A0)\n",
+ " A0 = normalizeMPS(A0)\n",
" \n",
" # calculate gradient\n",
" g = gradient(h, A0)\n",
@@ -550,15 +566,15 @@
" A = A0\n",
" \n",
" i = 0\n",
- " while not(np.all(np.abs(g) < tol)):\n",
+ " while not(np.linalg.norm(g) < tol):\n",
" # do a step\n",
" A = A - eps * g\n",
- " A = normaliseMPS(A)\n",
+ " A = normalizeMPS(A)\n",
" i += 1\n",
" \n",
- " if not(i % 100):\n",
+ " if verbose and not(i % 100):\n",
" E = np.real(expVal2Uniform(h, A))\n",
- " print('Current energy:', E)\n",
+ " print('iteration:\\t{:d}\\tenergy:\\t{:.12f}\\tgradient norm:\\t{:.4e}'.format(i, E, np.linalg.norm(g)))\n",
" \n",
" # calculate new gradient\n",
" g = gradient(h, A)\n",
@@ -586,14 +602,14 @@
"metadata": {},
"outputs": [],
"source": [
- "def groundStateMinimise(h, D, A0=None, tol=1e-4):\n",
+ "def groundStateMinimize(h, D, A0=None, tol=1e-4):\n",
" \"\"\"\n",
" Find the ground state using a scipy minimizer.\n",
" \n",
" Parameters\n",
" ----------\n",
" h : np.array (d, d, d, d)\n",
- " Hamiltonian to minimise,\n",
+ " Hamiltonian to minimize,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight.\n",
" D : int\n",
" Bond dimension\n",
@@ -608,7 +624,7 @@
" -------\n",
" E : float\n",
" expectation value @ minimum\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" ground state MPS,\n",
" ordered left-mid-right.\n",
" \"\"\"\n",
@@ -621,7 +637,7 @@
" \n",
" Parameters\n",
" ----------\n",
- " varA : np.array(2 * D * d * D)\n",
+ " varA : np.array (2 * D * d * D)\n",
" MPS tensor in real vector form.\n",
" D : int\n",
" Bond dimension.\n",
@@ -630,7 +646,7 @@
" \n",
" Returns\n",
" -------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
" \"\"\"\n",
@@ -649,13 +665,13 @@
" \n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor,\n",
" ordered left-bottom-right\n",
" \n",
" Returns\n",
" -------\n",
- " varA : np.array(2 * D * d * D)\n",
+ " varA : np.array (2 * D * d * D)\n",
" MPS tensor in real vector form.\n",
" \"\"\"\n",
" \n",
@@ -671,7 +687,7 @@
" # if no initial MPS, take random one\n",
" if A0 is None:\n",
" A0 = createMPS(D, d)\n",
- " A0 = normaliseMPS(A0)\n",
+ " A0 = normalizeMPS(A0)\n",
" \n",
" # define f for minimize in scipy\n",
" def f(varA):\n",
@@ -680,20 +696,20 @@
" \n",
" Parameters\n",
" ----------\n",
- " varA : np.array(2 * D * d * D)\n",
+ " varA : np.array (2 * D * d * D)\n",
" MPS tensor in real vector form.\n",
" \n",
" Returns\n",
" -------\n",
" e : float\n",
" function value @varA\n",
- " g : np.array(2 * D * d * D)\n",
+ " g : np.array (2 * D * d * D)\n",
" gradient vector @varA\n",
" \"\"\"\n",
" \n",
" # unwrap varA\n",
" A = unwrapper(varA)\n",
- " A = normaliseMPS(A)\n",
+ " A = normalizeMPS(A)\n",
" \n",
" # calculate fixed points\n",
" l, r = fixedPoints(A)\n",
@@ -721,7 +737,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "These methods can be tested for the specific case of the antiferromagnetic Heisenberg model. To this end we first define the spin 1 Heisenberg Hamiltonian:"
+ "To demonstrate these methods, we now have a look the specific case of the antiferromagnetic spin-1 Heisenberg model in one dimension. To this end we first define the spin-1 Heisenberg Hamiltonian:"
]
},
{
@@ -767,15 +783,15 @@
"source": [
"d, D = 3, 12\n",
"A = createMPS(D, d)\n",
- "A = normaliseMPS(A)\n",
+ "A = normalizeMPS(A)\n",
"\n",
"h = Heisenberg(-1, -1, -1, 0)\n",
"\n",
"# energy optimization using naive gradient descent\n",
- "# for D=12 or higher: tolerance lower than 1e-3 gives very long runtimes\n",
+ "# for D=12 or higher: tolerance lower than 1e-2 gives very long runtimes\n",
"print('Gradient descent optimization:\\n')\n",
"t0 = time()\n",
- "E1, A1 = groundStateGradDescent(h, D, eps=1e-1, A0=A, tol=1e-3, maxIter=1e4)\n",
+ "E1, A1 = groundStateGradDescent(h, D, eps=1e-1, A0=A, tol=1e-2, maxIter=1e4)\n",
"print('Time until convergence:', time()-t0, 's')\n",
"print('Computed energy:', E1, '\\n')\n",
"\n",
@@ -783,7 +799,7 @@
"# for D=12 and tolerance 1e-5: runtime of somewhere around 100s\n",
"print('Optimization using scipy minimize:\\n')\n",
"t0 = time()\n",
- "E2, A2 = groundStateMinimise(h, D, A0=A, tol=1e-4)\n",
+ "E2, A2 = groundStateMinimize(h, D, A0=A, tol=1e-4)\n",
"print('Time until convergence:', time()-t0, 's')\n",
"print('Computed energy:', E2, '\\n')"
]
@@ -792,12 +808,13 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "### 2.3 VUMPS\n",
- "In the previous section we have not made use of the fact that we can use specific gauges to optimalise the procedure. A variational ground-state optimization algorithm that does exploit the power of the mixed gauge is VUMPS, Variational Uniform Matrix Product States, described in Algorithm 4 in the lecture notes.\n",
+ "### 2.3 The VUMPS algorithm\n",
"\n",
- "In order to implement this algorithm, we repeat most of the steps of the previous sectionm however now working with the mixed gauge.\n",
+ "In the previous section we have derived an expression for the gradient starting from an MPS in the uniform gauge, which corresponds to an object that lives in the space of MPS tensors. We now discuss how to improve upon direct optimization schemes based on this form of the gradient by exploiting the structure of the MPS manifold as well as the mixed gauge for MPS.\n",
"\n",
- "We start off by implementing the regularisation of the two-site Hamiltonian, now using the mixed gauge algorithm:"
+ "Indeed, while the gradient in the above form indicates a direction in the space of complex tensors in which the energy decreases, intuitively it would make more sense if we could find a way to interpret the gradient as a direction *along the MPS manifold* along which we can decrease the energy. This can be achieved by interpreting the gradient as a *tangent vector in the tangent space to the MPS manifold*. By formulating the energy optimization in terms of this tangent space gradient written in mixed gauge, one arives at the [VUMPS](https://doi.org/10.1103/PhysRevB.97.045145) algorithm (which stand for 'variational uniform matrix product states'). The precise derivation of the tangent space gradient in mixed gauge falls beyond the scope of this tutorial, and can be found in the [lecture notes](https://doi.org/10.21468/SciPostPhysLectNotes.7). Instead we will simply illustrate the implementation of the VUMPS algorithm given the mixed gauge tangent space gradient.\n",
+ "\n",
+ "Most of the following required steps will be reminiscent of those outlined above, where we now consistently work in the mixed gauge. We start off by implementing the regularization of the two-site Hamiltonian in the mixed gauge."
]
},
{
@@ -808,18 +825,18 @@
"source": [
"def reducedHamMixed(h, Ac, Ar):\n",
" \"\"\"\n",
- " Regularise Hamiltonian such that its expectation value is 0.\n",
+ " Regularize Hamiltonian such that its expectation value is 0.\n",
" \n",
" Parameters\n",
" ----------\n",
" h : np.array (d, d, d, d)\n",
" Hamiltonian that needs to be reduced,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight.\n",
- " Ac : np.array(D, d, D)\n",
+ " Ac : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauged.\n",
- " Ar : np.array(D, d, D)\n",
+ " Ar : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" right gauged.\n",
@@ -838,39 +855,61 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "Then, we want to calculate the gradient in the mixed gauge. The expression for the gradient can be found by making use of the tangent space projector, such that the final expression is\n",
+ "The variational optimum of the energy is characterized by the condition that the gradient is zero at this point. Writing the tangent space gradient as $G$, we now wish to formulate an algorithm which minimizes the error measure\n",
+ "\n",
+ "$$ \\varepsilon = \\left( \\boldsymbol{G}^\\dagger \\boldsymbol{G} \\right)^{1/2} $$\n",
+ "\n",
+ "in an efficient way. The explicit form of the tangent space gradient in mixed gauge is given by\n",
"\n",
- "$$ G = A^\\prime_{A_C} - A_L C^\\prime, $$\n",
- "where the primed factors are defined as\n",
+ "$$ G = A^\\prime_{C} - A_L C^\\prime = A^\\prime_{C} - C^\\prime A_R, $$\n",
"\n",
+ "where $A^\\prime_{C}$ and $C^\\prime$ are defined as\n",
"\n",
- "\n",
+ "
\n",
"\n",
+ "and\n",
+ "\n",
+ "
\n",
"\n",
- "\n",
+ "Here, we again use $L_h$ and $R_h$ to indicate the partial contractions\n",
"\n",
- "If we define effective hamiltonians $H_{A_C}(\\bullet)$ and $H_C(\\bullet)$ such that \n",
+ "
\n",
"\n",
- "We characterise an optimal MPS with the consistency equations for the mixed gauge, supplemented by\n",
+ "where the transfer matrices $E^L_L$ and $E^R_R$ appearing in these expressions now contain only left-gauged and right-gauged MPS tensors $A_L$ and $A_R$ respectively.\n",
"\n",
- "$$ A_LC = CA_R = A_C $$\n",
- "$$ H_{A_C}(A_C) \\sim A_C $$\n",
- "$$ H_C(C) \\sim C. $$"
+ "If we interpret the two terms appearing in the tangent space gradient as defining the effective Hamiltonians $H_{A_C}(\\cdot)$ and $H_C(\\cdot)$ such that\n",
+ "\n",
+ "$$\n",
+ "\\begin{align}\n",
+ "H_{A_C}(A_C) = A_C^\\prime \\\\\n",
+ "H_C(C) = C^\\prime ,\n",
+ "\\end{align}\n",
+ "$$\n",
+ "\n",
+ "we can characterize the variational optimum in terms of the fixed points of these operators. Indeed, since the tangent space gradient should be zero at the variational optimum, this point satisfies $A_C' = A_L C' = C' A_R$. This implies that the optimal MPS should obey the following set of equations,\n",
+ "\n",
+ "$$\n",
+ "\\begin{align}\n",
+ "H_{A_C}(A_C) \\propto A_C \\\\\n",
+ "H_C(C) \\propto C \\\\\n",
+ "A_C = A_L C = C A_R ,\n",
+ "\\end{align}\n",
+ "$$\n",
+ "\n",
+ "meaning that the optimal MPS should correspond to a fixed point of the effective Hamiltonians $H_{A_C}$ and $H_C$ and satisfy the mixed gauge condition. The VUMPS algorithm then consists of an iterative method for finding a set $\\{A_L, A_C, A_R, C\\}$ that satisfies these equations simultaneously."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "Similar to before, we again have to compute contributions of right and left environment terms, which are now given by:\n",
- "\n",
- "and\n",
- "\n",
+ "#### Defining the required operators\n",
"\n",
- "In order to compute these tensors, we now require a function handle for the action of the right (resp. left) transfer matrix on a right (resp. left) matrix, with $A$ replaced by $A_L, A_R$. There is however no need to implement this action again for this specific case: we can reuse the implementations EtildeRight and EtildeLeft from above, if we take into account that the left (resp. right) fixed point of the right (resp. left) transfer matrix is precisely $C^\\dagger C$ (resp. $C C^\\dagger$); this can be easily verified using the property (15)."
+ "Similar to before, we again have to compute the contributions of the left and right environment terms $L_h$ and $R_h$ given above. We therefore require function handles defining the action of the left (resp. right) transfer matrix $E^L_L$ (resp. $E^R_R$) on a left (resp. right) matrix. To this end, we can simply reuse the implementations `EtildeLeft` and `EtildeRight` defined above, if we take into account that the left (resp. right) fixed point of $E^L_L$ (resp. $E^R_R$) is the identity while its right (resp. left) fixed point is precisely $C C^\\dagger$ (resp. $C^\\dagger C$). This last fact follows immediately from the mixed gauge condition."
]
},
{
@@ -879,21 +918,21 @@
"metadata": {},
"outputs": [],
"source": [
- "def RhMixed(hTilde, Ar, C, tol=1e-3):\n",
+ "def LhMixed(hTilde, Al, C, tol=1e-5):\n",
" \"\"\"\n",
- " Calculate Rh, for a given MPS in mixed gauge.\n",
+ " Calculate Lh, for a given MPS in mixed gauge.\n",
" \n",
" Parameters\n",
" ----------\n",
" hTilde : np.array (d, d, d, d)\n",
" reduced Hamiltonian,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight,\n",
- " renormalised.\n",
- " Ar : np.array (D, d, D)\n",
+ " renormalized.\n",
+ " Al : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
- " right-orthonormal.\n",
- " C : np.array(D, D)\n",
+ " left-orthonormal.\n",
+ " C : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right.\n",
" tol : float, optional\n",
@@ -901,12 +940,13 @@
" \n",
" Returns\n",
" -------\n",
- " Rh : np.array(D, D)\n",
+ " Lh : np.array (D, D)\n",
" result of contraction,\n",
- " ordered top-bottom.\n",
+ " ordered bottom-top.\n",
+ " \n",
" \"\"\"\n",
" \n",
- " return Rh"
+ " return Lh"
]
},
{
@@ -915,21 +955,21 @@
"metadata": {},
"outputs": [],
"source": [
- "def LhMixed(hTilde, Al, C, tol=1e-3):\n",
+ "def RhMixed(hTilde, Ar, C, tol=1e-5):\n",
" \"\"\"\n",
- " Calculate Lh, for a given MPS in mixed gauge.\n",
+ " Calculate Rh, for a given MPS in mixed gauge.\n",
" \n",
" Parameters\n",
" ----------\n",
" hTilde : np.array (d, d, d, d)\n",
" reduced Hamiltonian,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight,\n",
- " renormalised.\n",
- " Al : np.array (D, d, D)\n",
+ " renormalized.\n",
+ " Ar : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
- " left-orthonormal.\n",
- " C : np.array(D, D)\n",
+ " right-orthonormal.\n",
+ " C : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right.\n",
" tol : float, optional\n",
@@ -937,24 +977,23 @@
" \n",
" Returns\n",
" -------\n",
- " Lh : np.array(D, D)\n",
+ " Rh : np.array (D, D)\n",
" result of contraction,\n",
- " ordered bottom-top.\n",
- " \n",
+ " ordered top-bottom.\n",
" \"\"\"\n",
" \n",
- " return Lh"
+ " return Rh"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "We then implement the actions of the effective Hamiltonians $H_{A_C}$ and $H_{C}$ defined in equations (131) and (132) respectively:\n",
+ "Next we implement the actions of the effective Hamiltonians $H_{A_C}$ and $H_{C}$ defined above,\n",
"\n",
- "$H_{A_C}(A_C) = $ \n",
+ "
\n",
"\n",
- "$H_{C}(C) = $ "
+ "
"
]
},
{
@@ -963,7 +1002,6 @@
"metadata": {},
"outputs": [],
"source": [
- "# define handle for effective hamiltonian for Ac\n",
"def H_Ac(hTilde, Al, Ar, Lh, Rh, v):\n",
" \"\"\"\n",
" Action of the effective Hamiltonian for Ac (131) on a vector.\n",
@@ -973,7 +1011,7 @@
" hTilde : np.array (d, d, d, d)\n",
" reduced Hamiltonian,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight,\n",
- " renormalised.\n",
+ " renormalized.\n",
" Al : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
@@ -982,25 +1020,25 @@
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" right-orthonormal.\n",
- " Lh : np.array(D, D)\n",
+ " Lh : np.array (D, D)\n",
" left environment,\n",
" ordered bottom-top.\n",
- " Rh : np.array(D, D)\n",
+ " Rh : np.array (D, D)\n",
" right environment,\n",
" ordered top-bottom.\n",
- " v : np.array(D, d, D)\n",
+ " v : np.array (D, d, D)\n",
" Tensor of size (D, d, D)\n",
"\n",
" Returns\n",
" -------\n",
- " H_AcV : np.array(D, d, D)\n",
+ " H_AcV : np.array (D, d, D)\n",
" Result of the action of H_Ac on the vector v,\n",
" representing a tensor of size (D, d, D)\n",
"\n",
" \"\"\"\n",
- " \n",
- " # given as an example\n",
"\n",
+ " # given as an example\n",
+ " \n",
" # first term\n",
" term1 = ncon((Al, v, np.conj(Al), hTilde), ([4, 2, 1], [1, 3, -3], [4, 5, -1], [2, 3, 5, -2]))\n",
"\n",
@@ -1034,7 +1072,7 @@
" hTilde : np.array (d, d, d, d)\n",
" reduced Hamiltonian,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight,\n",
- " renormalised.\n",
+ " renormalized.\n",
" Al : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
@@ -1043,18 +1081,18 @@
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" right-orthonormal.\n",
- " Lh : np.array(D, D)\n",
+ " Lh : np.array (D, D)\n",
" left environment,\n",
" ordered bottom-top.\n",
- " Rh : np.array(D, D)\n",
+ " Rh : np.array (D, D)\n",
" right environment,\n",
" ordered top-bottom.\n",
- " v : np.array(D, D)\n",
+ " v : np.array (D, D)\n",
" Matrix of size (D, D)\n",
"\n",
" Returns\n",
" -------\n",
- " H_CV : np.array(D, D)\n",
+ " H_CV : np.array (D, D)\n",
" Result of the action of H_C on the matrix v.\n",
"\n",
" \"\"\"\n",
@@ -1066,11 +1104,26 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "The vumps algorithm consists now of an iterative method for finding $\\{A_L, A_R, A_C, C\\}$ that satisfies the equations that define the optimum simultaneously. This is done as follows.\n",
+ "#### Implementing the VUMPS algorithm\n",
+ "\n",
+ "In order to find a set $\\{A_L^*, A_C^*, A_R^*, C^*\\}$ that satisfies the VUMPS fixed point equations given above, we use an iterative method in which each iteration consists of the following steps, each time starting from a given set $\\{A_L, A_C, A_R, C\\}$:\n",
+ "\n",
+ "1. Solve the eigenvalue equations for $H_{A_C}$ and $H_C$, giving new center tensors $\\tilde{A}_C$ and $\\tilde{C}$.\n",
"\n",
- "1. calcNewCenter:\n",
+ "2. From these new center tensors, construct a set $\\{\\tilde{A}_L, \\tilde{A}_R, \\tilde{A}_C, \\tilde{C}\\}$.\n",
"\n",
- "We start off the algorithm by finding two new tensors $\\tilde{A_C}$ and $\\tilde{C}$, by solving the eigenvalue problem defined by the effective Hamiltonians we implemented before."
+ "3. Update the set of tensors $\\{A_L, A_C, A_R, C\\} \\leftarrow \\{\\tilde{A}_L, \\tilde{A}_C, \\tilde{A}_R, \\tilde{C}\\}$ and evaluate the norm of the gradient $\\varepsilon = \\left | \\left | H_{A_C} (A_C) - A_L H_C(C) \\right | \\right |$.\n",
+ "\n",
+ "4. If the norm of the gradient lies above the given tolerance, repeat."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "##### Updating the center tensors\n",
+ "\n",
+ "We start by defining a routine `calcNewCenter` which finds the new center tensors $\\tilde{A}_C$ and $\\tilde{C}$ by solving the eigenvalue problem defined by the effective Hamiltonians implemented above."
]
},
{
@@ -1079,7 +1132,7 @@
"metadata": {},
"outputs": [],
"source": [
- "def calcNewCenter(hTilde, Al, Ac, Ar, C, Lh=None, Rh=None, tol=1e-3):\n",
+ "def calcNewCenter(hTilde, Al, Ac, Ar, C, Lh=None, Rh=None, tol=1e-5):\n",
" \"\"\"\n",
" Find new guess for Ac and C as fixed points of the maps H_Ac and H_C.\n",
" \n",
@@ -1088,26 +1141,26 @@
" hTilde : np.array (d, d, d, d)\n",
" reduced Hamiltonian,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight,\n",
- " renormalised.\n",
- " Al : np.array(D, d, D)\n",
+ " renormalized.\n",
+ " Al : np.array (D, d, D)\n",
" MPS tensor zith 3 legs,\n",
" ordered left-bottom-right,\n",
" left orthonormal.\n",
- " Ar : np.array(D, d, D)\n",
+ " Ar : np.array (D, d, D)\n",
" MPS tensor zith 3 legs,\n",
" ordered left-bottom-right,\n",
" right orthonormal.\n",
- " Ac : np.array(D, d, D)\n",
+ " Ac : np.array (D, d, D)\n",
" MPS tensor zith 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauge.\n",
- " C : np.array(D, D)\n",
+ " C : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right.\n",
- " Lh : np.array(D, D)\n",
+ " Lh : np.array (D, D)\n",
" left environment,\n",
" ordered bottom-top.\n",
- " Rh : np.array(D, D)\n",
+ " Rh : np.array (D, D)\n",
" right environment,\n",
" ordered top-bottom.\n",
" tol : float, optional\n",
@@ -1115,11 +1168,11 @@
" \n",
" Returns\n",
" -------\n",
- " AcTilde : np.array(D, d, D)\n",
+ " AcTilde : np.array (D, d, D)\n",
" MPS tensor zith 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauge.\n",
- " CTilde : np.array(D, D)\n",
+ " CTilde : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right.\n",
" \"\"\"\n",
@@ -1131,18 +1184,31 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "2. minAcC\n",
+ "##### Extract a new set of mixed-gauge MPS tensors\n",
+ "\n",
+ "Once we have new center tensors, we can use these to construct a new set of mixed-gauge MPS tensors. To do this in a stable way, we will determine the global updates $\\tilde{A}_L$ and $\\tilde{A}_R$ as the left and right isometric tensors that minimize\n",
"\n",
- "Once we have new center tensors, we may use these to construct new mixed gauge MPS tensors according to algorithm 5 in the lecture notes. This is done with the following left and right polar decompositions:\n",
+ "$$\n",
+ "\\begin{align}\n",
+ "\\varepsilon_L = \\min ||\\tilde{A}_C - \\tilde{A}_L \\tilde{C}||_2 \\\\\n",
+ "\\varepsilon_R = \\min ||\\tilde{A}_C - \\tilde{C} \\tilde{A}_L||_2 .\n",
+ "\\end{align}\n",
+ "$$\n",
"\n",
- "$$ \\tilde{A_C} = U^l_{A_C} P^l_{A_C}, \\qquad \\tilde{C} = U^l_{C} P^l_{C} $$\n",
- "$$ \\tilde{A_C} = P^r_{A_C} U^r_{A_C} , \\qquad \\tilde{C} = P^r_{C} U^r_{C} $$\n",
+ "This can be achieved in a robust and close to optimal way by making use of the left and right polar decompositions\n",
"\n",
- "We then obtain:\n",
+ "$$\n",
+ "\\begin{align}\n",
+ "\\tilde{A}_C = U^l_{A_C} P^l_{A_C}, \\qquad \\tilde{C} = U^l_{C} P^l_{C}, \\\\\n",
+ "\\tilde{A}_C = P^r_{A_C} U^r_{A_C} , \\qquad \\tilde{C} = P^r_{C} U^r_{C},\n",
+ "\\end{align}\n",
+ "$$\n",
"\n",
- "$$ \\tilde{A_L} = U^l_{A_C} (U^l_C)^\\dagger, \\qquad \\tilde{A_R} = (U^r_C)^\\dagger U^r_{A_C}. $$\n",
+ "to obtain\n",
"\n",
- "In order to give the procedure some additional stability, we may consider using the $\\tilde{A_L}$ obtained through these polar decompositions to compute the tensors $\\tilde{A_R}$ and $\\tilde{A_C}$ by right orthonormalization of this $\\tilde{A_L}$. This approach ensures that the MPS is in proper mixed gauge at all times, which improves the convergence of the whole procedure."
+ "$$ \\tilde{A}_L = U^l_{A_C} (U^l_C)^\\dagger, \\qquad \\tilde{A}_R = (U^r_C)^\\dagger U^r_{A_C}. $$\n",
+ "\n",
+ "In order to give the procedure some additional stability, we may also choose to use the $\\tilde{A}_L$ obtained with these polar decompositions to compute the tensors $\\tilde{A}_R$ and $\\tilde{A}_C$ by right orthonormalization of this $\\tilde{A}_L$. This approach ensures that the MPS satisfies the mixed gauge condition at all times, improving the overal stabilitiy of the VUMPS algorithm. This procedure is implemented in the `minAcC` routine."
]
},
{
@@ -1151,36 +1217,36 @@
"metadata": {},
"outputs": [],
"source": [
- "def minAcC(AcTilde, CTilde):\n",
+ "def minAcC(AcTilde, CTilde, tol=1e-5):\n",
" \"\"\"\n",
" Find Al and Ar corresponding to Ac and C, according to algorithm 5 in the lecture notes.\n",
" \n",
" Parameters\n",
" ----------\n",
- " AcTilde : np.array(D, d, D)\n",
+ " AcTilde : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" new guess for center gauge. \n",
- " CTilde : np.array(D, D)\n",
+ " CTilde : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right,\n",
" new guess for center gauge\n",
" \n",
" Returns\n",
" -------\n",
- " Al : np.array(D, d, D)\n",
+ " Al : np.array (D, d, D)\n",
" MPS tensor zith 3 legs,\n",
" ordered left-bottom-right,\n",
" left orthonormal.\n",
- " Ar : np.array(D, d, D)\n",
+ " Ar : np.array (D, d, D)\n",
" MPS tensor zith 3 legs,\n",
" ordered left-bottom-right,\n",
" right orthonormal.\n",
- " Ac : np.array(D, d, D)\n",
+ " Ac : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauge. \n",
- " C : np.array(D, D)\n",
+ " C : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right,\n",
" center gauge\n",
@@ -1194,9 +1260,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "3. gradientNorm\n",
+ "##### Evaluating the norm of the gradient\n",
"\n",
- "As a last step, we require the norm of the gradient, as in Eq. (130), in order to check if we have converged."
+ "As a last step, we use the routine `gradientNorm` to compute the norm of the tangent space gradient in order to check if the procedure has converged."
]
},
{
@@ -1214,26 +1280,26 @@
" hTilde : np.array (d, d, d, d)\n",
" reduced Hamiltonian,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight,\n",
- " renormalised.\n",
- " Al : np.array(D, d, D)\n",
+ " renormalized.\n",
+ " Al : np.array (D, d, D)\n",
" MPS tensor zith 3 legs,\n",
" ordered left-bottom-right,\n",
" left orthonormal.\n",
- " Ar : np.array(D, d, D)\n",
+ " Ar : np.array (D, d, D)\n",
" MPS tensor zith 3 legs,\n",
" ordered left-bottom-right,\n",
" right orthonormal.\n",
- " Ac : np.array(D, d, D)\n",
+ " Ac : np.array (D, d, D)\n",
" MPS tensor zith 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauge.\n",
- " C : np.array(D, D)\n",
+ " C : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right.\n",
- " Lh : np.array(D, D)\n",
+ " Lh : np.array (D, D)\n",
" left environment,\n",
" ordered bottom-top.\n",
- " Rh : np.array(D, D)\n",
+ " Rh : np.array (D, D)\n",
" right environment,\n",
" ordered top-bottom.\n",
" \n",
@@ -1262,7 +1328,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "Finally, this allows to implement the VUMPS algorithm:"
+ "Finally, this allows to implement the VUMPS algorithm."
]
},
{
@@ -1271,14 +1337,14 @@
"metadata": {},
"outputs": [],
"source": [
- "def vumps(h, D, A0=None, tol=1e-4):\n",
+ "def vumps(h, D, A0=None, tol=1e-4, tolFactor=1e-1, verbose=True):\n",
" \"\"\"\n",
" Find the ground state of a given Hamiltonian using VUMPS.\n",
" \n",
" Parameters\n",
" ----------\n",
" h : np.array (d, d, d, d)\n",
- " Hamiltonian to minimise,\n",
+ " Hamiltonian to minimize,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight.\n",
" D : int\n",
" Bond dimension\n",
@@ -1293,19 +1359,19 @@
" -------\n",
" E : float\n",
" expectation value @ minimum\n",
- " Al : np.array(D, d, D)\n",
+ " Al : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" left orthonormal.\n",
- " Ar : np.array(D, d, D)\n",
+ " Ar : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" right orthonormal.\n",
- " Ac : np.array(D, d, D)\n",
+ " Ac : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauge.\n",
- " C : np.array(D, D)\n",
+ " C : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right.\n",
" \"\"\"\n",
@@ -1317,7 +1383,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "We can again test this implementation on the spin-1 Heisenberg antiferromagnet:"
+ "We can again test this implementation on the spin-1 Heisenberg antiferromagnet."
]
},
{
@@ -1328,14 +1394,14 @@
"source": [
"d, D = 3, 12\n",
"A = createMPS(D, d)\n",
- "A = normaliseMPS(A)\n",
+ "A = normalizeMPS(A)\n",
"\n",
"h = Heisenberg(-1, -1, -1, 0)\n",
"\n",
"# energy optimization using VUMPS\n",
"print('Energy optimization using VUMPS:\\n')\n",
"t0 = time()\n",
- "E, Al, Ac, Ar, C = vumps(h, D, A0=A, tol=1e-4)\n",
+ "E, Al, Ac, Ar, C = vumps(h, D, A0=A, tol=1e-4, tolFactor=1e-2, verbose=True)\n",
"print('\\nTime until convergence:', time()-t0, 's\\n')\n",
"print('Computed energy:', E, '\\n')"
]
@@ -1344,7 +1410,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "Once we have obtained this ground state MPS, it is worthwile to have a look at the corresponding entanglement spectrum."
+ "Having obtained this ground state MPS, it is worthwile to have a look at the corresponding entanglement spectrum."
]
},
{
@@ -1376,88 +1442,100 @@
"\n",
"#### Quasiparticle ansatz\n",
"\n",
- "The methods described above can be further extended beyond the calculation of the ground state, and we now briefly show how one can also obtain quasiparticle excitations on top of this ground state. For this, we define the quasiparticle ansatz, given by\n",
+ "The methods described above can be extended beyond computing the ground state. We briefly discuss how one can also study excitations on top of a given ground state. For this, we introduce the MPS quasiparticle ansatz, given by\n",
"\n",
- "\n",
+ "
\n",
"\n",
- "i.e. we change one $A$ tensor of the ground state at site $n$ and make a momentum superposition.\n",
+ "This ansatz cosists of defining a new state by changing one $A$ tensor of the ground state at site $n$ and taking a momentum superposition.\n",
"\n",
- "Before we start optimizing the tensor $B$, we first note that the excitation ansatw is, in fact, just a boosted version of a tangent vector. In particular, this allows for additional tricks and manipulations, similar to the previous sections. For example, the $B$ tensor has gauge degrees of freedom: the state is invariant under an additive gauge transformation of the form\n",
+ "Before describing how to optimize the tensor $B$, it is worthwile to investigate the corresponding variational space in a bit more detail. First, we note that this excitation ansatz can be interpreted as nothing more than a boosted version of a tangent vector to the MPS manifold. In particular, this means that we will be able to apply all kinds of useful tricks and manipulations to the tensor $B$ (cfr. the [lecture notes](https://doi.org/10.21468/SciPostPhysLectNotes.7) for an introduction to tangent vectors and their properties). For example, we can see that $B$ has gauge degrees of freedom, as the corresponding excited state is invariant under an additive gauge transformation of the form\n",
"\n",
- "\n",
+ "
\n",
"\n",
- "with $Y$ an arbitrary $D \\times D$ matrix. These correspond to zero modes in the variational subspace, which we can (and should) eliminate to make the variational optimization well-conditioned, by imposing a gauge fixing condition. We use the left gauge-fixing condition\n",
+ "where $Y$ is an arbitrary $D \\times D$ matrix. This gauge freedom can be eliminated, thereby removing the zero modes in the variational subspace, by imposing a *left gauge-fixing condition*\n",
"\n",
- "\n",
+ "
\n",
"\n",
- "which is automatically satisfied if we parametrise the $B$ tensor as\n",
+ "If we parametrize the tensor $B$ as\n",
"\n",
- "\n",
+ "
\n",
"\n",
- "where we find Vl as the orthonormalised $D(d-1)$ - dimensional null space of the matrix \n",
+ "where $V_L$ is the $ D \\times d \\times D(d-1)$ tensor corresponding to the $D(d-1)$-dimensional null space of $A_L$ satisfying\n",
"\n",
- "\n",
- ""
+ "
\n",
+ "\n",
+ "then the gauge condition is automatically satisfied. In particular, this fixing of the gauge freedom ensures that the excitation is orthogonal to the ground state,\n",
+ "\n",
+ "
\n",
+ "\n",
+ "In this form, we have put forward an ansatz for an excited state characterized by a single $D(d-1) \\times D$ matrix $X$ such that\n",
+ "\n",
+ "1. All gauge degrees of freedom are fixed.\n",
+ "2. All zero modes in the variational subspace are removed.\n",
+ "3. Calculating the norm becomes straightforward.\n",
+ "4. The excitation is orthogonal to the ground state."
]
},
{
"cell_type": "markdown",
- "metadata": {},
+ "metadata": {
+ "tags": []
+ },
"source": [
"#### Solving the eigenvalue problem\n",
"\n",
- "We have put forward an ansatz for excitations, characterised by a matrix $X$ such that\n",
+ "Having introduced an excitation ansatz which has all the right properties and is defined in terms of a single matrix $X$, all that is left to do is to minimize the energy function,\n",
"\n",
- "1. All gauge degrees of freedom are fixed.\n",
- "2. All zero modes in the variational subspace are removed.\n",
- "3. Calculating the norm becomes straightforward.\n",
- "4. The excitation is orthogonal to the ground state, even at momentum $0$.\n",
+ "$$ \\min_{X} \\frac{\\left \\langle \\Phi_p(X) \\middle | H \\middle | \\Phi_p(X) \\right \\rangle}{\\left \\langle \\Phi_p(X) \\middle | \\Phi_p(X) \\right \\rangle}. $$\n",
"\n",
- "All we have left to do is minimising the energy function,\n",
+ "As both the numerator and the denominator are quadratic functions of the variational parameters $X$, this optimization problem reduces to solving a generalized eigenvalue problem\n",
"\n",
- "\n",
+ "$$ H_{\\text{eff}}(q) X = \\omega N_{\\text{eff}}(q) X, $$\n",
"\n",
- "As both numerator and denominator are quadratic functions of the variational parameters $X$, this optimization problem reduces to solving the generalised eigenvalue problem\n",
+ "where the effective energy and normalization matrices are defined as\n",
"\n",
- "$$ H_{eff}(q) X = \\omega N_{eff}(q) X, $$\n",
+ "$$\n",
+ "\\begin{align}\n",
+ "& 2\\pi\\delta(p-p') (\\boldsymbol{X'})^\\dagger H_{\\text{eff}}(q) \\boldsymbol{X} = \\left \\langle \\Phi_{p'}(X') \\middle | H \\middle | \\Phi_p(X) \\right \\rangle \\\\\n",
+ "& 2\\pi\\delta(p-p') (\\boldsymbol{X'})^\\dagger N_{\\text{eff}}(q) \\boldsymbol{X} = \\left \\langle \\Phi_{p'}(X') \\middle | \\Phi_p(X) \\right \\rangle,\n",
+ "\\end{align}\n",
+ "$$\n",
"\n",
- "where the effective energy and normalization matrix are defined as\n",
+ "and $\\boldsymbol{X}$ denotes a vectorized version of the matrix $X$. Since the overlap between two excited states is of the simple Euclidean form (cfr. the [lecture notes](https://doi.org/10.21468/SciPostPhysLectNotes.7)), the effective normalization matrix reduces to the unit matrix, and we are left with an ordinary eigenvalue problem.\n",
"\n",
- "\n",
+ "To solve this eigenvalue problem, we need to find an expression for $H_{\\text{eff}}$, or rather of the action thereof on a trial vector $\\boldsymbol{Y}$. In order to find this action we first transform the vector $\\boldsymbol{X}$ into a tensor $B$ by contracting its corresponding matrix with the right leg of $V_L$, and then compute all different contributions that pop up in a matrix element of the form $\\left \\langle \\Phi_p(B') \\middle | H \\middle | \\Phi_p(B) \\right \\rangle$. This procedure is similar to what we have done when computing the gradient above, where we now need to take into account all different positions of the nearest-neighbor operator $h$ of the Hamiltonian, the input tensor $B$ and the output. Though slightly more involved than before, we can again define the following partion contractions\n",
"\n",
- "Now since the overlap between two excited states is of the simple Euclidean form, the effective normalization matrix reduces to the unit matrix, and we are left with an ordinary eigenvalue problem.\n",
+ "
\n",
"\n",
- "To solve this eigenvalue problem, we need to find an expression of $H_{eff}$, or rather of the action thereof on a trial vector."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The calculation is done by implementing the following partial contractions:\n",
- "\n",
- "\n",
- "\n",
- "\n",
- "\n",
- "\n",
- ""
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Using the above partial contractions, we find the action of the (reduced) Hamiltonian on a given input vector $B$ as\n",
+ " \n",
"\n",
- "\n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The total procedure is captured by the following python function:"
+ "
\n",
+ "\n",
+ " \n",
+ "\n",
+ "
\n",
+ "\n",
+ " \n",
+ "\n",
+ "
\n",
+ "\n",
+ " \n",
+ "\n",
+ "
\n",
+ "\n",
+ " \n",
+ "\n",
+ "
\n",
+ "\n",
+ "Using these partial contractions, we find the action of the effective energy matrix on a given input tensor $B(Y)$ as\n",
+ "\n",
+ "
\n",
+ "\n",
+ "In the last step, we need the action of $H_{\\text{eff}}(p)$ on the vector $\\boldsymbol{Y}$, so we need to perform a last contraction\n",
+ "\n",
+ "
\n",
+ "\n",
+ "The total procedure is implemented in the routine `quasiParticle`."
]
},
{
@@ -1477,7 +1555,7 @@
" def ApplyHeff(x):\n",
" \n",
" x = np.reshape(x, (D*(d-1), D))\n",
- " B = ncon((VL, x), ([-1, -2, 1], [1, -3]))\n",
+ " B = ncon((Vl, x), ([-1, -2, 1], [1, -3]))\n",
" \n",
" def ApplyELR(x, p):\n",
" x = x.reshape((D,D))\n",
@@ -1523,14 +1601,14 @@
" np.exp(-1j*p)*ncon((left,Ar),([-1,1],[1,-2,-3]))+\\\n",
" np.exp(+1j*p)*ncon((Lh,Al,right),([-1,1],[1,-2,2],[2,-3]))\n",
" \n",
- " y = ncon((y, np.conj(VL)), ([1, 2, -2], [1, 2, -1]))\n",
+ " y = ncon((y, np.conj(Vl)), ([1, 2, -2], [1, 2, -1]))\n",
" y = y.reshape(-1)\n",
" \n",
" return y\n",
" \n",
" # find reduced parametrization\n",
" L = np.reshape(np.moveaxis(np.conj(Al), -1, 0), (D, D*d))\n",
- " VL = np.reshape(null_space(L), (D, d, D*(d-1)))\n",
+ " Vl = np.reshape(null_space(L), (D, d, D*(d-1)))\n",
" handleHeff = LinearOperator((D**2*(d-1), D**2*(d-1)), matvec=lambda x: ApplyHeff(x))\n",
" e, x = eigs(handleHeff, k=num, which='SR')\n",
" \n",
@@ -1541,7 +1619,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "We can use this to compute the Haldane gap on top of the ground state for the spin-1 Heisenberg antiferromagnet we have just obtained using VUMPS:"
+ "We can use this to compute the Haldane gap on top of the ground state of the spin-1 Heisenberg antiferromagnet we have just obtained using VUMPS."
]
},
{
@@ -1566,7 +1644,7 @@
],
"metadata": {
"kernelspec": {
- "display_name": "Python 3",
+ "display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
@@ -1580,7 +1658,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.7.9"
+ "version": "3.8.3"
}
},
"nbformat": 4,
diff --git a/python/chapter2Solution.ipynb b/python/chapter2Solution.ipynb
index 2b10d46..88ff1fd 100644
--- a/python/chapter2Solution.ipynb
+++ b/python/chapter2Solution.ipynb
@@ -15,29 +15,30 @@
"from scipy.linalg import svd, polar, null_space\n",
"from functools import partial\n",
"from time import time\n",
- "from tutorialFunctions import createMPS, normaliseMPS, fixedPoints, rightOrthonormalise, mixedCanonical, expVal2Uniform, expVal2Mixed"
+ "from tutorialFunctions import createMPS, normalizeMPS, fixedPoints, rightOrthonormalize, mixedCanonical, expVal2Uniform, expVal2Mixed"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "# Tangent-space methods for uniform matrix product states\n",
- "\n",
- "https://arxiv.org/abs/1810.07006\n",
+ "# [Tangent-space methods for uniform matrix product states](https://doi.org/10.21468/SciPostPhysLectNotes.7)\n",
"\n",
"## 2. Finding ground states of local Hamiltonians\n",
"\n",
- "Having found a way of encoding the states, the next step is to implement a way of finding the ground state. To this end, we consider a nearest-neighbour Hamiltonian H, of the form\n",
- "$$H = \\sum_n h_{n, n+1}.$$\n",
- "Here $h_{n,n+1}$ is a hermitian operator acting non-trivially on the sites $n$ and $n+1$. As in any variational approach, the variational principle serves as a guide for finding ground-state approximations, we want to minimise the expectation value of the energy,\n",
- "$$ \\min_A \\frac{<\\Psi(\\bar{A})| H | \\Psi(A) >}{<\\Psi(\\bar{A})|\\Psi(A)>}. $$\n",
+ "In the previous chapter, we stated that uniform MPS can be used to efficiently approximate low-energy states of one-dimensional systems with gapped local Hamiltonians. Having defined ways of representing and manipulating MPS, the logical next step is therefore to have a look at how exactly they can be used to find ground states. To this end, we consider a nearest-neighbour Hamiltonian $H$ of the form\n",
+ "\n",
+ "$$H = \\sum_n h_{n, n+1}$$\n",
+ "\n",
+ "acting on an infinite one-dimensional system. Here, $h_{n,n+1}$ is a hermitian operator acting non-trivially on sites $n$ and $n+1$. As in any variational approach, the variational principle serves as a guide for finding ground-state approximations, dictating that the optimal MPS approximation of the ground state corresponds to the minimum of the expectation value of the energy,\n",
"\n",
- "In the thermodynamic limit the energy diverges with system size, but, since we are working with translation-invariant states only, we should rather minimise the energy density. We also will restrict to properly normalised states. Diagrammatically, the minimization problem is recast as\n",
+ "$$ \\min_A \\frac{\\left \\langle \\Psi(\\bar{A}) \\middle | H \\middle | \\Psi(A) \\right \\rangle}{\\left \\langle \\Psi(\\bar{A}) \\middle | \\Psi(A) \\right \\rangle}. $$\n",
"\n",
- "\n",
+ "In the thermodynamic limit the energy diverges with system size, but, since we are working with translation-invariant states only, we should rather minimize the energy density. In the following we will always restrict our discussion to preoperly normalized states. Diagrammatically, the minimization problem can then be recast as\n",
"\n",
- "We now turn to some numerical optimization strategies for minimizing this energy density directly."
+ "
\n",
+ "\n",
+ "In this notebook we illustratre numerical optimization strategies for minimizing this energy density directly."
]
},
{
@@ -46,20 +47,35 @@
"source": [
"### 2.1 The gradient\n",
"\n",
- "Any optimization problem relies on an efficient evaluation of the gradient, so the first thing to do is to compute this quantity (efficiently). The objective function $f$ that we want to minimize is a real function of the complex-valued $A$, or equivalently, the independent variables $A$ and $\\bar{A}$. The gradient $g$ is then obtained by differentiating $f(\\bar{A},A)$ with respect to $\\bar{A}$,\n",
+ "Any optimization problem relies on an efficient evaluation of the gradient, so the first thing to do is to compute this quantity. The objective function $f$ that we want to minimize is a real function of the complex-valued $A$, or equivalently, of the independent variables $A$ and $\\bar{A}$. The gradient $g$ is then obtained by differentiating $f(\\bar{A},A)$ with respect to $\\bar{A}$,\n",
+ "\n",
+ "$$\n",
+ "\\begin{align}\n",
+ "g &= 2 \\times \\frac{\\partial f(\\bar{A},A) }{ \\partial \\bar{A} } \\\\\n",
+ "&= 2\\times \\frac{\\partial_{\\bar{A}} \\left \\langle \\Psi(\\bar{A}) \\middle | h \\middle | \\Psi(A) \\right \\rangle } {\\left \\langle \\Psi(\\bar{A}) \\middle | \\Psi(A) \\right \\rangle} - 2\\times \\frac{\\left \\langle \\Psi(\\bar{A}) \\middle | h \\middle | \\Psi(A) \\right \\rangle} {\\left \\langle \\Psi(\\bar{A}) \\middle | \\Psi(A) \\right \\rangle^2} \\partial_{\\bar{A}} \\left \\langle \\Psi(\\bar{A}) \\middle | \\Psi(A) \\right \\rangle ,\\\\\n",
+ "&= 2\\times \\frac{\\partial_{\\bar{A}} \\left \\langle \\Psi(\\bar{A}) \\middle | h \\middle | \\Psi(A) \\right \\rangle - e \\partial_{\\bar{A}} \\left \\langle \\Psi(\\bar{A}) \\middle | \\Psi(A) \\right \\rangle } {\\left \\langle \\Psi(\\bar{A}) \\middle | \\Psi(A) \\right \\rangle},\\\\\n",
+ "\\end{align}\n",
+ "$$\n",
"\n",
- "\n",
+ "where we have clearly indicated $A$ and $\\bar{A}$ as independent variables and $e$ is the current energy density given by\n",
"\n",
- "If we make sure that the MPS is properly normalised, and subtract the current energy density from every term in the hamiltonian, the gradient takes on the simple form\n",
- "$$ g = 2 \\partial_{\\bar{A}} <\\Psi(\\bar{A})| h | \\Psi(A) >.$$\n",
+ "$$\n",
+ "e = \\frac{\\left \\langle \\Psi(\\bar{A}) \\middle | h \\middle | \\Psi(A) \\right \\rangle} {\\left \\langle \\Psi(\\bar{A}) \\middle | \\Psi(A) \\right \\rangle}.\n",
+ "$$\n",
+ "\n",
+ "If we make sure that the MPS is properly normalized and subtract the current energy density from every term in the hamiltonian, $h \\leftarrow h - e$, the gradient takes on the simple form\n",
+ "\n",
+ "$$ g = 2 \\times \\partial_{\\bar{A}} \\left \\langle \\Psi(\\bar{A}) \\middle | h \\middle | \\Psi(A) \\right \\rangle.$$\n",
"\n",
"Thus, the gradient is obtained by differentiating the expression\n",
"\n",
- "\n",
+ "
\n",
+ "\n",
+ "with respect to $\\bar{A}$. This gives rise to a sum over all sites, where in every term we differentiate with respect to one tensor $\\bar{A}$ in the bra layer. Differentiating with respect to one $\\bar{A}$ tensor amounts to leaving out that tensor, and interpreting the open legs as outgoing ones, i.e. each term looks like\n",
"\n",
- "with respect to $\\bar{A}$. Differentiating with respect to one $\\bar{A}$ tensor amounts to leaving out that tensor, and interpreting the open legs as outgoing ones, i.e. each term looks like\n",
+ "
\n",
"\n",
- ""
+ "The full gradient is then obtained as an infinite sum over these terms. By dividing the terms into three different classes and doing some bookkeeping as illustrated below, we can eventually write this sum in a relatively simple closed form."
]
},
{
@@ -67,9 +83,9 @@
"metadata": {},
"source": [
"#### Terms of the 'center' kind\n",
- "The first kind of terms that arise in the above expression for the gradient are obtained by removing one of the $\\bar{A}$ on the legs of the Hamiltonian term. This leads to\n",
+ "The first kind of terms that arise in the above expression for the gradient are obtained by differentiation with respect to an $\\bar{A}$ tensor on the legs of the Hamiltonian operator. This results in two 'center' terms\n",
"\n",
- "\n"
+ "
"
]
},
{
@@ -88,21 +104,21 @@
" reduced Hamiltonian,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight.\n",
" A : np.array (D, d, D)\n",
- " normalised MPS tensor with 3 legs,\n",
+ " normalized MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
- " l : np.array(D, D), optional\n",
+ " l : np.array (D, D), optional\n",
" left fixed point of transfermatrix,\n",
- " normalised.\n",
- " r : np.array(D, D), optional\n",
+ " normalized.\n",
+ " r : np.array (D, D), optional\n",
" right fixed point of transfermatrix,\n",
- " normalised.\n",
+ " normalized.\n",
" \n",
" Returns\n",
" -------\n",
- " term1 : np.array(D, d, D)\n",
+ " term1 : np.array (D, d, D)\n",
" first term of gradient,\n",
" ordered left-mid-right.\n",
- " term2 : np.array(D, d, D)\n",
+ " term2 : np.array (D, d, D)\n",
" second term of gradient,\n",
" ordered left-mid-right.\n",
" \"\"\"\n",
@@ -125,35 +141,31 @@
"metadata": {},
"source": [
"#### Terms of the 'left' kind\n",
- "For the terms where we omit an $\\bar{A}$ tensor to the left of the operator $h$, we can contract everything to the left of this missing $\\bar{A}$ tensor with the fixed point $l$, and everything to the right of the site containing the Hamiltonian is contracted with $r$.\n",
+ "For the terms where we leave out an $\\bar{A}$ tensor to the left of $h$, which we will call 'left' terms, we can contract everything to the left of this missing $\\bar{A}$ with the left fixed point $l$, while everything to the right of $h$ can be contracted with right fixed point $r$.\n",
"\n",
- "In between these two parts of the network, there is $E^n$, the transfermatrix multiplied $n$ times where $n$ is the separation between the two regions. Thus, summing all terms of the 'left' kind together means that we sum over $n$, and the relevant tensor becomes\n",
+ "In between these two outer parts of the network there remains a region where the regular MPS transfer matrix $E$ is applied a number of times. The action of this region is therefore captured by the operator $E^n$, where the power $n$ is determined by the seperation between the outer left and right parts for the specific term under consideration. When summing all left terms, the outer parts of the contraction always remain the same, while only the power $n$ differs for every term. Thus, summing all left terms corresponds to contracting the operator \n",
"\n",
- "$$E_\\text{sum} = 1 + E + E^2 + \\dots = \\frac{1}{1-E}.$$\n",
+ "$$E_\\text{sum} = 1 + E + E^2 + \\dots = \\frac{1}{1-E}$$\n",
"\n",
- "However, we must be careful when doing this, as the transfer matrix has leading eigenvalue $1$ (as defined by our normalization), this quantity will diverge. This can be solved by defining a regularized transfer matrix $\\tilde{E}$, substracting the divergent part:\n",
+ "between the left and right outer parts. Here, we have naively used the geometric series to write the sum in a closed form. However, since by our normalization the transfer matrix has leading eigenvalue $1$, this resulting expression will diverge and is therefore ill-defined. We can get around this by introducing a regularized transfer matrix $\\tilde{E}$ which is defined by subtracting the divergent part,\n",
"\n",
- "\n",
+ "
\n",
"\n",
- "and only then taking the inverse.\n",
+ "Since we have already shifted the energy density to have a zero expectation value, $h \\leftarrow h - e$, it can easily be verified that the contribution of the leading divergent part vanishes in every left term, meaning that we can simply replace the original transfer matrix by its regularized version without changing any of the terms, and only then take the infinite sum which now has a well defined expression in terms of an inverse,\n",
"\n",
- "$$ \\tilde{E}_\\text{sum} = \\frac{1}{1-\\tilde{E}} =: (1 - E)^p $$\n",
+ "$$ E_\\text{sum} \\rightarrow \\frac{1}{1-\\tilde{E}} \\equiv (1 - E)^P ,$$\n",
"\n",
- "To ensure that this substraction has no effect on the outcome, we will redefine the Hamiltonian to have expectation value $0$. Such a constant shift in energy has no effect when we calculate the gradient. The benefit is that the divergent part we substracted from the transfermatrix vanishes.\n",
+ "where we have introduced the pseudo-inverse defined as $(1 - E)^P = (1-\\tilde{E})^{-1}$.\n",
"\n",
- "$$ \\tilde{H} = H - e $$\n",
+ "Using this notation we can define the partial contraction\n",
"\n",
- "Using this, we define the partial contraction\n",
- "\n",
- "\n",
+ "
\n",
"\n",
"such that the sum of all left terms equals\n",
"\n",
- "\n",
- "\n",
- "Implementing this inverse naively would be an ill-defined problem, so we resort to other algorithms to find $R_h$. Multiplying both sides with $(1-\\tilde{E})$ results in an equation of the form $Ax = b$, which may be solved for $x$ by implementing a Generalized Minimal RESidual (GMRES) algorithm. \n",
+ "
\n",
"\n",
- "Note that again such an algorithm requires only the action of A on a vector, not the actual matrix, such that its construction should again be implemented using a function handle."
+ "If we would compute the partial contraction $R_h$ directly by explicitly computing the pseudo-inverse, this would entail a computational complexity $O(D^6)$. Instead, we can define $L_h$ as the solution of a linear system by multiplying both sides of the corresponding definition by $(1-\\tilde{E})$. This results in an equation of the form $Ax = b$, which may be solved for $x$ by using Krylov-based iterative methods such as a Generalized Minimal RESidual (GMRES) algorithm. Note that these methods only require the action of $A = (1-\\tilde{E})$ on a vector and not the full matrix $A$. This action can again be supplied to the linear solver using a function handle."
]
},
{
@@ -164,7 +176,7 @@
"source": [
"def reducedHamUniform(h, A, l=None, r=None):\n",
" \"\"\"\n",
- " Regularise Hamiltonian such that its expectation value is 0.\n",
+ " Regularize Hamiltonian such that its expectation value is 0.\n",
" \n",
" Parameters\n",
" ----------\n",
@@ -172,14 +184,14 @@
" Hamiltonian that needs to be reduced,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight.\n",
" A : np.array (D, d, D)\n",
- " normalised MPS tensor with 3 legs,\n",
+ " normalized MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
- " l : np.array(D, D), optional\n",
+ " l : np.array (D, D), optional\n",
" left fixed point of transfermatrix,\n",
- " normalised.\n",
- " r : np.array(D, D), optional\n",
+ " normalized.\n",
+ " r : np.array (D, D), optional\n",
" right fixed point of transfermatrix,\n",
- " normalised.\n",
+ " normalized.\n",
" \n",
" Returns\n",
" -------\n",
@@ -216,22 +228,22 @@
" Parameters\n",
" ----------\n",
" A : np.array (D, d, D)\n",
- " normalised MPS tensor with 3 legs,\n",
+ " normalized MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
- " l : np.array(D, D), optional\n",
+ " l : np.array (D, D), optional\n",
" left fixed point of transfermatrix,\n",
- " normalised.\n",
- " r : np.array(D, D), optional\n",
+ " normalized.\n",
+ " r : np.array (D, D), optional\n",
" right fixed point of transfermatrix,\n",
- " normalised.\n",
- " v : np.array(D**2)\n",
+ " normalized.\n",
+ " v : np.array (D**2)\n",
" right matrix of size (D, D) on which\n",
" (1 - Etilde) acts,\n",
" given as a vector of size (D**2,)\n",
" \n",
" Returns\n",
" -------\n",
- " vNew : np.array(D**2)\n",
+ " vNew : np.array (D**2)\n",
" result of action of (1 - Etilde)\n",
" on a right matrix,\n",
" given as a vector of size (D**2,)\n",
@@ -269,20 +281,20 @@
" hTilde : np.array (d, d, d, d)\n",
" reduced Hamiltonian,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight,\n",
- " renormalised.\n",
+ " renormalized.\n",
" A : np.array (D, d, D)\n",
- " normalised MPS tensor with 3 legs,\n",
+ " normalized MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
- " l : np.array(D, D), optional\n",
+ " l : np.array (D, D), optional\n",
" left fixed point of transfermatrix,\n",
- " normalised.\n",
- " r : np.array(D, D), optional\n",
+ " normalized.\n",
+ " r : np.array (D, D), optional\n",
" right fixed point of transfermatrix,\n",
- " normalised.\n",
+ " normalized.\n",
" \n",
" Returns\n",
" -------\n",
- " Rh : np.array(D, D)\n",
+ " Rh : np.array (D, D)\n",
" result of contraction,\n",
" ordered top-bottom.\n",
" \"\"\"\n",
@@ -318,20 +330,20 @@
" hTilde : np.array (d, d, d, d)\n",
" reduced Hamiltonian,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight,\n",
- " renormalised.\n",
+ " renormalized.\n",
" A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
- " l : np.array(D, D), optional\n",
+ " l : np.array (D, D), optional\n",
" left fixed point of transfermatrix,\n",
- " normalised.\n",
- " r : np.array(D, D), optional\n",
+ " normalized.\n",
+ " r : np.array (D, D), optional\n",
" right fixed point of transfermatrix,\n",
- " normalised.\n",
+ " normalized.\n",
" \n",
" Returns\n",
" -------\n",
- " leftTerms : np.array(D, d, D)\n",
+ " leftTerms : np.array (D, d, D)\n",
" left terms of gradient,\n",
" ordered left-mid-right.\n",
" \"\"\"\n",
@@ -354,13 +366,14 @@
"metadata": {},
"source": [
"#### Terms of the 'right' kind\n",
- "In a very similar way, the terms where we leave out an $\\bar{A}$ to the right of the operator $h$, can be evaluated with the following contractions:\n",
"\n",
- "\n",
+ "In a similar way, the terms where we leave out an $\\bar{A}$ to the right of $h$ can be evaluated by defining the partial contraction\n",
"\n",
- "such that the sum of all 'right' terms equals\n",
+ "
\n",
"\n",
- ""
+ "which can again be found by solving a linear system, such that the sum of all right terms can be written as\n",
+ "\n",
+ "
"
]
},
{
@@ -376,22 +389,22 @@
" Parameters\n",
" ----------\n",
" A : np.array (D, d, D)\n",
- " normalised MPS tensor with 3 legs,\n",
+ " normalized MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
- " l : np.array(D, D), optional\n",
+ " l : np.array (D, D), optional\n",
" left fixed point of transfermatrix,\n",
- " normalised.\n",
- " r : np.array(D, D), optional\n",
+ " normalized.\n",
+ " r : np.array (D, D), optional\n",
" right fixed point of transfermatrix,\n",
- " normalised.\n",
- " v : np.array(D**2)\n",
+ " normalized.\n",
+ " v : np.array (D**2)\n",
" right matrix of size (D, D) on which\n",
" (1 - Etilde) acts,\n",
" given as a vector of size (D**2,)\n",
" \n",
" Returns\n",
" -------\n",
- " vNew : np.array(D**2)\n",
+ " vNew : np.array (D**2)\n",
" result of action of (1 - Etilde)\n",
" on a left matrix,\n",
" given as a vector of size (D**2,)\n",
@@ -429,20 +442,20 @@
" hTilde : np.array (d, d, d, d)\n",
" reduced Hamiltonian,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight,\n",
- " renormalised.\n",
+ " renormalized.\n",
" A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
- " l : np.array(D, D), optional\n",
+ " l : np.array (D, D), optional\n",
" left fixed point of transfermatrix,\n",
- " normalised.\n",
- " r : np.array(D, D), optional\n",
+ " normalized.\n",
+ " r : np.array (D, D), optional\n",
" right fixed point of transfermatrix,\n",
- " normalised.\n",
+ " normalized.\n",
" \n",
" Returns\n",
" -------\n",
- " Lh : np.array(D, D)\n",
+ " Lh : np.array (D, D)\n",
" result of contraction,\n",
" ordered bottom-top.\n",
" \"\"\"\n",
@@ -478,20 +491,20 @@
" hTilde : np.array (d, d, d, d)\n",
" reduced Hamiltonian,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight,\n",
- " renormalised.\n",
+ " renormalized.\n",
" A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
- " l : np.array(D, D), optional\n",
+ " l : np.array (D, D), optional\n",
" left fixed point of transfermatrix,\n",
- " normalised.\n",
- " r : np.array(D, D), optional\n",
+ " normalized.\n",
+ " r : np.array (D, D), optional\n",
" right fixed point of transfermatrix,\n",
- " normalised.\n",
+ " normalized.\n",
" \n",
" Returns\n",
" -------\n",
- " rightTerms : np.array(D, d, D)\n",
+ " rightTerms : np.array (D, d, D)\n",
" right terms of gradient,\n",
" ordered left-mid-right.\n",
" \"\"\"\n",
@@ -513,9 +526,11 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "The gradient is then found by summing all these contributions:\n",
+ "#### The gradient\n",
+ "\n",
+ "The full gradient is then found by summing the contributions of all three types of terms,\n",
"\n",
- "\n"
+ "
"
]
},
{
@@ -533,20 +548,20 @@
" h : np.array (d, d, d, d)\n",
" Hamiltonian,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight,\n",
- " renormalised.\n",
+ " renormalized.\n",
" A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
- " l : np.array(D, D), optional\n",
+ " l : np.array (D, D), optional\n",
" left fixed point of transfermatrix,\n",
- " normalised.\n",
- " r : np.array(D, D), optional\n",
+ " normalized.\n",
+ " r : np.array (D, D), optional\n",
" right fixed point of transfermatrix,\n",
- " normalised.\n",
+ " normalized.\n",
" \n",
" Returns\n",
" -------\n",
- " grad : np.array(D, d, D)\n",
+ " grad : np.array (D, d, D)\n",
" Gradient,\n",
" ordered left-mid-right.\n",
" \"\"\"\n",
@@ -555,7 +570,7 @@
" if l is None or r is None:\n",
" l, r = fixedPoints(A)\n",
" \n",
- " # renormalise Hamiltonian\n",
+ " # renormalize Hamiltonian\n",
" hTilde = reducedHamUniform(h, A, l, r)\n",
" \n",
" # find terms\n",
@@ -573,11 +588,12 @@
"metadata": {},
"source": [
"### 2.2 Gradient descent algorithms\n",
- "The simplest way to use this information to find the ground state of a Hamiltonian is then to use a method of gradient descent, or just by iterating, for a small step $\\epsilon$,\n",
"\n",
- "$$ A_{i+1} = A_i - \\epsilon g $$\n",
+ "The most straightforward way to use this expression for the gradient to find the ground state of a Hamiltonian is to implement a gradient-search method for minimizing the energy expecation value. The simplest such method is a steepest-descent search, where in every iteration the tensor $A$ is updated in the direction opposite to the gradient along a small step $\\varepsilon$,\n",
+ "\n",
+ "$$ A_{i+1} = A_i - \\varepsilon g .$$\n",
"\n",
- "in order to find the optimal MPS tensor $A^*$ for which the gradient vanishes. However, this can be further improved upon by using more advanced algorithms, as for example those already implemented by the scipy package scipy.optimize."
+ "This procedure is repeated until we find the optimal MPS tensor $A^*$ for which the gradient vanishes. This approach can be improved upon by resorting to other optimization schemes such a conjugate-gradient or quasi-Newton methods. Below we demonstrate both a simple steepest-descent with a fixed step size, as well as an approach using routines supplied by the scipy package in `scipy.optimize`."
]
},
{
@@ -586,21 +602,21 @@
"metadata": {},
"outputs": [],
"source": [
- "def groundStateGradDescent(h, D, eps=1e-1, A0=None, tol=1e-4, maxIter=1e4):\n",
+ "def groundStateGradDescent(h, D, eps=1e-1, A0=None, tol=1e-4, maxIter=1e4, verbose=True):\n",
" \"\"\"\n",
" Find the ground state using gradient descent.\n",
" \n",
" Parameters\n",
" ----------\n",
" h : np.array (d, d, d, d)\n",
- " Hamiltonian to minimise,\n",
+ " Hamiltonian to minimize,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight.\n",
" D : int\n",
" Bond dimension\n",
" eps : float\n",
" Stepsize.\n",
" A0 : np.array (D, d, D)\n",
- " normalised MPS tensor with 3 legs,\n",
+ " normalized MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" initial guess.\n",
" tol : float\n",
@@ -610,7 +626,7 @@
" -------\n",
" E : float\n",
" expectation value @ minimum\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" ground state MPS,\n",
" ordered left-mid-right.\n",
" \"\"\"\n",
@@ -620,7 +636,7 @@
" # if no initial value, choose random\n",
" if A0 is None:\n",
" A0 = createMPS(D, d)\n",
- " A0 = normaliseMPS(A0)\n",
+ " A0 = normalizeMPS(A0)\n",
" \n",
" # calculate gradient\n",
" g = gradient(h, A0)\n",
@@ -629,15 +645,15 @@
" A = A0\n",
" \n",
" i = 0\n",
- " while not(np.all(np.abs(g) < tol)):\n",
+ " while not(np.linalg.norm(g) < tol):\n",
" # do a step\n",
" A = A - eps * g\n",
- " A = normaliseMPS(A)\n",
+ " A = normalizeMPS(A)\n",
" i += 1\n",
" \n",
- " if not(i % 100):\n",
+ " if verbose and not(i % 100):\n",
" E = np.real(expVal2Uniform(h, A))\n",
- " print('Current energy:', E)\n",
+ " print('iteration:\\t{:d}\\tenergy:\\t{:.12f}\\tgradient norm:\\t{:.4e}'.format(i, E, np.linalg.norm(g)))\n",
" \n",
" # calculate new gradient\n",
" g = gradient(h, A)\n",
@@ -665,14 +681,14 @@
"metadata": {},
"outputs": [],
"source": [
- "def groundStateMinimise(h, D, A0=None, tol=1e-4):\n",
+ "def groundStateMinimize(h, D, A0=None, tol=1e-4):\n",
" \"\"\"\n",
" Find the ground state using a scipy minimizer.\n",
" \n",
" Parameters\n",
" ----------\n",
" h : np.array (d, d, d, d)\n",
- " Hamiltonian to minimise,\n",
+ " Hamiltonian to minimize,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight.\n",
" D : int\n",
" Bond dimension\n",
@@ -687,7 +703,7 @@
" -------\n",
" E : float\n",
" expectation value @ minimum\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" ground state MPS,\n",
" ordered left-mid-right.\n",
" \"\"\"\n",
@@ -700,7 +716,7 @@
" \n",
" Parameters\n",
" ----------\n",
- " varA : np.array(2 * D * d * D)\n",
+ " varA : np.array (2 * D * d * D)\n",
" MPS tensor in real vector form.\n",
" D : int\n",
" Bond dimension.\n",
@@ -709,7 +725,7 @@
" \n",
" Returns\n",
" -------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right.\n",
" \"\"\"\n",
@@ -728,13 +744,13 @@
" \n",
" Parameters\n",
" ----------\n",
- " A : np.array(D, d, D)\n",
+ " A : np.array (D, d, D)\n",
" MPS tensor,\n",
" ordered left-bottom-right\n",
" \n",
" Returns\n",
" -------\n",
- " varA : np.array(2 * D * d * D)\n",
+ " varA : np.array (2 * D * d * D)\n",
" MPS tensor in real vector form.\n",
" \"\"\"\n",
" \n",
@@ -750,7 +766,7 @@
" # if no initial MPS, take random one\n",
" if A0 is None:\n",
" A0 = createMPS(D, d)\n",
- " A0 = normaliseMPS(A0)\n",
+ " A0 = normalizeMPS(A0)\n",
" \n",
" # define f for minimize in scipy\n",
" def f(varA):\n",
@@ -759,20 +775,20 @@
" \n",
" Parameters\n",
" ----------\n",
- " varA : np.array(2 * D * d * D)\n",
+ " varA : np.array (2 * D * d * D)\n",
" MPS tensor in real vector form.\n",
" \n",
" Returns\n",
" -------\n",
" e : float\n",
" function value @varA\n",
- " g : np.array(2 * D * d * D)\n",
+ " g : np.array (2 * D * d * D)\n",
" gradient vector @varA\n",
" \"\"\"\n",
" \n",
" # unwrap varA\n",
" A = unwrapper(varA)\n",
- " A = normaliseMPS(A)\n",
+ " A = normalizeMPS(A)\n",
" \n",
" # calculate fixed points\n",
" l, r = fixedPoints(A)\n",
@@ -800,7 +816,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "These methods can be tested for the specific case of the antiferromagnetic Heisenberg model. To this end we first define the spin 1 Heisenberg Hamiltonian:"
+ "To demonstrate these methods, we now have a look the specific case of the antiferromagnetic spin-1 Heisenberg model in one dimension. To this end we first define the spin-1 Heisenberg Hamiltonian:"
]
},
{
@@ -846,15 +862,15 @@
"source": [
"d, D = 3, 12\n",
"A = createMPS(D, d)\n",
- "A = normaliseMPS(A)\n",
+ "A = normalizeMPS(A)\n",
"\n",
"h = Heisenberg(-1, -1, -1, 0)\n",
"\n",
"# energy optimization using naive gradient descent\n",
- "# for D=12 or higher: tolerance lower than 1e-3 gives very long runtimes\n",
+ "# for D=12 or higher: tolerance lower than 1e-2 gives very long runtimes\n",
"print('Gradient descent optimization:\\n')\n",
"t0 = time()\n",
- "E1, A1 = groundStateGradDescent(h, D, eps=1e-1, A0=A, tol=1e-3, maxIter=1e4)\n",
+ "E1, A1 = groundStateGradDescent(h, D, eps=1e-1, A0=A, tol=1e-2, maxIter=1e4)\n",
"print('Time until convergence:', time()-t0, 's')\n",
"print('Computed energy:', E1, '\\n')\n",
"\n",
@@ -862,7 +878,7 @@
"# for D=12 and tolerance 1e-5: runtime of somewhere around 100s\n",
"print('Optimization using scipy minimize:\\n')\n",
"t0 = time()\n",
- "E2, A2 = groundStateMinimise(h, D, A0=A, tol=1e-4)\n",
+ "E2, A2 = groundStateMinimize(h, D, A0=A, tol=1e-4)\n",
"print('Time until convergence:', time()-t0, 's')\n",
"print('Computed energy:', E2, '\\n')"
]
@@ -871,12 +887,13 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "### 2.3 VUMPS\n",
- "In the previous section we have not made use of the fact that we can use specific gauges to optimalise the procedure. A variational ground-state optimization algorithm that does exploit the power of the mixed gauge is VUMPS, Variational Uniform Matrix Product States, described in Algorithm 4 in the lecture notes.\n",
+ "### 2.3 The VUMPS algorithm\n",
"\n",
- "In order to implement this algorithm, we repeat most of the steps of the previous sectionm however now working with the mixed gauge.\n",
+ "In the previous section we have derived an expression for the gradient starting from an MPS in the uniform gauge, which corresponds to an object that lives in the space of MPS tensors. We now discuss how to improve upon direct optimization schemes based on this form of the gradient by exploiting the structure of the MPS manifold as well as the mixed gauge for MPS.\n",
"\n",
- "We start off by implementing the regularisation of the two-site Hamiltonian, now using the mixed gauge algorithm:"
+ "Indeed, while the gradient in the above form indicates a direction in the space of complex tensors in which the energy decreases, intuitively it would make more sense if we could find a way to interpret the gradient as a direction *along the MPS manifold* along which we can decrease the energy. This can be achieved by interpreting the gradient as a *tangent vector in the tangent space to the MPS manifold*. By formulating the energy optimization in terms of this tangent space gradient written in mixed gauge, one arives at the [VUMPS](https://doi.org/10.1103/PhysRevB.97.045145) algorithm (which stand for 'variational uniform matrix product states'). The precise derivation of the tangent space gradient in mixed gauge falls beyond the scope of this tutorial, and can be found in the [lecture notes](https://doi.org/10.21468/SciPostPhysLectNotes.7). Instead we will simply illustrate the implementation of the VUMPS algorithm given the mixed gauge tangent space gradient.\n",
+ "\n",
+ "Most of the following required steps will be reminiscent of those outlined above, where we now consistently work in the mixed gauge. We start off by implementing the regularization of the two-site Hamiltonian in the mixed gauge."
]
},
{
@@ -887,18 +904,18 @@
"source": [
"def reducedHamMixed(h, Ac, Ar):\n",
" \"\"\"\n",
- " Regularise Hamiltonian such that its expectation value is 0.\n",
+ " Regularize Hamiltonian such that its expectation value is 0.\n",
" \n",
" Parameters\n",
" ----------\n",
" h : np.array (d, d, d, d)\n",
" Hamiltonian that needs to be reduced,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight.\n",
- " Ac : np.array(D, d, D)\n",
+ " Ac : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauged.\n",
- " Ar : np.array(D, d, D)\n",
+ " Ar : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" right gauged.\n",
@@ -925,39 +942,61 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "Then, we want to calculate the gradient in the mixed gauge. The expression for the gradient can be found by making use of the tangent space projector, such that the final expression is\n",
+ "The variational optimum of the energy is characterized by the condition that the gradient is zero at this point. Writing the tangent space gradient as $G$, we now wish to formulate an algorithm which minimizes the error measure\n",
+ "\n",
+ "$$ \\varepsilon = \\left( \\boldsymbol{G}^\\dagger \\boldsymbol{G} \\right)^{1/2} $$\n",
+ "\n",
+ "in an efficient way. The explicit form of the tangent space gradient in mixed gauge is given by\n",
+ "\n",
+ "$$ G = A^\\prime_{C} - A_L C^\\prime = A^\\prime_{C} - C^\\prime A_R, $$\n",
"\n",
- "$$ G = A^\\prime_{A_C} - A_L C^\\prime, $$\n",
- "where the primed factors are defined as\n",
+ "where $A^\\prime_{C}$ and $C^\\prime$ are defined as\n",
"\n",
+ "
\n",
+ "\n",
+ "Here, we again use $L_h$ and $R_h$ to indicate the partial contractions\n",
+ "\n",
+ "
\n",
+ "\n",
+ "and\n",
+ "\n",
+ "
\n",
"\n",
+ "where the transfer matrices $E^L_L$ and $E^R_R$ appearing in these expressions now contain only left-gauged and right-gauged MPS tensors $A_L$ and $A_R$ respectively.\n",
"\n",
- "\n",
+ "If we interpret the two terms appearing in the tangent space gradient as defining the effective Hamiltonians $H_{A_C}(\\cdot)$ and $H_C(\\cdot)$ such that\n",
"\n",
- "If we define effective hamiltonians $H_{A_C}(\\bullet)$ and $H_C(\\bullet)$ such that \n",
+ "$$\n",
+ "\\begin{align}\n",
+ "H_{A_C}(A_C) = A_C^\\prime \\\\\n",
+ "H_C(C) = C^\\prime ,\n",
+ "\\end{align}\n",
+ "$$\n",
"\n",
- "$$ A_C^\\prime = H_{A_C}(A_C) $$\n",
- "$$ C^\\prime = H_C(C), $$\n",
+ "we can characterize the variational optimum in terms of the fixed points of these operators. Indeed, since the tangent space gradient should be zero at the variational optimum, this point satisfies $A_C' = A_L C' = C' A_R$. This implies that the optimal MPS should obey the following set of equations,\n",
"\n",
- "We characterise an optimal MPS with the consistency equations for the mixed gauge, supplemented by\n",
+ "$$\n",
+ "\\begin{align}\n",
+ "H_{A_C}(A_C) \\propto A_C \\\\\n",
+ "H_C(C) \\propto C \\\\\n",
+ "A_C = A_L C = C A_R ,\n",
+ "\\end{align}\n",
+ "$$\n",
"\n",
- "$$ A_LC = CA_R = A_C $$\n",
- "$$ H_{A_C}(A_C) \\sim A_C $$\n",
- "$$ H_C(C) \\sim C. $$"
+ "meaning that the optimal MPS should correspond to a fixed point of the effective Hamiltonians $H_{A_C}$ and $H_C$ and satisfy the mixed gauge condition. The VUMPS algorithm then consists of an iterative method for finding a set $\\{A_L, A_C, A_R, C\\}$ that satisfies these equations simultaneously."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "Similar to before, we again have to compute contributions of right and left environment terms, which are now given by:\n",
- "\n",
- "and\n",
- "\n",
+ "#### Defining the required operators\n",
"\n",
- "In order to compute these tensors, we now require a function handle for the action of the right (resp. left) transfer matrix on a right (resp. left) matrix, with $A$ replaced by $A_L, A_R$. There is however no need to implement this action again for this specific case: we can reuse the implementations EtildeRight and EtildeLeft from above, if we take into account that the left (resp. right) fixed point of the right (resp. left) transfer matrix is precisely $C^\\dagger C$ (resp. $C C^\\dagger$); this can be easily verified using the property (15)."
+ "Similar to before, we again have to compute the contributions of the left and right environment terms $L_h$ and $R_h$ given above. We therefore require function handles defining the action of the left (resp. right) transfer matrix $E^L_L$ (resp. $E^R_R$) on a left (resp. right) matrix. To this end, we can simply reuse the implementations `EtildeLeft` and `EtildeRight` defined above, if we take into account that the left (resp. right) fixed point of $E^L_L$ (resp. $E^R_R$) is the identity while its right (resp. left) fixed point is precisely $C C^\\dagger$ (resp. $C^\\dagger C$). This last fact follows immediately from the mixed gauge condition."
]
},
{
@@ -966,21 +1005,21 @@
"metadata": {},
"outputs": [],
"source": [
- "def RhMixed(hTilde, Ar, C, tol=1e-3):\n",
+ "def LhMixed(hTilde, Al, C, tol=1e-5):\n",
" \"\"\"\n",
- " Calculate Rh, for a given MPS in mixed gauge.\n",
+ " Calculate Lh, for a given MPS in mixed gauge.\n",
" \n",
" Parameters\n",
" ----------\n",
" hTilde : np.array (d, d, d, d)\n",
" reduced Hamiltonian,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight,\n",
- " renormalised.\n",
- " Ar : np.array (D, d, D)\n",
+ " renormalized.\n",
+ " Al : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
- " right-orthonormal.\n",
- " C : np.array(D, D)\n",
+ " left-orthonormal.\n",
+ " C : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right.\n",
" tol : float, optional\n",
@@ -988,25 +1027,27 @@
" \n",
" Returns\n",
" -------\n",
- " Rh : np.array(D, D)\n",
+ " Lh : np.array (D, D)\n",
" result of contraction,\n",
- " ordered top-bottom.\n",
+ " ordered bottom-top.\n",
+ " \n",
" \"\"\"\n",
" \n",
- " D = Ar.shape[0]\n",
+ " D = Al.shape[0]\n",
+ " tol = max(tol, 1e-14)\n",
" \n",
- " # construct fixed points for Ar\n",
- " l = np.conj(C).T @ C # left fixed point of right transfer matrix\n",
- " r = np.eye(D) # right fixed point of right transfer matrix: right orthonormal\n",
- "\n",
+ " # construct fixed points for Al\n",
+ " l = np.eye(D) # left fixed point of left transfer matrix: left orthonormal\n",
+ " r = C @ np.conj(C).T # right fixed point of left transfer matrix\n",
+ " \n",
" # construct b\n",
- " b = ncon((Ar, Ar, np.conj(Ar), np.conj(Ar), hTilde), ([-1, 2, 1], [1, 3, 4], [-2, 7, 6], [6, 5, 4], [2, 3, 7, 5]))\n",
+ " b = ncon((Al, Al, np.conj(Al), np.conj(Al), hTilde), ([4, 2, 1], [1, 3, -2], [4, 5, 6], [6, 7, -1], [2, 3, 5, 7]))\n",
" \n",
- " # solve ax = b for x\n",
- " a = LinearOperator((D ** 2, D ** 2), matvec=partial(EtildeRight, Ar, l, r))\n",
- " Rh = gmres(a, b.reshape(D ** 2), tol=tol)[0]\n",
+ " # solve a x = b for x\n",
+ " a = LinearOperator((D ** 2, D ** 2), matvec=partial(EtildeLeft, Al, l, r)) \n",
+ " Lh = gmres(a, b.reshape(D ** 2), tol=tol)[0]\n",
" \n",
- " return Rh.reshape((D, D))"
+ " return Lh.reshape((D, D))"
]
},
{
@@ -1015,21 +1056,21 @@
"metadata": {},
"outputs": [],
"source": [
- "def LhMixed(hTilde, Al, C, tol=1e-3):\n",
+ "def RhMixed(hTilde, Ar, C, tol=1e-5):\n",
" \"\"\"\n",
- " Calculate Lh, for a given MPS in mixed gauge.\n",
+ " Calculate Rh, for a given MPS in mixed gauge.\n",
" \n",
" Parameters\n",
" ----------\n",
" hTilde : np.array (d, d, d, d)\n",
" reduced Hamiltonian,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight,\n",
- " renormalised.\n",
- " Al : np.array (D, d, D)\n",
+ " renormalized.\n",
+ " Ar : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
- " left-orthonormal.\n",
- " C : np.array(D, D)\n",
+ " right-orthonormal.\n",
+ " C : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right.\n",
" tol : float, optional\n",
@@ -1037,37 +1078,37 @@
" \n",
" Returns\n",
" -------\n",
- " Lh : np.array(D, D)\n",
+ " Rh : np.array (D, D)\n",
" result of contraction,\n",
- " ordered bottom-top.\n",
- " \n",
+ " ordered top-bottom.\n",
" \"\"\"\n",
" \n",
- " D = Al.shape[0]\n",
+ " D = Ar.shape[0]\n",
+ " tol = max(tol, 1e-14)\n",
" \n",
- " # construct fixed points for Al\n",
- " l = np.eye(D) # left fixed point of left transfer matrix: left orthonormal\n",
- " r = C @ np.conj(C).T # right fixed point of left transfer matrix\n",
- " \n",
+ " # construct fixed points for Ar\n",
+ " l = np.conj(C).T @ C # left fixed point of right transfer matrix\n",
+ " r = np.eye(D) # right fixed point of right transfer matrix: right orthonormal\n",
+ "\n",
" # construct b\n",
- " b = ncon((Al, Al, np.conj(Al), np.conj(Al), hTilde), ([4, 2, 1], [1, 3, -2], [4, 5, 6], [6, 7, -1], [2, 3, 5, 7]))\n",
+ " b = ncon((Ar, Ar, np.conj(Ar), np.conj(Ar), hTilde), ([-1, 2, 1], [1, 3, 4], [-2, 7, 6], [6, 5, 4], [2, 3, 7, 5]))\n",
" \n",
- " # solve a x = b for x\n",
- " a = LinearOperator((D ** 2, D ** 2), matvec=partial(EtildeLeft, Al, l, r)) \n",
- " Lh = gmres(a, b.reshape(D ** 2), tol=tol)[0]\n",
+ " # solve ax = b for x\n",
+ " a = LinearOperator((D ** 2, D ** 2), matvec=partial(EtildeRight, Ar, l, r))\n",
+ " Rh = gmres(a, b.reshape(D ** 2), tol=tol)[0]\n",
" \n",
- " return Lh.reshape((D, D))"
+ " return Rh.reshape((D, D))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "We then implement the actions of the effective Hamiltonians $H_{A_C}$ and $H_{C}$ defined in equations (131) and (132) respectively:\n",
+ "Next we implement the actions of the effective Hamiltonians $H_{A_C}$ and $H_{C}$ defined above,\n",
"\n",
- "$H_{A_C}(A_C) = $ \n",
+ "
\n",
"\n",
- "$H_{C}(C) = $ "
+ "
"
]
},
{
@@ -1076,7 +1117,6 @@
"metadata": {},
"outputs": [],
"source": [
- "# define handle for effective hamiltonian for Ac\n",
"def H_Ac(hTilde, Al, Ar, Lh, Rh, v):\n",
" \"\"\"\n",
" Action of the effective Hamiltonian for Ac (131) on a vector.\n",
@@ -1086,7 +1126,7 @@
" hTilde : np.array (d, d, d, d)\n",
" reduced Hamiltonian,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight,\n",
- " renormalised.\n",
+ " renormalized.\n",
" Al : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
@@ -1095,18 +1135,18 @@
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" right-orthonormal.\n",
- " Lh : np.array(D, D)\n",
+ " Lh : np.array (D, D)\n",
" left environment,\n",
" ordered bottom-top.\n",
- " Rh : np.array(D, D)\n",
+ " Rh : np.array (D, D)\n",
" right environment,\n",
" ordered top-bottom.\n",
- " v : np.array(D, d, D)\n",
+ " v : np.array (D, d, D)\n",
" Tensor of size (D, d, D)\n",
"\n",
" Returns\n",
" -------\n",
- " H_AcV : np.array(D, d, D)\n",
+ " H_AcV : np.array (D, d, D)\n",
" Result of the action of H_Ac on the vector v,\n",
" representing a tensor of size (D, d, D)\n",
"\n",
@@ -1145,7 +1185,7 @@
" hTilde : np.array (d, d, d, d)\n",
" reduced Hamiltonian,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight,\n",
- " renormalised.\n",
+ " renormalized.\n",
" Al : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
@@ -1154,18 +1194,18 @@
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" right-orthonormal.\n",
- " Lh : np.array(D, D)\n",
+ " Lh : np.array (D, D)\n",
" left environment,\n",
" ordered bottom-top.\n",
- " Rh : np.array(D, D)\n",
+ " Rh : np.array (D, D)\n",
" right environment,\n",
" ordered top-bottom.\n",
- " v : np.array(D, D)\n",
+ " v : np.array (D, D)\n",
" Matrix of size (D, D)\n",
"\n",
" Returns\n",
" -------\n",
- " H_CV : np.array(D, D)\n",
+ " H_CV : np.array (D, D)\n",
" Result of the action of H_C on the matrix v.\n",
"\n",
" \"\"\"\n",
@@ -1189,11 +1229,26 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "The vumps algorithm consists now of an iterative method for finding $\\{A_L, A_R, A_C, C\\}$ that satisfies the equations that define the optimum simultaneously. This is done as follows.\n",
+ "#### Implementing the VUMPS algorithm\n",
+ "\n",
+ "In order to find a set $\\{A_L^*, A_C^*, A_R^*, C^*\\}$ that satisfies the VUMPS fixed point equations given above, we use an iterative method in which each iteration consists of the following steps, each time starting from a given set $\\{A_L, A_C, A_R, C\\}$:\n",
+ "\n",
+ "1. Solve the eigenvalue equations for $H_{A_C}$ and $H_C$, giving new center tensors $\\tilde{A}_C$ and $\\tilde{C}$.\n",
+ "\n",
+ "2. From these new center tensors, construct a set $\\{\\tilde{A}_L, \\tilde{A}_R, \\tilde{A}_C, \\tilde{C}\\}$.\n",
"\n",
- "1. calcNewCenter:\n",
+ "3. Update the set of tensors $\\{A_L, A_C, A_R, C\\} \\leftarrow \\{\\tilde{A}_L, \\tilde{A}_C, \\tilde{A}_R, \\tilde{C}\\}$ and evaluate the norm of the gradient $\\varepsilon = \\left | \\left | H_{A_C} (A_C) - A_L H_C(C) \\right | \\right |$.\n",
"\n",
- "We start off the algorithm by finding two new tensors $\\tilde{A_C}$ and $\\tilde{C}$, by solving the eigenvalue problem defined by the effective Hamiltonians we implemented before."
+ "4. If the norm of the gradient lies above the given tolerance, repeat."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "##### Updating the center tensors\n",
+ "\n",
+ "We start by defining a routine `calcNewCenter` which finds the new center tensors $\\tilde{A}_C$ and $\\tilde{C}$ by solving the eigenvalue problem defined by the effective Hamiltonians implemented above."
]
},
{
@@ -1202,7 +1257,7 @@
"metadata": {},
"outputs": [],
"source": [
- "def calcNewCenter(hTilde, Al, Ac, Ar, C, Lh=None, Rh=None, tol=1e-3):\n",
+ "def calcNewCenter(hTilde, Al, Ac, Ar, C, Lh=None, Rh=None, tol=1e-5):\n",
" \"\"\"\n",
" Find new guess for Ac and C as fixed points of the maps H_Ac and H_C.\n",
" \n",
@@ -1211,26 +1266,26 @@
" hTilde : np.array (d, d, d, d)\n",
" reduced Hamiltonian,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight,\n",
- " renormalised.\n",
- " Al : np.array(D, d, D)\n",
+ " renormalized.\n",
+ " Al : np.array (D, d, D)\n",
" MPS tensor zith 3 legs,\n",
" ordered left-bottom-right,\n",
" left orthonormal.\n",
- " Ar : np.array(D, d, D)\n",
+ " Ar : np.array (D, d, D)\n",
" MPS tensor zith 3 legs,\n",
" ordered left-bottom-right,\n",
" right orthonormal.\n",
- " Ac : np.array(D, d, D)\n",
+ " Ac : np.array (D, d, D)\n",
" MPS tensor zith 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauge.\n",
- " C : np.array(D, D)\n",
+ " C : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right.\n",
- " Lh : np.array(D, D)\n",
+ " Lh : np.array (D, D)\n",
" left environment,\n",
" ordered bottom-top.\n",
- " Rh : np.array(D, D)\n",
+ " Rh : np.array (D, D)\n",
" right environment,\n",
" ordered top-bottom.\n",
" tol : float, optional\n",
@@ -1238,17 +1293,18 @@
" \n",
" Returns\n",
" -------\n",
- " AcTilde : np.array(D, d, D)\n",
+ " AcTilde : np.array (D, d, D)\n",
" MPS tensor zith 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauge.\n",
- " CTilde : np.array(D, D)\n",
+ " CTilde : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right.\n",
" \"\"\"\n",
" \n",
" D = Al.shape[0]\n",
" d = Al.shape[1]\n",
+ " tol = max(tol, 1e-14)\n",
" \n",
" # calculate left en right environment if they are not given\n",
" if Lh is None:\n",
@@ -1286,18 +1342,31 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "2. minAcC\n",
+ "##### Extract a new set of mixed-gauge MPS tensors\n",
+ "\n",
+ "Once we have new center tensors, we can use these to construct a new set of mixed-gauge MPS tensors. To do this in a stable way, we will determine the global updates $\\tilde{A}_L$ and $\\tilde{A}_R$ as the left and right isometric tensors that minimize\n",
"\n",
- "Once we have new center tensors, we may use these to construct new mixed gauge MPS tensors according to algorithm 5 in the lecture notes. This is done with the following left and right polar decompositions:\n",
+ "$$\n",
+ "\\begin{align}\n",
+ "\\varepsilon_L = \\min ||\\tilde{A}_C - \\tilde{A}_L \\tilde{C}||_2 \\\\\n",
+ "\\varepsilon_R = \\min ||\\tilde{A}_C - \\tilde{C} \\tilde{A}_L||_2 .\n",
+ "\\end{align}\n",
+ "$$\n",
"\n",
- "$$ \\tilde{A_C} = U^l_{A_C} P^l_{A_C}, \\qquad \\tilde{C} = U^l_{C} P^l_{C} $$\n",
- "$$ \\tilde{A_C} = P^r_{A_C} U^r_{A_C} , \\qquad \\tilde{C} = P^r_{C} U^r_{C} $$\n",
+ "This can be achieved in a robust and close to optimal way by making use of the left and right polar decompositions\n",
"\n",
- "We then obtain:\n",
+ "$$\n",
+ "\\begin{align}\n",
+ "\\tilde{A}_C = U^l_{A_C} P^l_{A_C}, \\qquad \\tilde{C} = U^l_{C} P^l_{C}, \\\\\n",
+ "\\tilde{A}_C = P^r_{A_C} U^r_{A_C} , \\qquad \\tilde{C} = P^r_{C} U^r_{C},\n",
+ "\\end{align}\n",
+ "$$\n",
"\n",
- "$$ \\tilde{A_L} = U^l_{A_C} (U^l_C)^\\dagger, \\qquad \\tilde{A_R} = (U^r_C)^\\dagger U^r_{A_C}. $$\n",
+ "to obtain\n",
"\n",
- "In order to give the procedure some additional stability, we may consider using the $\\tilde{A_L}$ obtained through these polar decompositions to compute the tensors $\\tilde{A_R}$ and $\\tilde{A_C}$ by right orthonormalization of this $\\tilde{A_L}$. This approach ensures that the MPS is in proper mixed gauge at all times, which improves the convergence of the whole procedure."
+ "$$ \\tilde{A}_L = U^l_{A_C} (U^l_C)^\\dagger, \\qquad \\tilde{A}_R = (U^r_C)^\\dagger U^r_{A_C}. $$\n",
+ "\n",
+ "In order to give the procedure some additional stability, we may also choose to use the $\\tilde{A}_L$ obtained with these polar decompositions to compute the tensors $\\tilde{A}_R$ and $\\tilde{A}_C$ by right orthonormalization of this $\\tilde{A}_L$. This approach ensures that the MPS satisfies the mixed gauge condition at all times, improving the overal stabilitiy of the VUMPS algorithm. This procedure is implemented in the `minAcC` routine."
]
},
{
@@ -1306,36 +1375,36 @@
"metadata": {},
"outputs": [],
"source": [
- "def minAcC(AcTilde, CTilde):\n",
+ "def minAcC(AcTilde, CTilde, tol=1e-5):\n",
" \"\"\"\n",
" Find Al and Ar corresponding to Ac and C, according to algorithm 5 in the lecture notes.\n",
" \n",
" Parameters\n",
" ----------\n",
- " AcTilde : np.array(D, d, D)\n",
+ " AcTilde : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" new guess for center gauge. \n",
- " CTilde : np.array(D, D)\n",
+ " CTilde : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right,\n",
" new guess for center gauge\n",
" \n",
" Returns\n",
" -------\n",
- " Al : np.array(D, d, D)\n",
+ " Al : np.array (D, d, D)\n",
" MPS tensor zith 3 legs,\n",
" ordered left-bottom-right,\n",
" left orthonormal.\n",
- " Ar : np.array(D, d, D)\n",
+ " Ar : np.array (D, d, D)\n",
" MPS tensor zith 3 legs,\n",
" ordered left-bottom-right,\n",
" right orthonormal.\n",
- " Ac : np.array(D, d, D)\n",
+ " Ac : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauge. \n",
- " C : np.array(D, D)\n",
+ " C : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right,\n",
" center gauge\n",
@@ -1344,6 +1413,7 @@
" \n",
" D = AcTilde.shape[0]\n",
" d = AcTilde.shape[1]\n",
+ " tol = max(tol, 1e-14)\n",
" \n",
" # polar decomposition of Ac\n",
" UlAc, _ = polar(AcTilde.reshape((D * d, D)))\n",
@@ -1354,8 +1424,8 @@
" # construct Al\n",
" Al = (UlAc @ np.conj(UlC).T).reshape(D, d, D)\n",
" \n",
- " # find corresponding Ar, C, and Ac through right orthonormalising Al\n",
- " C, Ar = rightOrthonormalise(Al)\n",
+ " # find corresponding Ar, C, and Ac through right orthonormalizing Al\n",
+ " C, Ar = rightOrthonormalize(Al, CTilde, tol=tol)\n",
" nrm = np.trace(C @ np.conj(C).T)\n",
" C = C / np.sqrt(nrm)\n",
" Ac = ncon((Al, C), ([-1, -2, 1], [1, -3]))\n",
@@ -1367,9 +1437,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "3. gradientNorm\n",
+ "##### Evaluating the norm of the gradient\n",
"\n",
- "As a last step, we require the norm of the gradient, as in Eq. (130), in order to check if we have converged."
+ "As a last step, we use the routine `gradientNorm` to compute the norm of the tangent space gradient in order to check if the procedure has converged."
]
},
{
@@ -1387,26 +1457,26 @@
" hTilde : np.array (d, d, d, d)\n",
" reduced Hamiltonian,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight,\n",
- " renormalised.\n",
- " Al : np.array(D, d, D)\n",
+ " renormalized.\n",
+ " Al : np.array (D, d, D)\n",
" MPS tensor zith 3 legs,\n",
" ordered left-bottom-right,\n",
" left orthonormal.\n",
- " Ar : np.array(D, d, D)\n",
+ " Ar : np.array (D, d, D)\n",
" MPS tensor zith 3 legs,\n",
" ordered left-bottom-right,\n",
" right orthonormal.\n",
- " Ac : np.array(D, d, D)\n",
+ " Ac : np.array (D, d, D)\n",
" MPS tensor zith 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauge.\n",
- " C : np.array(D, D)\n",
+ " C : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right.\n",
- " Lh : np.array(D, D)\n",
+ " Lh : np.array (D, D)\n",
" left environment,\n",
" ordered bottom-top.\n",
- " Rh : np.array(D, D)\n",
+ " Rh : np.array (D, D)\n",
" right environment,\n",
" ordered top-bottom.\n",
" \n",
@@ -1433,7 +1503,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "Finally, this allows to implement the VUMPS algorithm:"
+ "Finally, this allows to implement the VUMPS algorithm."
]
},
{
@@ -1442,14 +1512,14 @@
"metadata": {},
"outputs": [],
"source": [
- "def vumps(h, D, A0=None, tol=1e-4):\n",
+ "def vumps(h, D, A0=None, tol=1e-4, tolFactor=1e-1, verbose=True):\n",
" \"\"\"\n",
" Find the ground state of a given Hamiltonian using VUMPS.\n",
" \n",
" Parameters\n",
" ----------\n",
" h : np.array (d, d, d, d)\n",
- " Hamiltonian to minimise,\n",
+ " Hamiltonian to minimize,\n",
" ordered topLeft-topRight-bottomLeft-bottomRight.\n",
" D : int\n",
" Bond dimension\n",
@@ -1464,19 +1534,19 @@
" -------\n",
" E : float\n",
" expectation value @ minimum\n",
- " Al : np.array(D, d, D)\n",
+ " Al : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" left orthonormal.\n",
- " Ar : np.array(D, d, D)\n",
+ " Ar : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" right orthonormal.\n",
- " Ac : np.array(D, d, D)\n",
+ " Ac : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauge.\n",
- " C : np.array(D, D)\n",
+ " C : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right.\n",
" \"\"\"\n",
@@ -1492,20 +1562,17 @@
" \n",
" flag = True\n",
" delta = 1e-5\n",
+ " i = 0\n",
" \n",
" while flag:\n",
- " # regularise H\n",
+ " i += 1\n",
+ " \n",
+ " # regularize H\n",
" hTilde = reducedHamMixed(h, Ac, Ar)\n",
" \n",
" # calculate environments\n",
- " Lh = LhMixed(hTilde, Al, C, tol=delta/10)\n",
- " Rh = RhMixed(hTilde, Ar, C, tol=delta/10)\n",
- " \n",
- " # calculate new center\n",
- " AcTilde, CTilde = calcNewCenter(hTilde, Al, Ac, Ar, C, Lh, Rh, tol=delta/10)\n",
- " \n",
- " # find Al, Ar from Ac, C\n",
- " AlTilde, AcTilde, ArTilde, CTilde = minAcC(AcTilde, CTilde)\n",
+ " Lh = LhMixed(hTilde, Al, C, tol=delta*tolFactor)\n",
+ " Rh = RhMixed(hTilde, Ar, C, tol=delta*tolFactor)\n",
" \n",
" # calculate norm\n",
" delta = gradientNorm(hTilde, Al, Ac, Ar, C, Lh, Rh)\n",
@@ -1514,12 +1581,19 @@
" if delta < tol:\n",
" flag = False\n",
" \n",
+ " # calculate new center\n",
+ " AcTilde, CTilde = calcNewCenter(hTilde, Al, Ac, Ar, C, Lh, Rh, tol=delta*tolFactor)\n",
+ " \n",
+ " # find Al, Ar from AcTilde, CTilde\n",
+ " AlTilde, AcTilde, ArTilde, CTilde = minAcC(AcTilde, CTilde, tol=delta*tolFactor**2)\n",
+ " \n",
" # update tensors\n",
" Al, Ac, Ar, C = AlTilde, AcTilde, ArTilde, CTilde\n",
" \n",
- " # print current energy, optional...\n",
- " E = np.real(expVal2Mixed(h, Ac, Ar))\n",
- " print('Current energy:', E)\n",
+ " # print current energy\n",
+ " if verbose:\n",
+ " E = np.real(expVal2Mixed(h, Ac, Ar))\n",
+ " print('iteration:\\t{:d}\\tenergy:\\t{:.12f}\\tgradient norm:\\t{:.4e}'.format(i, E, delta))\n",
" \n",
" return E, Al, Ac, Ar, C"
]
@@ -1528,7 +1602,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "We can again test this implementation on the spin-1 Heisenberg antiferromagnet:"
+ "We can again test this implementation on the spin-1 Heisenberg antiferromagnet."
]
},
{
@@ -1539,14 +1613,14 @@
"source": [
"d, D = 3, 12\n",
"A = createMPS(D, d)\n",
- "A = normaliseMPS(A)\n",
+ "A = normalizeMPS(A)\n",
"\n",
"h = Heisenberg(-1, -1, -1, 0)\n",
"\n",
"# energy optimization using VUMPS\n",
"print('Energy optimization using VUMPS:\\n')\n",
"t0 = time()\n",
- "E, Al, Ac, Ar, C = vumps(h, D, A0=A, tol=1e-4)\n",
+ "E, Al, Ac, Ar, C = vumps(h, D, A0=A, tol=1e-4, tolFactor=1e-2, verbose=True)\n",
"print('\\nTime until convergence:', time()-t0, 's\\n')\n",
"print('Computed energy:', E, '\\n')"
]
@@ -1555,7 +1629,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "Once we have obtained this ground state MPS, it is worthwile to have a look at the corresponding entanglement spectrum."
+ "Having obtained this ground state MPS, it is worthwile to have a look at the corresponding entanglement spectrum."
]
},
{
@@ -1587,88 +1661,100 @@
"\n",
"#### Quasiparticle ansatz\n",
"\n",
- "The methods described above can be further extended beyond the calculation of the ground state, and we now briefly show how one can also obtain quasiparticle excitations on top of this ground state. For this, we define the quasiparticle ansatz, given by\n",
+ "The methods described above can be extended beyond computing the ground state. We briefly discuss how one can also study excitations on top of a given ground state. For this, we introduce the MPS quasiparticle ansatz, given by\n",
+ "\n",
+ "
\n",
+ "\n",
+ "This ansatz cosists of defining a new state by changing one $A$ tensor of the ground state at site $n$ and taking a momentum superposition.\n",
+ "\n",
+ "Before describing how to optimize the tensor $B$, it is worthwile to investigate the corresponding variational space in a bit more detail. First, we note that this excitation ansatz can be interpreted as nothing more than a boosted version of a tangent vector to the MPS manifold. In particular, this means that we will be able to apply all kinds of useful tricks and manipulations to the tensor $B$ (cfr. the [lecture notes](https://doi.org/10.21468/SciPostPhysLectNotes.7) for an introduction to tangent vectors and their properties). For example, we can see that $B$ has gauge degrees of freedom, as the corresponding excited state is invariant under an additive gauge transformation of the form\n",
+ "\n",
+ "
\n",
"\n",
- "\n",
+ "where $Y$ is an arbitrary $D \\times D$ matrix. This gauge freedom can be eliminated, thereby removing the zero modes in the variational subspace, by imposing a *left gauge-fixing condition*\n",
"\n",
- "i.e. we change one $A$ tensor of the ground state at site $n$ and make a momentum superposition.\n",
+ "
\n",
"\n",
- "Before we start optimizing the tensor $B$, we first note that the excitation ansatw is, in fact, just a boosted version of a tangent vector. In particular, this allows for additional tricks and manipulations, similar to the previous sections. For example, the $B$ tensor has gauge degrees of freedom: the state is invariant under an additive gauge transformation of the form\n",
+ "If we parametrize the tensor $B$ as\n",
"\n",
- "\n",
+ "
\n",
"\n",
- "with $Y$ an arbitrary $D \\times D$ matrix. These correspond to zero modes in the variational subspace, which we can (and should) eliminate to make the variational optimization well-conditioned, by imposing a gauge fixing condition. We use the left gauge-fixing condition\n",
+ "where $V_L$ is the $ D \\times d \\times D(d-1)$ tensor corresponding to the $D(d-1)$-dimensional null space of $A_L$ satisfying\n",
"\n",
- "\n",
+ "
\n",
"\n",
- "which is automatically satisfied if we parametrise the $B$ tensor as\n",
+ "then the gauge condition is automatically satisfied. In particular, this fixing of the gauge freedom ensures that the excitation is orthogonal to the ground state,\n",
"\n",
- "\n",
+ "
\n",
"\n",
- "where we find Vl as the orthonormalised $D(d-1)$ - dimensional null space of the matrix \n",
+ "In this form, we have put forward an ansatz for an excited state characterized by a single $D(d-1) \\times D$ matrix $X$ such that\n",
"\n",
- "\n",
- ""
+ "1. All gauge degrees of freedom are fixed.\n",
+ "2. All zero modes in the variational subspace are removed.\n",
+ "3. Calculating the norm becomes straightforward.\n",
+ "4. The excitation is orthogonal to the ground state."
]
},
{
"cell_type": "markdown",
- "metadata": {},
+ "metadata": {
+ "tags": []
+ },
"source": [
"#### Solving the eigenvalue problem\n",
"\n",
- "We have put forward an ansatz for excitations, characterised by a matrix $X$ such that\n",
+ "Having introduced an excitation ansatz which has all the right properties and is defined in terms of a single matrix $X$, all that is left to do is to minimize the energy function,\n",
"\n",
- "1. All gauge degrees of freedom are fixed.\n",
- "2. All zero modes in the variational subspace are removed.\n",
- "3. Calculating the norm becomes straightforward.\n",
- "4. The excitation is orthogonal to the ground state, even at momentum $0$.\n",
+ "$$ \\min_{X} \\frac{\\left \\langle \\Phi_p(X) \\middle | H \\middle | \\Phi_p(X) \\right \\rangle}{\\left \\langle \\Phi_p(X) \\middle | \\Phi_p(X) \\right \\rangle}. $$\n",
"\n",
- "All we have left to do is minimising the energy function,\n",
+ "As both the numerator and the denominator are quadratic functions of the variational parameters $X$, this optimization problem reduces to solving a generalized eigenvalue problem\n",
"\n",
- "\n",
+ "$$ H_{\\text{eff}}(q) X = \\omega N_{\\text{eff}}(q) X, $$\n",
"\n",
- "As both numerator and denominator are quadratic functions of the variational parameters $X$, this optimization problem reduces to solving the generalised eigenvalue problem\n",
+ "where the effective energy and normalization matrices are defined as\n",
"\n",
- "$$ H_{eff}(q) X = \\omega N_{eff}(q) X, $$\n",
+ "$$\n",
+ "\\begin{align}\n",
+ "& 2\\pi\\delta(p-p') (\\boldsymbol{X'})^\\dagger H_{\\text{eff}}(q) \\boldsymbol{X} = \\left \\langle \\Phi_{p'}(X') \\middle | H \\middle | \\Phi_p(X) \\right \\rangle \\\\\n",
+ "& 2\\pi\\delta(p-p') (\\boldsymbol{X'})^\\dagger N_{\\text{eff}}(q) \\boldsymbol{X} = \\left \\langle \\Phi_{p'}(X') \\middle | \\Phi_p(X) \\right \\rangle,\n",
+ "\\end{align}\n",
+ "$$\n",
"\n",
- "where the effective energy and normalization matrix are defined as\n",
+ "and $\\boldsymbol{X}$ denotes a vectorized version of the matrix $X$. Since the overlap between two excited states is of the simple Euclidean form (cfr. the [lecture notes](https://doi.org/10.21468/SciPostPhysLectNotes.7)), the effective normalization matrix reduces to the unit matrix, and we are left with an ordinary eigenvalue problem.\n",
"\n",
- "\n",
+ "To solve this eigenvalue problem, we need to find an expression for $H_{\\text{eff}}$, or rather of the action thereof on a trial vector $\\boldsymbol{Y}$. In order to find this action we first transform the vector $\\boldsymbol{X}$ into a tensor $B$ by contracting its corresponding matrix with the right leg of $V_L$, and then compute all different contributions that pop up in a matrix element of the form $\\left \\langle \\Phi_p(B') \\middle | H \\middle | \\Phi_p(B) \\right \\rangle$. This procedure is similar to what we have done when computing the gradient above, where we now need to take into account all different positions of the nearest-neighbor operator $h$ of the Hamiltonian, the input tensor $B$ and the output. Though slightly more involved than before, we can again define the following partion contractions\n",
"\n",
- "Now since the overlap between two excited states is of the simple Euclidean form, the effective normalization matrix reduces to the unit matrix, and we are left with an ordinary eigenvalue problem.\n",
+ "
\n",
"\n",
- "To solve this eigenvalue problem, we need to find an expression of $H_{eff}$, or rather of the action thereof on a trial vector."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The calculation is done by implementing the following partial contractions:\n",
- "\n",
- "\n",
- "\n",
- "\n",
- "\n",
- "\n",
- ""
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Using the above partial contractions, we find the action of the (reduced) Hamiltonian on a given input vector $B$ as\n",
+ " \n",
"\n",
- "\n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The total procedure is captured by the following python function:"
+ "
\n",
+ "\n",
+ " \n",
+ "\n",
+ "
\n",
+ "\n",
+ " \n",
+ "\n",
+ "
\n",
+ "\n",
+ " \n",
+ "\n",
+ "
\n",
+ "\n",
+ " \n",
+ "\n",
+ "
\n",
+ "\n",
+ "Using these partial contractions, we find the action of the effective energy matrix on a given input tensor $B(Y)$ as\n",
+ "\n",
+ "
\n",
+ "\n",
+ "In the last step, we need the action of $H_{\\text{eff}}(p)$ on the vector $\\boldsymbol{Y}$, so we need to perform a last contraction\n",
+ "\n",
+ "
\n",
+ "\n",
+ "The total procedure is implemented in the routine `quasiParticle`."
]
},
{
@@ -1688,7 +1774,7 @@
" def ApplyHeff(x):\n",
" \n",
" x = np.reshape(x, (D*(d-1), D))\n",
- " B = ncon((VL, x), ([-1, -2, 1], [1, -3]))\n",
+ " B = ncon((Vl, x), ([-1, -2, 1], [1, -3]))\n",
" \n",
" def ApplyELR(x, p):\n",
" x = x.reshape((D,D))\n",
@@ -1734,14 +1820,14 @@
" np.exp(-1j*p)*ncon((left,Ar),([-1,1],[1,-2,-3]))+\\\n",
" np.exp(+1j*p)*ncon((Lh,Al,right),([-1,1],[1,-2,2],[2,-3]))\n",
" \n",
- " y = ncon((y, np.conj(VL)), ([1, 2, -2], [1, 2, -1]))\n",
+ " y = ncon((y, np.conj(Vl)), ([1, 2, -2], [1, 2, -1]))\n",
" y = y.reshape(-1)\n",
" \n",
" return y\n",
" \n",
" # find reduced parametrization\n",
" L = np.reshape(np.moveaxis(np.conj(Al), -1, 0), (D, D*d))\n",
- " VL = np.reshape(null_space(L), (D, d, D*(d-1)))\n",
+ " Vl = np.reshape(null_space(L), (D, d, D*(d-1)))\n",
" handleHeff = LinearOperator((D**2*(d-1), D**2*(d-1)), matvec=lambda x: ApplyHeff(x))\n",
" e, x = eigs(handleHeff, k=num, which='SR')\n",
" \n",
@@ -1752,7 +1838,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "We can use this to compute the Haldane gap on top of the ground state for the spin-1 Heisenberg antiferromagnet we have just obtained using VUMPS:"
+ "We can use this to compute the Haldane gap on top of the ground state of the spin-1 Heisenberg antiferromagnet we have just obtained using VUMPS."
]
},
{
@@ -1777,7 +1863,7 @@
],
"metadata": {
"kernelspec": {
- "display_name": "Python 3",
+ "display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
@@ -1791,7 +1877,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.7.9"
+ "version": "3.8.3"
}
},
"nbformat": 4,
diff --git a/python/chapter3.ipynb b/python/chapter3.ipynb
index 19b0f16..8d39d7d 100644
--- a/python/chapter3.ipynb
+++ b/python/chapter3.ipynb
@@ -8,55 +8,135 @@
},
"outputs": [],
"source": [
+ "# do all necessary imports for this chapter\n",
"import numpy as np\n",
+ "import matplotlib.pyplot as plt\n",
+ "from scipy.linalg import sqrtm\n",
"from scipy.sparse.linalg import eigs, LinearOperator\n",
"from ncon import ncon\n",
"from tutorialFunctions import createMPS, mixedCanonical, minAcC\n",
- "from time import time\n",
- "import matplotlib.pyplot as plt"
+ "from time import time"
]
},
{
"cell_type": "markdown",
- "metadata": {},
+ "metadata": {
+ "tags": []
+ },
"source": [
- "# Tangent-space methods for uniform matrix product states\n",
- "\n",
- "https://arxiv.org/abs/1810.07006\n",
+ "# [Tangent-space methods for uniform matrix product states](https://doi.org/10.21468/SciPostPhysLectNotes.7)\n",
"\n",
"## 3. Transfer matrices and fixed points\n",
"\n",
- "### 3.1 The vumps algorithm for MPOs\n",
+ "### 3.1 MPS as fixed points of one-dimensional transfer matrices\n",
+ "\n",
+ "Matrix product states have been used extensively as a variational ansatz for ground states of local Hamiltonians. In recent years, it has been observed that they can also provide accurate approximations for fixed points of transfer matrices. In this notebook we investigate how tangent-space methods for MPS can be applied to one-dimensional transfer matrices.\n",
+ "\n",
+ "A one-dimensional transfer matrix in the form of a *matrix product operator* (MPO) is given by\n",
+ "\n",
+ "$$\n",
+ "T(O) = \\sum_{\\{i\\}, \\{j\\}} \\left( \\dots O^{i_{n-1}, j_{n-1}} O^{i_{n}, j_{n}} O^{i_{n+1}, j_{n+1}} \\dots \\right)\n",
+ "\\left | i_{n-1} \\right \\rangle \\left \\langle j_{n-1} \\right | \\otimes \\left | i_{n} \\right \\rangle \\left \\langle j_{n} \\right | \\otimes \\left | i_{n+1} \\right \\rangle \\left \\langle j_{n+1} \\right | \\dots \\;,\n",
+ "$$\n",
+ "\n",
+ "which can be represented diagrammatically as\n",
+ "\n",
+ "
\n",
+ "\n",
+ "Such an object naturally arises in the context of infinite two-dimensional tensor networks, which can be interpreted as an infinite power of a corresponding one-dimensional row-to-row transfer matrix. This means that the contraction of the network is equivalent to finding the leading eigenvector $\\left | \\Psi \\right \\rangle$, referred to as the *fixed point*, of the transfer matrix which satisfies the equation\n",
+ "\n",
+ "$$T(O) \\left | \\Psi \\right \\rangle \\propto \\left | \\Psi \\right \\rangle.$$\n",
+ "\n",
+ "We can now propose an MPS ansatz for this fixed point, such that it obeys the eigenvalue equation\n",
+ "\n",
+ "
\n",
+ "\n",
+ "This MPS fixed point can be computed through a variation on the VUMPS algorithm introduced in the previous chapter, as will be explained in the next section. Suppose for now that we have managed to find an MPS representation $\\left | \\Psi(A) \\right \\rangle$ of the fixed point of $T(O)$. The corresponding eigenvalue $\\Lambda$ is then given by\n",
+ "\n",
+ "$$ \\Lambda = \\left \\langle \\Psi(\\bar{A}) \\middle | T \\middle | \\Psi(A) \\right \\rangle , $$\n",
+ "\n",
+ "assuming as always that we are dealing with a properly normalized MPS. If we bring $\\left | \\Psi(A) \\right \\rangle$ in mixed canonical form, then $\\Lambda$ is given by the network\n",
+ "\n",
+ "
\n",
+ "\n",
+ "We can contract this resulting infinite network by finding the fixed points of the left and right channel operators $T_L$ and $T_R$ which are defined as\n",
+ "\n",
+ "
\n",
"\n",
- "Matrix product states have been used extensively as variational ansatz for ground states of local hamiltonians, but in the last years it has been observed that they can also provide accurate approximations for fixed points of transfer matrices. In this chapter we investigate tangent-space methods for one-dimensional transfer matrices.\n",
+ "The corresponding fixed points $F_L$ and $F_R$, also referred to as the left and right *environments*, satisfy\n",
"\n",
- "A one-dimensional transfer matrix in the form of matrix product operator (MPO) can be represented diagrammatically as\n",
+ "
\n",
"\n",
- "\n",
+ "and can be normalized such that\n",
"\n",
- "Whenever we contract an infinite two-dimensional tensor network, we want to find the fixed point of this operator, i.e. we want to solve the fixed-point equation\n",
+ "
\n",
"\n",
- "$$ T(O) |\\Psi> \\propto |\\Psi> $$\n",
+ "The eigenvalues $\\lambda_L$ and $\\lambda_R$ have to correspond to the same value $\\lambda$ by construction, so that $\\Lambda$ is given by\n",
"\n",
- "such that we can collapse the vertical direction of the network.\n",
+ "$$ \\Lambda = \\lim_{N \\to \\infty} \\lambda^N ,$$\n",
"\n",
- "We now make the ansatz that the fixed point (leading eigenvector) of this operator is an MPS. Suppose now we have indeed found an MPS representation $|\\Psi(A)>$ of the fixed point of $T(O)$, then the eigenvalue is given by\n",
+ "where $N$ is the number of sites in the horizontal direction. Finally, we note that we can associate a *free energy density* $f = -\\frac{1}{N} \\log \\Lambda = -\\log \\lambda$ to this MPS fixed point.\n",
"\n",
- "$$ \\Lambda = <\\Psi(A)| T |\\Psi(A) > $$\n",
"\n",
- "Bringing the MPS in mixed canonical form, we write\n",
+ "### 3.2 The VUMPS algorithm for MPOs\n",
"\n",
- "\n",
+ "In order to formulate an algorithm for finding this MPS fixed point we start by stating the optimality condition it must satisfy in order to qualify as an approximate eigenvector of $T(O)$. Intuitively, what we would like to impose is that the residual $T(O) \\left| \\Psi \\right \\rangle - \\Lambda \\left | \\Psi \\right \\rangle$ is equal to zero. While this condition can never be satisfied exactly for any MPS approximation, we can however demand that the tangent space projection of this residual vanishes,\n",
"\n",
- "Again, contracting this infinite network requires that we find $F_L$ and $F_R$, the fixed points of the left and right channel operators, such that we may now collapse the horizontal direction. These are found by considering the following diagrams:\n",
+ "$$\n",
+ "\\mathcal{P}_A \\left( T(O) \\left| \\Psi \\right \\rangle - \\Lambda \\left | \\Psi \\right \\rangle \\right) = 0,\n",
+ "$$\n",
"\n",
- "\n",
- "\n",
+ "where $\\mathcal{P}_A$ is the projector onto the tangent space to the MPS manifold at $A$. Similar to the Hamiltonian case, this projected residual can be characterized in terms of a tangent space gradient $G$,\n",
"\n",
- "Finally these are properly normalised if we set\n",
- "\n",
+ "$$\n",
+ "G = A_C' - A_L C' = A_c' - C' A_R,\n",
+ "$$\n",
+ "\n",
+ "where $A_C'$ and $C'$ are now given by\n",
+ "\n",
+ "
\n",
+ "\n",
+ "and\n",
+ "\n",
+ "
\n",
+ "\n",
+ "The optimality condition for the fixed point MPS is then equivalent to having $||G|| = 0$. In addition, if the MPO defining the transfer matrix is hermitian then it can be shown that the optimality condition corresponds to the variational minimum of the free energy density introduced above. Similar to the Hamiltonian case, if we introduce operators $O_{A_C}(\\cdot)$ and $O_C(\\cdot)$ such that\n",
+ "\n",
+ "$$\n",
+ "\\begin{align}\n",
+ "O_{A_C}(A_C) = A_C', \\\\\n",
+ "O_{C}(C) = C',\n",
+ "\\end{align}\n",
+ "$$\n",
+ "\n",
+ "then it follows that the fixed point is characterized by the set of equations\n",
+ "\n",
+ "$$\n",
+ "\\begin{align}\n",
+ "O_{A_C}(A_C) \\propto A_C, \\\\\n",
+ "O_{C}(C) \\propto C, \\\\\n",
+ "A_C = A_L C = C A_R.\n",
+ "\\end{align}\n",
+ "$$\n",
+ "\n",
+ "The VUMPS algorithm for MPOs then corresponds to an iterative scheme for finding the solutions to these equations starting from a given set $\\{A_L, A_C, A_R, C\\}$ which consists of the following steps:\n",
+ "\n",
+ "1. Find the left and right environments $F_L$ and $F_R$ and use these to solve the eigenvalue equations for $O_{A_C}$ and $O_C$, giving new center tensors $\\tilde{A}_C$ and $\\tilde{C}$.\n",
+ "\n",
+ "2. From these new center tensors, extract the $\\tilde{A}_L$ and $\\tilde{A}_R$ that minimize $||\\tilde{A}_C - \\tilde{A}_L \\tilde{C}||$ and $||\\tilde{A}_C - \\tilde{C} \\tilde{A}_L||$ using the `minAcC` routine from the previous chapter.\n",
+ "\n",
+ "3. Update the set of tensors $\\{A_L, A_C, A_R, C\\} \\leftarrow \\{\\tilde{A}_L, \\tilde{A}_C, \\tilde{A}_R, \\tilde{C}\\}$ and evaluate the optimality condition $\\varepsilon = \\left | \\left | O_{A_C} (A_C) - A_L O_C(C) \\right | \\right |$.\n",
+ "\n",
+ "4. If the optimality condition lies above the given tolerance, repeat."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Implementing the VUMPS algorithm\n",
"\n",
- "We therefore first require routines for finding and normalising these fixed points."
+ "We start by implementing the routines for finding and normalizing the left and right environments of the channel operators."
]
},
{
@@ -65,9 +145,9 @@
"metadata": {},
"outputs": [],
"source": [
- "def leftFixedPointMPO(O, Al, tol):\n",
+ "def leftEnvironment(O, Al, tol):\n",
" \"\"\"\n",
- " Computes the left fixed point (250).\n",
+ " Computes the left environment as the fixed point of the left channel operator.\n",
"\n",
" Parameters\n",
" ----------\n",
@@ -85,8 +165,8 @@
" -------\n",
" lam : float\n",
" Leading left eigenvalue.\n",
- " Fl : np.array(D, d, D)\n",
- " left fixed point,\n",
+ " Fl : np.array (D, d, D)\n",
+ " left environment,\n",
" ordered bottom-middle-top.\n",
"\n",
" \"\"\"\n",
@@ -95,19 +175,20 @@
" \n",
" D = Al.shape[0]\n",
" d = Al.shape[1]\n",
+ " tol = max(tol, 1e-14)\n",
" \n",
" # construct handle for the action of the relevant operator and cast to linear operator\n",
- " transferLeftHandleMPO = lambda v: (ncon((v.reshape((D,d,D)), Al, np.conj(Al), O),([5, 3, 1], [1, 2, -3], [5, 4, -1], [3, 2, -2, 4]))).reshape(-1)\n",
- " transferLeftMPO = LinearOperator((D**2*d, D**2*d), matvec=transferLeftHandleMPO)\n",
+ " channelLeftHandle = lambda v: (ncon((v.reshape((D,d,D)), Al, np.conj(Al), O),([5, 3, 1], [1, 2, -3], [5, 4, -1], [3, 2, -2, 4]))).reshape(-1)\n",
+ " channelLeft = LinearOperator((D**2*d, D**2*d), matvec=channelLeftHandle)\n",
" # compute the largest magnitude eigenvalue and corresponding eigenvector\n",
- " lam, Fl = eigs(transferLeftMPO, k=1, which=\"LM\", tol=tol)\n",
+ " lam, Fl = eigs(channelLeft, k=1, which=\"LM\", tol=tol)\n",
" \n",
- " return lam, Fl.reshape((D,d,D))\n",
+ " return lam[0], Fl.reshape((D,d,D))\n",
"\n",
"\n",
- "def rightFixedPointMPO(O, Ar, tol):\n",
+ "def rightEnvironment(O, Ar, tol):\n",
" \"\"\"\n",
- " Computes the right fixed point (250).\n",
+ " Computes the right environment as the fixed point of the right channel operator.\n",
"\n",
" Parameters\n",
" ----------\n",
@@ -125,8 +206,8 @@
" -------\n",
" lam : float\n",
" Leading right eigenvalue.\n",
- " Fr : np.array(D, d, D)\n",
- " right fixed point,\n",
+ " Fr : np.array (D, d, D)\n",
+ " right environment,\n",
" ordered top-middle-bottom.\n",
"\n",
" \"\"\"\n",
@@ -134,65 +215,58 @@
" return lam, Fr\n",
"\n",
"\n",
- "def overlapFixedPointsMPO(Fl, Fr, C):\n",
+ "def environments(O, Al, Ar, C, tol=1e-5):\n",
" \"\"\"\n",
- " Performs the contraction that gives the overlap of the fixed points (251).\n",
+ " Compute the left and right environments of the channel operators\n",
+ " as well as the corresponding eigenvalue.\n",
"\n",
" Parameters\n",
" ----------\n",
- " Fl : np.array(D, d, D)\n",
- " left fixed point,\n",
- " ordered bottom-middle-top.\n",
- " Fr : np.array(D, d, D)\n",
- " right fixed point,\n",
- " ordered top-middle-bottom.\n",
- " C : np.array(D, D)\n",
+ " O : np.array (d, d, d, d)\n",
+ " MPO tensor,\n",
+ " ordered left-top-right-bottom.\n",
+ " Al : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
+ " ordered left-bottom-right,\n",
+ " left-orthonormal.\n",
+ " Ar : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
+ " ordered left-bottom-right,\n",
+ " right-orthonormal.\n",
+ " C : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right.\n",
+ " tol : float, optional\n",
+ " tolerance for eigenvalue solver\n",
"\n",
" Returns\n",
" -------\n",
- " overlap : float\n",
- " Overlap of the fixed points.\n",
+ " lam : float\n",
+ " Leading eigenvalue of the channel\n",
+ " operators.\n",
+ " Fl : np.array (D, d, D)\n",
+ " left environment,\n",
+ " ordered bottom-middle-top.\n",
+ " Fr : np.array (D, d, D)\n",
+ " right environment,\n",
+ " ordered top-middle-bottom.\n",
"\n",
" \"\"\"\n",
" \n",
- " # given\n",
- " \n",
- " overlap = ncon((Fl, Fr, C, np.conj(C)), ([1, 3, 2], [5, 3, 4], [2, 5], [1, 4]))\n",
- " \n",
- " return overlap\n"
+ " return lam, Fl, Fr"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "We now want to use the VUMPS algorithm for finding this fixed point MPS of the MPO $T(O)$.\n",
- "\n",
- "To do so, we calculate the tangent-space projector in mixed gauge and again find two terms that can be characterised by the maps\n",
- "\n",
- "\n",
- "\n",
+ "Next we implement the action of the effective operators $O_{A_C}$ and $O_C$ on a given input tensor,\n",
"\n",
- "Together with the consistency conditions, a fixed point is thus characterised by the set of equations:\n",
+ "
\n",
"\n",
- "$$ O_{A_C}(A_C) \\propto A_C $$\n",
- "$$ O_C(C) \\propto C $$\n",
- "$$ A_C = A_L C = CA_R $$\n",
+ "and\n",
"\n",
- "The vumps algorithm for MPO's can then be formulated in a similar way as in the Hamiltonian case:\n",
- " (i) we start from a given MPS $\\{A^i_L , A^i_R , A^i_C , C^i \\}$\n",
- " (ii) determine $F_L$ and $F_R$\n",
- " (iii) solve the two eigenvalue equations obtaining $\\tilde{A}_C$ and $\\tilde{C}$, and\n",
- " (iv) determine the $A^{i+1}_L$ and $A^{i+1}_R$ that minimize $||\\tilde{A} - A^{i+1}_L \\tilde{C}||$ and $||\\tilde{A} - \\tilde{C}A^{i+1}_R||$."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "We can already determin the left and right fixed points, so we now need the action of the maps $O_{A_C}$ and $O_C$ on a given tensor:"
+ "
"
]
},
{
@@ -201,70 +275,70 @@
"metadata": {},
"outputs": [],
"source": [
- "def O_Ac(X, O, Fl, Fr, lam):\n",
+ "def O_Ac(x, O, Fl, Fr, lam):\n",
" \"\"\"\n",
- " Action of the map (256) on a given tensor.\n",
+ " Action of the operator O_Ac on a given tensor.\n",
"\n",
" Parameters\n",
" ----------\n",
- " X : np.array(D, d, D)\n",
+ " x : np.array (D, d, D)\n",
" Tensor of size (D, d, D)\n",
" O : np.array (d, d, d, d)\n",
" MPO tensor,\n",
" ordered left-top-right-bottom.\n",
- " Fl : np.array(D, d, D)\n",
- " left fixed point,\n",
+ " Fl : np.array (D, d, D)\n",
+ " left environment,\n",
" ordered bottom-middle-top.\n",
- " Fr : np.array(D, d, D)\n",
- " right fixed point,\n",
+ " Fr : np.array (D, d, D)\n",
+ " right environment,\n",
" ordered top-middle-bottom.\n",
" lam : float\n",
" Leading eigenvalue.\n",
"\n",
" Returns\n",
" -------\n",
- " Xnew : np.array(D, d, D)\n",
- " Result of the action of O_Ac on the tensor X.\n",
+ " y : np.array (D, d, D)\n",
+ " Result of the action of O_Ac on the tensor x.\n",
"\n",
" \"\"\"\n",
" \n",
" # given as an example\n",
" \n",
- " Xnew = ncon((Fl, Fr, X, O),([-1, 2, 1], [4, 5, -3], [1, 3, 4], [2, 3, 5, -2])) / lam\n",
+ " y = ncon((Fl, Fr, x, O),([-1, 2, 1], [4, 5, -3], [1, 3, 4], [2, 3, 5, -2])) / lam\n",
" \n",
- " return Xnew\n",
+ " return y\n",
"\n",
"\n",
- "def O_C(X, Fl, Fr):\n",
+ "def O_C(x, Fl, Fr):\n",
" \"\"\"\n",
- " Action of the map (257) on a given tensor.\n",
+ " Action of the operator O_C on a given tensor.\n",
"\n",
" Parameters\n",
" ----------\n",
- " X : np.array(D, D)\n",
+ " x : np.array (D, D)\n",
" Tensor of size (D, D)\n",
- " Fl : np.array(D, d, D)\n",
- " left fixed point,\n",
+ " Fl : np.array (D, d, D)\n",
+ " left environment,\n",
" ordered bottom-middle-top.\n",
- " Fr : np.array(D, d, D)\n",
- " right fixed point,\n",
+ " Fr : np.array (D, d, D)\n",
+ " right environment,\n",
" ordered top-middle-bottom.\n",
"\n",
" Returns\n",
" -------\n",
- " Xnew : np.array(D, d, D)\n",
- " Result of the action of O_C on the tensor X.\n",
+ " y : np.array (D, d, D)\n",
+ " Result of the action of O_C on the tensor x.\n",
"\n",
" \"\"\"\n",
" \n",
- " return Xnew"
+ " return y"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "Solving the eigenvalue equations for these operators gives an update for the center tensors, $\\tilde{A}_C$ and $\\tilde{C}$:"
+ "This then allows to define a new routine `calcNewCenterMpo` which finds the new center tensors $\\tilde{A}_C$ and $\\tilde{C}$ by solving the eigenvalue problems for $O_{A_C}$ and $O_C$."
]
},
{
@@ -273,7 +347,7 @@
"metadata": {},
"outputs": [],
"source": [
- "def calcNewCenterMPO(O, Ac, C, Fl, Fr, lam, tol=1e-3):\n",
+ "def calcNewCenterMpo(O, Ac, C, Fl, Fr, lam, tol=1e-5):\n",
" \"\"\"\n",
" Find new guess for Ac and C as fixed points of the maps O_Ac and O_C.\n",
" \n",
@@ -282,18 +356,18 @@
" O : np.array (d, d, d, d)\n",
" MPO tensor,\n",
" ordered left-top-right-bottom.\n",
- " Ac : np.array(D, d, D)\n",
+ " Ac : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauge.\n",
- " C : np.array(D, D)\n",
+ " C : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right.\n",
- " Fl : np.array(D, d, D)\n",
- " left fixed point,\n",
+ " Fl : np.array (D, d, D)\n",
+ " left environment,\n",
" ordered bottom-middle-top.\n",
- " Fr : np.array(D, d, D)\n",
- " right fixed point,\n",
+ " Fr : np.array (D, d, D)\n",
+ " right environment,\n",
" ordered top-middle-bottom.\n",
" lam : float\n",
" Leading eigenvalue.\n",
@@ -302,11 +376,11 @@
" \n",
" Returns\n",
" -------\n",
- " AcTilde : np.array(D, d, D)\n",
- " MPS tensor zith 3 legs,\n",
+ " AcTilde : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauge.\n",
- " CTilde : np.array(D, D)\n",
+ " CTilde : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right.\n",
"\n",
@@ -319,7 +393,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "And after retrieving $\\tilde{A}_C$ and $\\tilde{C}$ we can use the same 'minAcC()' function as in the case of Hamiltonian vumps to iterate the whole procedure until convergence. These building blocks now allow to implement the vumps algorithm for MPOs (algorithm 8):"
+ "Since the `minAcC` routine to extract a new set of mixed gauge MPS tensors from the updated $\\tilde{A}_C$ and $\\tilde{C}$ can be reused from the previous chapter, we now have all the tools needed to implement the VUMPS algorithm for MPOs."
]
},
{
@@ -328,7 +402,7 @@
"metadata": {},
"outputs": [],
"source": [
- "def vumpsMPO(O, D, A0=None, tol=1e-4):\n",
+ "def vumpsMpo(O, D, A0=None, tol=1e-4, tolFactor=1e-2, verbose=True):\n",
" \"\"\"\n",
" Find the fixed point MPS of a given MPO using VUMPS.\n",
" \n",
@@ -350,26 +424,26 @@
" -------\n",
" lam : float\n",
" Leading eigenvalue.\n",
- " Al : np.array(D, d, D)\n",
- " MPS tensor zith 3 legs,\n",
+ " Al : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" left orthonormal.\n",
- " Ar : np.array(D, d, D)\n",
- " MPS tensor zith 3 legs,\n",
+ " Ar : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" right orthonormal.\n",
- " Ac : np.array(D, d, D)\n",
+ " Ac : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauge.\n",
- " C : np.array(D, D)\n",
+ " C : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right.\n",
- " Fl : np.array(D, d, D)\n",
- " left fixed point,\n",
+ " Fl : np.array (D, d, D)\n",
+ " left environment,\n",
" ordered bottom-middle-top.\n",
- " Fr : np.array(D, d, D)\n",
- " right fixed point,\n",
+ " Fr : np.array (D, d, D)\n",
+ " right environment,\n",
" ordered top-middle-bottom.\n",
" \n",
" \"\"\"\n",
@@ -381,22 +455,53 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "The partition function is eventually given by the left and right fixed point eigenvalue lambda. To check this we define some functions calculating the free energy and the magnetization for the simulation and the exact solution."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### 3.2 The 2d classical Ising model\n",
+ "### 3.2 The two-dimensional classical Ising model\n",
+ "\n",
+ "Next we apply the VUMPS algorithm for MPOs to the two-dimensional classical Ising model. To this end, consider classical spins $s_i = \\pm 1$ placed on the sites of an infinite square lattice which interact according to the nearest-neighbor Hamiltonian\n",
+ "\n",
+ "$$H = -J \\sum_{\\langle i,j \\rangle} s_i s_j \\,.$$\n",
+ "\n",
+ "We now wish to compute the corresponding partition function,\n",
+ "\n",
+ "$$ \\mathcal{Z} = \\sum_{\\{s_i\\}} \\text{e}^{-\\beta H({\\{s_i\\}})},$$\n",
+ "\n",
+ "using our freshly implemented algorithm. In order to do this we first rewrite this partition function as the contraction of an infinite two-dimensional tensor network,\n",
+ "\n",
+ "
\n",
+ "\n",
+ "where every edge in the network has bond dimension $d = 2$. Here, the black dots represent $ \\delta $-tensors\n",
+ "\n",
+ "
\n",
+ "\n",
+ "and the matrices $t$ encode the Boltzmann weights associated to each nearest-neighbor interaction,\n",
+ "\n",
+ "
\n",
+ "\n",
+ "In order to arrive at a translation invariant network corresponding to a single hermitian MPO tensor we can take the matrix square root $q$ of each Boltzmann matrix such that\n",
+ "\n",
+ "
\n",
+ "\n",
+ "and absorb the result symmetrically into the $\\delta$-tensors at each vertex to define the MPO tensor\n",
+ "\n",
+ "
\n",
+ "\n",
+ "The partition function then becomes\n",
+ "\n",
+ "
\n",
+ "\n",
+ "which precisely corresponds to an infinite power of a row-to-row transfer matrix $T(O)$ of the kind defined above. We can therefore use the VUMPS algorithm to determine its fixed point, where the corresponding eigenvalue automatically gives us the free energy density as explained before.\n",
+ "\n",
+ "Having found this fixed point and its corresponding environments, we can easily evaluate expectation values of local observables. For example, say we want to find the expectation value of the magnetization at site $\\mu$,\n",
"\n",
- "We can use this algorithm to compute the partition function of the 2d classical Ising model, which can be written as a two-dimensional tensor network contraction. This is done by constructing a one-dimensional transfer matrix in the form of an MPO, and noting that the partition function is then an (infinite) product of these transfer matrices, stacked vertically.\n",
+ "$$ \\langle m \\rangle = \\frac{1}{\\mathcal{Z}} \\sum_{\\{s_i\\}} s_\\mu \\text{e}^{-\\beta H({\\{s_i\\}})}.$$\n",
"\n",
- "We start by defining the tensors that make up the transfer matrix for the Ising model. If we consider an infinite square lattice, we may simulate the model by placing a spin $1/2$ degree of freedom on each vertex, and then characterising the interactions by inserting a matrix $Q$ with Boltzmann weights on each of the edges. In the graphical language of tensor networks this corresponds to having a 4-legged 'delta tensor' on the vertices and a 2-legged $Q$ on the bonds.\n",
+ "We can access this quantity by introducing a magnetization tensor $M$, placing it at site $\\mu$ and contracting the partition function network around it as\n",
"\n",
- "If we want to rephrase this in terms of the transfer matrix we defined above, we split every bond interaction matrix over the two adjacent vertices and absorp them in the definition of the MPO tensor $O$. The partition function is given by contracting a tensor network consisting of all these rank 4 MPO tensors $O$, which have dimensions $(d,d,d,d)$. We can find the MPS fixed point of the corresponding transfer matrix using the vumps algorithm, which allows to perform this infinite contraction efficiently.\n",
+ "
\n",
"\n",
- "Below we define this Ising MPO operator $O$, along with a magnetization operator $M$ that can be obtained by contracting the spin-1/2 $Z$ operator into a given MPO tensor. We then construct routines that compute the Ising partition function and magnetization (as the expectation value of the $M$ operator) for a given fixed point MPS. This can be compared with the exact Onsager solution. Doing this for a range of different temperatures then allows us to compute the magnetization and free energy in the Ising model as a functions of temperature:"
+ "where the normalization factor $\\mathcal{Z}$ in the denominator is taken care of by the same contraction where $O$ is left at site $\\mu$ (which in this case is of course nothing more than the eigenvalue $\\lambda$). The magnetization tensor $M$ is defined entirely analogously to the MPO tensor $O$, but where instead of a regular $\\delta$-tensor the entry $i=j=k=l=1$ (using base-0 indexing) is set to $-1$ instead of $1$.\n",
+ "\n",
+ "We can now define the routines for constructing the Ising MPO and magnetization tensor an computing local expectation values, as well as a routine that implements [Onsager's exact solution for this model](https://en.wikipedia.org/wiki/Ising_model#Onsager's_exact_solution) to compare our results to."
]
},
{
@@ -425,13 +530,13 @@
" ordered left-top-right-bottom.\n",
"\n",
" \"\"\"\n",
- " # basic vertex tensor\n",
+ " # basic vertex delta tensor\n",
" vertex = np.zeros( (2,) * 4 )\n",
" vertex[tuple([np.arange(2)] * 4)] = 1\n",
- " # build square root of matrix of Boltzmann weights and pull into vertex edges\n",
- " c, s = np.sqrt(np.cosh(beta*J)), np.sqrt(np.sinh(beta*J))\n",
- " Qsqrt = 1/np.sqrt(2) * np.array([[c+s, c-s],[c-s, c+s]])\n",
- " O = ncon((Qsqrt, Qsqrt, Qsqrt, Qsqrt, vertex), ([-1,1], [-2,2], [-3,3], [-4,4], [1,2,3,4]))\n",
+ " # take matrix square root of Boltzmann weights and pull into vertex edges\n",
+ " t = np.array([[np.exp(beta*J), np.exp(-beta*J)], [np.exp(-beta*J), np.exp(beta*J)]])\n",
+ " q = sqrtm(t)\n",
+ " O = ncon((q, q, q, q, vertex), ([-1, 1], [-2, 2], [-3, 3], [-4, 4], [1, 2, 3, 4]))\n",
" return O\n",
"\n",
"\n",
@@ -450,87 +555,56 @@
" Returns\n",
" -------\n",
" M : np.array (2, 2, 2, 2)\n",
- " Magnetization MPO tensor,\n",
+ " Magnetization tensor,\n",
" ordered left-top-right-bottom.\n",
"\n",
" \"\"\"\n",
+ " # basic vertex delta tensor\n",
" vertex = np.zeros( (2,) * 4 )\n",
" vertex[tuple([np.arange(2)] * 4)] = 1\n",
- " Z = np.array([[1,0],[0,-1]])\n",
- " c, s = np.sqrt(np.cosh(beta*J)), np.sqrt(np.sinh(beta*J))\n",
- " Qsqrt = 1/np.sqrt(2) * np.array([[c+s, c-s],[c-s, c+s]])\n",
- " vertexZ = ncon((Z, vertex), ([-1,1], [1,-2,-3,-4]))\n",
- " M = ncon((Qsqrt, Qsqrt, Qsqrt, Qsqrt, vertexZ), ([-1,1], [-2,2], [-3,3], [-4,4], [1,2,3,4]))\n",
+ " # change sign of (1, 1, 1, 1) entry\n",
+ " vertex[1, 1, 1, 1] = -1;\n",
+ " # take matrix square root of Boltzmann weights and pull into vertex edges\n",
+ " t = np.array([[np.exp(beta*J), np.exp(-beta*J)], [np.exp(-beta*J), np.exp(beta*J)]])\n",
+ " q = sqrtm(t)\n",
+ " M = ncon((q, q, q, q, vertex), ([-1, 1], [-2, 2], [-3, 3], [-4, 4], [1, 2, 3, 4]))\n",
" return M\n",
"\n",
"\n",
- "def isingMagnetization(beta, J, Ac, Fl, Fr):\n",
- " \"\"\"\n",
- " Computes the expectation value of the magnetization in the Ising model\n",
- " for a given temperature and coupling\n",
- " \n",
- " Parameters\n",
- " ----------\n",
- " beta : float\n",
- " Inverse temperature.\n",
- " J : float\n",
- " Coupling strength.\n",
- " Ac : np.array(D, d, D)\n",
- " MPS tensor of the MPS fixed point,\n",
- " with 3 legs ordered left-bottom-right,\n",
- " center gauge.\n",
- " Fl : np.array(D, d, D)\n",
- " left fixed point,\n",
- " ordered bottom-middle-top.\n",
- " Fr : np.array(D, d, D)\n",
- " right fixed point,\n",
- " ordered top-middle-bottom.\n",
- " \n",
- " Returns\n",
- " -------\n",
- " M : float\n",
- " Expectation value of the magnetization at the given temperature\n",
- " and coupling.\n",
- "\n",
- " \"\"\"\n",
- " return ncon((Fl, Ac, isingM(beta, J), np.conj(Ac), Fr), (\n",
- " [1, 3, 2], [2,7,5],[3,7,8,6],[1,6,4], [5,8,4]))\n",
- "\n",
- "\n",
- "def isingZ(beta, J, Ac, Fl, Fr):\n",
+ "def expValMpo(O, Ac, Fl, Fr):\n",
" \"\"\"\n",
- " Computes the Ising model partition function for a given temperature and\n",
- " coupling\n",
+ " Gives the MPO tensor corresponding to the partition function of the 2d \n",
+ " classical Ising model at a given temperature and coupling, obtained by\n",
+ " distributing the Boltzmann weights evenly over all vertices.\n",
" \n",
" Parameters\n",
" ----------\n",
- " beta : float\n",
- " Inverse temperature.\n",
- " J : float\n",
- " Coupling strength.\n",
- " Ac : np.array(D, d, D)\n",
+ " O : np.array (2, 2, 2, 2)\n",
+ " local operator of which we want to\n",
+ " compute the expectation value,\n",
+ " ordered left-top-right-bottom.\n",
+ " Ac : np.array (D, d, D)\n",
" MPS tensor of the MPS fixed point,\n",
" with 3 legs ordered left-bottom-right,\n",
" center gauge.\n",
- " Fl : np.array(D, d, D)\n",
- " left fixed point,\n",
+ " Fl : np.array (D, d, D)\n",
+ " left environmnt,\n",
" ordered bottom-middle-top.\n",
- " Fr : np.array(D, d, D)\n",
- " right fixed point,\n",
+ " Fr : np.array (D, d, D)\n",
+ " right environmnt,\n",
" ordered top-middle-bottom.\n",
" \n",
" Returns\n",
" -------\n",
- " Z : float\n",
- " Value of the partition function at the given temperature and\n",
- " coupling.\n",
+ " e : float\n",
+ " expectation value of the operator O.\n",
+ " \n",
"\n",
" \"\"\"\n",
+ " e = ncon((Fl, Ac, O, np.conj(Ac), Fr), (\n",
+ " [1, 3, 2], [2, 7, 5], [3, 7, 8, 6], [1, 6, 4], [5, 8, 4]))\n",
" \n",
- " Z = ncon((Fl, Ac, isingO(beta, J), np.conj(Ac), Fr), (\n",
- " [1, 3, 2], [2,7,5],[3,7,8,6],[1,6,4], [5,8,4]))\n",
- " \n",
- " return Z\n",
+ " return e\n",
"\n",
"\n",
"def isingExact(beta, J):\n",
@@ -566,6 +640,13 @@
" return magnetization, free, energy"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We can now demonstrate the VUMPS algorithm for MPOs. We will fix $J = 1$ in the following, and investigate the behavior of the model as a function of temperature. Since we know the critical piont is located at $T_c = \\frac{2}{\\log\\left(1 + \\sqrt{2}\\right)} \\approx 2.26919$, let us first have a look at $T = 4$ and $T = 1$ far above and below the critical temperature, for which we expect a vanishing and non-vanishing magnetization respectively."
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -575,11 +656,65 @@
"D = 12\n",
"d = 2\n",
"J = 1\n",
+ "tol = 1e-8\n",
+ "tolFactor = 1e-4\n",
+ "A0 = createMPS(D, d)\n",
+ "\n",
+ "T = 4\n",
+ "print('Running for T = {}\\n'.format(T))\n",
+ "beta = 1 / T\n",
+ "O = isingO(beta, J)\n",
+ "M = isingM(beta, J)\n",
+ "lam, Al, Ac, Ar, C, Fl, Fr = vumpsMpo(O, D, A0=A0, tol=tol, tolFactor=tolFactor, verbose=True)\n",
+ "mag = np.abs(expValMpo(M, Ac, Fl, Fr)/expValMpo(O, Ac, Fl, Fr))\n",
+ "magExact = isingExact(beta, J)[0]\n",
+ "freeEnergy = -np.log(lam)/beta\n",
+ "freeEnergyExact = isingExact(beta, J)[1]\n",
+ "print('\\nFree energy: {:.10f}; \\trelative difference with exact solution: {:.4e}\\n'.format(\n",
+ " freeEnergy, np.abs((freeEnergy - freeEnergyExact) / freeEnergyExact)))\n",
+ "print('Magnetization: {:.10f}\\n'.format(mag))\n",
+ "\n",
+ "T = 1\n",
+ "print('Running for T = {}\\n'.format(T))\n",
+ "beta = 1 / T\n",
+ "O = isingO(beta, J)\n",
+ "M = isingM(beta, J)\n",
+ "lam, Al, Ac, Ar, C, Fl, Fr = vumpsMpo(O, D, A0=A0, tol=tol, tolFactor=tolFactor, verbose=True)\n",
+ "mag = np.abs(expValMpo(M, Ac, Fl, Fr)/expValMpo(O, Ac, Fl, Fr))\n",
+ "magExact = isingExact(beta, J)[0]\n",
+ "freeEnergy = -np.log(lam)/beta\n",
+ "freeEnergyExact = isingExact(beta, J)[1]\n",
+ "print('\\nFree energy: {:.10f}; \\trelative difference with exact solution: {:.4e}\\n'.format(\n",
+ " freeEnergy, np.abs((freeEnergy - freeEnergyExact) / freeEnergyExact)))\n",
+ "print('Magnetization: {:.10f}; \\trelative difference with exact solution: {:.4e}\\n'.format(\n",
+ " mag, np.abs((mag - magExact) / magExact)))\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We clearly see that far from the critical point the VUMPS algorithm achieves excellent agreement with the exact solution efficiently at very small bond dimensions.\n",
"\n",
- "print('Bond dimension: D =', D)\n",
+ "As a final demonstration, we compute the magnetization and free energy over a range from $T = 1$ to $T = 3.4$ and plot the results. Note that convergence of the algorithm slows down significantly near the critical point, as can be expected."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "D = 12\n",
+ "d = 2\n",
+ "J = 1\n",
+ "\n",
+ "print('Bond dimension: D = {}\\n'.format(D))\n",
"Al = createMPS(D, d)\n",
"# optimization parameters\n",
- "tol = 1e-5\n",
+ "tol = 1e-8\n",
+ "tolFactor = 1e-2\n",
+ "verbose = False\n",
"\n",
"Ts = np.linspace(1., 3.4, 100)\n",
"magnetizations = []\n",
@@ -590,15 +725,15 @@
"for T in Ts:\n",
" beta = 1/T\n",
" O = isingO(beta, J)\n",
- " print('T={}'.format(T))\n",
+ " M = isingM(beta, J)\n",
+ " print('Running for T = {:.5f}'.format(T), end=\"\\r\")\n",
" \n",
- " lam, Al, Ac, Ar, C, Fl, Fr = vumpsMPO(O, D, A0=Al, tol=tol)\n",
- " magnetizations.append(np.abs(isingMagnetization(beta, J, Ac, Fl, Fr)/isingZ(beta, J, Ac, Fl, Fr)))\n",
+ " lam, Al, Ac, Ar, C, Fl, Fr = vumpsMpo(O, D, A0=Al, tol=tol, tolFactor=tolFactor, verbose=verbose)\n",
+ " magnetizations.append(np.abs(expValMpo(M, Ac, Fl, Fr)/expValMpo(O, Ac, Fl, Fr)))\n",
" magnetizationsExact.append(isingExact(beta, J)[0])\n",
" freeEnergies.append(-np.log(lam)/beta)\n",
" freeEnergiesExact.append(isingExact(beta, J)[1])\n",
"\n",
- "\n",
"fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16,5), dpi=120)\n",
"ax1.set_title('Magnetization as a function of the temperature')\n",
"ax1.set(xlabel=r'$T$', ylabel=r'$$')\n",
@@ -622,7 +757,7 @@
],
"metadata": {
"kernelspec": {
- "display_name": "Python 3",
+ "display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
@@ -636,7 +771,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.7.9"
+ "version": "3.8.3"
}
},
"nbformat": 4,
diff --git a/python/chapter3Solution.ipynb b/python/chapter3Solution.ipynb
index 63d6a2b..6daa04f 100644
--- a/python/chapter3Solution.ipynb
+++ b/python/chapter3Solution.ipynb
@@ -8,55 +8,135 @@
},
"outputs": [],
"source": [
+ "# do all necessary imports for this chapter\n",
"import numpy as np\n",
+ "import matplotlib.pyplot as plt\n",
+ "from scipy.linalg import sqrtm\n",
"from scipy.sparse.linalg import eigs, LinearOperator\n",
"from ncon import ncon\n",
"from tutorialFunctions import createMPS, mixedCanonical, minAcC\n",
- "from time import time\n",
- "import matplotlib.pyplot as plt"
+ "from time import time"
]
},
{
"cell_type": "markdown",
- "metadata": {},
+ "metadata": {
+ "tags": []
+ },
"source": [
- "# Tangent-space methods for uniform matrix product states\n",
- "\n",
- "https://arxiv.org/abs/1810.07006\n",
+ "# [Tangent-space methods for uniform matrix product states](https://doi.org/10.21468/SciPostPhysLectNotes.7)\n",
"\n",
"## 3. Transfer matrices and fixed points\n",
"\n",
- "### 3.1 The vumps algorithm for MPOs\n",
+ "### 3.1 MPS as fixed points of one-dimensional transfer matrices\n",
+ "\n",
+ "Matrix product states have been used extensively as a variational ansatz for ground states of local Hamiltonians. In recent years, it has been observed that they can also provide accurate approximations for fixed points of transfer matrices. In this notebook we investigate how tangent-space methods for MPS can be applied to one-dimensional transfer matrices.\n",
+ "\n",
+ "A one-dimensional transfer matrix in the form of a *matrix product operator* (MPO) is given by\n",
+ "\n",
+ "$$\n",
+ "T(O) = \\sum_{\\{i\\}, \\{j\\}} \\left( \\dots O^{i_{n-1}, j_{n-1}} O^{i_{n}, j_{n}} O^{i_{n+1}, j_{n+1}} \\dots \\right)\n",
+ "\\left | i_{n-1} \\right \\rangle \\left \\langle j_{n-1} \\right | \\otimes \\left | i_{n} \\right \\rangle \\left \\langle j_{n} \\right | \\otimes \\left | i_{n+1} \\right \\rangle \\left \\langle j_{n+1} \\right | \\dots \\;,\n",
+ "$$\n",
+ "\n",
+ "which can be represented diagrammatically as\n",
+ "\n",
+ "
\n",
+ "\n",
+ "Such an object naturally arises in the context of infinite two-dimensional tensor networks, which can be interpreted as an infinite power of a corresponding one-dimensional row-to-row transfer matrix. This means that the contraction of the network is equivalent to finding the leading eigenvector $\\left | \\Psi \\right \\rangle$, referred to as the *fixed point*, of the transfer matrix which satisfies the equation\n",
+ "\n",
+ "$$T(O) \\left | \\Psi \\right \\rangle \\propto \\left | \\Psi \\right \\rangle.$$\n",
+ "\n",
+ "We can now propose an MPS ansatz for this fixed point, such that it obeys the eigenvalue equation\n",
+ "\n",
+ "
\n",
+ "\n",
+ "This MPS fixed point can be computed through a variation on the VUMPS algorithm introduced in the previous chapter, as will be explained in the next section. Suppose for now that we have managed to find an MPS representation $\\left | \\Psi(A) \\right \\rangle$ of the fixed point of $T(O)$. The corresponding eigenvalue $\\Lambda$ is then given by\n",
+ "\n",
+ "$$ \\Lambda = \\left \\langle \\Psi(\\bar{A}) \\middle | T \\middle | \\Psi(A) \\right \\rangle , $$\n",
+ "\n",
+ "assuming as always that we are dealing with a properly normalized MPS. If we bring $\\left | \\Psi(A) \\right \\rangle$ in mixed canonical form, then $\\Lambda$ is given by the network\n",
+ "\n",
+ "
\n",
+ "\n",
+ "We can contract this resulting infinite network by finding the fixed points of the left and right channel operators $T_L$ and $T_R$ which are defined as\n",
+ "\n",
+ "
\n",
+ "\n",
+ "The corresponding fixed points $F_L$ and $F_R$, also referred to as the left and right *environments*, satisfy\n",
+ "\n",
+ "
\n",
"\n",
- "Matrix product states have been used extensively as variational ansatz for ground states of local hamiltonians, but in the last years it has been observed that they can also provide accurate approximations for fixed points of transfer matrices. In this chapter we investigate tangent-space methods for one-dimensional transfer matrices.\n",
+ "and can be normalized such that\n",
"\n",
- "A one-dimensional transfer matrix in the form of matrix product operator (MPO) can be represented diagrammatically as\n",
+ "
\n",
"\n",
- "\n",
+ "The eigenvalues $\\lambda_L$ and $\\lambda_R$ have to correspond to the same value $\\lambda$ by construction, so that $\\Lambda$ is given by\n",
"\n",
- "Whenever we contract an infinite two-dimensional tensor network, we want to find the fixed point of this operator, i.e. we want to solve the fixed-point equation\n",
+ "$$ \\Lambda = \\lim_{N \\to \\infty} \\lambda^N ,$$\n",
"\n",
- "$$ T(O) |\\Psi> \\propto |\\Psi> $$\n",
+ "where $N$ is the number of sites in the horizontal direction. Finally, we note that we can associate a *free energy density* $f = -\\frac{1}{N} \\log \\Lambda = -\\log \\lambda$ to this MPS fixed point.\n",
"\n",
- "such that we can collapse the vertical direction of the network.\n",
"\n",
- "We now make the ansatz that the fixed point (leading eigenvector) of this operator is an MPS. Suppose now we have indeed found an MPS representation $|\\Psi(A)>$ of the fixed point of $T(O)$, then the eigenvalue is given by\n",
+ "### 3.2 The VUMPS algorithm for MPOs\n",
"\n",
- "$$ \\Lambda = <\\Psi(A)| T |\\Psi(A) > $$\n",
+ "In order to formulate an algorithm for finding this MPS fixed point we start by stating the optimality condition it must satisfy in order to qualify as an approximate eigenvector of $T(O)$. Intuitively, what we would like to impose is that the residual $T(O) \\left| \\Psi \\right \\rangle - \\Lambda \\left | \\Psi \\right \\rangle$ is equal to zero. While this condition can never be satisfied exactly for any MPS approximation, we can however demand that the tangent space projection of this residual vanishes,\n",
"\n",
- "Bringing the MPS in mixed canonical form, we write\n",
+ "$$\n",
+ "\\mathcal{P}_A \\left( T(O) \\left| \\Psi \\right \\rangle - \\Lambda \\left | \\Psi \\right \\rangle \\right) = 0,\n",
+ "$$\n",
"\n",
- "\n",
+ "where $\\mathcal{P}_A$ is the projector onto the tangent space to the MPS manifold at $A$. Similar to the Hamiltonian case, this projected residual can be characterized in terms of a tangent space gradient $G$,\n",
"\n",
- "Again, contracting this infinite network requires that we find $F_L$ and $F_R$, the fixed points of the left and right channel operators, such that we may now collapse the horizontal direction. These are found by considering the following diagrams:\n",
+ "$$\n",
+ "G = A_C' - A_L C' = A_c' - C' A_R,\n",
+ "$$\n",
"\n",
- "\n",
- "\n",
+ "where $A_C'$ and $C'$ are now given by\n",
"\n",
- "Finally these are properly normalised if we set\n",
- "\n",
+ "
\n",
"\n",
- "We therefore first require routines for finding and normalising these fixed points."
+ "and\n",
+ "\n",
+ "
\n",
+ "\n",
+ "The optimality condition for the fixed point MPS is then equivalent to having $||G|| = 0$. In addition, if the MPO defining the transfer matrix is hermitian then it can be shown that the optimality condition corresponds to the variational minimum of the free energy density introduced above. Similar to the Hamiltonian case, if we introduce operators $O_{A_C}(\\cdot)$ and $O_C(\\cdot)$ such that\n",
+ "\n",
+ "$$\n",
+ "\\begin{align}\n",
+ "O_{A_C}(A_C) = A_C', \\\\\n",
+ "O_{C}(C) = C',\n",
+ "\\end{align}\n",
+ "$$\n",
+ "\n",
+ "then it follows that the fixed point is characterized by the set of equations\n",
+ "\n",
+ "$$\n",
+ "\\begin{align}\n",
+ "O_{A_C}(A_C) \\propto A_C, \\\\\n",
+ "O_{C}(C) \\propto C, \\\\\n",
+ "A_C = A_L C = C A_R.\n",
+ "\\end{align}\n",
+ "$$\n",
+ "\n",
+ "The VUMPS algorithm for MPOs then corresponds to an iterative scheme for finding the solutions to these equations starting from a given set $\\{A_L, A_C, A_R, C\\}$ which consists of the following steps:\n",
+ "\n",
+ "1. Find the left and right environments $F_L$ and $F_R$ and use these to solve the eigenvalue equations for $O_{A_C}$ and $O_C$, giving new center tensors $\\tilde{A}_C$ and $\\tilde{C}$.\n",
+ "\n",
+ "2. From these new center tensors, extract the $\\tilde{A}_L$ and $\\tilde{A}_R$ that minimize $||\\tilde{A}_C - \\tilde{A}_L \\tilde{C}||$ and $||\\tilde{A}_C - \\tilde{C} \\tilde{A}_L||$ using the `minAcC` routine from the previous chapter.\n",
+ "\n",
+ "3. Update the set of tensors $\\{A_L, A_C, A_R, C\\} \\leftarrow \\{\\tilde{A}_L, \\tilde{A}_C, \\tilde{A}_R, \\tilde{C}\\}$ and evaluate the optimality condition $\\varepsilon = \\left | \\left | O_{A_C} (A_C) - A_L O_C(C) \\right | \\right |$.\n",
+ "\n",
+ "4. If the optimality condition lies above the given tolerance, repeat."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Implementing the VUMPS algorithm\n",
+ "\n",
+ "We start by implementing the routines for finding and normalizing the left and right environments of the channel operators."
]
},
{
@@ -65,9 +145,9 @@
"metadata": {},
"outputs": [],
"source": [
- "def leftFixedPointMPO(O, Al, tol):\n",
+ "def leftEnvironment(O, Al, tol):\n",
" \"\"\"\n",
- " Computes the left fixed point (250).\n",
+ " Computes the left environment as the fixed point of the left channel operator.\n",
"\n",
" Parameters\n",
" ----------\n",
@@ -85,26 +165,27 @@
" -------\n",
" lam : float\n",
" Leading left eigenvalue.\n",
- " Fl : np.array(D, d, D)\n",
- " left fixed point,\n",
+ " Fl : np.array (D, d, D)\n",
+ " left environment,\n",
" ordered bottom-middle-top.\n",
"\n",
" \"\"\"\n",
" D = Al.shape[0]\n",
" d = Al.shape[1]\n",
+ " tol = max(tol, 1e-14)\n",
" \n",
" # construct handle for the action of the relevant operator and cast to linear operator\n",
- " transferLeftHandleMPO = lambda v: (ncon((v.reshape((D,d,D)), Al, np.conj(Al), O),([5, 3, 1], [1, 2, -3], [5, 4, -1], [3, 2, -2, 4]))).reshape(-1)\n",
- " transferLeftMPO = LinearOperator((D**2*d, D**2*d), matvec=transferLeftHandleMPO)\n",
+ " channelLeftHandle = lambda v: (ncon((v.reshape((D,d,D)), Al, np.conj(Al), O),([5, 3, 1], [1, 2, -3], [5, 4, -1], [3, 2, -2, 4]))).reshape(-1)\n",
+ " channelLeft = LinearOperator((D**2*d, D**2*d), matvec=channelLeftHandle)\n",
" # compute the largest magnitude eigenvalue and corresponding eigenvector\n",
- " lam, Fl = eigs(transferLeftMPO, k=1, which=\"LM\", tol=tol)\n",
+ " lam, Fl = eigs(channelLeft, k=1, which=\"LM\", tol=tol)\n",
" \n",
- " return lam, Fl.reshape((D,d,D))\n",
+ " return lam[0], Fl.reshape((D,d,D))\n",
"\n",
"\n",
- "def rightFixedPointMPO(O, Ar, tol):\n",
+ "def rightEnvironment(O, Ar, tol):\n",
" \"\"\"\n",
- " Computes the right fixed point (250).\n",
+ " Computes the right environment as the fixed point of the right channel operator.\n",
"\n",
" Parameters\n",
" ----------\n",
@@ -122,80 +203,81 @@
" -------\n",
" lam : float\n",
" Leading right eigenvalue.\n",
- " Fr : np.array(D, d, D)\n",
- " right fixed point,\n",
+ " Fr : np.array (D, d, D)\n",
+ " right environment,\n",
" ordered top-middle-bottom.\n",
"\n",
" \"\"\"\n",
" D = Ar.shape[0]\n",
" d = Ar.shape[1]\n",
+ " tol = max(tol, 1e-14)\n",
" \n",
" # construct handle for the action of the relevant operator and cast to linear operator\n",
- " transferRightHandleMPO = lambda v: (ncon((v.reshape(D, d, D), Ar, np.conj(Ar), O), ([1, 3, 5], [-1, 2, 1], [-3, 4, 5], [-2, 2, 3, 4]))).reshape(-1)\n",
- " transferRightMPO = LinearOperator((D**2*d, D**2*d), matvec=transferRightHandleMPO)\n",
+ " channelRightHandle = lambda v: (ncon((v.reshape(D, d, D), Ar, np.conj(Ar), O), ([1, 3, 5], [-1, 2, 1], [-3, 4, 5], [-2, 2, 3, 4]))).reshape(-1)\n",
+ " channelRight = LinearOperator((D**2*d, D**2*d), matvec=channelRightHandle)\n",
" # compute the largest magnitude eigenvalue and corresponding eigenvector\n",
- " lam, Fr = eigs(transferRightMPO, k=1, which=\"LM\", tol=tol)\n",
+ " lam, Fr = eigs(channelRight, k=1, which=\"LM\", tol=tol)\n",
" \n",
- " return lam, Fr.reshape((D,d,D))\n",
- "\n",
+ " return lam[0], Fr.reshape((D,d,D))\n",
"\n",
- "def overlapFixedPointsMPO(Fl, Fr, C):\n",
+ "def environments(O, Al, Ar, C, tol=1e-5):\n",
" \"\"\"\n",
- " Performs the contraction that gives the overlap of the fixed points (251).\n",
+ " Compute the left and right environments of the channel operators\n",
+ " as well as the corresponding eigenvalue.\n",
"\n",
" Parameters\n",
" ----------\n",
- " Fl : np.array(D, d, D)\n",
- " left fixed point,\n",
- " ordered bottom-middle-top.\n",
- " Fr : np.array(D, d, D)\n",
- " right fixed point,\n",
- " ordered top-middle-bottom.\n",
- " C : np.array(D, D)\n",
+ " O : np.array (d, d, d, d)\n",
+ " MPO tensor,\n",
+ " ordered left-top-right-bottom.\n",
+ " Al : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
+ " ordered left-bottom-right,\n",
+ " left-orthonormal.\n",
+ " Ar : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
+ " ordered left-bottom-right,\n",
+ " right-orthonormal.\n",
+ " C : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right.\n",
+ " tol : float, optional\n",
+ " tolerance for eigenvalue solver\n",
"\n",
" Returns\n",
" -------\n",
- " overlap : float\n",
- " Overlap of the fixed points.\n",
+ " lam : float\n",
+ " Leading eigenvalue of the channel\n",
+ " operators.\n",
+ " Fl : np.array (D, d, D)\n",
+ " left environment,\n",
+ " ordered bottom-middle-top.\n",
+ " Fr : np.array (D, d, D)\n",
+ " right environment,\n",
+ " ordered top-middle-bottom.\n",
"\n",
" \"\"\"\n",
+ " tol = max(tol, 1e-14)\n",
+ " \n",
+ " lamL, Fl = leftEnvironment(O, Al, tol)\n",
+ " _, Fr = rightEnvironment(O, Ar, tol)\n",
" \n",
" overlap = ncon((Fl, Fr, C, np.conj(C)), ([1, 3, 2], [5, 3, 4], [2, 5], [1, 4]))\n",
" \n",
- " return overlap\n"
+ " return np.real(lamL), Fl / overlap, Fr"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "We now want to use the VUMPS algorithm for finding this fixed point MPS of the MPO $T(O)$.\n",
- "\n",
- "To do so, we calculate the tangent-space projector in mixed gauge and again find two terms that can be characterised by the maps\n",
+ "Next we implement the action of the effective operators $O_{A_C}$ and $O_C$ on a given input tensor,\n",
"\n",
- "\n",
- "\n",
+ "
\n",
"\n",
- "Together with the consistency conditions, a fixed point is thus characterised by the set of equations:\n",
+ "and\n",
"\n",
- "$$ O_{A_C}(A_C) \\propto A_C $$\n",
- "$$ O_C(C) \\propto C $$\n",
- "$$ A_C = A_L C = CA_R $$\n",
- "\n",
- "The vumps algorithm for MPO's can then be formulated in a similar way as in the Hamiltonian case:\n",
- " (i) we start from a given MPS $\\{A^i_L , A^i_R , A^i_C , C^i \\}$\n",
- " (ii) determine $F_L$ and $F_R$\n",
- " (iii) solve the two eigenvalue equations obtaining $\\tilde{A}_C$ and $\\tilde{C}$, and\n",
- " (iv) determine the $A^{i+1}_L$ and $A^{i+1}_R$ that minimize $||\\tilde{A} - A^{i+1}_L \\tilde{C}||$ and $||\\tilde{A} - \\tilde{C}A^{i+1}_R||$."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "We can already determin the left and right fixed points, so we now need the action of the maps $O_{A_C}$ and $O_C$ on a given tensor:"
+ "
"
]
},
{
@@ -204,70 +286,70 @@
"metadata": {},
"outputs": [],
"source": [
- "def O_Ac(X, O, Fl, Fr, lam):\n",
+ "def O_Ac(x, O, Fl, Fr, lam):\n",
" \"\"\"\n",
- " Action of the map (256) on a given tensor.\n",
+ " Action of the operator O_Ac on a given tensor.\n",
"\n",
" Parameters\n",
" ----------\n",
- " X : np.array(D, d, D)\n",
+ " x : np.array (D, d, D)\n",
" Tensor of size (D, d, D)\n",
" O : np.array (d, d, d, d)\n",
" MPO tensor,\n",
" ordered left-top-right-bottom.\n",
- " Fl : np.array(D, d, D)\n",
- " left fixed point,\n",
+ " Fl : np.array (D, d, D)\n",
+ " left environment,\n",
" ordered bottom-middle-top.\n",
- " Fr : np.array(D, d, D)\n",
- " right fixed point,\n",
+ " Fr : np.array (D, d, D)\n",
+ " right environment,\n",
" ordered top-middle-bottom.\n",
" lam : float\n",
" Leading eigenvalue.\n",
"\n",
" Returns\n",
" -------\n",
- " Xnew : np.array(D, d, D)\n",
- " Result of the action of O_Ac on the tensor X.\n",
+ " y : np.array (D, d, D)\n",
+ " Result of the action of O_Ac on the tensor x.\n",
"\n",
" \"\"\"\n",
" \n",
- " Xnew = ncon((Fl, Fr, X, O),([-1, 2, 1], [4, 5, -3], [1, 3, 4], [2, 3, 5, -2])) / lam\n",
+ " y = ncon((Fl, Fr, x, O),([-1, 2, 1], [4, 5, -3], [1, 3, 4], [2, 3, 5, -2])) / lam\n",
" \n",
- " return Xnew\n",
+ " return y\n",
"\n",
"\n",
- "def O_C(X, Fl, Fr):\n",
+ "def O_C(x, Fl, Fr):\n",
" \"\"\"\n",
- " Action of the map (257) on a given tensor.\n",
+ " Action of the operator O_C on a given tensor.\n",
"\n",
" Parameters\n",
" ----------\n",
- " X : np.array(D, D)\n",
+ " x : np.array (D, D)\n",
" Tensor of size (D, D)\n",
- " Fl : np.array(D, d, D)\n",
- " left fixed point,\n",
+ " Fl : np.array (D, d, D)\n",
+ " left environment,\n",
" ordered bottom-middle-top.\n",
- " Fr : np.array(D, d, D)\n",
- " right fixed point,\n",
+ " Fr : np.array (D, d, D)\n",
+ " right environment,\n",
" ordered top-middle-bottom.\n",
"\n",
" Returns\n",
" -------\n",
- " Xnew : np.array(D, d, D)\n",
- " Result of the action of O_C on the tensor X.\n",
+ " y : np.array (D, d, D)\n",
+ " Result of the action of O_C on the tensor x.\n",
"\n",
" \"\"\"\n",
" \n",
- " Xnew = ncon((Fl, Fr, X), ([-1, 3, 1], [2, 3, -2], [1, 2]))\n",
+ " y = ncon((Fl, Fr, x), ([-1, 3, 1], [2, 3, -2], [1, 2]))\n",
" \n",
- " return Xnew"
+ " return y"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "Solving the eigenvalue equations for these operators gives an update for the center tensors, $\\tilde{A}_C$ and $\\tilde{C}$:"
+ "This then allows to define a new routine `calcNewCenterMpo` which finds the new center tensors $\\tilde{A}_C$ and $\\tilde{C}$ by solving the eigenvalue problems for $O_{A_C}$ and $O_C$."
]
},
{
@@ -276,7 +358,7 @@
"metadata": {},
"outputs": [],
"source": [
- "def calcNewCenterMPO(O, Ac, C, Fl, Fr, lam, tol=1e-3):\n",
+ "def calcNewCenterMpo(O, Ac, C, Fl, Fr, lam, tol=1e-5):\n",
" \"\"\"\n",
" Find new guess for Ac and C as fixed points of the maps O_Ac and O_C.\n",
" \n",
@@ -285,18 +367,18 @@
" O : np.array (d, d, d, d)\n",
" MPO tensor,\n",
" ordered left-top-right-bottom.\n",
- " Ac : np.array(D, d, D)\n",
+ " Ac : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauge.\n",
- " C : np.array(D, D)\n",
+ " C : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right.\n",
- " Fl : np.array(D, d, D)\n",
- " left fixed point,\n",
+ " Fl : np.array (D, d, D)\n",
+ " left environment,\n",
" ordered bottom-middle-top.\n",
- " Fr : np.array(D, d, D)\n",
- " right fixed point,\n",
+ " Fr : np.array (D, d, D)\n",
+ " right environment,\n",
" ordered top-middle-bottom.\n",
" lam : float\n",
" Leading eigenvalue.\n",
@@ -305,11 +387,11 @@
" \n",
" Returns\n",
" -------\n",
- " AcTilde : np.array(D, d, D)\n",
- " MPS tensor zith 3 legs,\n",
+ " AcTilde : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauge.\n",
- " CTilde : np.array(D, D)\n",
+ " CTilde : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right.\n",
"\n",
@@ -317,12 +399,13 @@
" \n",
" D = Ac.shape[0]\n",
" d = Ac.shape[1]\n",
+ " tol = max(tol, 1e-14)\n",
" \n",
" # construct handle for O_Ac map and cast to linear operator\n",
- " handleAc = lambda X: (O_Ac(X.reshape((D,d,D)), O, Fl, Fr, lam)).reshape(-1)\n",
+ " handleAc = lambda x: (O_Ac(x.reshape((D,d,D)), O, Fl, Fr, lam)).reshape(-1)\n",
" handleAc = LinearOperator((D**2*d, D**2*d), matvec=handleAc)\n",
" # construct handle for O_C map and cast to linear operator\n",
- " handleC = lambda X: (O_C(X.reshape(D, D), Fl, Fr)).reshape(-1)\n",
+ " handleC = lambda x: (O_C(x.reshape(D, D), Fl, Fr)).reshape(-1)\n",
" handleC = LinearOperator((D**2, D**2), matvec=handleC)\n",
" # compute fixed points of these maps: gives new guess for center tensors\n",
" _, AcTilde = eigs(handleAc, k=1, which=\"LM\", v0=Ac.reshape(-1), tol=tol)\n",
@@ -339,7 +422,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "And after retrieving $\\tilde{A}_C$ and $\\tilde{C}$ we can use the same 'minAcC()' function as in the case of Hamiltonian vumps to iterate the whole procedure until convergence. These building blocks now allow to implement the vumps algorithm for MPOs (algorithm 8):"
+ "Since the `minAcC` routine to extract a new set of mixed gauge MPS tensors from the updated $\\tilde{A}_C$ and $\\tilde{C}$ can be reused from the previous chapter, we now have all the tools needed to implement the VUMPS algorithm for MPOs."
]
},
{
@@ -348,7 +431,7 @@
"metadata": {},
"outputs": [],
"source": [
- "def vumpsMPO(O, D, A0=None, tol=1e-4):\n",
+ "def vumpsMpo(O, D, A0=None, tol=1e-4, tolFactor=1e-2, verbose=True):\n",
" \"\"\"\n",
" Find the fixed point MPS of a given MPO using VUMPS.\n",
" \n",
@@ -370,31 +453,32 @@
" -------\n",
" lam : float\n",
" Leading eigenvalue.\n",
- " Al : np.array(D, d, D)\n",
- " MPS tensor zith 3 legs,\n",
+ " Al : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" left orthonormal.\n",
- " Ar : np.array(D, d, D)\n",
- " MPS tensor zith 3 legs,\n",
+ " Ar : np.array (D, d, D)\n",
+ " MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" right orthonormal.\n",
- " Ac : np.array(D, d, D)\n",
+ " Ac : np.array (D, d, D)\n",
" MPS tensor with 3 legs,\n",
" ordered left-bottom-right,\n",
" center gauge.\n",
- " C : np.array(D, D)\n",
+ " C : np.array (D, D)\n",
" Center gauge with 2 legs,\n",
" ordered left-right.\n",
- " Fl : np.array(D, d, D)\n",
- " left fixed point,\n",
+ " Fl : np.array (D, d, D)\n",
+ " left environment,\n",
" ordered bottom-middle-top.\n",
- " Fr : np.array(D, d, D)\n",
- " right fixed point,\n",
+ " Fr : np.array (D, d, D)\n",
+ " right environment,\n",
" ordered top-middle-bottom.\n",
" \n",
" \"\"\"\n",
" \n",
" d = O.shape[0]\n",
+ " tol = max(tol, 1e-14)\n",
" \n",
" # if no initial guess, random one\n",
" if A0 is None:\n",
@@ -405,21 +489,32 @@
" \n",
" delta = 1e-4\n",
" flag = True\n",
+ " i = 0\n",
+ " \n",
" while flag:\n",
- " # compute left and right fixed points\n",
- " lam, Fl = leftFixedPointMPO(O, Al, delta/10)\n",
- " _ , Fr = rightFixedPointMPO(O, Ar, delta/10)\n",
- " Fl /= overlapFixedPointsMPO(Fl, Fr, C)\n",
- " lam = np.real(lam)[0]\n",
- " # compute updates on Ac and C\n",
- " AcTilde, CTilde = calcNewCenterMPO(O, Ac, C, Fl, Fr, lam, delta/10)\n",
- " AlTilde, AcTilde, ArTilde, CTilde = minAcC(AcTilde, CTilde)\n",
+ " i += 1\n",
+ " \n",
+ " # compute left and right environments and corrsponding eigenvalue\n",
+ " lam, Fl, Fr = environments(O, Al, Ar, C, delta*tolFactor)\n",
+ " \n",
" # calculate convergence measure, check for convergence\n",
- " delta = np.linalg.norm(O_Ac(Ac, O, Fl, Fr, lam) - ncon((Al, O_C(C, Fl, Fr)), ([-1, -2, 1], [1, -3])))\n",
+ " G = O_Ac(Ac, O, Fl, Fr, lam) - ncon((Al, O_C(C, Fl, Fr)), ([-1, -2, 1], [1, -3]))\n",
+ " delta = np.linalg.norm(G)\n",
" if delta < tol:\n",
" flag = False\n",
+ " \n",
+ " # compute updates on Ac and C\n",
+ " AcTilde, CTilde = calcNewCenterMpo(O, Ac, C, Fl, Fr, lam, delta*tolFactor)\n",
+ " \n",
+ " # find Al, Ar from AcTilde, CTilde\n",
+ " AlTilde, AcTilde, ArTilde, CTilde = minAcC(AcTilde, CTilde, delta*tolFactor**2)\n",
+ " \n",
" # update tensors\n",
" Al, Ac, Ar, C = AlTilde, AcTilde, ArTilde, CTilde\n",
+ " \n",
+ " # print current eigenvalue\n",
+ " if verbose:\n",
+ " print('iteration:\\t{:d}\\tlambda:\\t{:.12f}\\tgradient norm:\\t{:.4e}'.format(i, lam, delta))\n",
" \n",
" return lam, Al, Ac, Ar, C, Fl, Fr"
]
@@ -428,22 +523,53 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "The partition function is eventually given by the left and right fixed point eigenvalue lambda. To check this we define some functions calculating the free energy and the magnetization for the simulation and the exact solution."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### 3.2 The 2d classical Ising model\n",
+ "### 3.2 The two-dimensional classical Ising model\n",
+ "\n",
+ "Next we apply the VUMPS algorithm for MPOs to the two-dimensional classical Ising model. To this end, consider classical spins $s_i = \\pm 1$ placed on the sites of an infinite square lattice which interact according to the nearest-neighbor Hamiltonian\n",
+ "\n",
+ "$$H = -J \\sum_{\\langle i,j \\rangle} s_i s_j \\,.$$\n",
+ "\n",
+ "We now wish to compute the corresponding partition function,\n",
+ "\n",
+ "$$ \\mathcal{Z} = \\sum_{\\{s_i\\}} \\text{e}^{-\\beta H({\\{s_i\\}})},$$\n",
+ "\n",
+ "using our freshly implemented algorithm. In order to do this we first rewrite this partition function as the contraction of an infinite two-dimensional tensor network,\n",
+ "\n",
+ "
\n",
+ "\n",
+ "where every edge in the network has bond dimension $d = 2$. Here, the black dots represent $ \\delta $-tensors\n",
+ "\n",
+ "
\n",
+ "\n",
+ "and the matrices $t$ encode the Boltzmann weights associated to each nearest-neighbor interaction,\n",
+ "\n",
+ "
\n",
+ "\n",
+ "In order to arrive at a translation invariant network corresponding to a single hermitian MPO tensor we can take the matrix square root $q$ of each Boltzmann matrix such that\n",
+ "\n",
+ "
\n",
+ "\n",
+ "and absorb the result symmetrically into the $\\delta$-tensors at each vertex to define the MPO tensor\n",
+ "\n",
+ "
\n",
+ "\n",
+ "The partition function then becomes\n",
"\n",
- "We can use this algorithm to compute the partition function of the 2d classical Ising model, which can be written as a two-dimensional tensor network contraction. This is done by constructing a one-dimensional transfer matrix in the form of an MPO, and noting that the partition function is then an (infinite) product of these transfer matrices, stacked vertically.\n",
+ "
\n",
"\n",
- "We start by defining the tensors that make up the transfer matrix for the Ising model. If we consider an infinite square lattice, we may simulate the model by placing a spin $1/2$ degree of freedom on each vertex, and then characterising the interactions by inserting a matrix $Q$ with Boltzmann weights on each of the edges. In the graphical language of tensor networks this corresponds to having a 4-legged 'delta tensor' on the vertices and a 2-legged $Q$ on the bonds.\n",
+ "which precisely corresponds to an infinite power of a row-to-row transfer matrix $T(O)$ of the kind defined above. We can therefore use the VUMPS algorithm to determine its fixed point, where the corresponding eigenvalue automatically gives us the free energy density as explained before.\n",
"\n",
- "If we want to rephrase this in terms of the transfer matrix we defined above, we split every bond interaction matrix over the two adjacent vertices and absorp them in the definition of the MPO tensor $O$. The partition function is given by contracting a tensor network consisting of all these rank 4 MPO tensors $O$, which have dimensions $(d,d,d,d)$. We can find the MPS fixed point of the corresponding transfer matrix using the vumps algorithm, which allows to perform this infinite contraction efficiently.\n",
+ "Having found this fixed point and its corresponding environments, we can easily evaluate expectation values of local observables. For example, say we want to find the expectation value of the magnetization at site $\\mu$,\n",
"\n",
- "Below we define this Ising MPO operator $O$, along with a magnetization operator $M$ that can be obtained by contracting the spin-1/2 $Z$ operator into a given MPO tensor. We then construct routines that compute the Ising partition function and magnetization (as the expectation value of the $M$ operator) for a given fixed point MPS. This can be compared with the exact Onsager solution. Doing this for a range of different temperatures then allows us to compute the magnetization and free energy in the Ising model as a functions of temperature:"
+ "$$ \\langle m \\rangle = \\frac{1}{\\mathcal{Z}} \\sum_{\\{s_i\\}} s_\\mu \\text{e}^{-\\beta H({\\{s_i\\}})}.$$\n",
+ "\n",
+ "We can access this quantity by introducing a magnetization tensor $M$, placing it at site $\\mu$ and contracting the partition function network around it as\n",
+ "\n",
+ "
\n",
+ "\n",
+ "where the normalization factor $\\mathcal{Z}$ in the denominator is taken care of by the same contraction where $O$ is left at site $\\mu$ (which in this case is of course nothing more than the eigenvalue $\\lambda$). The magnetization tensor $M$ is defined entirely analogously to the MPO tensor $O$, but where instead of a regular $\\delta$-tensor the entry $i=j=k=l=1$ (using base-0 indexing) is set to $-1$ instead of $1$.\n",
+ "\n",
+ "We can now define the routines for constructing the Ising MPO and magnetization tensor an computing local expectation values, as well as a routine that implements [Onsager's exact solution for this model](https://en.wikipedia.org/wiki/Ising_model#Onsager's_exact_solution) to compare our results to."
]
},
{
@@ -472,13 +598,13 @@
" ordered left-top-right-bottom.\n",
"\n",
" \"\"\"\n",
- " # basic vertex tensor\n",
+ " # basic vertex delta tensor\n",
" vertex = np.zeros( (2,) * 4 )\n",
" vertex[tuple([np.arange(2)] * 4)] = 1\n",
- " # build square root of matrix of Boltzmann weights and pull into vertex edges\n",
- " c, s = np.sqrt(np.cosh(beta*J)), np.sqrt(np.sinh(beta*J))\n",
- " Qsqrt = 1/np.sqrt(2) * np.array([[c+s, c-s],[c-s, c+s]])\n",
- " O = ncon((Qsqrt, Qsqrt, Qsqrt, Qsqrt, vertex), ([-1,1], [-2,2], [-3,3], [-4,4], [1,2,3,4]))\n",
+ " # take matrix square root of Boltzmann weights and pull into vertex edges\n",
+ " t = np.array([[np.exp(beta*J), np.exp(-beta*J)], [np.exp(-beta*J), np.exp(beta*J)]])\n",
+ " q = sqrtm(t)\n",
+ " O = ncon((q, q, q, q, vertex), ([-1, 1], [-2, 2], [-3, 3], [-4, 4], [1, 2, 3, 4]))\n",
" return O\n",
"\n",
"\n",
@@ -497,87 +623,56 @@
" Returns\n",
" -------\n",
" M : np.array (2, 2, 2, 2)\n",
- " Magnetization MPO tensor,\n",
+ " Magnetization tensor,\n",
" ordered left-top-right-bottom.\n",
"\n",
" \"\"\"\n",
+ " # basic vertex delta tensor\n",
" vertex = np.zeros( (2,) * 4 )\n",
" vertex[tuple([np.arange(2)] * 4)] = 1\n",
- " Z = np.array([[1,0],[0,-1]])\n",
- " c, s = np.sqrt(np.cosh(beta*J)), np.sqrt(np.sinh(beta*J))\n",
- " Qsqrt = 1/np.sqrt(2) * np.array([[c+s, c-s],[c-s, c+s]])\n",
- " vertexZ = ncon((Z, vertex), ([-1,1], [1,-2,-3,-4]))\n",
- " M = ncon((Qsqrt, Qsqrt, Qsqrt, Qsqrt, vertexZ), ([-1,1], [-2,2], [-3,3], [-4,4], [1,2,3,4]))\n",
+ " # change sign of (1, 1, 1, 1) entry\n",
+ " vertex[1, 1, 1, 1] = -1;\n",
+ " # take matrix square root of Boltzmann weights and pull into vertex edges\n",
+ " t = np.array([[np.exp(beta*J), np.exp(-beta*J)], [np.exp(-beta*J), np.exp(beta*J)]])\n",
+ " q = sqrtm(t)\n",
+ " M = ncon((q, q, q, q, vertex), ([-1, 1], [-2, 2], [-3, 3], [-4, 4], [1, 2, 3, 4]))\n",
" return M\n",
"\n",
"\n",
- "def isingMagnetization(beta, J, Ac, Fl, Fr):\n",
- " \"\"\"\n",
- " Computes the expectation value of the magnetization in the Ising model\n",
- " for a given temperature and coupling\n",
- " \n",
- " Parameters\n",
- " ----------\n",
- " beta : float\n",
- " Inverse temperature.\n",
- " J : float\n",
- " Coupling strength.\n",
- " Ac : np.array(D, d, D)\n",
- " MPS tensor of the MPS fixed point,\n",
- " with 3 legs ordered left-bottom-right,\n",
- " center gauge.\n",
- " Fl : np.array(D, d, D)\n",
- " left fixed point,\n",
- " ordered bottom-middle-top.\n",
- " Fr : np.array(D, d, D)\n",
- " right fixed point,\n",
- " ordered top-middle-bottom.\n",
- " \n",
- " Returns\n",
- " -------\n",
- " M : float\n",
- " Expectation value of the magnetization at the given temperature\n",
- " and coupling.\n",
- "\n",
- " \"\"\"\n",
- " return ncon((Fl, Ac, isingM(beta, J), np.conj(Ac), Fr), (\n",
- " [1, 3, 2], [2,7,5],[3,7,8,6],[1,6,4], [5,8,4]))\n",
- "\n",
- "\n",
- "def isingZ(beta, J, Ac, Fl, Fr):\n",
+ "def expValMpo(O, Ac, Fl, Fr):\n",
" \"\"\"\n",
- " Computes the Ising model partition function for a given temperature and\n",
- " coupling\n",
+ " Gives the MPO tensor corresponding to the partition function of the 2d \n",
+ " classical Ising model at a given temperature and coupling, obtained by\n",
+ " distributing the Boltzmann weights evenly over all vertices.\n",
" \n",
" Parameters\n",
" ----------\n",
- " beta : float\n",
- " Inverse temperature.\n",
- " J : float\n",
- " Coupling strength.\n",
- " Ac : np.array(D, d, D)\n",
+ " O : np.array (2, 2, 2, 2)\n",
+ " local operator of which we want to\n",
+ " compute the expectation value,\n",
+ " ordered left-top-right-bottom.\n",
+ " Ac : np.array (D, d, D)\n",
" MPS tensor of the MPS fixed point,\n",
" with 3 legs ordered left-bottom-right,\n",
" center gauge.\n",
- " Fl : np.array(D, d, D)\n",
- " left fixed point,\n",
+ " Fl : np.array (D, d, D)\n",
+ " left environmnt,\n",
" ordered bottom-middle-top.\n",
- " Fr : np.array(D, d, D)\n",
- " right fixed point,\n",
+ " Fr : np.array (D, d, D)\n",
+ " right environmnt,\n",
" ordered top-middle-bottom.\n",
" \n",
" Returns\n",
" -------\n",
- " Z : float\n",
- " Value of the partition function at the given temperature and\n",
- " coupling.\n",
+ " e : float\n",
+ " expectation value of the operator O.\n",
+ " \n",
"\n",
" \"\"\"\n",
+ " e = ncon((Fl, Ac, O, np.conj(Ac), Fr), (\n",
+ " [1, 3, 2], [2, 7, 5], [3, 7, 8, 6], [1, 6, 4], [5, 8, 4]))\n",
" \n",
- " Z = ncon((Fl, Ac, isingO(beta, J), np.conj(Ac), Fr), (\n",
- " [1, 3, 2], [2,7,5],[3,7,8,6],[1,6,4], [5,8,4]))\n",
- " \n",
- " return Z\n",
+ " return e\n",
"\n",
"\n",
"def isingExact(beta, J):\n",
@@ -613,6 +708,65 @@
" return magnetization, free, energy"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We can now demonstrate the VUMPS algorithm for MPOs. We will fix $J = 1$ in the following, and investigate the behavior of the model as a function of temperature. Since we know the critical piont is located at $T_c = \\frac{2}{\\log\\left(1 + \\sqrt{2}\\right)} \\approx 2.26919$, let us first have a look at $T = 4$ and $T = 1$ far above and below the critical temperature, for which we expect a vanishing and non-vanishing magnetization respectively."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "D = 12\n",
+ "d = 2\n",
+ "J = 1\n",
+ "tol = 1e-8\n",
+ "tolFactor = 1e-4\n",
+ "A0 = createMPS(D, d)\n",
+ "\n",
+ "T = 4\n",
+ "print('Running for T = {}\\n'.format(T))\n",
+ "beta = 1 / T\n",
+ "O = isingO(beta, J)\n",
+ "M = isingM(beta, J)\n",
+ "lam, Al, Ac, Ar, C, Fl, Fr = vumpsMpo(O, D, A0=A0, tol=tol, tolFactor=tolFactor, verbose=True)\n",
+ "mag = np.abs(expValMpo(M, Ac, Fl, Fr)/expValMpo(O, Ac, Fl, Fr))\n",
+ "magExact = isingExact(beta, J)[0]\n",
+ "freeEnergy = -np.log(lam)/beta\n",
+ "freeEnergyExact = isingExact(beta, J)[1]\n",
+ "print('\\nFree energy: {:.10f}; \\trelative difference with exact solution: {:.4e}\\n'.format(\n",
+ " freeEnergy, np.abs((freeEnergy - freeEnergyExact) / freeEnergyExact)))\n",
+ "print('Magnetization: {:.10f}\\n'.format(mag))\n",
+ "\n",
+ "T = 1\n",
+ "print('Running for T = {}\\n'.format(T))\n",
+ "beta = 1 / T\n",
+ "O = isingO(beta, J)\n",
+ "M = isingM(beta, J)\n",
+ "lam, Al, Ac, Ar, C, Fl, Fr = vumpsMpo(O, D, A0=A0, tol=tol, tolFactor=tolFactor, verbose=True)\n",
+ "mag = np.abs(expValMpo(M, Ac, Fl, Fr)/expValMpo(O, Ac, Fl, Fr))\n",
+ "magExact = isingExact(beta, J)[0]\n",
+ "freeEnergy = -np.log(lam)/beta\n",
+ "freeEnergyExact = isingExact(beta, J)[1]\n",
+ "print('\\nFree energy: {:.10f}; \\trelative difference with exact solution: {:.4e}\\n'.format(\n",
+ " freeEnergy, np.abs((freeEnergy - freeEnergyExact) / freeEnergyExact)))\n",
+ "print('Magnetization: {:.10f}; \\trelative difference with exact solution: {:.4e}\\n'.format(\n",
+ " mag, np.abs((mag - magExact) / magExact)))\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We clearly see that far from the critical point the VUMPS algorithm achieves excellent agreement with the exact solution efficiently at very small bond dimensions.\n",
+ "\n",
+ "As a final demonstration, we compute the magnetization and free energy over a range from $T = 1$ to $T = 3.4$ and plot the results. Note that convergence of the algorithm slows down significantly near the critical point, as can be expected."
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -623,10 +777,12 @@
"d = 2\n",
"J = 1\n",
"\n",
- "print('Bond dimension: D =', D)\n",
+ "print('Bond dimension: D = {}\\n'.format(D))\n",
"Al = createMPS(D, d)\n",
"# optimization parameters\n",
- "tol = 1e-5\n",
+ "tol = 1e-8\n",
+ "tolFactor = 1e-2\n",
+ "verbose = False\n",
"\n",
"Ts = np.linspace(1., 3.4, 100)\n",
"magnetizations = []\n",
@@ -637,15 +793,15 @@
"for T in Ts:\n",
" beta = 1/T\n",
" O = isingO(beta, J)\n",
- " print('T={}'.format(T))\n",
+ " M = isingM(beta, J)\n",
+ " print('Running for T = {:.5f}'.format(T), end=\"\\r\")\n",
" \n",
- " lam, Al, Ac, Ar, C, Fl, Fr = vumpsMPO(O, D, A0=Al, tol=tol)\n",
- " magnetizations.append(np.abs(isingMagnetization(beta, J, Ac, Fl, Fr)/isingZ(beta, J, Ac, Fl, Fr)))\n",
+ " lam, Al, Ac, Ar, C, Fl, Fr = vumpsMpo(O, D, A0=Al, tol=tol, tolFactor=tolFactor, verbose=verbose)\n",
+ " magnetizations.append(np.abs(expValMpo(M, Ac, Fl, Fr)/expValMpo(O, Ac, Fl, Fr)))\n",
" magnetizationsExact.append(isingExact(beta, J)[0])\n",
" freeEnergies.append(-np.log(lam)/beta)\n",
" freeEnergiesExact.append(isingExact(beta, J)[1])\n",
"\n",
- "\n",
"fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16,5), dpi=120)\n",
"ax1.set_title('Magnetization as a function of the temperature')\n",
"ax1.set(xlabel=r'$T$', ylabel=r'$$')\n",
@@ -669,7 +825,7 @@
],
"metadata": {
"kernelspec": {
- "display_name": "Python 3",
+ "display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
@@ -683,7 +839,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.7.9"
+ "version": "3.8.3"
}
},
"nbformat": 4,
diff --git a/python/img/2minham.png b/python/img/2minham.png
deleted file mode 100644
index 437aaa8..0000000
Binary files a/python/img/2minham.png and /dev/null differ
diff --git a/python/img/2minham.svg b/python/img/2minham.svg
new file mode 100644
index 0000000..5ef24e6
--- /dev/null
+++ b/python/img/2minham.svg
@@ -0,0 +1,119 @@
+
+
diff --git a/python/img/AcPrime2.svg b/python/img/AcPrime2.svg
new file mode 100644
index 0000000..fa8ba0d
--- /dev/null
+++ b/python/img/AcPrime2.svg
@@ -0,0 +1,149 @@
+
+
diff --git a/python/img/Acprime.png b/python/img/Acprime.png
deleted file mode 100644
index 0aec9cb..0000000
Binary files a/python/img/Acprime.png and /dev/null differ
diff --git a/python/img/Acprime.svg b/python/img/Acprime.svg
new file mode 100644
index 0000000..79d0034
--- /dev/null
+++ b/python/img/Acprime.svg
@@ -0,0 +1,361 @@
+
+
diff --git a/python/img/AlVl.png b/python/img/AlVl.png
deleted file mode 100644
index c2cf7e0..0000000
Binary files a/python/img/AlVl.png and /dev/null differ
diff --git a/python/img/CPrime2.svg b/python/img/CPrime2.svg
new file mode 100644
index 0000000..82b2e1c
--- /dev/null
+++ b/python/img/CPrime2.svg
@@ -0,0 +1,87 @@
+
+
diff --git a/python/img/Cprime.png b/python/img/Cprime.png
deleted file mode 100644
index 3b7ea5a..0000000
Binary files a/python/img/Cprime.png and /dev/null differ
diff --git a/python/img/Cprime.svg b/python/img/Cprime.svg
new file mode 100644
index 0000000..cf29994
--- /dev/null
+++ b/python/img/Cprime.svg
@@ -0,0 +1,328 @@
+
+
diff --git a/python/img/FL.png b/python/img/FL.png
deleted file mode 100644
index 32a6bdd..0000000
Binary files a/python/img/FL.png and /dev/null differ
diff --git a/python/img/FR.png b/python/img/FR.png
deleted file mode 100644
index 0001add..0000000
Binary files a/python/img/FR.png and /dev/null differ
diff --git a/python/img/FlFr.png b/python/img/FlFr.png
deleted file mode 100644
index 6f33023..0000000
Binary files a/python/img/FlFr.png and /dev/null differ
diff --git a/python/img/H_Ac.png b/python/img/H_Ac.png
deleted file mode 100644
index 5e91cc1..0000000
Binary files a/python/img/H_Ac.png and /dev/null differ
diff --git a/python/img/H_Ac.svg b/python/img/H_Ac.svg
new file mode 100644
index 0000000..09bd358
--- /dev/null
+++ b/python/img/H_Ac.svg
@@ -0,0 +1,285 @@
+
+
diff --git a/python/img/H_C.png b/python/img/H_C.png
deleted file mode 100644
index ba16b8a..0000000
Binary files a/python/img/H_C.png and /dev/null differ
diff --git a/python/img/H_C.svg b/python/img/H_C.svg
new file mode 100644
index 0000000..e4b76ba
--- /dev/null
+++ b/python/img/H_C.svg
@@ -0,0 +1,203 @@
+
+
diff --git a/python/img/Heff.png b/python/img/Heff.png
deleted file mode 100644
index 538454d..0000000
Binary files a/python/img/Heff.png and /dev/null differ
diff --git a/python/img/HeffExcitation.svg b/python/img/HeffExcitation.svg
new file mode 100644
index 0000000..9800f8c
--- /dev/null
+++ b/python/img/HeffExcitation.svg
@@ -0,0 +1,862 @@
+
+
diff --git a/python/img/L1.png b/python/img/L1.png
deleted file mode 100644
index 2e44103..0000000
Binary files a/python/img/L1.png and /dev/null differ
diff --git a/python/img/L1.svg b/python/img/L1.svg
new file mode 100644
index 0000000..52890dd
--- /dev/null
+++ b/python/img/L1.svg
@@ -0,0 +1,507 @@
+
+
diff --git a/python/img/LB.png b/python/img/LB.png
deleted file mode 100644
index 008dcfc..0000000
Binary files a/python/img/LB.png and /dev/null differ
diff --git a/python/img/LB.svg b/python/img/LB.svg
new file mode 100644
index 0000000..fcc7e75
--- /dev/null
+++ b/python/img/LB.svg
@@ -0,0 +1,165 @@
+
+
diff --git a/python/img/Lh.png b/python/img/Lh.png
deleted file mode 100644
index bb605c6..0000000
Binary files a/python/img/Lh.png and /dev/null differ
diff --git a/python/img/Lh.svg b/python/img/Lh.svg
new file mode 100644
index 0000000..250a3ab
--- /dev/null
+++ b/python/img/Lh.svg
@@ -0,0 +1,149 @@
+
+
diff --git a/python/img/Lh2.png b/python/img/Lh2.png
deleted file mode 100644
index bbd38c8..0000000
Binary files a/python/img/Lh2.png and /dev/null differ
diff --git a/python/img/LhMixed.svg b/python/img/LhMixed.svg
new file mode 100644
index 0000000..9ac7df3
--- /dev/null
+++ b/python/img/LhMixed.svg
@@ -0,0 +1,162 @@
+
+
diff --git a/python/img/Lhmixed.png b/python/img/Lhmixed.png
deleted file mode 100644
index 9b6278b..0000000
Binary files a/python/img/Lhmixed.png and /dev/null differ
diff --git a/python/img/MPSnormalised.png b/python/img/MPSnormalised.png
deleted file mode 100644
index f4607dc..0000000
Binary files a/python/img/MPSnormalised.png and /dev/null differ
diff --git a/python/img/MPSstate.png b/python/img/MPSstate.png
deleted file mode 100644
index 3001430..0000000
Binary files a/python/img/MPSstate.png and /dev/null differ
diff --git a/python/img/Mexp.svg b/python/img/Mexp.svg
new file mode 100644
index 0000000..2924d25
--- /dev/null
+++ b/python/img/Mexp.svg
@@ -0,0 +1,183 @@
+
+
diff --git a/python/img/O.svg b/python/img/O.svg
new file mode 100644
index 0000000..f22d277
--- /dev/null
+++ b/python/img/O.svg
@@ -0,0 +1,96 @@
+
+
diff --git a/python/img/OAc.png b/python/img/OAc.png
deleted file mode 100644
index ff73cf0..0000000
Binary files a/python/img/OAc.png and /dev/null differ
diff --git a/python/img/OC.png b/python/img/OC.png
deleted file mode 100644
index 91124ad..0000000
Binary files a/python/img/OC.png and /dev/null differ
diff --git a/python/img/O_Ac.svg b/python/img/O_Ac.svg
new file mode 100644
index 0000000..f903e5e
--- /dev/null
+++ b/python/img/O_Ac.svg
@@ -0,0 +1,165 @@
+
+
diff --git a/python/img/O_C.svg b/python/img/O_C.svg
new file mode 100644
index 0000000..736225e
--- /dev/null
+++ b/python/img/O_C.svg
@@ -0,0 +1,106 @@
+
+
diff --git a/python/img/R1.png b/python/img/R1.png
deleted file mode 100644
index f17b8c6..0000000
Binary files a/python/img/R1.png and /dev/null differ
diff --git a/python/img/R1.svg b/python/img/R1.svg
new file mode 100644
index 0000000..afe27f2
--- /dev/null
+++ b/python/img/R1.svg
@@ -0,0 +1,499 @@
+
+
diff --git a/python/img/RB.png b/python/img/RB.png
deleted file mode 100644
index 0c3d098..0000000
Binary files a/python/img/RB.png and /dev/null differ
diff --git a/python/img/RB.svg b/python/img/RB.svg
new file mode 100644
index 0000000..821e9dd
--- /dev/null
+++ b/python/img/RB.svg
@@ -0,0 +1,165 @@
+
+
diff --git a/python/img/Rh.png b/python/img/Rh.png
deleted file mode 100644
index e9375e8..0000000
Binary files a/python/img/Rh.png and /dev/null differ
diff --git a/python/img/Rh.svg b/python/img/Rh.svg
new file mode 100644
index 0000000..abdb1d8
--- /dev/null
+++ b/python/img/Rh.svg
@@ -0,0 +1,149 @@
+
+
diff --git a/python/img/Rh2.png b/python/img/Rh2.png
deleted file mode 100644
index c2c4bb4..0000000
Binary files a/python/img/Rh2.png and /dev/null differ
diff --git a/python/img/RhMixed.svg b/python/img/RhMixed.svg
new file mode 100644
index 0000000..7639cd6
--- /dev/null
+++ b/python/img/RhMixed.svg
@@ -0,0 +1,171 @@
+
+
diff --git a/python/img/Rhmixed.png b/python/img/Rhmixed.png
deleted file mode 100644
index 6b1c3b3..0000000
Binary files a/python/img/Rhmixed.png and /dev/null differ
diff --git a/python/img/SchmidtDecomp.png b/python/img/SchmidtDecomp.png
deleted file mode 100644
index 62c0d09..0000000
Binary files a/python/img/SchmidtDecomp.png and /dev/null differ
diff --git a/python/img/Vl.svg b/python/img/Vl.svg
new file mode 100644
index 0000000..e033808
--- /dev/null
+++ b/python/img/Vl.svg
@@ -0,0 +1,124 @@
+
+
diff --git a/python/img/VlX.png b/python/img/VlX.png
deleted file mode 100644
index 4fd1f7c..0000000
Binary files a/python/img/VlX.png and /dev/null differ
diff --git a/python/img/VlX.svg b/python/img/VlX.svg
new file mode 100644
index 0000000..2bc8724
--- /dev/null
+++ b/python/img/VlX.svg
@@ -0,0 +1,67 @@
+
+
diff --git a/python/img/Z.svg b/python/img/Z.svg
new file mode 100644
index 0000000..9f7289e
--- /dev/null
+++ b/python/img/Z.svg
@@ -0,0 +1,354 @@
+
+
diff --git a/python/img/Z2.svg b/python/img/Z2.svg
new file mode 100644
index 0000000..d9c8205
--- /dev/null
+++ b/python/img/Z2.svg
@@ -0,0 +1,114 @@
+
+
diff --git a/python/img/centerTerms.png b/python/img/centerTerms.png
deleted file mode 100644
index 08ea2e5..0000000
Binary files a/python/img/centerTerms.png and /dev/null differ
diff --git a/python/img/centerTerms.svg b/python/img/centerTerms.svg
new file mode 100644
index 0000000..a36d93b
--- /dev/null
+++ b/python/img/centerTerms.svg
@@ -0,0 +1,134 @@
+
+
diff --git a/python/img/channels.svg b/python/img/channels.svg
new file mode 100644
index 0000000..8c435b6
--- /dev/null
+++ b/python/img/channels.svg
@@ -0,0 +1,142 @@
+
+
diff --git a/python/img/delta.svg b/python/img/delta.svg
new file mode 100644
index 0000000..60e7852
--- /dev/null
+++ b/python/img/delta.svg
@@ -0,0 +1,172 @@
+
+
diff --git a/python/img/diagC.svg b/python/img/diagC.svg
new file mode 100644
index 0000000..d83ccd1
--- /dev/null
+++ b/python/img/diagC.svg
@@ -0,0 +1,140 @@
+
+
diff --git a/python/img/energyOptimum.png b/python/img/energyOptimum.png
deleted file mode 100644
index b34143c..0000000
Binary files a/python/img/energyOptimum.png and /dev/null differ
diff --git a/python/img/envNorm.svg b/python/img/envNorm.svg
new file mode 100644
index 0000000..df16296
--- /dev/null
+++ b/python/img/envNorm.svg
@@ -0,0 +1,88 @@
+
+
diff --git a/python/img/environments.svg b/python/img/environments.svg
new file mode 100644
index 0000000..d6f8cc0
--- /dev/null
+++ b/python/img/environments.svg
@@ -0,0 +1,203 @@
+
+
diff --git a/python/img/equations.png b/python/img/equations.png
deleted file mode 100644
index fb78e90..0000000
Binary files a/python/img/equations.png and /dev/null differ
diff --git a/python/img/excitation.svg b/python/img/excitation.svg
new file mode 100644
index 0000000..ef66171
--- /dev/null
+++ b/python/img/excitation.svg
@@ -0,0 +1,464 @@
+
+
diff --git a/python/img/excitationOrth.svg b/python/img/excitationOrth.svg
new file mode 100644
index 0000000..7e41eb3
--- /dev/null
+++ b/python/img/excitationOrth.svg
@@ -0,0 +1,179 @@
+
+
diff --git a/python/img/expVal.svg b/python/img/expVal.svg
new file mode 100644
index 0000000..0c14c52
--- /dev/null
+++ b/python/img/expVal.svg
@@ -0,0 +1,230 @@
+
+
diff --git a/python/img/expVal2.svg b/python/img/expVal2.svg
new file mode 100644
index 0000000..5840013
--- /dev/null
+++ b/python/img/expVal2.svg
@@ -0,0 +1,73 @@
+
+
diff --git a/python/img/expVal3.svg b/python/img/expVal3.svg
new file mode 100644
index 0000000..5a4e1e1
--- /dev/null
+++ b/python/img/expVal3.svg
@@ -0,0 +1,69 @@
+
+
diff --git a/python/img/expValHam.svg b/python/img/expValHam.svg
new file mode 100644
index 0000000..d742b7c
--- /dev/null
+++ b/python/img/expValHam.svg
@@ -0,0 +1,300 @@
+
+
diff --git a/python/img/expValue.png b/python/img/expValue.png
deleted file mode 100644
index f479174..0000000
Binary files a/python/img/expValue.png and /dev/null differ
diff --git a/python/img/expValue2.png b/python/img/expValue2.png
deleted file mode 100644
index 6299ff2..0000000
Binary files a/python/img/expValue2.png and /dev/null differ
diff --git a/python/img/expValue3.png b/python/img/expValue3.png
deleted file mode 100644
index eaf1fc3..0000000
Binary files a/python/img/expValue3.png and /dev/null differ
diff --git a/python/img/fixedPoint.svg b/python/img/fixedPoint.svg
new file mode 100644
index 0000000..ee734cd
--- /dev/null
+++ b/python/img/fixedPoint.svg
@@ -0,0 +1,190 @@
+
+
diff --git a/python/img/fixedPoints.png b/python/img/fixedPoints.png
deleted file mode 100644
index 3373f24..0000000
Binary files a/python/img/fixedPoints.png and /dev/null differ
diff --git a/python/img/fixedPoints.svg b/python/img/fixedPoints.svg
new file mode 100644
index 0000000..32e0bec
--- /dev/null
+++ b/python/img/fixedPoints.svg
@@ -0,0 +1,133 @@
+
+
diff --git a/python/img/gaugeExcitation.svg b/python/img/gaugeExcitation.svg
new file mode 100644
index 0000000..e26a6d2
--- /dev/null
+++ b/python/img/gaugeExcitation.svg
@@ -0,0 +1,126 @@
+
+
diff --git a/python/img/gaugeFix.png b/python/img/gaugeFix.png
deleted file mode 100644
index 0b99e34..0000000
Binary files a/python/img/gaugeFix.png and /dev/null differ
diff --git a/python/img/gaugeFix.svg b/python/img/gaugeFix.svg
new file mode 100644
index 0000000..e44fb9f
--- /dev/null
+++ b/python/img/gaugeFix.svg
@@ -0,0 +1,103 @@
+
+
diff --git a/python/img/gaugeTransform.png b/python/img/gaugeTransform.png
deleted file mode 100644
index fe2fe09..0000000
Binary files a/python/img/gaugeTransform.png and /dev/null differ
diff --git a/python/img/gaugeTransform.svg b/python/img/gaugeTransform.svg
new file mode 100644
index 0000000..47934c0
--- /dev/null
+++ b/python/img/gaugeTransform.svg
@@ -0,0 +1,89 @@
+
+
diff --git a/python/img/gaugeTransform2.png b/python/img/gaugeTransform2.png
deleted file mode 100644
index 06d589f..0000000
Binary files a/python/img/gaugeTransform2.png and /dev/null differ
diff --git a/python/img/grad.svg b/python/img/grad.svg
new file mode 100644
index 0000000..4798d41
--- /dev/null
+++ b/python/img/grad.svg
@@ -0,0 +1,170 @@
+
+
diff --git a/python/img/grad2.png b/python/img/grad2.png
deleted file mode 100644
index 1903e1f..0000000
Binary files a/python/img/grad2.png and /dev/null differ
diff --git a/python/img/gradFull.svg b/python/img/gradFull.svg
new file mode 100644
index 0000000..d9a7b9f
--- /dev/null
+++ b/python/img/gradFull.svg
@@ -0,0 +1,220 @@
+
+
diff --git a/python/img/gradTerm.svg b/python/img/gradTerm.svg
new file mode 100644
index 0000000..45f9e01
--- /dev/null
+++ b/python/img/gradTerm.svg
@@ -0,0 +1,142 @@
+
+
diff --git a/python/img/gradTerms.png b/python/img/gradTerms.png
deleted file mode 100644
index 0a8f5f6..0000000
Binary files a/python/img/gradTerms.png and /dev/null differ
diff --git a/python/img/gradient.png b/python/img/gradient.png
deleted file mode 100644
index 95afa18..0000000
Binary files a/python/img/gradient.png and /dev/null differ
diff --git a/python/img/gradientCalc.png b/python/img/gradientCalc.png
deleted file mode 100644
index 5730957..0000000
Binary files a/python/img/gradientCalc.png and /dev/null differ
diff --git a/python/img/hamExpVal.png b/python/img/hamExpVal.png
deleted file mode 100644
index d8a94dd..0000000
Binary files a/python/img/hamExpVal.png and /dev/null differ
diff --git a/python/img/lambda.svg b/python/img/lambda.svg
new file mode 100644
index 0000000..a07314d
--- /dev/null
+++ b/python/img/lambda.svg
@@ -0,0 +1,211 @@
+
+
diff --git a/python/img/leftGauge.svg b/python/img/leftGauge.svg
new file mode 100644
index 0000000..0294f3c
--- /dev/null
+++ b/python/img/leftGauge.svg
@@ -0,0 +1,78 @@
+
+
diff --git a/python/img/leftOrth.svg b/python/img/leftOrth.svg
new file mode 100644
index 0000000..59468c1
--- /dev/null
+++ b/python/img/leftOrth.svg
@@ -0,0 +1,72 @@
+
+
diff --git a/python/img/leftOrth2.svg b/python/img/leftOrth2.svg
new file mode 100644
index 0000000..fd0c119
--- /dev/null
+++ b/python/img/leftOrth2.svg
@@ -0,0 +1,210 @@
+
+
diff --git a/python/img/leftOrthonormal.png b/python/img/leftOrthonormal.png
deleted file mode 100644
index 3c3fece..0000000
Binary files a/python/img/leftOrthonormal.png and /dev/null differ
diff --git a/python/img/leftOrthonormal2.png b/python/img/leftOrthonormal2.png
deleted file mode 100644
index befa599..0000000
Binary files a/python/img/leftOrthonormal2.png and /dev/null differ
diff --git a/python/img/leftTerm.png b/python/img/leftTerm.png
deleted file mode 100644
index b2bdd49..0000000
Binary files a/python/img/leftTerm.png and /dev/null differ
diff --git a/python/img/leftTerms.svg b/python/img/leftTerms.svg
new file mode 100644
index 0000000..c5633e9
--- /dev/null
+++ b/python/img/leftTerms.svg
@@ -0,0 +1,62 @@
+
+
diff --git a/python/img/mixedGauge.png b/python/img/mixedGauge.png
deleted file mode 100644
index b8f9cda..0000000
Binary files a/python/img/mixedGauge.png and /dev/null differ
diff --git a/python/img/mixedGauge.svg b/python/img/mixedGauge.svg
new file mode 100644
index 0000000..8cb4adb
--- /dev/null
+++ b/python/img/mixedGauge.svg
@@ -0,0 +1,226 @@
+
+
diff --git a/python/img/mixedGauge2.png b/python/img/mixedGauge2.png
deleted file mode 100644
index 58c290b..0000000
Binary files a/python/img/mixedGauge2.png and /dev/null differ
diff --git a/python/img/mixedGauge2.svg b/python/img/mixedGauge2.svg
new file mode 100644
index 0000000..4913fb7
--- /dev/null
+++ b/python/img/mixedGauge2.svg
@@ -0,0 +1,91 @@
+
+
diff --git a/python/img/mixedGauge3.png b/python/img/mixedGauge3.png
deleted file mode 100644
index 600f395..0000000
Binary files a/python/img/mixedGauge3.png and /dev/null differ
diff --git a/python/img/mpsNorm.svg b/python/img/mpsNorm.svg
new file mode 100644
index 0000000..93309cc
--- /dev/null
+++ b/python/img/mpsNorm.svg
@@ -0,0 +1,323 @@
+
+
diff --git a/python/img/orthonormal.png b/python/img/orthonormal.png
deleted file mode 100644
index bf21b67..0000000
Binary files a/python/img/orthonormal.png and /dev/null differ
diff --git a/python/img/partFunc.png b/python/img/partFunc.png
deleted file mode 100644
index 477862d..0000000
Binary files a/python/img/partFunc.png and /dev/null differ
diff --git a/python/img/q.svg b/python/img/q.svg
new file mode 100644
index 0000000..50ca81c
--- /dev/null
+++ b/python/img/q.svg
@@ -0,0 +1,118 @@
+
+
diff --git a/python/img/qrConv.svg b/python/img/qrConv.svg
new file mode 100644
index 0000000..5caf51c
--- /dev/null
+++ b/python/img/qrConv.svg
@@ -0,0 +1,91 @@
+
+
diff --git a/python/img/qrStep.svg b/python/img/qrStep.svg
new file mode 100644
index 0000000..efc51e8
--- /dev/null
+++ b/python/img/qrStep.svg
@@ -0,0 +1,120 @@
+
+
diff --git a/python/img/quasi_inveff.svg b/python/img/quasi_inveff.svg
new file mode 100644
index 0000000..71e8026
--- /dev/null
+++ b/python/img/quasi_inveff.svg
@@ -0,0 +1,164 @@
+
+
diff --git a/python/img/quasiparticle.png b/python/img/quasiparticle.png
deleted file mode 100644
index 391c8a5..0000000
Binary files a/python/img/quasiparticle.png and /dev/null differ
diff --git a/python/img/regTransfer.png b/python/img/regTransfer.png
deleted file mode 100644
index f6bf509..0000000
Binary files a/python/img/regTransfer.png and /dev/null differ
diff --git a/python/img/regTransfer.svg b/python/img/regTransfer.svg
new file mode 100644
index 0000000..208285f
--- /dev/null
+++ b/python/img/regTransfer.svg
@@ -0,0 +1,106 @@
+
+
diff --git a/python/img/rightGauge.svg b/python/img/rightGauge.svg
new file mode 100644
index 0000000..cd126ec
--- /dev/null
+++ b/python/img/rightGauge.svg
@@ -0,0 +1,98 @@
+
+
diff --git a/python/img/rightOrth.svg b/python/img/rightOrth.svg
new file mode 100644
index 0000000..756e2f3
--- /dev/null
+++ b/python/img/rightOrth.svg
@@ -0,0 +1,72 @@
+
+
diff --git a/python/img/rightOrthonormal.png b/python/img/rightOrthonormal.png
deleted file mode 100644
index be1c47f..0000000
Binary files a/python/img/rightOrthonormal.png and /dev/null differ
diff --git a/python/img/rightTerm.png b/python/img/rightTerm.png
deleted file mode 100644
index 865bce3..0000000
Binary files a/python/img/rightTerm.png and /dev/null differ
diff --git a/python/img/rightTerms.svg b/python/img/rightTerms.svg
new file mode 100644
index 0000000..f6d2b32
--- /dev/null
+++ b/python/img/rightTerms.svg
@@ -0,0 +1,62 @@
+
+
diff --git a/python/img/t.svg b/python/img/t.svg
new file mode 100644
index 0000000..662a486
--- /dev/null
+++ b/python/img/t.svg
@@ -0,0 +1,145 @@
+
+
diff --git a/python/img/tm.svg b/python/img/tm.svg
new file mode 100644
index 0000000..52ab081
--- /dev/null
+++ b/python/img/tm.svg
@@ -0,0 +1,131 @@
+
+
diff --git a/python/img/traceNorm.png b/python/img/traceNorm.png
deleted file mode 100644
index 08b83f6..0000000
Binary files a/python/img/traceNorm.png and /dev/null differ
diff --git a/python/img/traceNorm.svg b/python/img/traceNorm.svg
new file mode 100644
index 0000000..e2b66ad
--- /dev/null
+++ b/python/img/traceNorm.svg
@@ -0,0 +1,54 @@
+
+
diff --git a/python/img/transferMPO.png b/python/img/transferMPO.png
deleted file mode 100644
index 0a1af63..0000000
Binary files a/python/img/transferMPO.png and /dev/null differ
diff --git a/python/img/transferMatrix.png b/python/img/transferMatrix.png
deleted file mode 100644
index cd3b25b..0000000
Binary files a/python/img/transferMatrix.png and /dev/null differ
diff --git a/python/img/transferMpo.svg b/python/img/transferMpo.svg
new file mode 100644
index 0000000..0f8acda
--- /dev/null
+++ b/python/img/transferMpo.svg
@@ -0,0 +1,136 @@
+
+
diff --git a/python/img/transferPower.svg b/python/img/transferPower.svg
new file mode 100644
index 0000000..fbfa9d7
--- /dev/null
+++ b/python/img/transferPower.svg
@@ -0,0 +1,87 @@
+
+
diff --git a/python/img/truncMPS.png b/python/img/truncMPS.png
deleted file mode 100644
index fd71fa5..0000000
Binary files a/python/img/truncMPS.png and /dev/null differ
diff --git a/python/img/truncMPS.svg b/python/img/truncMPS.svg
new file mode 100644
index 0000000..087a9f4
--- /dev/null
+++ b/python/img/truncMPS.svg
@@ -0,0 +1,318 @@
+
+
diff --git a/python/img/umps.svg b/python/img/umps.svg
new file mode 100644
index 0000000..84ae9a9
--- /dev/null
+++ b/python/img/umps.svg
@@ -0,0 +1,119 @@
+
+
diff --git a/python/img/unitaryGauge.svg b/python/img/unitaryGauge.svg
new file mode 100644
index 0000000..b51482a
--- /dev/null
+++ b/python/img/unitaryGauge.svg
@@ -0,0 +1,72 @@
+
+
diff --git a/python/tutorialFunctions.py b/python/tutorialFunctions.py
index 8ece92b..8bd76a0 100755
--- a/python/tutorialFunctions.py
+++ b/python/tutorialFunctions.py
@@ -32,12 +32,12 @@ def createMPS(D, d):
A : np.array (D, d, D)
MPS tensor with 3 legs,
ordered left-bottom-right,
- normalised.
+ normalized.
"""
A = np.random.rand(D, d, D) + 1j * np.random.rand(D, d, D)
- return normaliseMPS(A)
+ return normalizeMPS(A)
def createTransfermatrix(A):
@@ -62,9 +62,9 @@ def createTransfermatrix(A):
return E
-def normaliseMPS(A):
+def normalizeMPS(A):
"""
- Normalise an MPS tensor.
+ Normalize an MPS tensor.
Parameters
----------
@@ -185,7 +185,7 @@ def rightFixedPoint(A):
def fixedPoints(A):
"""
- Find normalised fixed points.
+ Find normalized fixed points.
Parameters
----------
@@ -256,7 +256,7 @@ def rqPos(A):
return R, Q
-def rightOrthonormalise(A, R0=None, tol=1e-14, maxIter=1e5):
+def rightOrthonormalize(A, R0=None, tol=1e-14, maxIter=1e5):
"""
Transform A to right-orthonormal gauge.
@@ -280,23 +280,24 @@ def rightOrthonormalise(A, R0=None, tol=1e-14, maxIter=1e5):
right gauge with 2 legs,
ordered left-right.
Ar : np.array(D, d, D)
- MPS tensor zith 3 legs,
+ MPS tensor with 3 legs,
ordered left-bottom-right,
right-orthonormal
"""
D = A.shape[0]
d = A.shape[1]
+ tol = max(tol, 1e-14)
i = 1
# Random guess for R0 if none specified
if R0 is None:
R0 = np.random.rand(D, D)
- # Normalise R0
+ # Normalize R0
R0 = R0 / np.linalg.norm(R0)
- # Initialise loop
+ # Initialize loop
R, Ar = rqPos(np.reshape(ncon((A, R0), ([-1, -2, 1], [1, -3])), (D, D * d)))
R = R / np.linalg.norm(R)
convergence = np.linalg.norm(R - R0)
@@ -306,7 +307,7 @@ def rightOrthonormalise(A, R0=None, tol=1e-14, maxIter=1e5):
# calculate AR and decompose
Rnew, Ar = rqPos(np.reshape(ncon((A, R), ([-1, -2, 1], [1, -3])), (D, D * d)))
- # normalise new R
+ # normalize new R
Rnew = Rnew / np.linalg.norm(Rnew)
# calculate convergence criterium
@@ -361,7 +362,7 @@ def qrPos(A):
return Q, R
-def leftOrthonormalise(A, L0=None, tol=1e-14, maxIter=1e5):
+def leftOrthonormalize(A, L0=None, tol=1e-14, maxIter=1e5):
"""
Transform A to left-orthonormal gauge.
@@ -385,23 +386,24 @@ def leftOrthonormalise(A, L0=None, tol=1e-14, maxIter=1e5):
left gauge with 2 legs,
ordered left-right.
Al : np.array(D, d, D)
- MPS tensor zith 3 legs,
+ MPS tensor with 3 legs,
ordered left-bottom-right,
left-orthonormal
"""
D = A.shape[0]
d = A.shape[1]
+ tol = max(tol, 1e-14)
i = 1
# Random guess for L0 if none specified
if L0 is None:
L0 = np.random.rand(D, D)
- # Normalise L0
+ # Normalize L0
L0 = L0 / np.linalg.norm(L0)
- # Initialise loop
+ # Initialize loop
Al, L = qrPos(np.reshape(ncon((L0, A), ([-1, 1], [1, -2, -3])), (D * d, D)))
L = L / np.linalg.norm(L)
convergence = np.linalg.norm(L - L0)
@@ -411,7 +413,7 @@ def leftOrthonormalise(A, L0=None, tol=1e-14, maxIter=1e5):
# calculate LA and decompose
Al, Lnew = qrPos(np.reshape(ncon((L, A), ([-1, 1], [1, -2, -3])), (D * d, D)))
- # normalise new L
+ # normalize new L
Lnew = Lnew / np.linalg.norm(Lnew)
# calculate convergence criterium
@@ -440,15 +442,15 @@ def mixedCanonical(A, L0=None, R0=None, tol=1e-14, maxIter=1e5):
Returns
-------
Al : np.array(D, d, D)
- MPS tensor zith 3 legs,
+ MPS tensor with 3 legs,
ordered left-bottom-right,
left orthonormal.
Ac : np.array(D, d, D)
- MPS tensor zith 3 legs,
+ MPS tensor with 3 legs,
ordered left-bottom-right,
center gauge.
Ar : np.array(D, d, D)
- MPS tensor zith 3 legs,
+ MPS tensor with 3 legs,
ordered left-bottom-right,
right orthonormal.
C : np.array(D, D)
@@ -462,6 +464,7 @@ def mixedCanonical(A, L0=None, R0=None, tol=1e-14, maxIter=1e5):
"""
D = A.shape[0]
+ tol = max(tol, 1e-14)
# Random guess for L0 if none specified
if L0 is None:
@@ -472,13 +475,13 @@ def mixedCanonical(A, L0=None, R0=None, tol=1e-14, maxIter=1e5):
R0 = np.random.rand(D, D)
# Compute left and right orthonormal forms
- L, Al = leftOrthonormalise(A, L0, tol, maxIter)
- R, Ar = rightOrthonormalise(A, R0, tol, maxIter)
+ L, Al = leftOrthonormalize(A, L0, tol, maxIter)
+ R, Ar = rightOrthonormalize(A, R0, tol, maxIter)
# center matrix C is matrix multiplication of L and R
C = L @ R
- # singular value decomposition to diagonalise C
+ # singular value decomposition to diagonalize C
U, S, Vdag = svd(C)
C = np.diag(S)
@@ -486,7 +489,7 @@ def mixedCanonical(A, L0=None, R0=None, tol=1e-14, maxIter=1e5):
Al = ncon((np.conj(U).T, Al, U), ([-1, 1], [1, -2, 2], [2, -3]))
Ar = ncon((Vdag, Ar, np.conj(Vdag).T), ([-1, 1], [1, -2, 2], [2, -3]))
- # normalise center matrix
+ # normalize center matrix
norm = np.trace(C @ np.conj(C).T)
C /= np.sqrt(norm)
@@ -541,15 +544,15 @@ def truncateMPS(A, Dtrunc):
Returns
-------
AlTilde : np.array(Dtrunc, d, Dtrunc)
- MPS tensor zith 3 legs,
+ MPS tensor with 3 legs,
ordered left-bottom-right,
left orthonormal.
AcTilde : np.array(Dtrunc, d, Dtrunc)
- MPS tensor zith 3 legs,
+ MPS tensor with 3 legs,
ordered left-bottom-right,
center gauge.
ArTilde : np.array(Dtrunc, d, Dtrunc)
- MPS tensor zith 3 legs,
+ MPS tensor with 3 legs,
ordered left-bottom-right,
right orthonormal.
CTilde : np.array(Dtrunc, Dtrunc)
@@ -571,7 +574,7 @@ def truncateMPS(A, Dtrunc):
ArTilde = ncon((Vdag, Ar, np.conj(Vdag).T), ([-1, 1], [1, -2, 2], [2, -3]))
CTilde = np.diag(S)
- # renormalise
+ # renormalize
norm = np.trace(CTilde @ np.conj(CTilde).T)
CTilde /= np.sqrt(norm)
@@ -593,10 +596,10 @@ def expVal1Uniform(O, A, l=None, r=None):
ordered left-bottom-right.
l : np.array(D, D), optional
left fixed point of transfermatrix,
- normalised.
+ normalized.
r : np.array(D, D), optional
right fixed point of transfermatrix,
- normalised.
+ normalized.
Returns
-------
@@ -653,10 +656,10 @@ def expVal2Uniform(O, A, l=None, r=None):
ordered left-bottom-right.
l : np.array(D, D), optional
left fixed point of transfermatrix,
- normalised.
+ normalized.
r : np.array(D, D), optional
right fixed point of transfermatrix,
- normalised.
+ normalized.
Returns
-------
@@ -719,14 +722,14 @@ def gradCenterTerms(hTilde, A, l=None, r=None):
reduced Hamiltonian,
ordered topLeft-topRight-bottomLeft-bottomRight.
A : np.array (D, d, D)
- normalised MPS tensor with 3 legs,
+ normalized MPS tensor with 3 legs,
ordered left-bottom-right.
l : np.array(D, D), optional
left fixed point of transfermatrix,
- normalised.
+ normalized.
r : np.array(D, D), optional
right fixed point of transfermatrix,
- normalised.
+ normalized.
Returns
-------
@@ -753,7 +756,7 @@ def gradCenterTerms(hTilde, A, l=None, r=None):
def reducedHamUniform(h, A, l=None, r=None):
"""
- Regularise Hamiltonian such that its expectation value is 0.
+ Regularize Hamiltonian such that its expectation value is 0.
Parameters
----------
@@ -761,14 +764,14 @@ def reducedHamUniform(h, A, l=None, r=None):
Hamiltonian that needs to be reduced,
ordered topLeft-topRight-bottomLeft-bottomRight.
A : np.array (D, d, D)
- normalised MPS tensor with 3 legs,
+ normalized MPS tensor with 3 legs,
ordered left-bottom-right.
l : np.array(D, D), optional
left fixed point of transfermatrix,
- normalised.
+ normalized.
r : np.array(D, D), optional
right fixed point of transfermatrix,
- normalised.
+ normalized.
Returns
-------
@@ -799,14 +802,14 @@ def EtildeRight(A, l, r, v):
Parameters
----------
A : np.array (D, d, D)
- normalised MPS tensor with 3 legs,
+ normalized MPS tensor with 3 legs,
ordered left-bottom-right.
l : np.array(D, D), optional
left fixed point of transfermatrix,
- normalised.
+ normalized.
r : np.array(D, D), optional
right fixed point of transfermatrix,
- normalised.
+ normalized.
v : np.array(D**2)
right matrix of size (D, D) on which
(1 - Etilde) acts,
@@ -846,16 +849,16 @@ def RhUniform(hTilde, A, l=None, r=None):
hTilde : np.array (d, d, d, d)
reduced Hamiltonian,
ordered topLeft-topRight-bottomLeft-bottomRight,
- renormalised.
+ renormalized.
A : np.array (D, d, D)
- normalised MPS tensor with 3 legs,
+ normalized MPS tensor with 3 legs,
ordered left-bottom-right.
l : np.array(D, D), optional
left fixed point of transfermatrix,
- normalised.
+ normalized.
r : np.array(D, D), optional
right fixed point of transfermatrix,
- normalised.
+ normalized.
Returns
-------
@@ -889,16 +892,16 @@ def gradLeftTerms(hTilde, A, l=None, r=None):
hTilde : np.array (d, d, d, d)
reduced Hamiltonian,
ordered topLeft-topRight-bottomLeft-bottomRight,
- renormalised.
+ renormalized.
A : np.array (D, d, D)
MPS tensor with 3 legs,
ordered left-bottom-right.
l : np.array(D, D), optional
left fixed point of transfermatrix,
- normalised.
+ normalized.
r : np.array(D, D), optional
right fixed point of transfermatrix,
- normalised.
+ normalized.
Returns
-------
@@ -927,14 +930,14 @@ def EtildeLeft(A, l, r, v):
Parameters
----------
A : np.array (D, d, D)
- normalised MPS tensor with 3 legs,
+ normalized MPS tensor with 3 legs,
ordered left-bottom-right.
l : np.array(D, D), optional
left fixed point of transfermatrix,
- normalised.
+ normalized.
r : np.array(D, D), optional
right fixed point of transfermatrix,
- normalised.
+ normalized.
v : np.array(D**2)
right matrix of size (D, D) on which
(1 - Etilde) acts,
@@ -974,16 +977,16 @@ def LhUniform(hTilde, A, l=None, r=None):
hTilde : np.array (d, d, d, d)
reduced Hamiltonian,
ordered topLeft-topRight-bottomLeft-bottomRight,
- renormalised.
+ renormalized.
A : np.array (D, d, D)
MPS tensor with 3 legs,
ordered left-bottom-right.
l : np.array(D, D), optional
left fixed point of transfermatrix,
- normalised.
+ normalized.
r : np.array(D, D), optional
right fixed point of transfermatrix,
- normalised.
+ normalized.
Returns
-------
@@ -1017,16 +1020,16 @@ def gradRightTerms(hTilde, A, l=None, r=None):
hTilde : np.array (d, d, d, d)
reduced Hamiltonian,
ordered topLeft-topRight-bottomLeft-bottomRight,
- renormalised.
+ renormalized.
A : np.array (D, d, D)
MPS tensor with 3 legs,
ordered left-bottom-right.
l : np.array(D, D), optional
left fixed point of transfermatrix,
- normalised.
+ normalized.
r : np.array(D, D), optional
right fixed point of transfermatrix,
- normalised.
+ normalized.
Returns
-------
@@ -1057,16 +1060,16 @@ def gradient(h, A, l=None, r=None):
h : np.array (d, d, d, d)
Hamiltonian,
ordered topLeft-topRight-bottomLeft-bottomRight,
- renormalised.
+ renormalized.
A : np.array (D, d, D)
MPS tensor with 3 legs,
ordered left-bottom-right.
l : np.array(D, D), optional
left fixed point of transfermatrix,
- normalised.
+ normalized.
r : np.array(D, D), optional
right fixed point of transfermatrix,
- normalised.
+ normalized.
Returns
-------
@@ -1079,7 +1082,7 @@ def gradient(h, A, l=None, r=None):
if l is None or r is None:
l, r = fixedPoints(A)
- # renormalise Hamiltonian
+ # renormalize Hamiltonian
hTilde = reducedHamUniform(h, A, l, r)
# find terms
@@ -1099,14 +1102,14 @@ def groundStateGradDescent(h, D, eps=1e-1, A0=None, tol=1e-4, maxIter=1e4):
Parameters
----------
h : np.array (d, d, d, d)
- Hamiltonian to minimise,
+ Hamiltonian to minimize,
ordered topLeft-topRight-bottomLeft-bottomRight.
D : int
Bond dimension
eps : float
Stepsize.
A0 : np.array (D, d, D)
- normalised MPS tensor with 3 legs,
+ normalized MPS tensor with 3 legs,
ordered left-bottom-right,
initial guess.
tol : float
@@ -1126,7 +1129,7 @@ def groundStateGradDescent(h, D, eps=1e-1, A0=None, tol=1e-4, maxIter=1e4):
# if no initial value, choose random
if A0 is None:
A0 = createMPS(D, d)
- A0 = normaliseMPS(A0)
+ A0 = normalizeMPS(A0)
# calculate gradient
g = gradient(h, A0)
@@ -1137,7 +1140,7 @@ def groundStateGradDescent(h, D, eps=1e-1, A0=None, tol=1e-4, maxIter=1e4):
while not(np.all(np.abs(g) < tol)):
# do a step
A = A - eps * g
- A = normaliseMPS(A)
+ A = normalizeMPS(A)
i += 1
if not(i % 100):
@@ -1157,14 +1160,14 @@ def groundStateGradDescent(h, D, eps=1e-1, A0=None, tol=1e-4, maxIter=1e4):
return E, A
-def groundStateMinimise(h, D, A0=None, tol=1e-4):
+def groundStateMinimize(h, D, A0=None, tol=1e-4):
"""
Find the ground state using a scipy minimizer.
Parameters
----------
h : np.array (d, d, d, d)
- Hamiltonian to minimise,
+ Hamiltonian to minimize,
ordered topLeft-topRight-bottomLeft-bottomRight.
D : int
Bond dimension
@@ -1242,7 +1245,7 @@ def wrapper(A):
# if no initial MPS, take random one
if A0 is None:
A0 = createMPS(D, d)
- A0 = normaliseMPS(A0)
+ A0 = normalizeMPS(A0)
# define f for minimize in scipy
def f(varA):
@@ -1264,7 +1267,7 @@ def f(varA):
# unwrap varA
A = unwrapper(varA)
- A = normaliseMPS(A)
+ A = normalizeMPS(A)
# calculate fixed points
l, r = fixedPoints(A)
@@ -1318,7 +1321,7 @@ def Heisenberg(Jx, Jy, Jz, hz):
def reducedHamMixed(h, Ac, Ar):
"""
- Regularise Hamiltonian such that its expectation value is 0.
+ Regularize Hamiltonian such that its expectation value is 0.
Parameters
----------
@@ -1352,7 +1355,7 @@ def reducedHamMixed(h, Ac, Ar):
return hTilde
-def RhMixed(hTilde, Ar, C, tol=1e-3):
+def RhMixed(hTilde, Ar, C, tol=1e-5):
"""
Calculate Rh, for a given MPS in mixed gauge.
@@ -1361,7 +1364,7 @@ def RhMixed(hTilde, Ar, C, tol=1e-3):
hTilde : np.array (d, d, d, d)
reduced Hamiltonian,
ordered topLeft-topRight-bottomLeft-bottomRight,
- renormalised.
+ renormalized.
Ar : np.array (D, d, D)
MPS tensor with 3 legs,
ordered left-bottom-right,
@@ -1380,6 +1383,7 @@ def RhMixed(hTilde, Ar, C, tol=1e-3):
"""
D = Ar.shape[0]
+ tol = max(tol, 1e-14)
# construct fixed points for Ar
l = np.conj(C).T @ C # left fixed point of right transfer matrix
@@ -1395,7 +1399,7 @@ def RhMixed(hTilde, Ar, C, tol=1e-3):
return Rh.reshape((D, D))
-def LhMixed(hTilde, Al, C, tol=1e-3):
+def LhMixed(hTilde, Al, C, tol=1e-5):
"""
Calculate Lh, for a given MPS in mixed gauge.
@@ -1404,7 +1408,7 @@ def LhMixed(hTilde, Al, C, tol=1e-3):
hTilde : np.array (d, d, d, d)
reduced Hamiltonian,
ordered topLeft-topRight-bottomLeft-bottomRight,
- renormalised.
+ renormalized.
Al : np.array (D, d, D)
MPS tensor with 3 legs,
ordered left-bottom-right,
@@ -1424,6 +1428,7 @@ def LhMixed(hTilde, Al, C, tol=1e-3):
"""
D = Al.shape[0]
+ tol = max(tol, 1e-14)
# construct fixed points for Al
l = np.eye(D) # left fixed point of left transfer matrix: left orthonormal
@@ -1448,7 +1453,7 @@ def H_Ac(hTilde, Al, Ar, Lh, Rh, v):
hTilde : np.array (d, d, d, d)
reduced Hamiltonian,
ordered topLeft-topRight-bottomLeft-bottomRight,
- renormalised.
+ renormalized.
Al : np.array (D, d, D)
MPS tensor with 3 legs,
ordered left-bottom-right,
@@ -1501,7 +1506,7 @@ def H_C(hTilde, Al, Ar, Lh, Rh, v):
hTilde : np.array (d, d, d, d)
reduced Hamiltonian,
ordered topLeft-topRight-bottomLeft-bottomRight,
- renormalised.
+ renormalized.
Al : np.array (D, d, D)
MPS tensor with 3 legs,
ordered left-bottom-right,
@@ -1541,7 +1546,7 @@ def H_C(hTilde, Al, Ar, Lh, Rh, v):
return H_CV
-def calcNewCenter(hTilde, Al, Ac, Ar, C, Lh=None, Rh=None, tol=1e-3):
+def calcNewCenter(hTilde, Al, Ac, Ar, C, Lh=None, Rh=None, tol=1e-5):
"""
Find new guess for Ac and C as fixed points of the maps H_Ac and H_C.
@@ -1550,17 +1555,17 @@ def calcNewCenter(hTilde, Al, Ac, Ar, C, Lh=None, Rh=None, tol=1e-3):
hTilde : np.array (d, d, d, d)
reduced Hamiltonian,
ordered topLeft-topRight-bottomLeft-bottomRight,
- renormalised.
+ renormalized.
Al : np.array(D, d, D)
- MPS tensor zith 3 legs,
+ MPS tensor with 3 legs,
ordered left-bottom-right,
left orthonormal.
Ar : np.array(D, d, D)
- MPS tensor zith 3 legs,
+ MPS tensor with 3 legs,
ordered left-bottom-right,
right orthonormal.
Ac : np.array(D, d, D)
- MPS tensor zith 3 legs,
+ MPS tensor with 3 legs,
ordered left-bottom-right,
center gauge.
C : np.array(D, D)
@@ -1579,7 +1584,7 @@ def calcNewCenter(hTilde, Al, Ac, Ar, C, Lh=None, Rh=None, tol=1e-3):
Returns
-------
AcTilde : np.array(D, d, D)
- MPS tensor zith 3 legs,
+ MPS tensor with 3 legs,
ordered left-bottom-right,
center gauge.
CTilde : np.array(D, D)
@@ -1589,6 +1594,7 @@ def calcNewCenter(hTilde, Al, Ac, Ar, C, Lh=None, Rh=None, tol=1e-3):
D = Al.shape[0]
d = Al.shape[1]
+ tol = max(tol, 1e-14)
# calculate left en right environment if they are not given
if Lh is None:
@@ -1622,7 +1628,7 @@ def calcNewCenter(hTilde, Al, Ac, Ar, C, Lh=None, Rh=None, tol=1e-3):
return AcTilde, CTilde
-def minAcC(AcTilde, CTilde):
+def minAcC(AcTilde, CTilde, tol=1e-5):
"""
Find Al and Ar corresponding to Ac and C, according to algorithm 5 in the lecture notes.
@@ -1660,6 +1666,7 @@ def minAcC(AcTilde, CTilde):
D = AcTilde.shape[0]
d = AcTilde.shape[1]
+ tol = max(tol, 1e-14)
# polar decomposition of Ac
UlAc, _ = polar(AcTilde.reshape((D * d, D)))
@@ -1670,8 +1677,8 @@ def minAcC(AcTilde, CTilde):
# construct Al
Al = (UlAc @ np.conj(UlC).T).reshape(D, d, D)
- # find corresponding Ar, C, and Ac through right orthonormalising Al
- C, Ar = rightOrthonormalise(Al)
+ # find corresponding Ar, C, and Ac through right orthonormalizing Al
+ C, Ar = rightOrthonormalize(Al, CTilde, tol=tol)
nrm = np.trace(C @ np.conj(C).T)
C = C / np.sqrt(nrm)
Ac = ncon((Al, C), ([-1, -2, 1], [1, -3]))
@@ -1688,17 +1695,17 @@ def gradientNorm(hTilde, Al, Ac, Ar, C, Lh, Rh):
hTilde : np.array (d, d, d, d)
reduced Hamiltonian,
ordered topLeft-topRight-bottomLeft-bottomRight,
- renormalised.
+ renormalized.
Al : np.array(D, d, D)
- MPS tensor zith 3 legs,
+ MPS tensor with 3 legs,
ordered left-bottom-right,
left orthonormal.
Ar : np.array(D, d, D)
- MPS tensor zith 3 legs,
+ MPS tensor with 3 legs,
ordered left-bottom-right,
right orthonormal.
Ac : np.array(D, d, D)
- MPS tensor zith 3 legs,
+ MPS tensor with 3 legs,
ordered left-bottom-right,
center gauge.
C : np.array(D, D)
@@ -1727,14 +1734,14 @@ def gradientNorm(hTilde, Al, Ac, Ar, C, Lh, Rh):
return norm
-def vumps(h, D, A0=None, tol=1e-4):
+def vumps(h, D, A0=None, tol=1e-4, tolFactor=1e-2, verbose=True):
"""
Find the ground state of a given Hamiltonian using VUMPS.
Parameters
----------
h : np.array (d, d, d, d)
- Hamiltonian to minimise,
+ Hamiltonian to minimize,
ordered topLeft-topRight-bottomLeft-bottomRight.
D : int
Bond dimension
@@ -1767,18 +1774,18 @@ def vumps(h, D, A0=None, tol=1e-4):
delta = 1e-5
while flag:
- # regularise H
+ # regularize H
hTilde = reducedHamMixed(h, Ac, Ar)
# calculate environments
- Lh = LhMixed(hTilde, Al, C, tol=delta/10)
- Rh = RhMixed(hTilde, Ar, C, tol=delta/10)
+ Lh = LhMixed(hTilde, Al, C, tol=delta*tolFactor)
+ Rh = RhMixed(hTilde, Ar, C, tol=delta*tolFactor)
# calculate new center
- AcTilde, CTilde = calcNewCenter(hTilde, Al, Ac, Ar, C, Lh, Rh, tol=delta/10)
+ AcTilde, CTilde = calcNewCenter(hTilde, Al, Ac, Ar, C, Lh, Rh, tol=delta*tolFactor)
# find Al, Ar from Ac, C
- AlTilde, AcTilde, ArTilde, CTilde = minAcC(AcTilde, CTilde)
+ AlTilde, AcTilde, ArTilde, CTilde = minAcC(AcTilde, CTilde, tol=delta*tolFactor**2)
# calculate norm
delta = gradientNorm(hTilde, Al, Ac, Ar, C, Lh, Rh)
@@ -1801,18 +1808,6 @@ def vumps(h, D, A0=None, tol=1e-4):
Chapter 3
"""
-
-
-
-
-
-
-
-
-
-
-
-
def leftFixedPointMPO(O, Al, tol):
"""
Computes the left fixed point (250).
@@ -1972,7 +1967,7 @@ def O_C(X, Fl, Fr):
return Xnew
-def calcNewCenterMPO(O, Ac, C, Fl, Fr, lam, tol=1e-3):
+def calcNewCenterMPO(O, Ac, C, Fl, Fr, lam, tol=1e-5):
"""
Find new guess for Ac and C as fixed points of the maps O_Ac and O_C.
@@ -2002,7 +1997,7 @@ def calcNewCenterMPO(O, Ac, C, Fl, Fr, lam, tol=1e-3):
Returns
-------
AcTilde : np.array(D, d, D)
- MPS tensor zith 3 legs,
+ MPS tensor with 3 legs,
ordered left-bottom-right,
center gauge.
CTilde : np.array(D, D)
@@ -2031,7 +2026,7 @@ def calcNewCenterMPO(O, Ac, C, Fl, Fr, lam, tol=1e-3):
return AcTilde, CTilde
-def vumpsMPO(O, D, A0=None, tol=1e-4):
+def vumpsMpo(O, D, A0=None, tol=1e-4):
"""
Find the fixed point MPS of a given MPO using VUMPS.
@@ -2054,11 +2049,11 @@ def vumpsMPO(O, D, A0=None, tol=1e-4):
lam : float
Leading eigenvalue.
Al : np.array(D, d, D)
- MPS tensor zith 3 legs,
+ MPS tensor with 3 legs,
ordered left-bottom-right,
left orthonormal.
Ar : np.array(D, d, D)
- MPS tensor zith 3 legs,
+ MPS tensor with 3 legs,
ordered left-bottom-right,
right orthonormal.
Ac : np.array(D, d, D)