From 05671c4be4453546c94fb3e6b1ac85b17718f8ce Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 22 Jun 2022 18:34:20 +0200 Subject: [PATCH 001/245] Similar add docstring add function --- src/tensors/Tensor.m | 113 ++++++++++++++++++++++++++++++------------- 1 file changed, 80 insertions(+), 33 deletions(-) diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index f69f6c8..a250f1a 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -148,6 +148,86 @@ t.var = fill_tensor_fun(t.var, data, trees); end end + + function t = similar(fun, tensors, indices, kwargs) + % Create a tensor based on the indices of other tensors. + % + % Usage + % ----- + % :code:`t = similar(fun, tensors, indices, kwargs)` + % + % Arguments + % --------- + % fun : :class:`function_handle` + % function to fill the tensor data with. This should have signature + % based on keyword Mode. If left empty, this defaults to a random complex + % tensor. + % + % Repeating Arguments + % ------------------- + % tensors : :class:`Tensor` + % input tensors used to copy legs. + % + % indices : int + % array with length equal to the legs of the tensor, where zero values + % indicate unused legs, and nonzero values indicate the leg of the output + % tensor. + % + % Keyword Arguments + % ----------------- + % Rank : (1, 2) int + % rank of the output tensor, by default this is :code:`[nspaces(t) 0]`. + % + % Conj : logical + % flag to indicate whether the space should be equal to the input space, or + % fit onto the input space. This can be either an array of size(tensors), or a + % scalar. + % + % Mode : 'tensor' or 'matrix' + % method of filling the tensor data. By default this is matrix, where the + % function should be of signature :code:`fun(dims, charge)`, for 'tensor' this + % should be of signature :code:`fun(dims, tree)`. + % + % Returns + % ------- + % t : :class:`Tensor` + % output tensor. + % + % Examples + % -------- + % :code:`t = similar([], mps, [3 0 0], mpo, [0 0 0 2], mpsbar, [1 0 0], 'Conj', + % true)` creates a left mps environment tensor. + + arguments + fun = [] + end + + arguments (Repeating) + tensors + indices + end + + arguments + kwargs.Rank (1, 2) = [sum(cellfun(@length, indices)) 0] + kwargs.Conj = false(size(tensors)) + kwargs.Mode = 'matrix' + end + + for i = 1:length(tensors) + inds = find(indices{i} > 0); + if any(inds) + if kwargs.Conj(i) + spaces(indices{i}(inds)) = conj(space(tensors{i}, ... + adjointindices(tensors{i}, inds))); + else + spaces(indices{i}(inds)) = space(tensors{i}, inds); + end + end + end + + t = Tensor.new(fun, spaces(1:kwargs.Rank(1)), ... + spaces((1:kwargs.Rank(2)) + kwargs.Rank(1))', 'Mode', kwargs.Mode); + end end methods (Static) @@ -298,8 +378,6 @@ end - - %% Structure methods function n = indin(t) @@ -1610,37 +1688,6 @@ end - %% Copy constructors - methods - function t = similar(tensors, indices, options) - - arguments (Repeating) - tensors - indices - end - arguments - options.Rank (1, 2) = [sum(cellfun(@length, indices)) 0] - options.Conj = false(size(tensors)) - end - - for i = 1:length(tensors) - inds = find(indices{i} > 0); - if any(inds) - if options.Conj(i) - spaces(indices{i}(inds)) = conj(space(tensors{i}, ... - adjointindices(tensors{i}, inds))); - else - spaces(indices{i}(inds)) = space(tensors{i}, inds); - end - end - end - - t = Tensor(spaces(1:options.Rank(1)), ... - spaces((1:options.Rank(2)) + options.Rank(1))'); - end - end - - %% Solvers methods function varargout = linsolve(A, b, x0, M1, M2, options) From a3463670e1a0c5d764290c0d0a2d16ef3b8813c6 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 23 Jun 2022 01:19:41 +0200 Subject: [PATCH 002/245] Add A4 charges. --- .gitignore | 2 + src/tensors/charges/A4.m | 101 +++++++++++++++++++++++++++ src/tensors/charges/A4_data.mat | Bin 0 -> 1537 bytes src/tensors/charges/AbstractCharge.m | 66 ++++++++++++----- test/TestCharge.m | 12 +++- 5 files changed, 161 insertions(+), 20 deletions(-) create mode 100644 src/tensors/charges/A4.m create mode 100644 src/tensors/charges/A4_data.mat diff --git a/.gitignore b/.gitignore index fc4d59a..b298dfc 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ _build .ipynb_checkpoints docs/src/examples/**/*.m +resources/ +*.prj \ No newline at end of file diff --git a/src/tensors/charges/A4.m b/src/tensors/charges/A4.m new file mode 100644 index 0000000..0dac682 --- /dev/null +++ b/src/tensors/charges/A4.m @@ -0,0 +1,101 @@ +classdef A4 < AbstractCharge & uint8 + % Irreducible representations of the alternating group of order 4. This is the group of + % even permutations on four elements, or of orientation-preserving symmetries of a + % regular tetrahedron. This is a non-abelian group with multiplicities. + % + % The representations are labeled by the integers 1-4, with degrees [1 1 1 3]. + + methods + function charge = A4(varargin) + if nargin == 0 + labels = []; + else + labels = horzcat(varargin{:}); + end + charge@uint8(labels); + end + + function style = braidingstyle(~) + style = BraidingStyle.Bosonic; + end + + function b = conj(a) + b = a; + b(a == 2) = A4(3); + b(a == 3) = A4(2); + end + + function varargout = cumprod(a) + [varargout{1:nargout}] = cumprod@AbstractCharge(a); + end + + function F = Fsymbol(a, b, c, d, e, f) + persistent Fcache + if isempty(Fcache) + load('A4_data.mat', 'Fcache'); + end + + if ~(Nsymbol(a, b, e) && Nsymbol(e, c, d) && ... + Nsymbol(b, c, f) && Nsymbol(a, f, d)) + assert(isempty(Fcache{a,b,c,d,e,f})); + F = zeros(Nsymbol(a,b,e), Nsymbol(e,c,d), Nsymbol(b,c,f), Nsymbol(a, f, d)); + return + end + + F = Fcache{a, b, c, d, e, f}; + end + + function style = fusionstyle(~) + style = FusionStyle.Generic; + end + + function C = fusiontensor(a, b, c) + persistent Ccache + if isempty(Ccache) + load('A4_data.mat', 'Ccache'); + end + + C = Ccache{a, b, c}; + end + + function c = mtimes(a, b) + c = A4(1:4); + c(~Nsymbol(repmat(a, 1, 4), repmat(b, 1, 4), c)) = []; + end + + function N = Nsymbol(a, b, c) + persistent Ncache + if isempty(Ncache) + load('A4_data.mat', 'Ncache'); + end + ind = sub2ind([4 4 4], a, b, c); + N = Ncache(ind); + end + + function e = one(~) + e = A4(1); + end + + function varargout = prod(varargin) + [varargout{1:nargout}] = prod@AbstractCharge(varargin{:}); + if nargout > 0 + varargout{1} = A4(varargout{1}); + end + end + + function d = qdim(a) + d = ones(size(a)); + d(a == 4) = 3; + end + + function R = Rsymbol(a, b, c, ~) + persistent Rcache + if isempty(Rcache) + load('A4_data.mat', 'Rcache'); + end + + R = Rcache{a, b, c}; + end + end +end + diff --git a/src/tensors/charges/A4_data.mat b/src/tensors/charges/A4_data.mat new file mode 100644 index 0000000000000000000000000000000000000000..19313366fc7a2245865ad51f72bc0e1f3ecbb15d GIT binary patch literal 1537 zcmeZu4DoSvQZUssQ1EpO(M`+DN!3vZ$Vn_o%P-2cQgHY2i*PhE(NSMVPV*1Rq`)@M?Y$&zn{$khJ^*k zSQB2gIsIB%y5~@a*CR$Yj-%}BejI$R$1n5lkzA!%7J3siD-)Z};Q91j1)bAa>^Oxptd|AIKa>nn9-XH5H zRr>@Mbz0uasDHP*Vt;+_O5caSgQwTFmO93$eDBzA{=JyzH`g+aohh*bh4XjLPyVp` zkNJcgJ#oOcOJ*=N_Y&#un! zWjA>6O}6~KN<$FG*-2esb2!6xL?%FrC1PQXn+gvTFo`Jl-ZAvz_p36g?YZP^c$a6( z-{!lvTb8xYu4n%6qn)!W@!vz1BvyrKhVvfm+VkmBUB?S%iCli5Q{M+x?YO-7CD0AN z^&l&EWOB1T`pl^wVk5${?iN4WcYd*|Eqf$EUU~xY(o*($a++^vT>O+aMXm?vJsIBn zfwOj*z#X_Ruknk!OJ!6koN|SP?qg8Y2Ubl{n`p+6WTx<};gZ4uuBXOIi2?~snx$+ z_6aC#*{P#o_Bf#fXjaBD>#W)BtM?q6-k12)l*Q)8{!{9YT(54sZg5LZ%}(M`@f5jP zFN*}07gtAAwRiGA`>|-dUCh@-49rS%BqalG*MC_f-tJ}bDBJxp*YSfPdi@6q)KZ)QdFCo#HLJkBd=DNq?`*)U@wBU6JD z57;2d1)mhQI-cx2DY=q|r^aC$BeRpe^rX5Ol|3HMj2I&o%;cx|^Z4q1X5Ve?-esSe w`>w#WUBEZDx@~o(vuiwqT&+(4&*kI5%*bQ#A<=%y#RjkX4eSi>3I!Jd0GYXpwEzGB literal 0 HcmV?d00001 diff --git a/src/tensors/charges/AbstractCharge.m b/src/tensors/charges/AbstractCharge.m index d48320c..6191400 100644 --- a/src/tensors/charges/AbstractCharge.m +++ b/src/tensors/charges/AbstractCharge.m @@ -343,10 +343,10 @@ Fblocks = cell(length(f), length(e)); for i = 1:length(e) for j = 1:length(f) - Fblocks{j, i} = Fsymbol(a, b, c, d, e(i), f(j)); - Fblocks{j, i} = reshape(Fblocks{j, i}, ... - size(Fblocks{j, i}, 1) * size(Fblocks{j, i}, 2), ... - size(Fblocks{j, i}, 3) * size(Fblocks{j, i}, 4)); + Fblocks{i, j} = Fsymbol(a, b, c, d, e(i), f(j)); + sz = size(Fblocks{i, j}, 1:4); + Fblocks{i, j} = reshape(Fblocks{i, j}, ... + sz(1) * sz(2), sz(3) * sz(4)); end end F = cell2mat(Fblocks); @@ -356,7 +356,7 @@ F = zeros(length(f), length(e)); for i = 1:length(e) for j = 1:length(f) - F(j, i) = Fsymbol(a, b, c, d, e(i), f(j)); + F(i, j) = Fsymbol(a, b, c, d, e(i), f(j)); end end end @@ -389,8 +389,11 @@ if fusionstyle(a) == FusionStyle.Unique d = ones(size(a)); else - F = Fsymbol(a, conj(a), a, a, one(a), one(a)); - d = abs(1 / F(1)); + d = zeros(size(a)); + for i = 1:numel(d) + F = Fsymbol(a(i), conj(a(i)), a(i), a(i), one(a(i)), one(a(i))); + d(i) = abs(1 / F(1)); + end end end @@ -488,8 +491,36 @@ end case FusionStyle.Generic - - error('TBA'); + d = a(1); + N = 1; + for i = 2:length(a) + d_ = d(1) * a(i); + if nargout > 1 + N_(1:length(d_)) = N(1) .* ... + Nsymbol(repmat(d(1), 1, length(d_)), ... + repmat(a(i), 1, length(d_)), d_); + end + + for j = 2:length(d) + c = d(j) * a(i); + d_(end + (1:length(c))) = c; + if nargout > 1 + N_(end + (1:length(c))) = N(j) .* ... + Nsymbol(repmat(d(j), 1, length(c)), ... + repmat(a(i), 1, length(c)), c); + end + end + + if nargout < 2 + d = unique(d_); + else + [d, ~, ic] = unique(d_); + N = zeros(size(d)); + for i = 1:length(N) + N(i) = sum(N_(ic == i)); + end + end + end end end @@ -579,9 +610,8 @@ vertices = []; for f = a(1) * a(2) N = Nsymbol(a(1), a(2), f); - charges = vertcat(charges, ... - repmat([a(1) f], N, 1)); - vertices = vertcat(vertices, (1:N)'); + charges = [charges repmat([a(1); f], 1, N)]; + vertices = [vertices 1:N]; end return end @@ -589,13 +619,11 @@ [chargepart, vertexpart] = cumprod(a(1:end-1)); charges = []; vertices = []; - for i = 1:size(chargepart, 1) - for f = chargepart(i, end) * a(end) - N = Nsymbol(chargepart(i, end), a(end), f); - charges = vertcat(charges, ... - repmat([chargepart(i, :) f], N, 1)); - vertices = vertcat(vertices, ... - [repmat(vertexpart(i,:), N, 1) (1:N)']); + for i = 1:size(chargepart, 2) + for f = chargepart(end, i) * a(end) + N = Nsymbol(chargepart(end, i), a(end), f); + charges = [charges, repmat([chargepart(:, i); f], 1, N)]; + vertices = [vertices [repmat(vertexpart(:, i), 1, N); 1:N]]; end end end diff --git a/test/TestCharge.m b/test/TestCharge.m index a477230..b949d72 100644 --- a/test/TestCharge.m +++ b/test/TestCharge.m @@ -12,7 +12,9 @@ 'U1', U1(-2:2), ... 'O2', O2([0 0 1 2], [0 1 2 2]), ... 'SU2', SU2(1:4), ... - 'Z2xU1', ProductCharge(Z2(0, 0, 0, 1, 1, 1), U1(-1, 0, 1, -1, 0, 1))) + 'A4', A4(1:4), ... + 'Z2xU1', ProductCharge(Z2(0, 0, 0, 1, 1, 1), U1(-1, 0, 1, -1, 0, 1))... + ) end methods (Test) @@ -61,6 +63,14 @@ function Fmove(testCase, smallset) end, end, end end + function qdims(testCase, smallset) + for a = smallset + F = Fsymbol(a, conj(a), a, a, one(a), one(a)); + testCase.verifyTrue(isapprox(qdim(a), abs(1 / F(1))), ... + 'Fsymbol and qdim incompatible'); + end + end + function Funitary(testCase, smallset) % Funitary - Test if the Fsymbols are unitary. % Funitary(testCase, smallset) From 76fda9e93b4cab7efd35a207bb81947bfabf8265 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 23 Jun 2022 01:20:38 +0200 Subject: [PATCH 003/245] minor performance improvements. --- src/tensors/charges/O2.m | 4 ++-- src/tensors/charges/SU2.m | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tensors/charges/O2.m b/src/tensors/charges/O2.m index c45ec88..7c3d7e7 100644 --- a/src/tensors/charges/O2.m +++ b/src/tensors/charges/O2.m @@ -39,8 +39,8 @@ return end - if ~(Nsymbol(a, b, e) && Nsymbol(e, c, d) && ... - Nsymbol(b, c, f) && Nsymbol(a, f, d)) + if ~Nsymbol(a, b, e) || ~Nsymbol(e, c, d) || ... + ~Nsymbol(b, c, f) || ~Nsymbol(a, f, d) F = 0; return end diff --git a/src/tensors/charges/SU2.m b/src/tensors/charges/SU2.m index 33136f2..645721c 100644 --- a/src/tensors/charges/SU2.m +++ b/src/tensors/charges/SU2.m @@ -20,8 +20,8 @@ function a = conj(a) end - function [charges, vertices] = cumprod(a) - [charges, vertices] = cumprod@AbstractCharge(a); + function varargout = cumprod(a) + [varargout{1:nargout}] = cumprod@AbstractCharge(a); end function nu = frobeniusschur(a) From fb09b24bb801ed3c9a6c204a17cefd64c322cb73 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 23 Jun 2022 11:11:52 +0200 Subject: [PATCH 004/245] Minor bugfixes Fmatrix --- src/tensors/charges/AbstractCharge.m | 10 +++++----- src/tensors/charges/O2.m | 6 +++--- test/TestFusionTree.m | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/tensors/charges/AbstractCharge.m b/src/tensors/charges/AbstractCharge.m index 6191400..1122372 100644 --- a/src/tensors/charges/AbstractCharge.m +++ b/src/tensors/charges/AbstractCharge.m @@ -343,10 +343,10 @@ Fblocks = cell(length(f), length(e)); for i = 1:length(e) for j = 1:length(f) - Fblocks{i, j} = Fsymbol(a, b, c, d, e(i), f(j)); - sz = size(Fblocks{i, j}, 1:4); - Fblocks{i, j} = reshape(Fblocks{i, j}, ... - sz(1) * sz(2), sz(3) * sz(4)); + Fblocks{j, i} = Fsymbol(a, b, c, d, e(i), f(j)); + sz = size(Fblocks{j, i}, 1:4); + Fblocks{j, i} = reshape(Fblocks{j, i}, ... + sz(1) * sz(2), sz(3) * sz(4)).'; end end F = cell2mat(Fblocks); @@ -356,7 +356,7 @@ F = zeros(length(f), length(e)); for i = 1:length(e) for j = 1:length(f) - F(i, j) = Fsymbol(a, b, c, d, e(i), f(j)); + F(j, i) = Fsymbol(a, b, c, d, e(i), f(j)); end end end diff --git a/src/tensors/charges/O2.m b/src/tensors/charges/O2.m index 7c3d7e7..bfde591 100644 --- a/src/tensors/charges/O2.m +++ b/src/tensors/charges/O2.m @@ -38,9 +38,9 @@ F = reshape(arrayfun(@Fsymbol, a, b, c, d, e, f), size(a)); return end - - if ~Nsymbol(a, b, e) || ~Nsymbol(e, c, d) || ... - ~Nsymbol(b, c, f) || ~Nsymbol(a, f, d) + + if (~Nsymbol(a, b, e) || ~Nsymbol(e, c, d) || ... + ~Nsymbol(b, c, f) || ~Nsymbol(a, f, d)) F = 0; return end diff --git a/test/TestFusionTree.m b/test/TestFusionTree.m index 00b1279..1553124 100644 --- a/test/TestFusionTree.m +++ b/test/TestFusionTree.m @@ -148,12 +148,12 @@ function braiding(tc) [c1, f1] = braid(f, p.', lvl, [indout, f.legs - indout]); % basic properties - verifyEqual(tc, size(c1), [length(f) length(f1)], ... + assertEqual(tc, size(c1), [length(f) length(f1)], ... 'Coefficients have the wrong size.'); - verifyEqual(tc, full(abs(c1).^2 * qdim(f1.coupled)), ... + assertEqual(tc, full(abs(c1).^2 * qdim(f1.coupled)), ... qdim(f.coupled), 'AbsTol', tc.tol, ... 'Braiding must preserve centernorm.'); - verifyEqual(tc, isallowed(f1), true(length(f1), 1), ... + assertEqual(tc, isallowed(f1), true(length(f1), 1), ... 'Output trees are not allowed.'); % compatible with fusiontensor @@ -167,17 +167,17 @@ function braiding(tc) for k = 1:length(val) a2 = a2 + val(k) * a2_cell{col(k)}; end - verifyEqual(tc, a2, a1, 'AbsTol', tc.tol, ... + assertEqual(tc, a2, a1, 'AbsTol', tc.tol, ... 'Braiding should be compatible with fusiontensors.'); end end % invertible [c2, f2] = braid(f1, invperm(p.'), lvl(p), f.rank); - verifyEqual(tc, c1 * c2, speye(length(f)), ... + assertEqual(tc, c1 * c2, speye(length(f)), ... 'AbsTol', tc.tol, 'RelTol', tc.tol, ... 'Braiding should be invertible.'); - verifyEqual(tc, f2, f, 'Braiding should be invertible.'); + assertEqual(tc, f2, f, 'Braiding should be invertible.'); end end end From b2ef2fc3e2924a26a445e7ae88301192acb061cf Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 23 Jun 2022 11:31:30 +0200 Subject: [PATCH 005/245] Update similar to new syntax --- src/tensors/Tensor.m | 47 ++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index a250f1a..ed06962 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -169,9 +169,7 @@ % input tensors used to copy legs. % % indices : int - % array with length equal to the legs of the tensor, where zero values - % indicate unused legs, and nonzero values indicate the leg of the output - % tensor. + % array of which indices to copy for each input tensor. % % Keyword Arguments % ----------------- @@ -181,7 +179,7 @@ % Conj : logical % flag to indicate whether the space should be equal to the input space, or % fit onto the input space. This can be either an array of size(tensors), or a - % scalar. + % scalar, in which case it applies to all tensors. % % Mode : 'tensor' or 'matrix' % method of filling the tensor data. By default this is matrix, where the @@ -195,8 +193,8 @@ % % Examples % -------- - % :code:`t = similar([], mps, [3 0 0], mpo, [0 0 0 2], mpsbar, [1 0 0], 'Conj', - % true)` creates a left mps environment tensor. + % :code:`t = similar([], mpsbar, 1, mpo, 4, mps, 1, 'Conj', true)` creates a + % left mps environment tensor. arguments fun = [] @@ -213,20 +211,27 @@ kwargs.Mode = 'matrix' end - for i = 1:length(tensors) - inds = find(indices{i} > 0); - if any(inds) - if kwargs.Conj(i) - spaces(indices{i}(inds)) = conj(space(tensors{i}, ... - adjointindices(tensors{i}, inds))); - else - spaces(indices{i}(inds)) = space(tensors{i}, inds); - end + if isempty(fun) + fun = @(dims, charge) randnc(dims); + end + + ntotal = sum(cellfun('length', indices)); + ctr = 0; + for i = length(indices):-1:1 + ctr = ctr + length(indices{i}); + if kwargs.Conj(i) + sp(ntotal - ctr + (1:length(indices{i}))) = ... + conj(space(tensors{i}, indices{i})); + else + sp(ntotal - ctr + (1:length(indices{i}))) = ... + space(tensors{i}, indices{i}); end end - t = Tensor.new(fun, spaces(1:kwargs.Rank(1)), ... - spaces((1:kwargs.Rank(2)) + kwargs.Rank(1))', 'Mode', kwargs.Mode); + assert(sum(kwargs.Rank) == ntotal, 'tensors:ArgumentError', ... + 'Invalid rank specified.'); + t = Tensor.new(fun, sp(1:kwargs.Rank(1)), ... + sp((1:kwargs.Rank(2)) + kwargs.Rank(1))', 'Mode', kwargs.Mode); end end @@ -731,7 +736,7 @@ med = get(cache, key); if isempty(med) med = struct; - med.structure = similar(t, invperm(p), 'Rank', r); + med.structure = similar(@(x,charge) uninit(x), t, p, 'Rank', r); med.map = permute(fusiontrees(t), p, r); cache = set(cache, key, med); end @@ -739,7 +744,7 @@ tdst = med.structure; map = med.map; else - tdst = similar(t, invperm(p), 'Rank', r); + tdst = similar(t, p, 'Rank', r); map = permute(fusiontrees(t.codomain, t.domain), p, r); end @@ -857,11 +862,11 @@ med = get(cache, key); if isempty(med) med = struct; - A_ = similar(A, invperm(iA), 'Rank', rA); + A_ = similar(@(x,charge) uninit(x), A, iA, 'Rank', rA); med.varA = A_.var; med.mapA = permute(fusiontrees(A), iA, rA); - B_ = similar(B, invperm(iB), 'Rank', rB); + B_ = similar(@(x,charge) uninit(x), B, iB, 'Rank', rB); med.varB = B_.var; med.mapB = permute(fusiontrees(B), iB, rB); From 215295b74da798839ff3c4469869e0408af48c95 Mon Sep 17 00:00:00 2001 From: leburgel Date: Thu, 23 Jun 2022 12:02:06 +0200 Subject: [PATCH 006/245] add transfer matrix notebook --- docs/src/conf.py | 8 + docs/src/examples/examples.rst | 1 + docs/src/examples/uniformMps/img/AcPrime2.svg | 149 +++ docs/src/examples/uniformMps/img/CPrime2.svg | 87 ++ docs/src/examples/uniformMps/img/Mexp.svg | 183 +++ docs/src/examples/uniformMps/img/O.svg | 96 ++ docs/src/examples/uniformMps/img/O_Ac.svg | 165 +++ docs/src/examples/uniformMps/img/O_C.svg | 106 ++ docs/src/examples/uniformMps/img/Z.svg | 354 ++++++ docs/src/examples/uniformMps/img/Z2.svg | 114 ++ docs/src/examples/uniformMps/img/channels.svg | 142 +++ docs/src/examples/uniformMps/img/delta.svg | 172 +++ docs/src/examples/uniformMps/img/envNorm.svg | 88 ++ .../examples/uniformMps/img/environments.svg | 203 ++++ .../examples/uniformMps/img/fixedPoint.svg | 190 +++ docs/src/examples/uniformMps/img/ising.pdf | Bin 0 -> 93025 bytes docs/src/examples/uniformMps/img/lambda.svg | 211 ++++ docs/src/examples/uniformMps/img/q.svg | 118 ++ docs/src/examples/uniformMps/img/t.svg | 145 +++ .../examples/uniformMps/img/transferMpo.svg | 136 +++ .../uniformMps/localHamiltonians.ipynb | 58 +- .../uniformMps/transferMatrices.ipynb | 1057 +++++++++++++++++ docs/src/examples/uniformMps/uniformMps.ipynb | 24 +- 23 files changed, 3775 insertions(+), 32 deletions(-) create mode 100644 docs/src/examples/uniformMps/img/AcPrime2.svg create mode 100644 docs/src/examples/uniformMps/img/CPrime2.svg create mode 100644 docs/src/examples/uniformMps/img/Mexp.svg create mode 100644 docs/src/examples/uniformMps/img/O.svg create mode 100644 docs/src/examples/uniformMps/img/O_Ac.svg create mode 100644 docs/src/examples/uniformMps/img/O_C.svg create mode 100644 docs/src/examples/uniformMps/img/Z.svg create mode 100644 docs/src/examples/uniformMps/img/Z2.svg create mode 100644 docs/src/examples/uniformMps/img/channels.svg create mode 100644 docs/src/examples/uniformMps/img/delta.svg create mode 100644 docs/src/examples/uniformMps/img/envNorm.svg create mode 100644 docs/src/examples/uniformMps/img/environments.svg create mode 100644 docs/src/examples/uniformMps/img/fixedPoint.svg create mode 100644 docs/src/examples/uniformMps/img/ising.pdf create mode 100644 docs/src/examples/uniformMps/img/lambda.svg create mode 100644 docs/src/examples/uniformMps/img/q.svg create mode 100644 docs/src/examples/uniformMps/img/t.svg create mode 100644 docs/src/examples/uniformMps/img/transferMpo.svg create mode 100644 docs/src/examples/uniformMps/transferMatrices.ipynb diff --git a/docs/src/conf.py b/docs/src/conf.py index aefa38f..72377ee 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -271,3 +271,11 @@ def linkcode_resolve(domain, info): matlab_src_dir = repo_root primary_domain = 'mat' autoclass_content = 'class' + + +# -- nbsphinx ----------------------------------------------- +nbsphinx_thumbnails = { + 'examples/uniformMps/uniformMps': 'examples/uniformMps/img/leftOrth.svg', + 'examples/uniformMps/localHamiltonians': 'examples/uniformMps/img/2minham.svg', + 'examples/uniformMps/transferMatrices': 'examples/uniformMps/img/Z2.svg', +} diff --git a/docs/src/examples/examples.rst b/docs/src/examples/examples.rst index 70a2f1a..e3623a8 100644 --- a/docs/src/examples/examples.rst +++ b/docs/src/examples/examples.rst @@ -8,3 +8,4 @@ Uniform matrix product states .. nbgallery:: uniformMps/uniformMps uniformMps/localHamiltonians + uniformMps/transferMatrices diff --git a/docs/src/examples/uniformMps/img/AcPrime2.svg b/docs/src/examples/uniformMps/img/AcPrime2.svg new file mode 100644 index 0000000..fa8ba0d --- /dev/null +++ b/docs/src/examples/uniformMps/img/AcPrime2.svg @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/examples/uniformMps/img/CPrime2.svg b/docs/src/examples/uniformMps/img/CPrime2.svg new file mode 100644 index 0000000..82b2e1c --- /dev/null +++ b/docs/src/examples/uniformMps/img/CPrime2.svg @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/examples/uniformMps/img/Mexp.svg b/docs/src/examples/uniformMps/img/Mexp.svg new file mode 100644 index 0000000..2924d25 --- /dev/null +++ b/docs/src/examples/uniformMps/img/Mexp.svg @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/examples/uniformMps/img/O.svg b/docs/src/examples/uniformMps/img/O.svg new file mode 100644 index 0000000..f22d277 --- /dev/null +++ b/docs/src/examples/uniformMps/img/O.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/examples/uniformMps/img/O_Ac.svg b/docs/src/examples/uniformMps/img/O_Ac.svg new file mode 100644 index 0000000..f903e5e --- /dev/null +++ b/docs/src/examples/uniformMps/img/O_Ac.svg @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/examples/uniformMps/img/O_C.svg b/docs/src/examples/uniformMps/img/O_C.svg new file mode 100644 index 0000000..736225e --- /dev/null +++ b/docs/src/examples/uniformMps/img/O_C.svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/examples/uniformMps/img/Z.svg b/docs/src/examples/uniformMps/img/Z.svg new file mode 100644 index 0000000..9f7289e --- /dev/null +++ b/docs/src/examples/uniformMps/img/Z.svg @@ -0,0 +1,354 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/examples/uniformMps/img/Z2.svg b/docs/src/examples/uniformMps/img/Z2.svg new file mode 100644 index 0000000..d9c8205 --- /dev/null +++ b/docs/src/examples/uniformMps/img/Z2.svg @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/examples/uniformMps/img/channels.svg b/docs/src/examples/uniformMps/img/channels.svg new file mode 100644 index 0000000..8c435b6 --- /dev/null +++ b/docs/src/examples/uniformMps/img/channels.svg @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/examples/uniformMps/img/delta.svg b/docs/src/examples/uniformMps/img/delta.svg new file mode 100644 index 0000000..60e7852 --- /dev/null +++ b/docs/src/examples/uniformMps/img/delta.svg @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/examples/uniformMps/img/envNorm.svg b/docs/src/examples/uniformMps/img/envNorm.svg new file mode 100644 index 0000000..df16296 --- /dev/null +++ b/docs/src/examples/uniformMps/img/envNorm.svg @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/examples/uniformMps/img/environments.svg b/docs/src/examples/uniformMps/img/environments.svg new file mode 100644 index 0000000..d6f8cc0 --- /dev/null +++ b/docs/src/examples/uniformMps/img/environments.svg @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/examples/uniformMps/img/fixedPoint.svg b/docs/src/examples/uniformMps/img/fixedPoint.svg new file mode 100644 index 0000000..ee734cd --- /dev/null +++ b/docs/src/examples/uniformMps/img/fixedPoint.svg @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/examples/uniformMps/img/ising.pdf b/docs/src/examples/uniformMps/img/ising.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3d3a1d3575e0cd8dfb2dade0e69638066734bcd6 GIT binary patch literal 93025 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/examples/uniformMps/img/q.svg b/docs/src/examples/uniformMps/img/q.svg new file mode 100644 index 0000000..50ca81c --- /dev/null +++ b/docs/src/examples/uniformMps/img/q.svg @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/examples/uniformMps/img/t.svg b/docs/src/examples/uniformMps/img/t.svg new file mode 100644 index 0000000..662a486 --- /dev/null +++ b/docs/src/examples/uniformMps/img/t.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/examples/uniformMps/img/transferMpo.svg b/docs/src/examples/uniformMps/img/transferMpo.svg new file mode 100644 index 0000000..0f8acda --- /dev/null +++ b/docs/src/examples/uniformMps/img/transferMpo.svg @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/examples/uniformMps/localHamiltonians.ipynb b/docs/src/examples/uniformMps/localHamiltonians.ipynb index 5b668a0..9107f45 100644 --- a/docs/src/examples/uniformMps/localHamiltonians.ipynb +++ b/docs/src/examples/uniformMps/localHamiltonians.ipynb @@ -17,9 +17,9 @@ "source": [ "# Finding ground states of local Hamiltonians\n", "\n", - "This notebook demonstrates how to find the ground state of local one-dimensional Hamiltonians with MPS methods, using the `Tensor` backend. It utilizes the functionalities defined in the notebook on uniform MPS (which should therefore be run before this notebook). Our discussion is based on the fourth and sixth chapters of the [lecture notes on tangent space methods for uniform MPS](https://doi.org/10.21468/SciPostPhysLectNotes.7) by Laurens Vanderstraeten, Jutho Haegeman and Frank Verstraete.\n", + "This notebook demonstrates how to find the ground state of local one-dimensional Hamiltonians with MPS methods, using the `Tensor` backend. It utilizes the functionalities defined in the notebook on uniform MPS (which should therefore be run first to generate the function files called from this notebook). Our discussion is based on the fourth and sixth chapters of the [lecture notes on tangent space methods for uniform MPS](https://doi.org/10.21468/SciPostPhysLectNotes.7) by Laurens Vanderstraeten, Jutho Haegeman and Frank Verstraete.\n", "\n", - "The contents of this notebook mirror that of a tutorial given 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, which can be found [here](https://github.com/leburgel/BadHonnefTutorial).\n", + "The contents of this notebook mirror that of a tutorial given 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, which can be found [here](https://github.com/leburgel/uniformMpsTutorial).\n", "\n", "## 1 Introduction\n", "\n", @@ -969,7 +969,7 @@ "iteration:\t40,\tenergy:\t-1.394092\tgradient norm\t2.4404e-02\n", "iteration:\t60,\tenergy:\t-1.396492\tgradient norm\t1.6779e-02\n", "iteration:\t80,\tenergy:\t-1.397722\tgradient norm\t1.2414e-02\n", - "Time until convergence: 25.7345 s\n", + "Time until convergence: 25.8593 s\n", "Computed energy: -1.398312125810\n", "Optimization using Matlab\"s builtin fminunc:\n", "\n", @@ -978,7 +978,7 @@ "Optimization completed because the size of the gradient is less than\n", "the value of the optimality tolerance.\n", "\n", - "Time until convergence: 68.3774 s\n", + "Time until convergence: 59.9676 s\n", "Computed energy: -1.401346835477\n" ] } @@ -1287,7 +1287,7 @@ " % ----------\n", " % v : :class:`Tensor` (D, d, D)\n", " % Tensor of size (D, d, D)\n", - " % hTilde : np.array (d, d, d, d)\n", + " % hTilde : :class:`Tensor` (d, d, d, d)\n", " % reduced Hamiltonian,\n", " % ordered topLeft-topRight-bottomLeft-bottomRight,\n", " % renormalized.\n", @@ -1414,7 +1414,7 @@ "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." + "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." ] }, { @@ -1487,6 +1487,8 @@ " tol = 1e-3\n", " end\n", " \n", + " tol = max(tol, 1e-14);\n", + " \n", " % calculate left en right environment if they are not given\n", " if isempty(Lh)\n", " Lh = LhMixed(hTilde, Al, C, tol);\n", @@ -1590,6 +1592,8 @@ " tol = 1e-3\n", " end\n", " \n", + " tol = max(tol, 1e-4);\n", + " \n", " % polar decomposition of Ac\n", " [UlAc, ~] = leftorth(AcTilde, [1, 2], 3, 'polar');\n", " \n", @@ -1701,7 +1705,7 @@ ], "source": [ "%%file vumps.m\n", - "function [E, Al, Ar, C, Ac] = vumps(h, D, A0, tol)\n", + "function [E, Al, Ar, C, Ac] = vumps(h, D, A0, tol, tolFactor, verbose)\n", " % Find the ground state of a given Hamiltonian using VUMPS.\n", " %\n", " % Parameters\n", @@ -1743,6 +1747,8 @@ " D\n", " A0 = []\n", " tol = 1e-4\n", + " tolFactor = 1e-1\n", + " verbose = true\n", " end\n", " \n", " % if no initial guess, random one\n", @@ -1755,7 +1761,7 @@ " i = 0;\n", " \n", " % go to mixed gauge\n", - " [Al, Ar, C, Ac] = mixedCanonical(A0, [], [], delta/100);\n", + " [Al, Ar, C, Ac] = mixedCanonical(A0, [], [], delta*tolFactor^2);\n", " while flag\n", " i = i + 1;\n", " \n", @@ -1784,8 +1790,10 @@ " Al = AlTilde; Ar = ArTilde; C = CTilde; Ac = AcTilde;\n", " \n", " % print current energy\n", - " E = real(expVal2Mixed(h, Ac, Ar));\n", - " fprintf('iteration:\\t%d,\\tenergy:\\t%.12f\\tgradient norm\\t%.4e\\n', i, E, delta)\n", + " if verbose\n", + " E = real(expVal2Mixed(h, Ac, Ar));\n", + " fprintf('iteration:\\t%d,\\tenergy:\\t%.12f\\tgradient norm\\t%.4e\\n', i, E, delta)\n", + " end\n", " end\n", "end" ] @@ -1817,15 +1825,15 @@ "iteration:\t6,\tenergy:\t-1.401307669639\tgradient norm\t1.6703e-02\n", "iteration:\t7,\tenergy:\t-1.401343224227\tgradient norm\t1.5580e-02\n", "iteration:\t8,\tenergy:\t-1.401367665850\tgradient norm\t1.3053e-02\n", - "iteration:\t9,\tenergy:\t-1.401379018822\tgradient norm\t7.3319e-03\n", - "iteration:\t10,\tenergy:\t-1.401380421344\tgradient norm\t2.4273e-03\n", - "iteration:\t11,\tenergy:\t-1.401380612479\tgradient norm\t8.6638e-04\n", - "iteration:\t12,\tenergy:\t-1.401380639024\tgradient norm\t3.0713e-04\n", - "iteration:\t13,\tenergy:\t-1.401380642830\tgradient norm\t1.1264e-04\n", - "iteration:\t14,\tenergy:\t-1.401380643411\tgradient norm\t4.1935e-05\n", + "iteration:\t9,\tenergy:\t-1.401379016824\tgradient norm\t7.3319e-03\n", + "iteration:\t10,\tenergy:\t-1.401380585597\tgradient norm\t2.4268e-03\n", + "iteration:\t11,\tenergy:\t-1.401380952944\tgradient norm\t8.6606e-04\n", + "iteration:\t12,\tenergy:\t-1.401380942432\tgradient norm\t3.0612e-04\n", + "iteration:\t13,\tenergy:\t-1.401380781660\tgradient norm\t1.1279e-04\n", + "iteration:\t14,\tenergy:\t-1.401380741714\tgradient norm\t4.2223e-05\n", "\n", - "Time until convergence: 4.0988 s\n", - "Computed energy: -1.401380643411" + "Time until convergence: 3.4687 s\n", + "Computed energy: -1.401380741714" ] } ], @@ -1861,7 +1869,7 @@ "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjAAAAGkCAIAAACgjIjwAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH5gYUCgYgPFFlzwAAACR0RVh0U29mdHdhcmUATUFUTEFCLCBUaGUgTWF0aFdvcmtzLCBJbmMuPFjdGAAAACJ0RVh0Q3JlYXRpb24gVGltZQAyMC1KdW4tMjAyMiAxMjowNjozMqRWY1gAACAASURBVHic7d1/VFR1/sfxC8NIiuJBFEuTypTBI1qk6Fr++OpiVuaWmqvb6mqmnkXTLCX1rLkp1prZumc7tEZqK9uubWlmYJqzEmrij9ZQQwwGRw8CFmpmSDo7v75/3NM90wDjMDPM/dzr8/HXcOfO/bzvnXvvi3vv596JcLvdEgAAaotUuwAAACSJQAIACIJAAgAIgUACAAiBQAIACIFAAgAIgUACAAiBQAIACCFK7QJE5HA4rl+/3uhbUVFRt9xyS5jrCdJnn32Wk5MjSdKgQYPmzZundjnQht27d2/duvWHH36QJCkqKuof//iH2hW1lFdfffXYsWOSJM2bN2/QoEFql3NTI5AakZ+fP3bs2EbfGj16dH5+vp/TKS0tffXVVyVJioqK2rBhQ8jqayar1free+/JrzUUSIIsvWbRYs2NWr58+UsvvaT8GR0dreNA2r9//44dOyRJevzxxwMLpGC+d92sMyFBILWgmpqa3NxcSZKio6NZ1ZpLi0tPizU35HK5Xn75Zfn1s88+O3DgwKgodhS+BPO962OdCRXWsxuQ1xXF7bff3tSYLpdLkqTIyOZdlnM4HJGRkf58KrDp+zNZl8vlzx7H4XB4jXbDkvyfeKjmzs8WQ1V5w2XivyBnOYCvo2EBjc6jw+Gw2+3y6z//+c8hX+Wkxkr1f1UJuAn/3/UxzYCL9H9LD227GuNGA9u2bbvh8nn33Xe7dOnSpUuXjIyMQ4cODRgwQJIkg8EwePDg06dPu93uyZMnx8XFKdPp8pMjR44cOnTo4YcfbteuncFgkN/t2LHjtGnTKisr/Z++4vjx40OHDjUYDAaDYejQoSdPnpw8ebL82e3bt7vd7vXr18utTJo0SflUXV3d0qVL77zzTvmt1q1bz5gxo6qqShlh48aNSgE7duzo3r27JEkxMTErVqxwu93//e9/+/XrJ5c0atSompoaz5JuOPFgll6jX8eVK1eee+65hIQEeXyDwZCYmDhhwoSysjKv5goLC1NTU5XKvRbmDSt3u93Xrl1buXKlyWSSvz6DwZCamlpYWOi7Zs8aioqK5BrS09MzMzPl4ZmZmUoT/fr1kwcWFBQE/3U0XFaLFi1KTEyU64yOjn7ssceKi4vldzMyMrp06eI1F5MnT25qajdc/XzM+w2Lcbvdfi6fZm0v999/v/Lu8ePHR48eLbf+3nvvNbXEfKxdwWzpvtcZf9ZGnSGQGuEZSHU/Z7fb5XGUvbzJZIqOjpY89OrVy+12K2u5l3379ilXdLwkJCRcvHjRz+nLLBZL+/btPd+Ni4vr3bu35wbWMJBqa2t79erVaAEWi0UeJzs7Wx7Ys2dPr9GmT5/erl07zyH333+/UpI/Ew9m6TX6lTV1zc9sNns217NnT6PR6DlCly5dlGXuT+XfffedvD/1sn79et81KzXcc889MTEx8uv09PQZM2bIr2fMmKHMjhIJO3bsCPLr8FJbW6vs4Ly8//77brd70qRJDd8aPXp0o1PzZ/XzMe83LMbtdvu5fILZXpQvvalA8r12BbOl+/isP2uj/tDt+wba/dzmzZu9RigrKxs0aNDWrVuXLVsmDzl16tTevXsXLFiwaNEieYjRaMz9SVJS0u233/7GG2+cPHnS6XQ6nc5Tp07J+7ja2lplu7rh9OXXCxcuvHLliiRJiYmJ27dv37lz5y9+8YuTJ0/6nqnFixefOnVKkqRRo0ZVVVXZ7Xa51Nra2qlTp3qNbLFYJkyYsG3btjFjxshDNm7cGB8f/957761atUoeUlRUVF5eHsDEA1h6DWfH5XJ9/PHHkiT17t1bPji4du1aXl7e3LlzvfbUFotl4sSJn3766bvvviv/w1tTU/OnP/3J/8oXLlxYXFwsSVK7du3Wrl1bWFhoNptXrFgRGxvrZ83Hjx+PiYlZunTp2rVrR4wY4eNralRzvw4vCxcuPHv2rPTTCmM2m5UEevrppy9cuDB79uyNGzcq48uzsGDBgqam1qzVz2veb1iM/4tF4Xt7efHFFxsWLH/pTbnh2hXMlu7js83ajvRD7UQUkecRkpfc3Fx5HCU54uLi6uvr5YF9+vSRB/7rX/9yu91ms1n+Mzo6umErV65c2blz544dO3bs2PHcc8/JYyoHMf5M3263K6cCtm3bpkxWOQho9AjJbrcrI5w9e1apR/m/Uj4RofxLfuuttzqdTrfbLfdEksn/G7rdbuWimnx+xs+JB7/0PDmdTnk5dOnSZevWradOnZILVijNJSYmKgM9Z9DPyj3HeeuttxpW4qNmpQaDweD5H65yBDBr1ixloI8jpGZ9HV4861dWGKfTeeutt8oD5dXbZrMpU/ax2P1c/Zqadz+L8XP5+LNGOZ1OpUXlCKzRgj3dcO1yB7elN/pZP7cj/eEI6QZyf+6BBx7wGmHEiBFt2rSRXyunwh0Oh49pWq3WIUOGtG/f/uGHHx49evTo0aPXrl0rv+W5L7jh9CsrK51OpzzkoYcekl/ExsY2ek5Jcfz4ceWS9Z133hnxE6Xpr7/+2nP8IUOGyFdiPY82lH/tk5OT5RfyVeLmTjyApddQZGTko48+KklSTU3N+PHje/Xq1apVqyFDhqxbt85rzIEDByqvlQOXb775xuFw+FO55zi/+93vmlWk4sEHH+zRo0fD4V7nEpvSrK/DS3FxsVL/I488Ir+IjIxUFsvnn3/uTw2yAFY/z3lvbjF+Lh8fa1R1dbXSonJ8GRsb27dvXx8T9H/taqhZW7qn5m5HunETdNsIzpQpU3yP4Od24mnkyJFWq1WSpF69eg0aNCgyMrKioqKwsLC501f+P/Wi7CYa5XnPr9fZdv9baaqzUHMnHsDSa1Rubu6KFSs2b95cU1MjSZLT6fz8888///zzy5cvL1myxJ+m/ancc5yA+555XcNQeEbItWvXmvp4s74OL03VH1j3rQBWP895b24xfi4fH2tUfX19o8OVy1pNCWDtkjVrS/fU3O1INzhCCrfS0lJ5HTUYDEeOHNmwYcPbb789dOjQACbVrVs3ZfPbvn27/OLbb7+VbztvSlpamrIrOXfu3PUGfvWrXwVQTBgm7kNsbOyaNWuqq6ttNltBQYFyzUM++68oKSlRXldVVckvYmJioqKi/Kncc5xdu3aFdhaU3eW3334rPx8h5DwPEA8ePKi8lq+KSZJ0zz33+D+1wFa/gIsJfvl0795d+fqUFh0Ox+HDh31/0M+1y0swW7pa25HqCKQbmPlzs2fP9v+zyhZls9neeeedTz75ZPfu3Z7/6NXW1kqSVFFRIT/ap7kiIyMnTpwov37++ec3bdr00UcfjRs3zvcRUqtWrdLT0+XXc+bMUf4X++GHH955553hw4cHUElLTLzRpdfUyC+++OIXX3whFzB8+PDHHntMHu71r/SpU6eWL1/ucDi+/vrr5cuXywMnTJjgZ+WtWrVSzk1lZGR89tlnkiQ5HI4PP/xQvmzerJplSp+0Xbt2HTx4sLS0dPLkyb6/wYB5zuMLL7xw/vx5l8u1fPlyZb/ZVKevRgW2+jW3mBAuH/lUm/x68eLF8o5+zpw5vs+eSX6sXcFs6Y1+tkU3UqGpfRErEOXl5cuWLcvMzNy9e3dLTN9HpwblwmOjN/co27PS98FkMnl+vF27dm63W76JRJIko9GYkJAg39YgDxk7dmyzpl9ZWancHiFLSEhQVvGmun1bLBblU3INys0QHTt2lMdRrqIrn9q3b1/D1UbZbJTr0v5MPJil1yj5gkp0dHRCQkLHjh2V/y6XLl3q2ZzXspKnqVxm96fyhgtctn79et81NzrLbrf77NmzXue+Wrdu3bp1a/l1w04Nzf06vJSVlXne+OLplVdekcfxs1NDo0uj4ernY979KcbP5ePnGnXkyJGGZxqVniBNdfv2vXbJAt7Sm/qsP2uj/mjvCKmsrGz8+PGdO3fu16/fihUrNm3apHZFvnz44Yfp6eleZ4G3b98u32Fgt9svXbr0+9//fuXKlYFNv1u3bkeOHBk7dmx0dHR0dPT48eOPHDminIVv6tJCjx49jhw5MmHCBIPBYLfba2trL1++LElSnz59gn/YXQgn3ujSa9SIESOio6NtNlttbe3FixedTmdMTExmZqZyGKSM9tZbbynXDBITE3fs2KFcZven8m7dun355ZeTJ0/2vFZhNBpvu+225tYsu+OOOzZt2qSUdOedd3766adN7aaDl5SUJN+t6blf7t69e25uru/LIY0KbPVrVjGhXT5paWkffvihsqNPSEjYvn37DU9U+rN2BbOlN/rZFt1IhRXhdrvVrqF5Zs2a1b1798WLF0uStHfv3mefffbo0aNNXV8VWUlJyTfffDN48OAgHx/ucrk8t/zz589369ZNPqfx1VdfpaSk+P7sF198UVdX1759+9TU1NA+m6RFJ96oS5cuyVcgbr/99qSkJGWxbNiwQe49PGnSpM2bN7tcrgMHDrRv376p7lX+VO5yuY4ePXrlyhWvtgLjcDgOHDgQFxfnu8dXCP3vf/87ePCg3W7v3bu3kqYBCGb187+Y0C4fl8slXzcaOHCg/19cU2vXDQW5pYd/O1KR9gKpd+/eb7755rBhwyRJcrlcvXv3zsnJUU4N34RSUlKmTJnSp0+fqKioCxcuZGVllZWVSZI0ePDg/fv3q12dELwCSe1ydIXVDyGksbC9du2aw+G444475D8jIyPbtGnTQl2StKKiokI+XvRkMpmaemwJEEKsfgghjQWSfDzneR3VaDS2UJckrVizZs2+ffsqKirOnTt32223JScnjxo1asqUKa1atVK7NFHccccd8uPI0tLS1K5Fb1j9EEIqnLJzOp1FRUVWq7WkpMThcKxatcrrap7NZlu3bl1BQcHVq1eHDRuWkZHRqVMn+S273Z6SkvLPf/6zf//+8pB77733tddeGzlyZJjnAgAQWiocIR0+fHjGjBlGo7Fz585VVVXKT4EpZs2aVVpampGRERMTk5OTU1hYmJ+fLz8OxGg0dunSRb5fWpKkCxcuXLt2rdEHsQAAtEWFbt8mkyk/P7+kpGTu3LkN3927d++hQ4dWr149ffr0iRMn5ubmnj9/3vOHFMeOHbthwwb5Vol169alpqbedddd4aseANAyVDhCio+Pj4+Pb+rdvLy82NhY5Vbkrl27DhkyZNeuXUp6ZWRklJWVpaWltWvXLjY2NrBnHAAARCNcp4aKigqvK8+JiYn79+9XbncwGo3Z2dk//PDDlStXunXr5mNSnvc/y11RAQDCEi6QKisrvX4TMzk52eVy2Ww25XkhkiTFxsbGxsbecGrkEABohYiPDoqIiGg4UHM38AIAmkW4QGrbtq3nb4FIPz0o96b6URAAuAkJF0h9+vQ5c+aM5xCr1RoXF6fFp9UBAPwnXCANGDCgvLy8urpa/tPpdB44cCCw368DAGiIOoFkNpvNZrP885179uwxm83Kr0yOGzeuQ4cOmZmZNTU1dXV1S5Ys+f7772fOnBlYQ14/NAIAEJY6T/tumBPp6enKT5CdOHFi/vz58kFSXFxcVlZWYE8GMplM9LIDAK0Q9+cnLBZLfX193759A/6lGQIJADREuPuQFF53IwEA9E24Tg0AgJsTgQQAEAKBBAAQAoEEABCCzgOJ+5AAQCt0Hkh0+wYArdB5IAEAtIJAAgAIgUACAAiBQApKxIICXbYFAOFHIAXF/foIr5xoudgIZ1sAEH4EUrA8cyJiQYH79RH6aAsAwkzcp30HT74JKTw9v+WcCE9ChLMtAAgbnR8hcR8SAGiFzgMpPOSzZw2v8Wi9LQAIJwIpWJ7Xclo6J8LZFgCEmc6vIbX0Kbtw9iygFwMAfSOQAABC4JQdAEAIBBIAQAgEEgBACAQSAEAIOg8kfjEWALRC54FELzsA0AqdBxIAQCsIJACAEAgkAIAQCCQAgBAIJACAEAgkAIAQCCQAgBAIJACAEAgkAIAQdB5IPDoIALRC54HEo4MAQCt0HkgAAK0gkAAAQiCQAABCIJAAAEIgkAAAQiCQAABCIJAAAEIgkAAAQiCQAABCIJAAAEIgkAAAQiCQAABC0Hkg8bRvANAKnQcST/sGAK3QeSABALSCQAIACIFAAgAIgUACAAiBQAIACIFAAgAIgUACAAiBQAIACIFAAgAIgUACAAiBQAIACIFAAgAIgUACAAiBQAIACIFAAgAIgUACAAhB54HEL8YCgFboPJD4xVgA0AqdBxIAQCsIJACAEAgkAIAQCCQAgBAIJACAEAgkAIAQCCQAgBAIJACAEAgkAIAQCCQAgBAIJACAEAgkAIAQCCQAgBAIJACAEAgkAIAQCCQAgBAIJACAEAgkAIAQCCQAgBAIJACAEAgkAIAQCCQAgBAIJACAEAgkAIAQdB5IJpNJ7RIAAH7ReSCVlZWpXQIAwC86DyQAgFYQSAAAIRBIAAAhEEgAACEQSAAAIRBIAAAhEEgAACEQSAAAIRBIAAAhEEgAACEQSAAAIRBIAAAhEEgAACEQSAAAIRBIAAAhEEgAACEQSAAAIRBIAAAhEEgAACEQSAAAIRBIAAAhEEgAACEQSAAAIRBIAAAhEEgAACEQSAAAIRBIAAAhEEgAACEQSAAAIRBIAAAhEEgAACEQSAAAIUSpXUAgXC7Xl19+WVVV5XQ6x48fr3Y5AIAQ0GQgLVu2bOfOnXfffXdpaSmBBAD6oMlTdn/84x+PHj06Z84ctQsBAISMJgPJaDSqXQIAIMQ0GUgAAP3RwDUkl8vldDrl1xwbAYBehSmQnE5nUVGR1WotKSlxOByrVq2Kjo72HMFms61bt66goODq1avDhg3LyMjo1KmT/Nbu3bsXLlwovy4uLiaTAECXwhRIhw8fnjFjhtFo7Ny5c1VV1csvv+w1wqxZs0pLSzMyMmJiYnJycgoLC/Pz89u0aSNJ0kMPPfTQQw+Fp04AgFrCdA3JZDLl5+eXlJTMnTu34bt79+49dOjQ6tWrp0+fPnHixNzc3PPnz2/YsKGpqblcLrvd7nA4JEmy2+12u70FSwcAhEWYAik+Pr5nz55NvZuXlxcbGzt8+HD5z65duw4ZMmTXrl1Njb9z586UlJTZs2fb7faUlJSUlJSmMsn0kyDrBwC0NCE6NVRUVKSlpXkOSUxM3L9/v8vlioxsJDJHjx49evRof6ZcVlYWmhIBAC1MiG7flZWVMTExnkOSk5NdLpfNZlOrJABAmAkRSJIkRURENBzodrvDXwkAQBVCBFLbtm2vX7/uOaS2tlaSJK+u4QAAHRMikPr06XPmzBnPIVarNS4uzmAwqFUSACDMhAikAQMGlJeXV1dXy386nc4DBw4MHTpU3aoAAOEUvkAym81ms7mkpESSpD179pjN5mPHjslvjRs3rkOHDpmZmTU1NXV1dUuWLPn+++9nzpwZfKN0+AYArYgIW8eBhtmQnp6enZ0tvz5x4sT8+fPlg6S4uLisrKyRI0cG3yLdvgMTsaDA/foI2tJWc4DWhS+Q/GGxWOrr6/v27dvo7UfNRSAFw2tn2qL7Vr22Ff7mAE0TK5BCi0AKkrL3DMNuVK9thb85QLsIJPgSsaBAkqTw7Eb12lb4mwM0SohedgAAEEhoknyKyf36CPkffNrSRHOAduk8kOj2HTDPCx4tvTPVa1vhbw7QNK4hoRF67YpNt29AZAQSAEAIOj9lBwDQCgIJACAEAgkAIAQCCQAgBAIJACAEAgkAIASdBxI3xgKAVug8kLgPCQC0QueBBADQCgIJACAEAgkAIAQCCQAgBAIJACAEAgkAIAQCCQAgBJ0HEjfGAoBW6DyQuDEWALRC54EEANAKAgkAIAQCCQAgBAIJACAEAgkAIAQCCQAgBAIJACAEAgkAIAQCCQAgBAIJACAEnQcSz7IDAK3QeSDxLDsA0AqdBxIAQCsIJACAEAgkAIAQCCQAgBAIJACAEAgkAIAQCCQAgBAIJACAEAgkAIAQCCQAgBAIJACAEAgkAIAQdB5IPO0bALRC54HE074BQCt0HkgAAK0gkAAAQiCQAABCIJAAAEIgkAAAQiCQAABCIJAAAEIgkAAAQiCQAABCIJAAAEIgkAAAQiCQAABCIJAAAEIgkAAAQiCQAABCIJAAAELQeSDxi7EAoBU6DyR+MRYAtELngQQA0AoCCQAgBAIJACAEAgkAIAQCCQAgBAIJACAEAgkAIAQCCQAgBAIJACAEAgkAIAQCCQAgBAIJACAEAgkAIAQCCQAgBAIJACAEAgkAIAQCCQAgBAIJACAEAgkAIAQCCQAgBAIJACAEAgkAIAQCCQAgBJ0HkslkUrsEAIBfdB5IZWVlapcAAPCLzgMJAKAVBBIAQAgEEqAHEQsKdNkWbioEEqAH7tdHeOVEy8VGONvCTYVAAnTCMyciFhS4Xx+hj7Zw84hwu91q19BSTCYTvexws5FzIjwJEc62cDPgCAkAIAQCCdAP+exZw2s8Wm8LNwkCCdAJz2s5LZ0T4WwLNw+uIQF6EM6eBfRiQAshkAAAQuCUHQBACAQSAEAIBBIAQAgEEgBACAQSAEAIBBIAQAgEEgBACAQSAEAIBBIAQAgEEgBACAQSAEAIBBIAQAgEEgBACAQSAEAIBBIAQAgEEgBACAQSAEAIBBIAQAgEEgBACAQSAEAIBBIAQAhRahcQCIvFYjabz5w506ZNm8cee+y+++5TuyIAQLA0eYT05JNPnjlzZuDAgUajccqUKdu2bVO7IgBAsDR5hLRnz57Y2Fj5dfv27d98882xY8eqWxIAIEiaPEJS0kiSpISEBIfDoWIxAICQ0GQgKex2e25u7rhx49QuBAAQLA2csnO5XE6nU35tNBo931qwYEHHjh1nz56tRl0AgFAKUyA5nc6ioiKr1VpSUuJwOFatWhUdHe05gs1mW7duXUFBwdWrV4cNG5aRkdGpUyf5rd27dy9cuFB+XVxcrGTSwoULa2trN27caDAYwjMXAICWE+F2u8PQTFFR0VNPPWU0Gjt37lxVVVVcXNymTRvPEaZOnVpaWpqRkRETE5OTk+N2u/Pz873G8fTCCy+Ul5fn5uZ6Xk/yYjKZysrKQjkbAIAWE6ZrSCaTKT8/v6SkZO7cuQ3f3bt376FDh1avXj19+vSJEyfm5uaeP39+w4YNTU1t6dKlX3311dtvv926dWu73W6321uydgBAOITplF18fHx8fHxT7+bl5cXGxg4fPlz+s2vXrkOGDNm1a1ej6SVJ0gcffCBJ0uDBg+U/jUZjSUlJqEsGAISVEJ0aKioq0tLSPIckJibu37/f5XJFRjZyDOf/iTiTydTcjwAAVCFEIFVWVvbs2dNzSHJyssvlstlsrVu3DmbK5BAAaIUo9yFFREQ0HBieDhcAABEIEUht27a9fv2655Da2lpJkry6hgMAdEyIQOrTp8+ZM2c8h1it1ri4OG4wAoCbhxCBNGDAgPLy8urqavlPp9N54MCBoUOHqlsVACCcwhdIZrPZbDbL/bP37NljNpuPHTsmvzVu3LgOHTpkZmbW1NTU1dUtWbLk+++/nzlzZthqAwCoLkxPapA8emAr0tPTs7Oz5dcnTpyYP3++fJAUFxeXlZU1cuTIkLRIRzsA0ITwBZI/LBZLfX193759G739qLl4dBAAaIgQ9yEpvO5GAgDcPITo1AAAAIEEABACgQQAEAKBBAAQgs4DqWFfcwCAmHQeSHT7BgCt0HkgAQC0gkACAAiBQAIACIFAAgAIgUACAAiBQAIACEHngcR9SACgFToPJO5DAgCt0HkgAQC0gkACAAiBQAIACIFAAgAIgUACAAiBQAIACIFAAgAIgUACAAhB54HEkxoAQCt0Hkg8qQEAtELngQQA0AoCCQAgBAIJACAEAgkAIAQCCQAgBAIJACAEAgkAIAQCCQAgBAIJACAEnQcSjw4CAK3QeSDx6CAA0AqdBxIAQCsIJACAEAgkAIAQCCQAgBAIJACAEAgk7dFrX3a9zpek31kLw3xFLCho6SYatqWz+ZLCO2vBIJAAiMv9+givfXfL7cr12lb4mwsYgQRAaJ4704gFBe7XR9CW+M0FJsLtdqtdQ0sR/OAUgP/KH/2bJElJ+Rm0FXxzYqaRJElRahfQgnhMA6Ab8n/34dmo9dqWJOqZOgWn7ACITj7F1PBCCG2J3FwACCQAQvO84NHSO1O9thX+5gKj52tIALQunJff9dpW+JsLGIEEABACp+wAAEIgkAAAQiCQAABCIJAAAELQ4Y2xNptt3bp1BQUFV69eHTZsWEZGRqdOndQuKlgWiyUvL6+kpKS0tLRHjx4DBw58+umn27Rpo3ZdIbZt27Z9+/b179//t7/9rdq1hIbFYnn77bdPnDgRERGRnJz8zDPP3H333WoXFSyLxbJx48Zjx47V19enpqY++eSTAwcOVLuoQDidzqKiIqvVWlJS4nA4Vq1aFR0d7TmCRncmvudL5J2JDnvZTZ06tbS0NCMjIyYmJicnx+125+fnC7K4AzZr1qzTp0/ff//999xzz/Hjx7ds2WIymT744AOj0ah2aSFz9uzZxx9/3GazjRkzZvXq1WqXEwKfffbZM888YzKZHnnkkcjIyK+++mrEiBFjxoxRu66gnD59esKECZ07d542bVp0dPT7779/9OjRnJycYcOGqV1asxUVFT311FNGo7Fz585VVVXFxcVeOwqN7kx8z5fQOxO3vhQWFiYlJRUUFMh/VlVVJScn//Wvf1W3quCVl5d7/rl9+/akpKT8/Hy16mkJv/nNb/7yl7/07t07MzNT7VpC4LvvvrvvvvsWLFigdiEhlpWVlZycXFtbK//pcDj+7//+LyMjQ92qAnPx4kV5y9q2bVtSUlJ9fb3nu9rdmfieL5F3Jnq7hpSXlxcbGzt8+HD5z65duw4ZMmTXrl3qVhW8nj17ev758MMPS5JUWlqqUjmht2nTpu+++2727NlqFxIyW7du/fHHBAt/HwAABNxJREFUHxctWqR2ISF29epVg8GgnLkyGAx33XWXw+FQt6rAxMfHe21ZnrS7M/E9XyLvTPQWSBUVFWlpaZ5DEhMTrVary+VSq6SWcPToUUmS7r33XrULCY3q6uq1a9euXLlSiJMGIVJcXCz/c5qVlTVv3rysrCyLxaJ2USHw61//2m63K/vlkydPHj58+IEHHlC3qpbAziT89BZIlZWVMTExnkOSk5NdLpfNZlOrpJC7du3a8uXLe/To8ctf/lLtWkLjD3/4w6OPPtq/f3+1Cwml8vJyh8Mxbty4ixcvxsXFHTp06PHHHz906JDadQXrvvvuy8nJeeWVV8aMGfPEE09MmzZtyZIlU6dOVbuu0GNnEn467GUXERHRcKBbL303XC7XvHnzLl++vHnz5shIPfw/8e9///v06dNvvPGG2oWEWF1d3eXLlxctWjR9+nRJkmw227hx45YvX75z5061SwvKpUuXsrOzjUbjgw8+GBMT88knn2zcuDEtLU2XPz/GziTMhCgihNq2bXv9+nXPIbW1tZIkefXm1K65c+d++eWXOTk5d911l9q1hMCFCxdeffXVxYsX33LLLXa73W63S5LkdrvtdrvWT4zI3bunTJki/xkdHT127Fir1frjjz+qWlew1qxZc+7cuS1btsydO3f69Olbtmzp0KHDCy+8oHZdocfOJPz0Fkh9+vQ5c+aM5xCr1RoXF2cwGNQqKYSeffbZgwcPrl+/vm/fvmrXEhoWi6W+vv75559P+Yndbv/4449TUlL279+vdnVBue222yIjIz2visXHx0uSdOnSJfWKCoHCwsIBAwbExcUpQ4YOHfr11187nU4Vq2oJ7EzCT2+n7AYMGPCf//ynurq6a9eukiQ5nc4DBw4MHTpU7bpC4Lnnntu7d+/69etTU1PVriVkevXqlZOT4zlk9uzZ/fv3nz59ekpKilpVhcQDDzyQl5dnsViUTk1HjhyJioqS10ztSkhIOH/+vOeQc+fORUZGNnp2S9PYmYSf4aWXXlK7hlC6++67t2zZ8sUXXwwaNEiSpGXLlp04cWLNmjXyP6fatXLlyq1bt06aNKljx47Wn9hsNk3cN+5D69at7/y5v/3tb6mpqTNnzhT/9kPfkpKS8vLy9u3bN2DAgPbt23/00UfZ2dlTpkzR+h7t8uXL27dvt9ls9957b2Rk5AcffLB+/fpRo0bJvYc1x2w2W63W4uLiEydO9OzZs7Kysq6u7tZbb5U0vjPxMV8i70x0+KSGEydOzJ8/v7q6WpKkuLi4rKyskSNHql1UsKZNm3bw4EGvgRMmTFi5cqUq9bSclJSURx55RB9Paqiurp4zZ86pU6ckSYqMjHziiSdeeuklHZzwee211/7+978r9x6NGjVq5cqVsbGx6lYVmIZ9MdLT07Ozs+XX2t2Z+JgvkXcmOgwkmXxxom/fvoL0HsFNq7q6+ty5c6mpqbq5GC5JksvlOn78uM1m69evn57uHmsUO5Ow0W0gAQC0hcAHAAiBQAIACIFAAgAIgUACAAiBQAIACIFAAgAIgUACAAiBQAIACIFAAgAIgUACAAiBQAIACIFAAgAIgUACAAiBQAIACIFAAgAIgUACAAiBQAIACIFAAgAIgUACAAiBQAIACIFAAgAIgUACAAiBQAIACOH/AfMYud3dH/qtAAAAAElFTkSuQmCC\n", + "image/png": "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\n", "text/plain": [ "" ] @@ -2017,7 +2025,7 @@ " Vl = leftnull(Al, [1, 2], 3);\n", " \n", " % solve eigenvalue problem\n", - " x0 = Vl.randnc([Vl.dims(3), Al.dims(3)], 'Rank', [1, 1]);\n", + " x0 = similar([], Vl, 3, Al, 3, 'Conj', [true, false], 'Rank', [1, 1]);\n", " [x, e] = eigsolve(@ApplyHeff, x0, num, 'smallestreal', 'Tol', tol);\n", " e = diag(e);\n", " \n", @@ -2097,12 +2105,12 @@ ] }, { - "cell_type": "code", - "execution_count": null, - "id": "50d79755-67c1-48de-99ef-aeba8af6d627", + "cell_type": "markdown", + "id": "be5d7a90-0079-40e8-95e9-5230721fce52", "metadata": {}, - "outputs": [], - "source": [] + "source": [ + "[thumbnail](img/2minham.svg)" + ] } ], "metadata": { diff --git a/docs/src/examples/uniformMps/transferMatrices.ipynb b/docs/src/examples/uniformMps/transferMatrices.ipynb new file mode 100644 index 0000000..cbcd3d4 --- /dev/null +++ b/docs/src/examples/uniformMps/transferMatrices.ipynb @@ -0,0 +1,1057 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "ccc8c998-7e77-4be2-be93-5b741a361dcd", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "addpath(genpath('../../../../src'))" + ] + }, + { + "cell_type": "markdown", + "id": "0a3055a2-cbfd-4d7e-8c2a-256db03a72a5", + "metadata": { + "tags": [] + }, + "source": [ + "# Transfer matrices and fixed points\n", + "\n", + "This notebook demonstrates how to find fixed points of infinite one-dimensional transfer matrices with MPS methods, using the `Tensor` backend. It utilizes the functionalities defined in the notebooks on uniform MPS and local Hamiltonians (which should therefore be run first to generate the function files called from this notebook). Our discussion is based on the seventh chapter of the [lecture notes on tangent space methods for uniform MPS](https://doi.org/10.21468/SciPostPhysLectNotes.7) by Laurens Vanderstraeten, Jutho Haegeman and Frank Verstraete.\n", + "\n", + "The contents of this notebook mirror that of a tutorial given 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, which can be found [here](https://github.com/leburgel/uniformMpsTutorial).\n", + "\n", + "## 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", + "
\"transferMpo\"/
\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", + "
\"fixedPoint\"/
\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", + "
\"lambda\"/
\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", + "
\"channels\"/
\n", + "\n", + "The corresponding fixed points $F_L$ and $F_R$, also referred to as the left and right *environments*, satisfy\n", + "\n", + "
\"environments\"/
\n", + "\n", + "and can be normalized such that\n", + "\n", + "
\"envNorm\"/
\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", + "$$ \\Lambda = \\lim_{N \\to \\infty} \\lambda^N ,$$\n", + "\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", + "\n", + "## 2 The VUMPS algorithm for MPOs\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", + "$$\n", + "\\mathcal{P}_A \\left( T(O) \\left| \\Psi \\right \\rangle - \\Lambda \\left | \\Psi \\right \\rangle \\right) = 0,\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", + "$$\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", + "
\"AcPrime2\"/
\n", + "\n", + "and\n", + "\n", + "
\"CPrime2\"/
\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", + "id": "93d3938d-aacf-4207-9b92-2447fd3a940d", + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "fb933fd6-4bce-4361-80c5-c7cef6d71078", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Created file '/home/leburgel/git/TensorTrack/docs/src/examples/uniformMps/leftEnvironment.m'.\n" + ] + } + ], + "source": [ + "%%file leftEnvironment.m\n", + "function [lam, Fl] = leftEnvironment(O, Al, tol)\n", + " % Computes the left environment as the fixed point of the left channel operator.\n", + " %\n", + " % Parameters\n", + " % ----------\n", + " % O : :class:`Tensor` (d, d, d, d)\n", + " % MPO tensor,\n", + " % ordered top-right-bottom-left.\n", + " % Al : :class:`Tensor` (D, d, D)\n", + " % MPS tensor with 3 legs,\n", + " % ordered left-bottom-right,\n", + " % left-orthonormal.\n", + " % tol : float, optional\n", + " % tolerance for eigenvalue solver\n", + " %\n", + " % Returns\n", + " % -------\n", + " % lam : double\n", + " % Leading left eigenvalue.\n", + " % Fl : :class:`Tensor` (D, d, D)\n", + " % left environment,\n", + " % ordered bottom-middle-top.\n", + " \n", + " tol = max(tol, 1e-14);\n", + " \n", + " % initialize handle for the action of the left channel operator on a given input tensor\n", + " channelLeft = @(x) contract(x, [5, 3, 1], Al, [1, 2, -3], conj(Al), [5, 4, -1], O, [2, -2, 4, 3]);\n", + " % compute the largest magnitude eigenvalue and corresponding eigenvector\n", + " x0 = similar([], Al, 1, O, 4, Al, 1, 'Conj', [false, true, true]);\n", + " [Fl, lam] = eigsolve(channelLeft, x0, 1, 'largestabs', 'Tol', tol);\n", + "end" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "60c79ac6-479f-462e-af3f-feea250d685e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Created file '/home/leburgel/git/TensorTrack/docs/src/examples/uniformMps/rightEnvironment.m'.\n" + ] + } + ], + "source": [ + "%%file rightEnvironment.m\n", + "function [lam, Fr] = rightEnvironment(O, Ar, tol)\n", + " % Computes the right environment as the fixed point of the right channel operator.\n", + " %\n", + " % Parameters\n", + " % ----------\n", + " % O : :class:`Tensor` (d, d, d, d)\n", + " % MPO tensor,\n", + " % ordered top-right-bottom-left.\n", + " % Ar : :class:`Tensor` (D, d, D)\n", + " % MPS tensor with 3 legs,\n", + " % ordered left-bottom-right,\n", + " % right-orthonormal.\n", + " % tol : float, optional\n", + " % tolerance for eigenvalue solver\n", + " %\n", + " % Returns\n", + " % -------\n", + " % lam : double\n", + " % Leading right eigenvalue.\n", + " % Fr : :class:`Tensor` (D, d, D)\n", + " % right environment,\n", + " % ordered top-middle-bottom.\n", + "\n", + " tol = max(tol, 1e-14);\n", + " \n", + " % initialize handle for the action of the left channel operator on a given input tensor\n", + " channelRight = @(x) contract(x, [1, 3, 5], Ar, [-1, 2, 1], conj(Ar), [-3, 4, 5], O, [2, 3, 4, -2]);\n", + " % compute the largest magnitude eigenvalue and corresponding eigenvector\n", + " x0 = similar([], Ar, 3, O, 2, Ar, 3, 'Conj', [true, true, false]);\n", + " [Fr, lam] = eigsolve(channelRight, x0, 1, 'largestabs', 'Tol', tol);\n", + "end" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "e06cbb2f-4481-4766-9376-9eaf5bf69304", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Created file '/home/leburgel/git/TensorTrack/docs/src/examples/uniformMps/environments.m'.\n" + ] + } + ], + "source": [ + "%%file environments.m\n", + "function [lam, Fl, Fr] = environments(O, Al, Ar, C, tol)\n", + " % Compute the left and right environments of the channel operators\n", + " % as well as the corresponding eigenvalue.\n", + " %\n", + " % Parameters\n", + " % ----------\n", + " % O : :class:`Tensor` (d, d, d, d)\n", + " % MPO tensor,\n", + " % ordered top-right-bottom-left.\n", + " % Al : :class:`Tensor` (D, d, D)\n", + " % MPS tensor with 3 legs,\n", + " % ordered left-bottom-right,\n", + " % left-orthonormal.\n", + " % Ar : :class:`Tensor` (D, d, D)\n", + " % MPS tensor with 3 legs,\n", + " % ordered left-bottom-right,\n", + " % right-orthonormal.\n", + " % C : :class:`Tensor` (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", + " % lam : double\n", + " % Leading eigenvalue of the channel\n", + " % operators.\n", + " % Fl : :class:`Tensor` (D, d, D)\n", + " % left environment,\n", + " % ordered bottom-middle-top.\n", + " % Fr : :class:`Tensor` (D, d, D)\n", + " % right environment,\n", + " % ordered top-middle-bottom.\n", + " \n", + " arguments\n", + " O\n", + " Al\n", + " Ar\n", + " C\n", + " tol = 1e-5\n", + " end\n", + "\n", + " tol = max(tol, 1e-14);\n", + " \n", + " [lam, Fl] = leftEnvironment(O, Al, tol);\n", + " [~, Fr] = rightEnvironment(O, Ar, tol);\n", + " lam = real(lam);\n", + " \n", + " overlap = contract(Fl, [1, 3, 2], Fr, [5, 3, 4], C, [2, 5], conj(C), [1, 4]);\n", + " Fl = Fl / overlap;\n", + "end" + ] + }, + { + "cell_type": "markdown", + "id": "559aea24-e22d-4648-a050-ef277e66c7ec", + "metadata": {}, + "source": [ + "Next we implement the action of the effective operators $O_{A_C}$ and $O_C$ on a given input tensor,\n", + "\n", + "
\"O_Ac\"/
\n", + "\n", + "and\n", + "\n", + "
\"O_C\"/
" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "2d061e4d-9aaf-4f48-a5c8-4403359cb46e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Created file '/home/leburgel/git/TensorTrack/docs/src/examples/uniformMps/O_Ac.m'.\n" + ] + } + ], + "source": [ + "%%file O_Ac.m\n", + "function y = O_Ac(x, O, Fl, Fr, lam)\n", + " % Action of the operator O_Ac on a given tensor.\n", + " %\n", + " % Parameters\n", + " % ----------\n", + " % x : :class:`Tensor` (D, d, D)\n", + " % Tensor of size (D, d, D)\n", + " % O : :class:`Tensor` (d, d, d, d)\n", + " % MPO tensor,\n", + " % ordered left-top-right-bottom.\n", + " % Fl : :class:`Tensor` (D, d, D)\n", + " % left environment,\n", + " % ordered bottom-middle-top.\n", + " % Fr : :class:`Tensor` (D, d, D)\n", + " % right environment,\n", + " % ordered top-middle-bottom.\n", + " % lam : float\n", + " % Leading eigenvalue.\n", + " %\n", + " % Returns\n", + " % -------\n", + " % y : :class:`Tensor` (D, d, D)\n", + " % Result of the action of O_Ac on the tensor x.\n", + " \n", + " y = contract(Fl, [-1, 2, 1], Fr, [4, 5, -3], x, [1, 3, 4], O, [3, 5, -2, 2]) / lam;\n", + "end" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "aab578f5-e928-4933-bf05-9b26570252b7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Created file '/home/leburgel/git/TensorTrack/docs/src/examples/uniformMps/O_C.m'.\n" + ] + } + ], + "source": [ + "%%file O_C.m\n", + "function y = O_C(x, Fl, Fr)\n", + " % Action of the operator O_C on a given tensor.\n", + " %\n", + " % Parameters\n", + " % ----------\n", + " % x : :class:`Tensor` (D, D)\n", + " % Tensor of size (D, D)\n", + " % Fl : :class:`Tensor` (D, d, D)\n", + " % left environment,\n", + " % ordered bottom-middle-top.\n", + " % Fr : :class:`Tensor` (D, d, D)\n", + " % right environment,\n", + " % ordered top-middle-bottom.\n", + " %\n", + " % Returns\n", + " % -------\n", + " % y : :class:`Tensor` (D, d, D)\n", + " % Result of the action of O_C on the tensor x.\n", + " \n", + " y = contract(Fl, [-1, 3, 1], Fr, [2, 3, -2], x, [1, 2]);\n", + "end" + ] + }, + { + "cell_type": "markdown", + "id": "b2f54914-ac3c-41ba-a90e-0df537bed96a", + "metadata": {}, + "source": [ + "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$." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "d7e7cd19-96cf-4d76-8ef5-947a91e4a69c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Created file '/home/leburgel/git/TensorTrack/docs/src/examples/uniformMps/calcNewCenterMpo.m'.\n" + ] + } + ], + "source": [ + "%%file calcNewCenterMpo.m\n", + "function [AcTilde, CTilde] = calcNewCenterMpo(O, Ac, C, Fl, Fr, lam, tol)\n", + " % Find new guess for Ac and C as fixed points of the maps O_Ac and O_C.\n", + " %\n", + " % Parameters\n", + " % ----------\n", + " % O : :class:`Tensor` (d, d, d, d)\n", + " % MPO tensor,\n", + " % ordered left-top-right-bottom.\n", + " % Ac : :class:`Tensor` (D, d, D)\n", + " % MPS tensor with 3 legs,\n", + " % ordered left-bottom-right,\n", + " % center gauge.\n", + " % C : :class:`Tensor` (D, D)\n", + " % Center gauge with 2 legs,\n", + " % ordered left-right.\n", + " % Fl : :class:`Tensor` (D, d, D)\n", + " % left environment,\n", + " % ordered bottom-middle-top.\n", + " % Fr : :class:`Tensor` (D, d, D)\n", + " % right environment,\n", + " % ordered top-middle-bottom.\n", + " % lam : double\n", + " % Leading eigenvalue.\n", + " % tol : double, optional\n", + " % current tolerance\n", + " %\n", + " % Returns\n", + " % -------\n", + " % AcTilde : :class:`Tensor` (D, d, D)\n", + " % MPS tensor with 3 legs,\n", + " % ordered left-bottom-right,\n", + " % center gauge.\n", + " % CTilde : :class:`Tensor` (D, D)\n", + " % Center gauge with 2 legs,\n", + " % ordered left-right.\n", + " \n", + " arguments\n", + " O\n", + " Ac\n", + " C\n", + " Fl\n", + " Fr\n", + " lam\n", + " tol = 1e-5\n", + " end\n", + " \n", + " tol = max(tol, 1e-14);\n", + " \n", + " % compute fixed points of O_Ac and O_C\n", + " [AcTilde, ~] = eigsolve(@(x) O_Ac(x, O, Fl, Fr, lam), Ac, 1, 'largestabs', 'Tol', tol);\n", + " [CTilde, ~] = eigsolve(@(x) O_C(x, Fl, Fr), C, 1, 'largestabs', 'Tol', tol);\n", + "end" + ] + }, + { + "cell_type": "markdown", + "id": "dcb32788-4f14-4ae5-84ed-cec84b3c00f0", + "metadata": {}, + "source": [ + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "8b52825c-23b6-4d17-94c5-10572c1bf605", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Created file '/home/leburgel/git/TensorTrack/docs/src/examples/uniformMps/vumpsMpo.m'.\n" + ] + } + ], + "source": [ + "%%file vumpsMpo.m\n", + "function [lam, Al, Ar, C, Ac, Fl, Fr] = vumpsMpo(O, D, A0, tol, tolFactor, verbose)\n", + " % Find the fixed point MPS of a given MPO using VUMPS.\n", + "\n", + " % Parameters\n", + " % ----------\n", + " % O : :class:`Tensor` (d, d, d, d)\n", + " % MPO tensor,\n", + " % ordered left-top-right-bottom.\n", + " % D : int\n", + " % Bond dimension\n", + " % A0 : :class:`Tensor` (D, d, D)\n", + " % MPS tensor with 3 legs,\n", + " % ordered left-bottom-right,\n", + " % initial guess.\n", + " % tol : float\n", + " % Relative convergence criterium.\n", + " %\n", + " % Returns\n", + " % -------\n", + " % lam : float\n", + " % Leading eigenvalue.\n", + " % Al : :class:`Tensor` (D, d, D)\n", + " % MPS tensor with 3 legs,\n", + " % ordered left-bottom-right,\n", + " % left orthonormal.\n", + " % Ar : :class:`Tensor` (D, d, D)\n", + " % MPS tensor with 3 legs,\n", + " % ordered left-bottom-right,\n", + " % right orthonormal.\n", + " % Ac : :class:`Tensor` (D, d, D)\n", + " % MPS tensor with 3 legs,\n", + " % ordered left-bottom-right,\n", + " % center gauge.\n", + " % C : :class:`Tensor` (D, D)\n", + " % Center gauge with 2 legs,\n", + " % ordered left-right.\n", + " % Fl : :class:`Tensor` (D, d, D)\n", + " % left environment,\n", + " % ordered bottom-middle-top.\n", + " % Fr : :class:`Tensor` (D, d, D)\n", + " % right environment,\n", + " % ordered top-middle-bottom.\n", + " \n", + " arguments\n", + " O\n", + " D\n", + " A0 = []\n", + " tol = 1e-4\n", + " tolFactor = 1e-2\n", + " verbose = true\n", + " end\n", + " \n", + " tol = max(tol, 1e-14);\n", + " delta = 1e-4;\n", + " \n", + " % if no initial guess, random one\n", + " if isempty(A0)\n", + " A0 = createMPS(D, d);\n", + " end\n", + " \n", + " % go to mixed gauge\n", + " [Al, Ar, C, Ac] = mixedCanonical(A0, [], [], delta*tolFactor^2);\n", + " \n", + " flag = true;\n", + " i = 0;\n", + " \n", + " while flag\n", + " i = 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", + " G = O_Ac(Ac, O, Fl, Fr, lam) - contract(Al, [-1, -2, 1], O_C(C, Fl, Fr), [1, -3]);\n", + " delta = norm(G);\n", + " if delta < tol\n", + " flag = false;\n", + " end\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, ArTilde, CTilde, AcTilde] = minAcC(AcTilde, CTilde, delta*tolFactor^2);\n", + " \n", + " % update tensors\n", + " Al = AlTilde; Ar = ArTilde; C = CTilde; Ac = AcTilde;\n", + " \n", + " % print current eigenvalue\n", + " if verbose\n", + " fprintf('iteration:\\t%d,\\tenergy:\\t%.12f\\tgradient norm\\t%.4e\\n', i, lam, delta)\n", + " end\n", + " end\n", + "end" + ] + }, + { + "cell_type": "markdown", + "id": "0eb5447b-6dac-4a87-a444-0eebd86d1adb", + "metadata": {}, + "source": [ + "## 3 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", + "
\"Z\"/
\n", + "\n", + "where every edge in the network has bond dimension $d = 2$. Here, the black dots represent $ \\delta $-tensors\n", + "\n", + "
\"delta\"/
\n", + "\n", + "and the matrices $t$ encode the Boltzmann weights associated to each nearest-neighbor interaction,\n", + "\n", + "
\"t\"/
\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", + "
\"q\"/
\n", + "\n", + "and absorb the result symmetrically into the $\\delta$-tensors at each vertex to define the MPO tensor\n", + "\n", + "
\"O\"/
\n", + "\n", + "The partition function then becomes\n", + "\n", + "
\"Z2p\"/
\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", + "$$ \\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", + "
\"Mexp\"/
\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=2$ (using base-1 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." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "b61ea667-8ccf-4e8c-a69a-4c13c1a86732", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Created file '/home/leburgel/git/TensorTrack/docs/src/examples/uniformMps/isingO.m'.\n" + ] + } + ], + "source": [ + "%%file isingO.m\n", + "function O = isingO(beta, J)\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", + " %\n", + " % Returns\n", + " % -------\n", + " % O : :class:`Tensor` (2, 2, 2, 2)\n", + " % MPO tensor,\n", + " % ordered top-right-bottom-left.\n", + "\n", + " % basic vertex delta tensor\n", + " vertex = zeros(repmat(2, 1, 4));\n", + " for i = 1:2\n", + " sbs = num2cell(repmat(i, 1, 4));\n", + " vertex(sbs{:}) = 1;\n", + " end\n", + " vertex = Tensor(vertex);\n", + " % take matrix square root of Boltzmann weights and pull into vertex edges\n", + " t = Tensor([exp(beta*J), exp(-beta*J); exp(-beta*J), exp(beta*J)]); t = repartition(t, [1, 1]);\n", + " q = sqrtm(t);\n", + " O = contract(q, [-1, 1], q, [-2, 2], q, [-3, 3], q, [-4, 4], vertex, [1, 2, 3, 4]);\n", + "end" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "0f570faa-eccb-4dd5-9273-d84e32b39b7a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Created file '/home/leburgel/git/TensorTrack/docs/src/examples/uniformMps/isingM.m'.\n" + ] + } + ], + "source": [ + "%%file isingM.m\n", + "function M = isingM(beta, J)\n", + " % Gives the magnetization tensor for the 2d classical Ising model at a\n", + " % given temperature and coupling.\n", + " %\n", + " % Parameters\n", + " % ----------\n", + " % beta : float\n", + " % Inverse temperature.\n", + " % J : float\n", + " % Coupling strength.\n", + " %\n", + " % Returns\n", + " % -------\n", + " % M : :class:`Tensor` (2, 2, 2, 2)\n", + " % Magnetization tensor,\n", + " % ordered left-top-right-bottom.\n", + " \n", + " % basic vertex delta tensor\n", + " vertex = zeros(repmat(2, 1, 4));\n", + " for i = 1:2\n", + " sbs = num2cell(repmat(i, 1, 4));\n", + " vertex(sbs{:}) = 1;\n", + " end\n", + " vertex(2, 2, 2, 2) = -1;\n", + " vertex = Tensor(vertex);\n", + " % take matrix square root of Boltzmann weights and pull into vertex edges\n", + " t = Tensor([exp(beta*J), exp(-beta*J); exp(-beta*J), exp(beta*J)]); t = repartition(t, [1, 1]);\n", + " q = sqrtm(t);\n", + " M = contract(q, [-1, 1], q, [-2, 2], q, [-3, 3], q, [-4, 4], vertex, [1, 2, 3, 4]);\n", + "end" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "e42ff000-be97-47b1-bf01-58d1c23e9df6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Created file '/home/leburgel/git/TensorTrack/docs/src/examples/uniformMps/expValMpo.m'.\n" + ] + } + ], + "source": [ + "%%file expValMpo.m\n", + "function e = expValMpo(O, Ac, Fl, Fr)\n", + " % Gives the expectation value of a local operator O.\n", + " %\n", + " % Parameters\n", + " % ----------\n", + " % O : :class:`Tensor` (2, 2, 2, 2)\n", + " % local operator of which we want to\n", + " % compute the expectation value,\n", + " % ordered top-right-bottom-left.\n", + " % Ac : :class:`Tensor` (D, d, D)\n", + " % MPS tensor of the MPS fixed point,\n", + " % with 3 legs ordered left-bottom-right,\n", + " % center gauge.\n", + " % Fl : :class:`Tensor` (D, d, D)\n", + " % left environmnt,\n", + " % ordered bottom-middle-top.\n", + " % Fr : :class:`Tensor` (D, d, D)\n", + " % right environmnt,\n", + " % ordered top-middle-bottom.\n", + " %\n", + " % Returns\n", + " % -------\n", + " % e : double\n", + " % expectation value of the operator O.\n", + " \n", + " e = contract( Fl, [1, 3, 2], ...\n", + " Ac, [2, 7, 5], ...\n", + " O, [7, 8, 6, 3], ...\n", + " conj(Ac), [1, 6, 4], ...\n", + " Fr, [5, 8, 4]);\n", + "end" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "497b3cdb-a3d3-4a0f-a2d5-e4bd261da4c5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Created file '/home/leburgel/git/TensorTrack/docs/src/examples/uniformMps/isingExact.m'.\n" + ] + } + ], + "source": [ + "%%file isingExact.m\n", + "function [magnetization, free, energy] = isingExact(beta, J)\n", + " % Exact Onsager solution for the 2d classical Ising Model\n", + " % \n", + " % Parameters\n", + " % ----------\n", + " % beta : float\n", + " % Inverse temperature.\n", + " % J : float\n", + " % Coupling strength.\n", + " % \n", + " % Returns\n", + " % -------\n", + " % magnetization : float\n", + " % Magnetization at given temperature and coupling.\n", + " % free : float\n", + " % Free energy at given temperature and coupling.\n", + " % energy : float\n", + " % Energy at given temperature and coupling.\n", + "\n", + " theta = 0:1e-6:pi/2;\n", + " x = 2*sinh(2*J*beta)/cosh(2*J*beta)^2;\n", + " if 1-(sinh(2*J*beta))^(-4)>0\n", + " magnetization = (1-(sinh(2*J*beta))^(-4))^(1/8);\n", + " else\n", + " magnetization = 0;\n", + " end\n", + " free = -1/beta*(log(2*cosh(2*J*beta))+1/pi*trapz(theta,log(1/2*(1+sqrt(1-x^2*sin(theta).^2)))));\n", + " K = trapz(theta,1./sqrt(1-x^2*sin(theta).^2));\n", + " energy = -J*cosh(2*J*beta)/sinh(2*J*beta)*(1+2/pi*(2*tanh(2*J*beta)^2-1)*K);\n", + "end" + ] + }, + { + "cell_type": "markdown", + "id": "318ce072-8910-4bc0-8a3f-9bc3ce0acf2e", + "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": 13, + "id": "dad00118-b7a5-4fb5-92a7-9542cd7e4947", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running for T = 4.0000\n", + "iteration:\t1,\tenergy:\t1.368895562189\tgradient norm\t1.3761e+00\n", + "iteration:\t2,\tenergy:\t2.134012743237\tgradient norm\t2.5997e-02\n", + "iteration:\t3,\tenergy:\t2.136366460032\tgradient norm\t7.1440e-05\n", + "iteration:\t4,\tenergy:\t2.136366478122\tgradient norm\t3.2529e-10\n", + "\n", + "Free energy: -3.0364259141; \trelative difference with exact solution: 3.2919e-08\n", + "\n", + "Magnetization: 0.0000000001\n", + "\n", + "Running for T = 1.0000\n", + "iteration:\t1,\tenergy:\t4.354039087277\tgradient norm\t4.3820e+00\n", + "iteration:\t2,\tenergy:\t7.391405784890\tgradient norm\t5.4451e-03\n", + "iteration:\t3,\tenergy:\t7.391630034942\tgradient norm\t2.0650e-08\n", + "iteration:\t4,\tenergy:\t7.391630034938\tgradient norm\t3.8242e-15\n", + "\n", + "Free energy: -2.0003482837; \trelative difference with exact solution: 3.8102e-09\n", + "\n", + "Magnetization: 0.9992757520; \trelative difference with exact solution: 0.0000e+00\n" + ] + } + ], + "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", + "fprintf('Running for T = %.4f\\n', T)\n", + "beta = 1 / T;\n", + "O = isingO(beta, J);\n", + "M = isingM(beta, J);\n", + "[lam, Al, Ar, C, Ac, Fl, Fr] = vumpsMpo(O, D, A0, tol, tolFactor, true);\n", + "mag = abs(expValMpo(M, Ac, Fl, Fr) / expValMpo(O, Ac, Fl, Fr));\n", + "freeEnergy = -log(lam) / beta;\n", + "[~, freeEnergyExact] = isingExact(beta, J);\n", + "fprintf('\\nFree energy: %.10f; \\trelative difference with exact solution: %.4e\\n', ...\n", + " freeEnergy, abs((freeEnergy - freeEnergyExact) / freeEnergyExact))\n", + "fprintf('\\nMagnetization: %.10f\\n\\n', mag)\n", + "\n", + "T = 1;\n", + "fprintf('Running for T = %.4f\\n', T)\n", + "beta = 1 / T;\n", + "O = isingO(beta, J);\n", + "M = isingM(beta, J);\n", + "[lam, Al, Ar, C, Ac, Fl, Fr] = vumpsMpo(O, D, A0, tol, tolFactor, true);\n", + "mag = abs(expValMpo(M, Ac, Fl, Fr) / expValMpo(O, Ac, Fl, Fr));\n", + "freeEnergy = -log(lam) / beta;\n", + "[magExact, freeEnergyExact] = isingExact(beta, J);\n", + "fprintf('\\nFree energy: %.10f; \\trelative difference with exact solution: %.4e\\n', ...\n", + " freeEnergy, abs((freeEnergy - freeEnergyExact) / freeEnergyExact))\n", + "fprintf('\\nMagnetization: %.10f; \\trelative difference with exact solution: %.4e\\n', ...\n", + " mag, abs((mag - magExact) / magExact))\n" + ] + }, + { + "cell_type": "markdown", + "id": "6badd4a3-1ca4-4991-a11c-49834e55d7a8", + "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": 14, + "id": "7185d707-b2be-44c4-8189-cefb9c0887ff", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Bond dimension: D = 12\n", + "Running for T = 3.40000\r" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "D = 12;\n", + "d = 2;\n", + "J = 1;\n", + "N = 100;\n", + "\n", + "fprintf('Bond dimension: D = %d\\n', D)\n", + "Al = createMPS(D, d);\n", + "% optimization parameters\n", + "tol = 1e-8;\n", + "tolFactor = 1e-2;\n", + "verbose = false;\n", + "\n", + "Ts = linspace(1., 3.4, N);\n", + "magnetizations = zeros(1, N);\n", + "magnetizationsExact = zeros(1, N);\n", + "freeEnergies = zeros(1, N);\n", + "freeEnergiesExact = zeros(1, N);\n", + "\n", + "for i = 1:N\n", + " T = Ts(i);\n", + " beta = 1/T;\n", + " O = isingO(beta, J);\n", + " M = isingM(beta, J);\n", + " fprintf('Running for T = %.5f\\r', T)\n", + " \n", + " [lam, Al, Ar, C, Ac, Fl, Fr] = vumpsMpo(O, D, Al, tol, tolFactor, verbose);\n", + " magnetizations(i) = abs(expValMpo(M, Ac, Fl, Fr)/expValMpo(O, Ac, Fl, Fr));\n", + " freeEnergies(i) = -log(lam) / beta;\n", + " [magnetizationsExact(i), freeEnergiesExact(i)] = isingExact(beta, J);\n", + "end\n", + "\n", + "%plot results\n", + "width = 6;\n", + "height = 3;\n", + "units = 'inches';\n", + "subplot(1, 2, 1)\n", + "hold on\n", + "plot(Ts, magnetizationsExact)\n", + "scatter(Ts, magnetizations, 'm', '^')\n", + "hold off\n", + "legend({sprintf('D = %d', D), 'exact'})\n", + "title('Magnetization as a function of the temperature')\n", + "xlabel('T')\n", + "ylabel('')\n", + "subplot(1, 2, 2)\n", + "hold on\n", + "plot(Ts, freeEnergiesExact)\n", + "scatter(Ts, freeEnergies, 'm', '^')\n", + "hold off\n", + "legend({sprintf('D = %d', D), 'exact'})\n", + "title('Free energy as a function of the temperature')\n", + "xlabel('T')\n", + "ylabel('f')\n", + "set(gcf, 'Units', units, 'PaperUnits', units, 'position', [0 0 2*width height], 'PaperPosition', [0 0 2*width height]);" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c50c1596-02b4-44a0-afa7-5e3afd6ac461", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b0e2ea95-9885-428b-92f5-1d7489de72c9", + "metadata": {}, + "source": [ + "[thumbnail](img/Z2.svg)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Matlab", + "language": "matlab", + "name": "matlab" + }, + "language_info": { + "codemirror_mode": "octave", + "file_extension": ".m", + "help_links": [ + { + "text": "MetaKernel Magics", + "url": "https://metakernel.readthedocs.io/en/latest/source/README.html" + } + ], + "mimetype": "text/x-octave", + "name": "matlab", + "version": "0.17.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/src/examples/uniformMps/uniformMps.ipynb b/docs/src/examples/uniformMps/uniformMps.ipynb index 9688854..e5de9e6 100644 --- a/docs/src/examples/uniformMps/uniformMps.ipynb +++ b/docs/src/examples/uniformMps/uniformMps.ipynb @@ -835,7 +835,7 @@ " handleERight = @(v) contract(A, [-1, 2, 1], conj(A), [-2, 2, 3], v, [1, 3], 'Rank', [1, 1]);\n", " \n", " % start from random initial guess for fixed point\n", - " r0 = A.randnc(A.dims([3, 3]), 'Rank', [1, 1]);\n", + " r0 = similar([], A, 3, A, 3, 'Conj', [true, false], 'Rank', [1, 1]);\n", " \n", " % calculate eigenvalue\n", " lam = eigsolve(handleERight, r0);\n", @@ -884,7 +884,7 @@ " handleELeft = @(v) contract(A, [1, 2, -2], conj(A), [3, 2, -1], v, [3, 1], 'Rank', [1, 1]);\n", " \n", " % start from random initial guess for fixed point\n", - " l0 = A.randnc(A.dims([1, 1]), 'Rank', [1, 1]);\n", + " l0 = similar([], A, 1, A, 1, 'Conj', [false, true], 'Rank', [1, 1]);\n", " \n", " % calculate fixed point\n", " [l, ~] = eigsolve(handleELeft, l0);\n", @@ -936,7 +936,7 @@ " handleERight = @(v) contract(A, [-1, 2, 1], conj(A), [-2, 2, 3], v, [1, 3], 'Rank', [1, 1]);\n", " \n", " % start from random initial guess for fixed point\n", - " r0 = A.randnc(A.dims([3, 3]), 'Rank', [1, 1]);\n", + " r0 = similar([], A, 3, A, 3, 'Conj', [true, false], 'Rank', [1, 1]);\n", " \n", " % calculate fixed point\n", " [r, ~] = eigsolve(handleERight, r0);\n", @@ -1091,7 +1091,7 @@ " end\n", " \n", " if isempty(R0)\n", - " R0 = A.randnc(A.dims([3, 3]), 'Rank', [1, 1]);\n", + " R0 = similar([], A, 3, A, 3, 'Conj', [true, false], 'Rank', [1, 1]);\n", " end\n", " \n", " % Normalize R0\n", @@ -1176,7 +1176,7 @@ " end\n", " \n", " if isempty(L0)\n", - " L0 = A.randnc(A.dims([1, 1]), 'Rank', [1, 1]);\n", + " L0 = similar([], A, 1, A, 1, 'Conj', [false, true], 'Rank', [1, 1]);\n", " end\n", " \n", " % Normalize L0\n", @@ -1266,11 +1266,13 @@ " maxIter = 1e5\n", " end\n", " \n", + " tol = max(tol, 1e-14);\n", + " \n", " if isempty(L0)\n", - " L0 = A.randnc(A.dims([1, 1]), 'Rank', [1, 1]);\n", + " L0 = similar([], A, 1, A, 1, 'Conj', [false, true], 'Rank', [1, 1]);\n", " end\n", " if isempty(R0)\n", - " R0 = A.randnc(A.dims([3, 3]), 'Rank', [1, 1]);\n", + " R0 = similar([], A, 3, A, 3, 'Conj', [true, false], 'Rank', [1, 1]);\n", " end\n", "\n", " % Compute left and right orthonormal forms\n", @@ -1579,6 +1581,14 @@ "metadata": {}, "outputs": [], "source": [] + }, + { + "cell_type": "markdown", + "id": "7ab234b3-c958-431b-a1bc-3ce760fdf6fa", + "metadata": {}, + "source": [ + "[thumbnail](img/leftOrth.svg)" + ] } ], "metadata": { From 40598da1b3d1f5eded0d73d8b89edd9eab1e110d Mon Sep 17 00:00:00 2001 From: Lander Burgelman <39218680+leburgel@users.noreply.github.com> Date: Thu, 23 Jun 2022 12:18:33 +0200 Subject: [PATCH 007/245] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 52a42c2..0ece692 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@
- [![Documentation Status](https://readthedocs.org/projects/tensortrack/badge/?version=latest)](https://tensortrack.readthedocs.io/en/latest/index.html) + [![Documentation Status](https://readthedocs.org/projects/tensortrack/badge/?version=stable)](https://tensortrack.readthedocs.io/en/stable/?badge=stable) [![CI](https://github.com/quantumghent/TensorTrack/actions/workflows/CI.yml/badge.svg)](https://github.com/quantumghent/TensorTrack/actions/workflows/CI.yml) [![Codecov](https://codecov.io/gh/quantumghent/TensorTrack/branch/main/graph/badge.svg?token=1I0XEB69TQ)](https://codecov.io/gh/quantumghent/TensorTrack) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) From 6b7f664f16d40ee9a880f5f04c47c291c77bc52b Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 17 Aug 2022 20:39:24 +0200 Subject: [PATCH 008/245] bug in ~= --- src/tensors/charges/Z1.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tensors/charges/Z1.m b/src/tensors/charges/Z1.m index ecdd847..6c28e1d 100644 --- a/src/tensors/charges/Z1.m +++ b/src/tensors/charges/Z1.m @@ -44,12 +44,12 @@ function bools = ne(A, B) if isscalar(A) - bools = true(size(B)); + bools = false(size(B)); elseif isscalar(B) - bools = true(size(A)); + bools = false(size(A)); else assert(all(size(A) == size(B)), 'tensors:charges:SizeError'); - bools = true(size(A)); + bools = false(size(A)); end end From fd41edb7a639c635a66ab2bb9b63d68df11c21d9 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 17 Aug 2022 20:42:23 +0200 Subject: [PATCH 009/245] elementary traces --- src/tensors/FusionTree.m | 232 ++++++++++++++++++++++--------- src/utility/indices/traceinds.m | 7 + src/utility/indices/unique2.m | 10 -- src/utility/linalg/contract.m | 2 +- src/utility/linalg/tensortrace.m | 21 ++- test/TestFusionTree.m | 46 +++++- 6 files changed, 235 insertions(+), 83 deletions(-) create mode 100644 src/utility/indices/traceinds.m delete mode 100644 src/utility/indices/unique2.m diff --git a/src/tensors/FusionTree.m b/src/tensors/FusionTree.m index b366d20..0a5ccff 100644 --- a/src/tensors/FusionTree.m +++ b/src/tensors/FusionTree.m @@ -1,20 +1,20 @@ classdef FusionTree < matlab.mixin.CustomDisplay -% Splitting and fusion tree pair in canonical form. -% -% Properties -% ---------- -% charges : AbstractCharge -% labels for the edges of the fusion trees -% -% vertices : int -% labels for the vertices of the fusion trees -% -% isdual : logical -% indicator of duality transform on the external edges. -% -% rank : int -% amount of splitting tree and fusion tree legs. - + % Splitting and fusion tree pair in canonical form. + % + % Properties + % ---------- + % charges : AbstractCharge + % labels for the edges of the fusion trees + % + % vertices : int + % labels for the vertices of the fusion trees + % + % isdual : logical + % indicator of duality transform on the external edges. + % + % rank : int + % amount of splitting tree and fusion tree legs. + properties charges (:, :) vertices (:, :) uint8 @@ -55,7 +55,7 @@ end %% Trivial tree with no external legs - if sum(rank) == 0 + if sum(rank) == 0 if isempty(charges) && isempty(vertices) && isempty(isdual) f.rank = rank; return @@ -67,12 +67,12 @@ %% General tree N = length(isdual); if N ~= sum(rank) || ... - size(charges, 2) ~= sum(treeindsequence(rank)) + 1 || ... - (N > 2 && hasmultiplicity(fusionstyle(charges)) && ... - size(vertices, 2) ~= max(N - 2, 0)) + size(charges, 2) ~= sum(treeindsequence(rank)) + 1 || ... + (N > 2 && hasmultiplicity(fusionstyle(charges)) && ... + size(vertices, 2) ~= max(N - 2, 0)) error('tensors:tree:argError', 'Incompatible input sizes.'); end - + f.charges = charges; f.vertices = vertices; f.isdual = isdual; @@ -104,15 +104,15 @@ charges (1, :) {mustBeA(charges, 'AbstractCharge')} isdual (1, 1) logical end - + assert(sum(rank) == length(isdual), 'tensors:trees:ArgError', ... - 'Rank incompatible with the amount of legs specified.'); - + 'Rank incompatible with the amount of legs specified.'); + if sum(rank) == 0 f = FusionTree([], [], [], rank); return end - + %% Splitting tree if rank(1) == 0 @@ -128,7 +128,7 @@ charges_l = charges_l(2:end, :).'; case FusionStyle.Simple charges_l = []; - + for unc_l = combvec(charges{1:rank(1)}) inn_l = cumprod(unc_l); temp = reshape( ... @@ -177,18 +177,18 @@ end charge_cell = cell(size(coupled)); - + for i = 1:length(coupled) ind_l = coupled(i) == charges_l(:, end); nnz_l = nnz(ind_l); ind_r = coupled(i) == charges_r(:, end); nnz_r = nnz(ind_r); charge_cell{i} = [reshape(repmat(reshape( ... - charges_l(ind_l, 1:end - 1), 1, []), nnz_r, 1), nnz_r * nnz_l, []), ... - repmat(coupled(i), nnz_r * nnz_l, 1), ... - repmat(charges_r(ind_r, end - 1:-1:1), nnz_l, 1)]; + charges_l(ind_l, 1:end - 1), 1, []), nnz_r, 1), nnz_r * nnz_l, []), ... + repmat(coupled(i), nnz_r * nnz_l, 1), ... + repmat(charges_r(ind_r, end - 1:-1:1), nnz_l, 1)]; end - + f = FusionTree(vertcat(charge_cell{:}), [], [isdual{:}], rank); f = sort(f); end @@ -214,23 +214,23 @@ % % Returns % ------- - % c : sparse double + % c : sparse double % matrix of coefficients that transform input to output trees. % f(i) --> c(i,j) * f(j) % % f : FusionTree % braided trees in canonical form. if nargin < 3, inv = false; end - + if hasmultiplicity(fusionstyle(f)) error('trees:TBA', 'Not implemented yet.'); end - + assert(f.rank(2) == 0, 'Only defined for splitting trees.'); - + %% Special case - Abelian if braidingstyle(f) == BraidingStyle.Abelian - + if i == 1 f.charges(:, 1:2) = f.charges(:, [2 1]); f.isdual(1:2) = f.isdual([2 1]); @@ -238,7 +238,7 @@ c = sparse(1:length(f), invperm(p), ones(1, length(f))); return end - + f.charges(:, [2 * i - 2 2 * i]) = f.charges(:, [2 * i 2 * i - 2]); f.charges(:, 2 * i - 1) = f.charges(:, 2 * i - 3) * f.charges(:, 2 * i - 2); f.isdual(i:i + 1) = f.isdual([i + 1 i]); @@ -246,7 +246,7 @@ c = sparse(1:length(f), invperm(p), ones(1, length(f))); return end - + %% Special case - first legs % braiding by only an R-move if i == 1 @@ -261,7 +261,7 @@ c = sparse(1:length(f), invperm(p), vals); return end - + %% General case % braiding by R-move, F-move, R-move charges = f.charges; %#ok<*PROPLC> @@ -272,9 +272,9 @@ charges1 = charges(ia1, :); blocks = cell(1, length(ia1)); newcharges = cell(1, length(ia1)); - + [~, ia2, ic2] = unique(charges1(:, [2 * i - 3 2 * i - 2 2 * i 2 * i + 1]), 'rows'); - + for j = 1:length(ia2) a = charges1(ia2(j), 2 * i - 3); b = charges1(ia2(j), 2 * i - 2); @@ -283,15 +283,15 @@ e = charges1(ia2(j), 2 * i + 1); fs = intersect(a * d, e * conj(b)).'; blocks{j} = braidingmatrix(a, b, cs, d, e, fs, inv); - + for k = find(j == ic2).' newcharges{k} = repmat(charges1(k, :), length(fs), 1); newcharges{k}(:, 2 * i - 2:2 * i) = [newcharges{k}(:, 2 * i) fs(:) ... - newcharges{k}(:, 2 * i - 2)]; + newcharges{k}(:, 2 * i - 2)]; end - + end - + f.charges = vertcat(newcharges{:}); f.isdual(i:i + 1) = f.isdual([i + 1 i]); c = sparse(blkdiag(blocks{ic2})); @@ -309,7 +309,7 @@ % % Returns % ------- - % c : sparse double + % c : sparse double % matrix of coefficients that transform input to output trees. % f(i) --> c(i,j) * f(j) % @@ -321,7 +321,7 @@ c = conj(c); return end - + function [c, f] = bendright(f) % Compute the coefficients for bending a splitting leg to a fusing leg. % @@ -332,7 +332,7 @@ % % Returns % ------- - % c : sparse double + % c : sparse double % matrix of coefficients that transform input to output trees. % f(i) --> c(i,j) * f(j) % @@ -341,7 +341,7 @@ if hasmultiplicity(fusionstyle(f)) error('TBA'); end - + if f.rank(1) == 0 error('tensors:trees:ArgError', 'Invalid rank [%d %d]', f.rank(1), f.rank(2)); elseif f.rank(1) == 1 @@ -352,21 +352,21 @@ [abc, ~, ic] = unique(f.charges(:, ind), 'rows'); a = abc(:, 1); b = abc(:, 2); c = abc(:, 3); end - + vals = sqrt(qdim(c) ./ qdim(a)) .* Bsymbol(a, b, c); if f.isdual(f.rank(1)), vals = vals .* conj(frobeniusschur(conj(b))); end - + f.isdual(f.rank(1)) = ~f.isdual(f.rank(1)); - + if f.rank(2) < 2 chargesR = [conj(f.charges(:, end - f.rank(2) - 1)) f.charges(:, end - f.rank(2) + 1:end)]; else chargesR = [conj(f.charges(:, treeindsequence(f.rank(1)))) ... - f.charges(:, end - treeindsequence(f.rank(2)):end)]; + f.charges(:, end - treeindsequence(f.rank(2)):end)]; end - + chargesC = a(ic); - + if f.rank(1) == 1 chargesL = []; elseif f.rank(1) == 2 @@ -374,9 +374,9 @@ else chargesL = f.charges(:, 1:treeindsequence(f.rank(1) - 1)); end - + f.charges = [chargesL chargesC chargesR]; - + f.rank = f.rank + int16([-1 +1]); c = sparse(1:length(f), 1:length(f), vals(ic)); end @@ -424,13 +424,13 @@ N = length(p); assert(isperm(p), 'tensors:trees:ArgError', 'p is not a valid permutation.'); assert(N == f.legs, 'tensors:trees:ArgError', ... - 'p has the wrong number of elements.'); - + 'p has the wrong number of elements.'); + if all(p == 1:f.legs) [c, f] = repartition(f, rank); return end - + swaps = perm2swap(p); [c, f] = repartition(f, [f.legs 0]); for s = swaps @@ -442,6 +442,99 @@ c = c * c_; end + function [c, f] = elementary_trace(f, i) + N = f.legs; + assert(N > 1, 'tensors:trees:ArgError', ... + 'Tree needs at least two legs to trace.'); + assert(f.isdual(i) ~= f.isdual(mod1(i+1, f.legs)), 'tensors:trees:ArgError', ... + 'Tracing only defined between space and its dual.'); + + + + L1 = length(f); + + if i == 1 + mask = find(f.charges(:, 1) == conj(f.charges(:, 2)) & ... + f.charges(:, 3) == one(f.charges)); + + b = f.charges(mask, 1); + vals = sqrt(qdim(b)); + if f.isdual(i), vals = frobeniusschur(b) .* vals; end + if N > 4 + f.charges = f.charges(mask, 4:end); + elseif N == 4 + f.charges = f.charges(mask, [4 6 7]); + else + f.charges = f.charges(mask, 3:end); + end + f.isdual = f.isdual(3:end); + if hasmultiplicity(fusionstyle(f)) && ~isempty(f.vertices) + f.vertices = f.vertices(mask, 2:end); + end + f.rank(1) = f.rank(1) - 2; + try + [f, ~, locb] = unique(f); + catch + bla + end + c = sparse(mask, locb, vals, L1, length(f)); + return + end + + if i == N + assert(all(f.coupled == one(f.coupled)), 'tensors:trees:ArgError', ... + 'Tracing final and first leg allowed iff coupled charge is trivial.'); + if N == 2 + [c, f] = elementary_trace(f, 1); + c = conj(c); + return + end + + [c, f] = cycleanticlockwise(f); + [c_, f] = elementary_trace(f, 1); + c = c * c_; + return + end + + mask = find(f.charges(:, 2 * i - 2) == conj(f.charges(:, 2 * i)) & ... + f.charges(:, 2 * i - 3) == f.charges(:, 2 * i + 1)); + charges1 = f.charges(mask, (2 * i - 3):(2 * i + 1)); + + b = charges1(:, 2); + vals = sqrt(qdim(b)); + if hasmultiplicity(fusionstyle(f)) + u = one(b); + mu = f.vertices(mask, i-1); + nu = f.vertices(mask, i); + for i = 1:length(vals) + F = Fsymbol(charges1(:, 1), b, charges1(:, 4), ... + charges1(:, 1), charges1(:, 3), u); + vals(i) = vals(i) * F(mu(i), nu(i), 1, 1); + end + else + vals = vals .* Fsymbol(charges1(:, 1), b, charges1(:, 4), ... + charges1(:, 1), charges1(:, 3), repmat(one(charges1), length(mask), 1)); + end + + if f.isdual(i), vals = vals .* frobeniusschur(b); end + + if f.legs == 3 + f.charges = f.charges(mask, [1 end]); + else + f.charges = f.charges(mask, [1:(2 * i - 3) (2 * i + 2):end]); + end + f.isdual(i:i+1) = []; + + if hasmultiplicity(fusionstyle(f)) + f.vertices = f.vertices(mask, [1:i-2 i+1:end]); + end + f.rank(1) = f.rank(1) - 2; + + [f, ~, locb] = unique(f); + + c = sparse(mask, locb, vals, L1, length(f)); + end + function [c, f] = permute(f, p, r) % Compute the coefficients that bring a permuted tree into canonical form. % @@ -502,7 +595,7 @@ oldrank = f.rank; assert(sum(newrank) == sum(oldrank), 'tensors:trees:ArgError', ... 'New rank is incompatible with old rank.'); - + if all(newrank == oldrank) c = speye(length(f)); return @@ -525,7 +618,8 @@ c = c(:, p); end end - + + %% Utility methods function style = braidingstyle(f) @@ -800,17 +894,17 @@ function displayNonScalarObject(f) C = num2cell(ones(size(f))); return; end - + if length(f) > 1 C = arrayfun(@fusiontensor, f); -% C = cell(size(f)); -% for i = 1:length(f) -% C(i) = fusiontensor(f(i)); -% end + % C = cell(size(f)); + % for i = 1:length(f) + % C(i) = fusiontensor(f(i)); + % end return end - + multi = hasmultiplicity(fusionstyle(f)); switch f.rank(1) case 0 @@ -888,7 +982,7 @@ function displayNonScalarObject(f) end end if f.rank(1) == 0 - C = permute(conj(C_fuse), f.rank(2):-1:1); + C = permute(conj(C_fuse), max(f.rank(2), 2):-1:1); elseif f.rank(2) == 0 C = C_split; else diff --git a/src/utility/indices/traceinds.m b/src/utility/indices/traceinds.m new file mode 100644 index 0000000..8340e66 --- /dev/null +++ b/src/utility/indices/traceinds.m @@ -0,0 +1,7 @@ +function [dimA1, dimA2] = traceinds(ia) + +ind = find(ia(:) == ia & ~tril(true(length(ia)))).' - 1; +dimA1 = mod(ind, length(ia)) + 1; +dimA2 = floor(ind / length(ia)) + 1; + +end diff --git a/src/utility/indices/unique2.m b/src/utility/indices/unique2.m deleted file mode 100644 index e56e310..0000000 --- a/src/utility/indices/unique2.m +++ /dev/null @@ -1,10 +0,0 @@ -function inds = unique2(inds) -% unique2 - Find the elements that appear more than once. -% inds = unique2(inds) -% deletes all elements that appear only once. - -[~, ia1] = unique(inds, 'first'); -[~, ia2] = unique(inds, 'last'); -inds(ia1 == ia2) = []; - -end diff --git a/src/utility/linalg/contract.m b/src/utility/linalg/contract.m index 9219202..7deb1e0 100644 --- a/src/utility/linalg/contract.m +++ b/src/utility/linalg/contract.m @@ -86,7 +86,7 @@ ic = [ia ib]; % permute last tensor -if ~isempty(ic) +if ~isempty(ic) && length(ic) > 1 [~, order] = sort(ic, 'descend'); if isnumeric(C) C = permute(C, order); diff --git a/src/utility/linalg/tensortrace.m b/src/utility/linalg/tensortrace.m index 103a340..35a1f77 100644 --- a/src/utility/linalg/tensortrace.m +++ b/src/utility/linalg/tensortrace.m @@ -1,4 +1,4 @@ -function [C, ic] = tensortrace(A, ia, ic) +function C = tensortrace(A, ia) % tensortrace - Compute the (partial) trace of a tensor. % [C, ic] = tensortrace(A, ia) % traces over the indices that appear twice in ia. @@ -6,6 +6,23 @@ % [C, ic] = tensortrace(A, ia, ic) % optionally specifies the output indices' order. -error('Tensors:TBA', 'Traces are not implemented.'); +arguments + A + ia +end + +[dimA1, dimA2] = traceinds(ia); + +szA1 = size(A, dimA1); +szA2 = size(A, dimA2); + +assert(all(szA1 == szA2)); + +E = reshape(eye(prod(szA1)), [szA1 szA2]); + +indsA = -(1:ndims(A)); +indsA(dimA1) = 1:length(dimA1); +indsA(dimA2) = (1:length(dimA2)) + length(dimA1); +C = contract(A, indsA, E, [1:length(dimA1) (1:length(dimA2)) + length(dimA1)]); end diff --git a/test/TestFusionTree.m b/test/TestFusionTree.m index 1553124..15c0131 100644 --- a/test/TestFusionTree.m +++ b/test/TestFusionTree.m @@ -89,7 +89,9 @@ function generateTrees(tc, charge, weight) end args = cell(2, rank1+rank2); args(1, :) = {chargeset}; - args(2, :) = num2cell(randi([0 1], 1, rank1+rank2)); +% args(2, :) = num2cell(randi([0 1], 1, rank1+rank2)); + args(2, 1:2:end) = {false}; + args(2, 2:2:end) = {true}; trees = FusionTree.new([rank1 rank2], args{:}); nTrees = length(trees); while nTrees < 1 || nTrees > maxTrees @@ -182,6 +184,48 @@ function braiding(tc) end end end + + function traces(tc) + for i = 1:numel(tc.trees) + if isempty(tc.trees{i}) || tc.trees{i}.legs < 2 || tc.trees{i}.rank(2) ~= 0 + continue; + end + + f = tc.trees{i}; + for j = 1:f.legs-1 + [c1, f1] = elementary_trace(f, j); + + assertEqual(tc, size(c1), [length(f) length(f1)], ... + 'Coefficients have the wrong size.'); + assertEqual(tc, isallowed(f1), true(length(f1), 1), ... + 'Output trees are not allowed.'); + assertEqual(tc, unique(f1), f1, ... + 'Output trees are not unique.'); + + % compatible with fusiontensor + if issymmetric(braidingstyle(f)) + a1_cell = fusiontensor(f); + a2_cell = fusiontensor(f1); + + for k = 1:length(f) + if f.uncoupled(k, j) ~= conj(f.uncoupled(k, j+1)) + assertEqual(tc, norm(c1(k, :)), 0); + else + a1 = tensortrace(a1_cell{k}, [1:j-1 j j j+2:f.legs+1]); + a2 = zeros(size(a1)); + [~, col, val] = find(c1(k, :)); + for l = 1:length(val) + a2 = a2 + val(l) * a2_cell{col(l)}; + end + + assertEqual(tc, a2, a1, 'AbsTol', tc.tol, ... + 'Tracing should be compatible with fusiontensors.'); + end + end + end + end + end + end % function repartitioning(tc) % for i = 1:numel(tc.trees) % if isempty(tc.trees{i}) || tc.trees{i}.legs < 2 From a7962d3ddbf303c289b4c54ca2d1565359f2e48d Mon Sep 17 00:00:00 2001 From: lkdvos Date: Fri, 2 Sep 2022 15:20:13 +0200 Subject: [PATCH 010/245] Multiple fusion. --- src/tensors/FusionTree.m | 322 +++++++++++++++++++++------ src/tensors/Tensor.m | 10 +- src/tensors/charges/A4.m | 39 +++- src/tensors/charges/AbstractCharge.m | 31 ++- src/tensors/charges/O2.m | 6 +- src/tensors/charges/ProductCharge.m | 4 +- src/tensors/kernels/MatrixBlock.m | 2 - src/utility/simulsortrows.m | 43 +++- src/utility/simulunique.m | 142 ++++++++++++ test/TestFusionTree.m | 98 ++++---- test/TestTensor.m | 30 ++- 11 files changed, 583 insertions(+), 144 deletions(-) create mode 100644 src/utility/simulunique.m diff --git a/src/tensors/FusionTree.m b/src/tensors/FusionTree.m index 0a5ccff..0070dc3 100644 --- a/src/tensors/FusionTree.m +++ b/src/tensors/FusionTree.m @@ -69,7 +69,7 @@ if N ~= sum(rank) || ... size(charges, 2) ~= sum(treeindsequence(rank)) + 1 || ... (N > 2 && hasmultiplicity(fusionstyle(charges)) && ... - size(vertices, 2) ~= max(N - 2, 0)) + size(vertices, 2) ~= max(rank(1) - 1, 0) + max(rank(2) - 1, 0)) error('tensors:tree:argError', 'Incompatible input sizes.'); end @@ -77,6 +77,8 @@ f.vertices = vertices; f.isdual = isdual; f.rank = rank; + +% assert(all(isallowed(f))); % comment out for debugging end end @@ -115,10 +117,13 @@ %% Splitting tree + vertices_l = []; if rank(1) == 0 charges_l = one(charges{1}); + vertices_l = uint8.empty(1, 0); elseif rank(1) == 1 charges_l = [charges{1}(:) charges{1}(:)]; + vertices_l = uint8.empty(size(charges_l, 1), 0); else switch fusionstyle(charges{1}) case FusionStyle.Unique @@ -126,9 +131,9 @@ inn_l = cumprod(unc_l); charges_l = reshape([unc_l(:).'; inn_l(:).'], [2 1] .* size(unc_l)); charges_l = charges_l(2:end, :).'; + vertices_l = uint8.empty(size(charges_l, 1), 0); case FusionStyle.Simple charges_l = []; - for unc_l = combvec(charges{1:rank(1)}) inn_l = cumprod(unc_l); temp = reshape( ... @@ -136,8 +141,18 @@ [2 1] .* size(inn_l)); charges_l = [charges_l; temp(2:end, :).']; %#ok end - otherwise - error('TBA'); + vertices_l = uint8.empty(size(charges_l, 1), 0); + case FusionStyle.Generic + charges_l = []; + vertices_l = []; + for unc_l = combvec(charges{1:rank(1)}) + [inn_l, vert_l] = cumprod(unc_l); + temp = reshape( ... + [repmat(unc_l(:).', 1, size(inn_l, 2)); inn_l(:).'], ... + [2 1] .* size(inn_l)); + charges_l = [charges_l; temp(2:end, :).']; %#ok + vertices_l = [vertices_l; vert_l.']; %#ok + end end end @@ -145,8 +160,10 @@ %% Fusion tree if rank(2) == 0 charges_r = one(charges{1}); + vertices_r = uint8.empty(1, 0); elseif rank(2) == 1 charges_r = [charges{end}(:) charges{end}(:)]; + vertices_r = uint8.empty(size(charges_r, 1), 0); else switch fusionstyle(charges{end}) case FusionStyle.Unique @@ -154,6 +171,7 @@ inn_r = cumprod(unc_r); charges_r = reshape([unc_r(:).'; inn_r(:).'], [2 1] .* size(unc_r)); charges_r = charges_r(2:end, :).'; + vertices_r = uint8.empty(size(charges_r, 1), 0); case FusionStyle.Simple charges_r = []; for unc_r = combvec(charges{end:-1:rank(1) + 1}) @@ -163,8 +181,18 @@ [2 1] .* size(inn_r)); charges_r = [charges_r; temp(2:end, :).']; %#ok end - otherwise - error('TBA'); + vertices_r = uint8.empty(size(charges_r, 1), 0); + case FusionStyle.Generic + charges_r = []; + vertices_r = []; + for unc_r = combvec(charges{end:-1:rank(1) + 1}) + [inn_r, vert_r] = cumprod(unc_r); + temp = reshape( ... + [repmat(unc_r(:).', 1, size(inn_r, 2)); inn_r(:).'], ... + [2 1] .* size(inn_r)); + charges_r = [charges_r; temp(2:end, :).']; %#ok + vertices_r = [vertices_r; vert_r.']; %#ok + end end end @@ -177,7 +205,7 @@ end charge_cell = cell(size(coupled)); - + vertices_cell = cell(size(coupled)); for i = 1:length(coupled) ind_l = coupled(i) == charges_l(:, end); nnz_l = nnz(ind_l); @@ -187,9 +215,16 @@ charges_l(ind_l, 1:end - 1), 1, []), nnz_r, 1), nnz_r * nnz_l, []), ... repmat(coupled(i), nnz_r * nnz_l, 1), ... repmat(charges_r(ind_r, end - 1:-1:1), nnz_l, 1)]; + + if hasmultiplicity(fusionstyle(charges{1})) + vertices_cell{i} = [reshape(repmat(reshape( ... + vertices_l(ind_l, :), 1, []), nnz_r, 1), nnz_r * nnz_l, []), ... + repmat(vertices_r(ind_r, end:-1:1), nnz_l, 1)]; + end end - f = FusionTree(vertcat(charge_cell{:}), [], [isdual{:}], rank); + f = FusionTree(vertcat(charge_cell{:}), vertcat(vertices_cell{:}), ... + [isdual{:}], rank); f = sort(f); end end @@ -222,15 +257,10 @@ % braided trees in canonical form. if nargin < 3, inv = false; end - if hasmultiplicity(fusionstyle(f)) - error('trees:TBA', 'Not implemented yet.'); - end - assert(f.rank(2) == 0, 'Only defined for splitting trees.'); %% Special case - Abelian if braidingstyle(f) == BraidingStyle.Abelian - if i == 1 f.charges(:, 1:2) = f.charges(:, [2 1]); f.isdual(1:2) = f.isdual([2 1]); @@ -250,49 +280,122 @@ %% Special case - first legs % braiding by only an R-move if i == 1 - if inv - vals = conj(Rsymbol(f.charges(:, 2), f.charges(:, 1), f.charges(:, 3))); - else - vals = Rsymbol(f.charges(:, 1), f.charges(:, 2), f.charges(:, 3)); + if ~hasmultiplicity(fusionstyle(f)) + R = Rsymbol(f.charges(:, 1), f.charges(:, 2), f.charges(:, 3), inv); + f.charges(:, 1:2) = f.charges(:, [2 1]); + f.isdual(1:2) = f.isdual([2 1]); + [f, p] = sort(f); + c = sparse(1:length(f), invperm(p), R); + return end - f.charges(:, 1:2) = f.charges(:, [2 1]); + + charges = f.charges; + [order, diagcharges, diagvertices] = simulsortrows(charges, f.vertices(:, 2:end)); + [~, ia1] = simulunique(diagcharges, diagvertices, 'rows','stable'); + + + [abc, ia2, ic2] = unique(diagcharges(ia1, 1:3), 'rows'); + blocks = cell(1, length(ia2)); + for j = 1:length(ia2) + blocks{j} = Rsymbol(abc(j, 1), abc(j, 2), abc(j, 3), inv); + end + f.charges = f.charges(order, [2 1 3:end]); + f.vertices = f.vertices(order, :); f.isdual(1:2) = f.isdual([2 1]); + c = sparse(blkdiag(blocks{ic2})); [f, p] = sort(f); - c = sparse(1:length(f), invperm(p), vals); + c = c(invperm(order), p); return end + %% General case % braiding by R-move, F-move, R-move - charges = f.charges; %#ok<*PROPLC> - mask = true(1, size(charges, 2)); mask(2 * i - 1) = false; - [diagcharges, order] = sortrows(charges(:, mask)); - charges = charges(order, :); - [~, ia1, ic1] = unique(diagcharges, 'rows'); + if ~hasmultiplicity(fusionstyle(f)) + charges = f.charges; %#ok<*PROPLC> + mask = true(1, size(charges, 2)); mask(2 * i - 1) = false; + [diagcharges, order] = sortrows(charges(:, mask)); + charges = charges(order, :); + [~, ia1, ic1] = unique(diagcharges, 'rows'); + charges1 = charges(ia1, :); + blocks = cell(1, length(ia1)); + newcharges = cell(1, length(ia1)); + + [~, ia2, ic2] = unique(charges1(:, [2*i-3 2*i-2 2*i 2*i+1]), 'rows'); + + for j = 1:length(ia2) + a = charges1(ia2(j), 2 * i - 3); + b = charges1(ia2(j), 2 * i - 2); + cs = charges(ic1 == ia2(j), 2 * i - 1); + d = charges1(ia2(j), 2 * i); + e = charges1(ia2(j), 2 * i + 1); + fs = intersect(a * d, e * conj(b)).'; + blocks{j} = braidingmatrix(a, b, cs, d, e, fs, inv); + + for k = find(j == ic2).' + newcharges{k} = repmat(charges1(k, :), length(fs), 1); + newcharges{k}(:, 2 * i - 2:2 * i) = [newcharges{k}(:, 2 * i) fs(:) ... + newcharges{k}(:, 2 * i - 2)]; + end + end + + f.charges = vertcat(newcharges{:}); + f.isdual(i:i + 1) = f.isdual([i + 1 i]); + c = sparse(blkdiag(blocks{ic2})); + [f, p] = sort(f); + c = c(invperm(order), p); + return + end + + charges = f.charges; + mask1 = true(1, size(charges, 2)); mask1(2*i-1) = false; + vertices = f.vertices; + mask2 = true(1, size(vertices, 2)); mask2(i-1:i) = false; + [order, diagcharges, diagvertices] = ... + simulsortrows(charges(:, mask1), vertices(:, mask2)); + charges = charges(order, :); vertices = vertices(order, :); + [~, ia1, ic1] = simulunique(diagcharges, diagvertices, 'rows'); charges1 = charges(ia1, :); - blocks = cell(1, length(ia1)); - newcharges = cell(1, length(ia1)); + vertices1 = vertices(ia1, :); - [~, ia2, ic2] = unique(charges1(:, [2 * i - 3 2 * i - 2 2 * i 2 * i + 1]), 'rows'); + newcharges = cell(1, length(ia1)); + newvertices = cell(1, length(ia1)); + [~, ia2, ic2] = unique(charges1(:, [2*i-3 2*i-2 2*i 2*i+1]), 'rows'); + blocks = cell(1, length(ia2)); for j = 1:length(ia2) a = charges1(ia2(j), 2 * i - 3); b = charges1(ia2(j), 2 * i - 2); - cs = charges(ic1 == ia2(j), 2 * i - 1); d = charges1(ia2(j), 2 * i); e = charges1(ia2(j), 2 * i + 1); + + cs = intersect(a * b, conj(conj(e) * d)).'; fs = intersect(a * d, e * conj(b)).'; blocks{j} = braidingmatrix(a, b, cs, d, e, fs, inv); + newfs = a.empty(size(blocks{j}, 2), 0); + newverts = zeros([length(newfs), 2], 'uint8'); + ctr = 0; + for f1 = fs.' + N1 = Nsymbol(a, d, f1); + N2 = Nsymbol(e, conj(b), f1); + newfs(ctr + (1:(N1*N2))) = f1; + newverts(ctr + (1:(N1*N2)), :) = combvec(1:N1, 1:N2).'; + ctr = ctr + (N1*N2); + end + assert(size(blocks{j}, 2) == ctr); + for k = find(j == ic2).' - newcharges{k} = repmat(charges1(k, :), length(fs), 1); - newcharges{k}(:, 2 * i - 2:2 * i) = [newcharges{k}(:, 2 * i) fs(:) ... + newcharges{k} = repmat(charges1(k, :), length(newfs), 1); + newcharges{k}(:, 2 * i - 2:2 * i) = [newcharges{k}(:, 2 * i) newfs(:) ... newcharges{k}(:, 2 * i - 2)]; + newvertices{k} = repmat(vertices1(k, :), length(newfs), 1); + newvertices{k}(:, i-1:i) = newverts; end - end f.charges = vertcat(newcharges{:}); + f.vertices = vertcat(newvertices{:}); f.isdual(i:i + 1) = f.isdual([i + 1 i]); c = sparse(blkdiag(blocks{ic2})); [f, p] = sort(f); @@ -338,23 +441,68 @@ % % f : FusionTree % bent trees in canonical form. - if hasmultiplicity(fusionstyle(f)) - error('TBA'); + + if ~hasmultiplicity(fusionstyle(f)) + if f.rank(1) == 0 + error('tensors:trees:ArgError', 'Invalid rank [%d %d]', f.rank(1), f.rank(2)); + elseif f.rank(1) == 1 + [bc, ~, ic] = unique(f.charges(:, 1:2), 'rows'); + b = bc(:, 1); a = repmat(one(b), size(b)); c = bc(:, 2); + else + ind = treeindsequence(f.rank(1)) - 1:treeindsequence(f.rank(1)) + 1; + [abc, ~, ic] = unique(f.charges(:, ind), 'rows'); + a = abc(:, 1); b = abc(:, 2); c = abc(:, 3); + end + + vals = sqrt(qdim(c) ./ qdim(a)) .* Bsymbol(a, b, c); + if f.isdual(f.rank(1)), vals = vals .* conj(frobeniusschur(conj(b))); end + + + f.isdual(f.rank(1)) = ~f.isdual(f.rank(1)); + + if f.rank(2) < 2 + chargesR = [conj(f.charges(:, end - f.rank(2) - 1)) f.charges(:, end - f.rank(2) + 1:end)]; + else + chargesR = [conj(f.charges(:, treeindsequence(f.rank(1)))) ... + f.charges(:, end - treeindsequence(f.rank(2)):end)]; + end + + chargesC = a(ic); + + if f.rank(1) == 1 + chargesL = []; + elseif f.rank(1) == 2 + chargesL = f.charges(:, 1); + else + chargesL = f.charges(:, 1:treeindsequence(f.rank(1) - 1)); + end + + f.charges = [chargesL chargesC chargesR]; + + f.rank = f.rank + int16([-1 +1]); + c = sparse(1:length(f), 1:length(f), vals(ic)); + return end - if f.rank(1) == 0 - error('tensors:trees:ArgError', 'Invalid rank [%d %d]', f.rank(1), f.rank(2)); - elseif f.rank(1) == 1 - [bc, ~, ic] = unique(f.charges(:, 1:2), 'rows'); - b = bc(:, 1); a = repmat(one(b), size(b)); c = bc(:, 2); + [~, ia1] = simulunique(f.charges, ... + f.vertices(:, [1:f.rank(1)-2 f.rank(1):end]), 'rows', 'stable'); + if f.rank(1) == 1 + charges1 = [repmat(one(f.charges), size(ia1)) f.charges(ia1, :)]; + [abc, ia2, ic2] = unique(charges1(:, 1:3), 'rows'); else ind = treeindsequence(f.rank(1)) - 1:treeindsequence(f.rank(1)) + 1; - [abc, ~, ic] = unique(f.charges(:, ind), 'rows'); - a = abc(:, 1); b = abc(:, 2); c = abc(:, 3); + [abc, ia2, ic2] = unique(f.charges(ia1, ind), 'rows'); end - vals = sqrt(qdim(c) ./ qdim(a)) .* Bsymbol(a, b, c); - if f.isdual(f.rank(1)), vals = vals .* conj(frobeniusschur(conj(b))); end + blocks = cell(1, length(ia2)); + for j = 1:length(ia2) + blocks{j} = sqrt(qdim(abc(j, 3)) / qdim(abc(j, 1))) * ... + Bsymbol(abc(j, 1), abc(j, 2), abc(j, 3)); + if f.isdual(f.rank(1)) + blocks{j} = blocks{j} * conj(frobeniusschur(conj(abc(j, 2)))); + end + end + c = sparse(blkdiag(blocks{ic2})); f.isdual(f.rank(1)) = ~f.isdual(f.rank(1)); @@ -365,7 +513,11 @@ f.charges(:, end - treeindsequence(f.rank(2)):end)]; end - chargesC = a(ic); + if f.rank(1) == 1 + chargesC = repmat(one(charges1), size(f.charges, 1), 1); + else + chargesC = f.charges(:, ind(1)); + end if f.rank(1) == 1 chargesL = []; @@ -377,8 +529,13 @@ f.charges = [chargesL chargesC chargesR]; + if f.rank(2) == 0 && f.rank(1) > 1 + f.vertices = f.vertices(:, 1:end-1); + elseif f.rank(1) == 1 + f.vertices = [ones(size(f.charges, 1), 1, 'uint8') f.vertices]; + end + f.rank = f.rank + int16([-1 +1]); - c = sparse(1:length(f), 1:length(f), vals(ic)); end function [c, f] = braid(f, p, lvl, rank) @@ -472,11 +629,7 @@ f.vertices = f.vertices(mask, 2:end); end f.rank(1) = f.rank(1) - 2; - try [f, ~, locb] = unique(f); - catch - bla - end c = sparse(mask, locb, vals, L1, length(f)); return end @@ -506,10 +659,10 @@ u = one(b); mu = f.vertices(mask, i-1); nu = f.vertices(mask, i); - for i = 1:length(vals) - F = Fsymbol(charges1(:, 1), b, charges1(:, 4), ... - charges1(:, 1), charges1(:, 3), u); - vals(i) = vals(i) * F(mu(i), nu(i), 1, 1); + for j = 1:length(vals) + F = Fsymbol(charges1(j, 1), b(j), charges1(j, 4), ... + charges1(j, 1), charges1(j, 3), u); + vals(j) = vals(j) * F(mu(j), nu(j), 1, 1); end else vals = vals .* Fsymbol(charges1(:, 1), b, charges1(:, 4), ... @@ -650,7 +803,29 @@ end return end - error('TBA'); + if f.rank(1) == 0 + bool = f.charges(:, 1) == one(f.charges); + elseif f.rank(1) == 1 + bool = f.charges(:, 1) == f.charges(:, 2); + else + bool = true(length(f), 1); + for i = 1:f.rank(1)-1 + bool = bool & ... + f.vertices(:, i) <= ... + Nsymbol(f.charges(:, 2*i-1), f.charges(:, 2*i), f.charges(:, 2*i+1)); + end + end + if f.rank(2) == 0 + bool = bool & f.charges(:, end) == one(f.charges); + elseif f.rank(2) == 1 + bool = bool & f.charges(:, end) == f.charges(:, end-1); + else + for i = 1:f.rank(2)-1 + bool = bool & ... + f.vertices(:, end-i+1) <= ... + Nsymbol(f.charges(:, end-2*i+2), f.charges(:, end-2*i+1), f.charges(:, end-2*i)); + end + end end function f = flip(f) @@ -671,18 +846,29 @@ % sort - Sort the fusion trees into canonical order. % [f, p] = sort(f) if isempty(f), p = []; return; end - if hasmultiplicity(fusionstyle(f)), error('TBA'); end - cols = [treeindsequence(f.rank(1)) + 1 ... - (1:treeindsequence(f.rank(2))) + treeindsequence(f.rank(1)) + 1 ... - fliplr(1:treeindsequence(f.rank(1)))]; - if nargout > 1 - [f.charges, p] = sortrows(f.charges, cols); + if ~isempty(f.vertices) && hasmultiplicity(fusionstyle(f)) + cols = [treeindsequence(f.rank(1)) + 1 ... % center charge + (1:treeindsequence(f.rank(2))) + treeindsequence(f.rank(1)) + 1 ... % fuse charges + (f.rank(1):size(f.vertices, 2)) + size(f.charges, 2) ... % fuse vertices + fliplr(1:treeindsequence(f.rank(1))) ... % split charges + (1:f.rank(1)-1) + size(f.charges, 2)]; % split vertices + + [p, f.charges, f.vertices] = simulsortrows(f.charges, f.vertices, ... + 'Col', cols); else - f.charges = sortrows(f.charges, cols); + cols = [treeindsequence(f.rank(1)) + 1 ... % center charge + (1:treeindsequence(f.rank(2))) + treeindsequence(f.rank(1)) + 1 ... % fuse charges + fliplr(1:treeindsequence(f.rank(1)))]; % split charges + if nargout > 1 + [f.charges, p] = sortrows(f.charges, cols); + else + f.charges = sortrows(f.charges, cols); + end end end end + %% Setters and Getters methods function c = get.coupled(f) @@ -698,8 +884,10 @@ function f = split(f) f.isdual(f.rank(1)+1:end) = []; f.charges(:, treeindsequence(f.rank(1)) + 2:end) = []; - if f.legs > 2 && hasmultiplicity(fusionstyle(f)) + if f.rank(1) >= 2 && hasmultiplicity(fusionstyle(f)) f.vertices(:, f.rank(1):end) = []; + else + f.vertices = uint8.empty(length(f), 0); end f.rank(2) = 0; end @@ -707,8 +895,10 @@ function f = fuse(f) f.isdual(1:f.rank(1)) = []; f.charges(:, 1:treeindsequence(f.rank(1))) = []; - if f.legs > 2 && hasmultiplicity(fusionstyle(f)) + if f.rank(2) >= 2 && hasmultiplicity(fusionstyle(f)) f.vertices(:, 1:f.rank(1)-1) = []; + else + f.vertices = uint8.empty(length(f), 0); end f.rank(1) = 0; end @@ -843,6 +1033,10 @@ function displayNonScalarObject(f) '| %s |', ... repmat(' %s', 1, max(f.rank(2), 2*f.rank(2)-2)) '\n']; fprintf(chargeFormat, chargeStr.'); + if hasmultiplicity(fusionstyle(f)) + fprintf(' vertices:\n'); + disp(f.vertices); + end end end @@ -934,7 +1128,7 @@ function displayNonScalarObject(f) C_split_ = fusiontensor(f.charges(2*i-3), f.charges(2*i-2), ... f.charges(2*i-1)); if multi - C_split_ = C_split_(:, :, :, f.vertices(i)); + C_split_ = C_split_(:, :, :, f.vertices(i-1)); end if f.isdual(i) C_split_ = contract(C_split_, [-1 1 -3], ... @@ -971,7 +1165,7 @@ function displayNonScalarObject(f) C_fuse_ = fusiontensor(f.charges(end-2*i+4), f.charges(end-2*i+3), ... f.charges(end-2*i+2)); if multi - C_fuse_ = C_fuse_(:, :, :, f.vertices(end-i+1)); + C_fuse_ = C_fuse_(:, :, :, f.vertices(end-i+2)); end if f.isdual(end-i+1) C_fuse_ = contract(C_fuse_, [-1 1 -3], ... diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index ed06962..8ab9ce0 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -725,6 +725,10 @@ if isempty(p), p = 1:nspaces(t); end if isempty(r), r = rank(t); end + + assert(length(p) == nspaces(t), 'Invalid permutation.'); + assert(length(p) == sum(r), 'Invalid new rank.'); + if (all(p == 1:nspaces(t)) && all(rank(t) == r)), return; end persistent cache @@ -744,8 +748,8 @@ tdst = med.structure; map = med.map; else - tdst = similar(t, p, 'Rank', r); - map = permute(fusiontrees(t.codomain, t.domain), p, r); + tdst = similar(@(x,charge) uninit(x), t, p, 'Rank', r); + map = permute(fusiontrees(t), p, r); end tdst.var = axpby(1, t.var, 0, tdst.var, p, map); @@ -1385,8 +1389,6 @@ error('TBA'); end - - if ~doTrunc W1 = prod(t.codomain); W2 = prod(t.domain); diff --git a/src/tensors/charges/A4.m b/src/tensors/charges/A4.m index 0dac682..8c536ab 100644 --- a/src/tensors/charges/A4.m +++ b/src/tensors/charges/A4.m @@ -15,6 +15,10 @@ charge@uint8(labels); end + function B = Bsymbol(a, b, c) + B = eye(Nsymbol(a, b, c)); + end + function style = braidingstyle(~) style = BraidingStyle.Bosonic; end @@ -29,6 +33,10 @@ [varargout{1:nargout}] = cumprod@AbstractCharge(a); end + function nu = frobeniusschur(a) + nu = ones(size(a)); + end + function F = Fsymbol(a, b, c, d, e, f) persistent Fcache if isempty(Fcache) @@ -59,8 +67,26 @@ end function c = mtimes(a, b) - c = A4(1:4); - c(~Nsymbol(repmat(a, 1, 4), repmat(b, 1, 4), c)) = []; + if a > b + c = b * a; + return + end + + if a == 1 + c = b; + elseif a == 4 + c = A4(1:4); + elseif b == 4 + c = b; + elseif a == 3 + c = A4(2); + else % a == 2 + if b == 2 + c = A4(3); + else % b == 3 + c = A4(1); + end + end end function N = Nsymbol(a, b, c) @@ -68,8 +94,9 @@ if isempty(Ncache) load('A4_data.mat', 'Ncache'); end - ind = sub2ind([4 4 4], a, b, c); - N = Ncache(ind); + + linearinds = double(a(:) + 4 * (b(:)-1) + 16 * (c(:)-1)); + N = reshape(Ncache(linearinds), size(a)); end function e = one(~) @@ -96,6 +123,10 @@ R = Rcache{a, b, c}; end + + function s = string(a) + s = compose("%d", a); + end end end diff --git a/src/tensors/charges/AbstractCharge.m b/src/tensors/charges/AbstractCharge.m index 1122372..34ec7ed 100644 --- a/src/tensors/charges/AbstractCharge.m +++ b/src/tensors/charges/AbstractCharge.m @@ -270,15 +270,28 @@ % Complete docstring. % if hasmultiplicity(a.fusionstyle) - error('Not implemented.'); - end - if inv - R1 = conj(Rsymbol(repmat(d, length(c), 1), c, repmat(e, length(c), 1))); - R2 = Rsymbol(repmat(d, length(f), 1), repmat(a, length(f), 1), f); - else - R1 = Rsymbol(c, repmat(d, length(c), 1), repmat(e, length(c), 1)); - R2 = conj(Rsymbol(repmat(a, length(f), 1), repmat(d, length(f), 1), f)); + R1 = arrayfun(@(x) Rsymbol(d, x, e, inv), c, 'UniformOutput', false); + R2 = arrayfun(@(x) Rsymbol(d, a, x, ~inv), f, 'UniformOutput', false); + F = arrayfun(@(x,y) Fsymbol(d, a, b, e, x, y), ... + repmat(f(:).', length(c), 1), repmat(c(:), 1, length(f)), ... + 'UniformOutput', false); + + blocks = cell(length(c), length(f)); + for i = 1:length(c) + for j = 1:length(f) + blocks{i,j} = contract(... + R1{i}, [-2 1], ... + F{i,j}, [2 -3 -1 1], ... + R2{j}, [-4 2]); + sz = size(blocks{i,j}, 1:4); + blocks{i,j} = reshape(blocks{i,j}, sz(1) * sz(2), sz(3) * sz(4)); + end + end + B = cell2mat(blocks); + return end + R1 = Rsymbol(c, repmat(d, length(c), 1), repmat(e, length(c), 1), inv); + R2 = Rsymbol(repmat(d, length(f), 1), repmat(a, length(f), 1), f, ~inv); R1 = reshape(R1, [], 1); R2 = reshape(R2, 1, []); @@ -496,7 +509,7 @@ for i = 2:length(a) d_ = d(1) * a(i); if nargout > 1 - N_(1:length(d_)) = N(1) .* ... + N_ = N(1) .* ... Nsymbol(repmat(d(1), 1, length(d_)), ... repmat(a(i), 1, length(d_)), d_); end diff --git a/src/tensors/charges/O2.m b/src/tensors/charges/O2.m index bfde591..854bb8d 100644 --- a/src/tensors/charges/O2.m +++ b/src/tensors/charges/O2.m @@ -278,7 +278,11 @@ d([a.j] ~= 0) = 2; end - function R = Rsymbol(a, b, c) + function R = Rsymbol(a, b, c, inv) + if nargin > 3 && inv + R = Rsymbol(b, a, c); + return + end R = Nsymbol(a, b, c); R([c.s] == 1 & [a.j] > 0) = -R([c.s] == 1 & [a.j] > 0); end diff --git a/src/tensors/charges/ProductCharge.m b/src/tensors/charges/ProductCharge.m index 1afd14a..4dc109d 100644 --- a/src/tensors/charges/ProductCharge.m +++ b/src/tensors/charges/ProductCharge.m @@ -366,8 +366,10 @@ col = 1:size(a, 2) direction = 'ascend' end + + [I, a.charges{:}] = simulsortrows(a.charges{:}, ... - 'Col', col, 'Direction', direction); + 'Col', repmat({col}, size(a.charges)), 'Direction', direction); end function s = string(a) diff --git a/src/tensors/kernels/MatrixBlock.m b/src/tensors/kernels/MatrixBlock.m index 8b9b671..a23ce38 100644 --- a/src/tensors/kernels/MatrixBlock.m +++ b/src/tensors/kernels/MatrixBlock.m @@ -28,7 +28,6 @@ [lia, locb] = ismember(uncoupled, ch, 'rows'); tdims = d(locb(lia), :); -% tdims = computedegeneracies(codomain, domain, uncoupled); mdims = [prod(tdims(:, 1:rank(1)), 2) prod(tdims(:, (1:rank(2)) + rank(1)), 2)]; splits = split(trees); fuses = fuse(trees); @@ -153,7 +152,6 @@ for j = 1:length(rowsz) - 1 ind = j + (k-1) * (length(rowsz) - 1); offset = offset + 1; - vars_in{offset} = reshape(permute(reshape(... var_in(rowsz(j)+1:rowsz(j+1), colsz(k)+1:colsz(k+1)), ... oldtdims(ind, :)), ... diff --git a/src/utility/simulsortrows.m b/src/utility/simulsortrows.m index dd72157..0be7897 100644 --- a/src/utility/simulsortrows.m +++ b/src/utility/simulsortrows.m @@ -38,7 +38,7 @@ end arguments - kwargs.Col = 1:size(arrays{1}, 2) + kwargs.Col = [] kwargs.Direction = 'ascend' end @@ -46,20 +46,48 @@ sz = size(arrays{1}); assert(length(sz) == 2, 'Input should be matrices.'); for i = 2:length(arrays) - assert(all(sz == size(arrays{i})), 'Input arrays should have equal sizes.'); + assert(all(sz(1) == size(arrays{i}, 1)), 'Input arrays should have equal sizes.'); end -%% Sort last array -[varargout{length(arrays)}, I] = sortrows(arrays{end}, kwargs.Col, kwargs.Direction); +%% Sort with columns +if ~isempty(kwargs.Col) + cols = cellfun(@(x) size(x, 2), arrays); + arrayinds = zeros(1, length(cols)); + ctr = 0; + for i = 1:length(arrays) + arrayinds(ctr+(1:cols(i))) = i; + ctr = ctr + cols(i); + end + + col = kwargs.Col(end); + [varargout{arrayinds(col)}, I] = sortrows(arrays{arrayinds(col)}, ... + col - sum(cols(1:arrayinds(col)-1))); + for k = [length(arrays):-1:arrayinds(col)+1 arrayinds(col)-1:-1:1] + varargout{k} = arrays{k}(I, :); + end + + for col = kwargs.Col(end-1:-1:1) + [varargout{arrayinds(col)}, I_] = sortrows(varargout{arrayinds(col)}, ... + col - sum(cols(1:arrayinds(col)-1))); + for k = 1:length(arrays) + if k == arrayinds(col), continue; end + varargout{k} = varargout{k}(I_, :); + end + I = I(I_); + end + return; +end + + +%% Sort without columns +[varargout{length(arrays)}, I] = sortrows(arrays{end}, kwargs.Direction); for k = length(arrays)-1:-1:1 varargout{k} = arrays{k}(I, :); end - -%% Sort other arrays for n = length(arrays)-1:-1:1 - [varargout{n}, I_] = sortrows(varargout{n}, kwargs.Col, kwargs.Direction); + [varargout{n}, I_] = sortrows(varargout{n}, kwargs.Direction); for k = 1:length(arrays) if k == n, continue; end varargout{k} = varargout{k}(I_, :); @@ -67,4 +95,5 @@ I = I(I_); end + end diff --git a/src/utility/simulunique.m b/src/utility/simulunique.m new file mode 100644 index 0000000..27a2e6a --- /dev/null +++ b/src/utility/simulunique.m @@ -0,0 +1,142 @@ +function varargout = simulunique(varargin) +%SIMULUNIQUE Set unique over multiple arrays. + + +nlhs = max(1, nargout); +narginchk(1, 4); + +nrhs = nargin; + +% acceptable combinations, with optional inputs denoted in []: +% unique(A..., ['rows'], ['first'/'last']) +% unique(A..., ['rows'], ['sorted'/'stable']) +% where the optional arguments may have reversed order. + +flagvals = ["rows" "first" "last" "sorted" "stable"]; +flaginds = false(size(flagvals)); + +i = nrhs; +while i > 1 + flag = varargin{i}; + if ~isstring(flag) && ~ischar(flag), break; end + + foundflag = startsWith(flagvals, flag, 'IgnoreCase', true); + if sum(foundflag) ~= 1 + error('utility:simulunique:argerror', 'Unknown flag.'); + end + if flaginds(foundflag) + error('utility:simulunique:argerror', 'Repeated flag.'); + end + flaginds(foundflag) = true; + i = i - 1; +end +if sum(flaginds(2:5)) == 0, flaginds(4) = true; end + +first = flaginds(2); last = flaginds(3); +assert(~(first && last), 'utility:simulunique:argerror', 'Flag combination not allowed.'); +sorted = flaginds(4); stable = flaginds(5); +assert(~(sorted && stable), ... + 'utility:simulunique:argerror', 'Flag combination not allowed,'); +assert(~((sorted || stable) && (first || last)), ... + 'utility:simulunique:argerror', 'Flag combination not allowed.'); + +if flaginds(1) + [varargout{1:nlhs}] = simuluniquerows(varargin(1:i), first, last, sorted, stable); +else + [varargout{1:nlhs}] = simuluniqueels(varargin(1:i), first, last, sorted, stable); +end + +end + +function [C, indA, indC] = simuluniquerows(A, first, last, sorted, stable) + +assert(all(cellfun(@ismatrix, A)), 'utility:simulunique:argerror', 'Input not matrices.'); + +numRows = size(A{1}, 1); +numCols = sum(cellfun(@(x) size(x, 2), A)); + +sortA = cell(size(A)); +[indSortA, sortA{:}] = simulsortrows(A{:}); + +groupsSortA = any(sortA{1}(1:numRows-1,:) ~= sortA{1}(2:numRows, :), 2); +for i = 2:length(A) + groupsSortA = groupsSortA | ... + any(sortA{i}(1:numRows-1,:) ~= sortA{i}(2:numRows,:), 2); +end + +if numRows ~= 0 + if last + groupsSortA = [groupsSortA; true]; + else + groupsSortA = [true; groupsSortA]; + end +end + +% create output +C = cell(size(A)); +if stable + invIndSortA = invperm(indSortA); + logIndA = groupsSortA(invIndSortA); + for i = 1:length(A) + C{i} = A{i}(logIndA, :); + end +else + for i = 1:length(A) + C{i} = sortA{i}(groupsSortA, :); + end +end + +% find indA +if nargout > 1 + if stable + indA = find(logIndA); + else + indA = indSortA(groupsSortA); + end +end + +% find indC +if nargout == 3 + if last + if numRows == 0 + indC = cumsum(full(groupsSortA)); + else + indC = cumsum([1; full(groupsSortA(1:end-1))]); + end + indC(indSortA) = indC; + elseif sorted + indC = cumsum(full(groupsSortA)); + indC(indSortA) = indC; + else % stable + if numCols == 0 + indC = ones(numRows, 1); + elseif numRows == 0 + indC = zeros(0, 1); + else + indSortC = simulsortrows(C{:}); + + lengthGroupSortA = diff(find([groupsSortA; true])); + + diffIndSortC = diff(indSortC); + diffIndSortC = [indSortC(1); diffIndSortC]; + + indLengthGroupSortA = cumsum([1; lengthGroupSortA]); + indLengthGroupSortA(end) = []; + + indCOrderedBySortA(indLengthGroupSortA, 1) = diffIndSortC; + + if sum(lengthGroupSortA) ~= length(indCOrderedBySortA) + indCOrderedBySortA(sum(lengthGroupSortA), 1) = 0; + end + + indCOrderedBySortA = cumsum(indCOrderedBySortA); + indC = indCOrderedBySortA(invIndSortA); + end + end +end +end + + +function simuluniqueels(arrays, first, last, sorted, stable) +error('TBA'); +end \ No newline at end of file diff --git a/test/TestFusionTree.m b/test/TestFusionTree.m index 15c0131..3544b2c 100644 --- a/test/TestFusionTree.m +++ b/test/TestFusionTree.m @@ -5,7 +5,7 @@ properties (ClassSetupParameter) weight = {'small', 'medium'}%, 'large'} - charge = {'Z1', 'Z2', 'U1', 'O2', 'SU2', 'Z2xU1'} + charge = {'A4', 'Z1', 'Z2', 'U1', 'O2', 'SU2', 'Z2xU1'} end methods (TestClassSetup) @@ -55,13 +55,24 @@ function generateTrees(tc, charge, weight) chargeset = ProductCharge(Z2([0 0 0 0 0 1 1 1 1 1]), ... U1([-2:2 -2:2])); end + + case 'A4' + switch weight + case 'small' + chargeset = A4([1 4]); + case 'medium' + chargeset = A4(1:4); + case 'large' + chargeset = A4(1:4); + end + end %% Setup weight switch weight case 'small' legs = 0:3; - maxTrees = 25; + maxTrees = 15; maxLegs = 4; tc.testWeight = 0.5; case 'medium' @@ -129,7 +140,7 @@ function trees_properties(tc) if i == 1 && j == 1 || isempty(tc.trees{i,j}) continue; end - verifyTrue(tc, all(isallowed(tc.trees{i,j})), ... + assertTrue(tc, all(isallowed(tc.trees{i,j})), ... 'Generated invalid fusion trees.'); end end @@ -143,7 +154,7 @@ function braiding(tc) if rand < tc.testWeight f = tc.trees{i}; ps = perms(1:f.legs); - for p = ps(randperm(size(ps, 2), min(size(ps, 2), 5)), :).' + for p = ps(randperm(size(ps, 1), min(size(ps, 1), 5)), :).' lvl = randperm(f.legs); indout = randi([0 f.legs]); @@ -226,44 +237,47 @@ function traces(tc) end end end - % function repartitioning(tc) - % for i = 1:numel(tc.trees) - % if isempty(tc.trees{i}) || tc.trees{i}.legs < 2 - % continue; - % end - % if rand < tc.testWeight - % f = tc.trees{i}; - % for n = 0:f.legs - % [f1, c1] = repartition(f, [n f.legs-n]); - % verifyEqual(tc, full(abs(c1).^2 * qdim(f1.coupled)), ... - % qdim(f.coupled), 'AbsTol', tc.tol, 'RelTol', tc.tol, ... - % 'Repartition must preserve centernorm.'); - % verifyEqual(tc, isallowed(f1), true(size(f1), 1), ... - % 'Output trees are not allowed.'); - % - % [f2, c2] = repartition(f1, f.rank); - % verifyEqual(tc, c1 * c2, speye(size(f)), 'AbsTol', tc.tol, ... - % 'RelTol', tc.tol, 'Repartition should be invertible.'); - % verifyEqual(tc, f, f2, 'Repartition should be invertible.'); - % - % if issymmetric(braidingstyle(f)) - % a1_cell = arrayfun(@double, f, 'UniformOutput', false); - % a2_cell = arrayfun(@double, f1, 'UniformOutput', false); - % for j = 1:length(f) - % a1 = a1_cell{j}; - % a2 = zeros(size(a1)); - % [~, col, val] = find(c1(j, :)); - % for k = 1:length(val) - % a2 = a2 + val(k) * a2_cell{col(k)}; - % end - % verifyEqual(tc, a2, a1, 'AbsTol', tc.tol, ... - % 'Repartition should be compatible with fusiontensors.'); - % end - % end - % end - % end - % end - % end + function repartitioning(tc) + for i = 1:numel(tc.trees) + if isempty(tc.trees{i}) || tc.trees{i}.legs < 2 + continue; + end + if rand < tc.testWeight + f = tc.trees{i}; + for n = 0:f.legs + [c1, f1] = repartition(f, [n f.legs-n]); + + assertEqual(tc, size(c1), [length(f) length(f1)], ... + 'Coefficients have the wrong size.'); + assertEqual(tc, full(abs(c1).^2 * qdim(f1.coupled)), ... + qdim(f.coupled), 'AbsTol', tc.tol, 'RelTol', tc.tol, ... + 'Repartition must preserve centernorm.'); + assertEqual(tc, isallowed(f1), true(length(f1), 1), ... + 'Output trees are not allowed.'); + + [c2, f2] = repartition(f1, f.rank); + assertEqual(tc, c1 * c2, speye(length(f)), 'AbsTol', tc.tol, ... + 'RelTol', tc.tol, 'Repartition should be invertible.'); + assertEqual(tc, f, f2, 'Repartition should be invertible.'); + + if issymmetric(braidingstyle(f)) + a1_cell = fusiontensor(f); + a2_cell = fusiontensor(f1); + for j = 1:length(f) + a1 = a1_cell{j}; + a2 = zeros(size(a1)); + [~, col, val] = find(c1(j, :)); + for k = 1:length(val) + a2 = a2 + val(k) * a2_cell{col(k)}; + end + assertEqual(tc, a2, a1, 'AbsTol', tc.tol, ... + 'Repartition should be compatible with fusiontensors.'); + end + end + end + end + end + end % end % diff --git a/test/TestTensor.m b/test/TestTensor.m index 60b4fdc..7c1bfc9 100644 --- a/test/TestTensor.m +++ b/test/TestTensor.m @@ -5,6 +5,14 @@ tol = 1e-12 end + methods (TestClassSetup) + function classSetup(tc) + orig = Options.CacheEnabled; + Options.CacheEnabled(false); + tc.addTeardown(@Options.CacheEnabled, orig); + end + end + properties (TestParameter) spaces = struct(... 'cartesian', CartesianSpace(3, [], 4, [], 5, [], 6, [], 7, []), ... @@ -15,7 +23,9 @@ U1(0, 1, -1), [2 2 1], true, U1(0, 1, -1), [1, 2, 3], false, ... U1(0, 1, -1), [1 3 3], true), ... 'SU2', GradedSpace.new(SU2(1, 2), [3 1], false, SU2(1, 3), [2 1], false, ... - SU2(2, 3), [1 1], true, SU2(1, 2), [2 2], false, SU2(1, 2, 4), [1 1 1], true) ... + SU2(2, 3), [1 1], true, SU2(1, 2), [2 2], false, SU2(1, 2, 4), [1 1 1], true), ... + 'A4', GradedSpace.new(A4(1:2), [2 1], false, A4([1 4]), [1 2], false, ... + A4(1:4), [2 1 3 1], true, A4(1:4), [2 2 1 2], false, A4(2:3), [1 2], true) ... ) end @@ -55,7 +65,6 @@ function basic_linear_algebra(tc, spaces) function matrix_functions(tc, spaces) for i = 1:3 t = Tensor.randnc(spaces(1:i), spaces(1:i)); - assertTrue(tc, isapprox(t*t, t^2, 'AbsTol', tc.tol, 'RelTol', tc.tol)); assertTrue(tc, isapprox(t*t*t, t^3, 'AbsTol', tc.tol, 'RelTol', tc.tol)); @@ -78,6 +87,7 @@ function matrix_functions(tc, spaces) end end + function permute_via_inner(tc, spaces) rng(213); t1 = Tensor.rand(spaces, []); @@ -110,10 +120,10 @@ function permute_via_conversion(tc, spaces) a = double(t); rng(123); tc.assertTrue(all(dims(t) == size(a, 1:nspaces(t)))); - for k = 0:5 - ps = perms(1:5).'; + for k = 0:nspaces(t) + ps = perms(1:nspaces(t)).'; for p = ps(:, randperm(size(ps, 2), min(size(ps, 2), 10))) - t2 = permute(t, p.', [k 5-k]); + t2 = permute(t, p.', [k nspaces(t)-k]); a2 = double(t2); tc.assertTrue(all(dims(t2) == size(a2, 1:nspaces(t)))); tc.assertTrue(all(dims(t2) == size(a, p.'))); @@ -168,7 +178,7 @@ function orthogonalize(tc, spaces) p1 = [3 4 2]; p2 = [1 5]; - for alg = ["qr", "qrpos", "ql", "qlpos", "polar", "svd"] + for alg = ["qr", "qrpos", "polar", "svd"] % ql qlpos [Q, R] = leftorth(t, p1, p2, alg); assertTrue(tc, ... @@ -197,7 +207,7 @@ function orthogonalize(tc, spaces) %% Right orthogonalize p1 = [3 4]; p2 = [2 1 5]; - for alg = ["rq", "rqpos", "lq", "lqpos", "polar", "svd"] + for alg = ["lq", "lqpos", "polar", "svd"] % rq rqpos [L, Q] = rightorth(t, p1, p2, alg); assertTrue(tc, ... @@ -211,13 +221,13 @@ function orthogonalize(tc, spaces) switch alg case 'polar' - assertTrue(tc, isposdef(L)); + assertTrue(tc, isposdef(L), 'L should be positive definite.'); case {'rq', 'rqpos'} - assertTrue(tc, istriu(L)); + assertTrue(tc, istriu(L), 'L should be upper triangular.'); case {'lq', 'lqpos'} - assertTrue(tc, istril(L)); + assertTrue(tc, istril(L) ,'L should be lower triangular.'); end end From 450d622563140125d661cb1f8a5dd8b52914d5f6 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sat, 3 Sep 2022 10:10:27 +0200 Subject: [PATCH 011/245] Fix sortrows for product charges. --- src/tensors/charges/ProductCharge.m | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/tensors/charges/ProductCharge.m b/src/tensors/charges/ProductCharge.m index 4dc109d..2f52533 100644 --- a/src/tensors/charges/ProductCharge.m +++ b/src/tensors/charges/ProductCharge.m @@ -363,13 +363,20 @@ function [a, I] = sortrows(a, col, direction) arguments a - col = 1:size(a, 2) + col (1,:) double = [] direction = 'ascend' end + if nargin == 1 || isempty(col) || isequal(col, 1:size(a, 2)) + [I, a.charges{:}] = simulsortrows(a.charges{:}, ... + 'Direction', direction); + return + end + newcol = size(a.charges, 2) * (col(:) - 1) + (1:size(a.charges, 2)); [I, a.charges{:}] = simulsortrows(a.charges{:}, ... - 'Col', repmat({col}, size(a.charges)), 'Direction', direction); + 'Col', reshape(newcol, 1, []), ... + 'Direction', direction); end function s = string(a) From b503fead047c2ca8384ee153b8e8978546d995f8 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sun, 4 Sep 2022 13:41:16 +0200 Subject: [PATCH 012/245] ZN charges. --- src/tensors/charges/Z2.m | 1 - src/tensors/charges/ZN.m | 187 +++++++++++++++++++++++++++++++++++++++ test/TestCharge.m | 1 + 3 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 src/tensors/charges/ZN.m diff --git a/src/tensors/charges/Z2.m b/src/tensors/charges/Z2.m index b14fbdd..58a9376 100644 --- a/src/tensors/charges/Z2.m +++ b/src/tensors/charges/Z2.m @@ -10,7 +10,6 @@ % See also AbstractCharge methods - function A = Asymbol(a, b, c) A = double(Nsymbol(a, b, c)); end diff --git a/src/tensors/charges/ZN.m b/src/tensors/charges/ZN.m new file mode 100644 index 0000000..88daf6d --- /dev/null +++ b/src/tensors/charges/ZN.m @@ -0,0 +1,187 @@ +classdef ZN < AbstractCharge & uint8 + % ZN - Irreducible representations of ZN. + % This class represents representations of the cyclic group of order N. + % + % See also AbstractCharge, Z2, Z3, Z4 + + properties + N (1,1) uint8 = uint8(1) + end + + methods + function A = Asymbol(a, b, c) + A = double(Nsymbol(a, b, c)); + end + + function style = braidingstyle(~) + style = BraidingStyle.Abelian; + end + + function B = Bsymbol(a, b, c) + B = double(Nsymbol(a, b, c)); + end + + function c = cat(dim, varargin) + if nargin == 2 + c = varargin{1}; + return + end + if nargin > 3 + c = cat(dim, cat(dim, varargin{1:floor(end/2)}), ... + cat(dim, varargin{floor(end/2)+1:end})); + return + end + if isempty(varargin{1}) + c = varargin{2}; + return + end + if isempty(varargin{2}) + c = varargin{1}; + return + end + a = varargin{1}; + b = varargin{2}; + assert(a.N == b.N); + c = ZN(a.N, cat(dim, uint8(a), uint8(b))); + end + + function b = conj(a) + b = ZN(a.N, mod(a.N - a, a.N)); + end + + function [charges, vertices] = cumprod(a) + charges = ZN(a.N, mod(cumsum(a), a.N)); + vertices = []; + end + + function nu = frobeniusschur(a) + nu = ones(size(a)); + end + + function F = Fsymbol(a, b, c, d, e, f) + F = double(Nsymbol(a, b, e) .* Nsymbol(b, c, f) .* ... + Nsymbol(e, c, d) .* Nsymbol(a, f, d)); + end + + function style = fusionstyle(~) + style = FusionStyle.Unique; + end + + function C = fusiontensor(a, b, c) + C = double(Nsymbol(a, b, c)); + end + + function s = GetMD5_helper(data) + s.N = data.N; + s.a = uint8(data); + end + + function c = horzcat(varargin) + c = cat(2, varargin{:}); + end + + function c = mtimes(a, b) + assert(a.N == b.N); + c = ZN(a.N, mod(a + b, a.N)); + end + + function N = Nsymbol(a, b, c) + assert(a.N == b.N && b.N == c.N) + N = mod(a + b, a.N) == c; + end + + function e = one(a) + e = ZN(a.N, uint8(0)); + end + + function [d, N] = prod(a, dim) + if nargin < 2 || isempty(dim) + dim = find(size(a) ~= 1, 1); + end + d = ZN(a.N, mod(sum(a, dim), a.N)); + if nargout > 1 + N = ones(size(d)); + end + end + + function b = repmat(a, varargin) + b = ZN(a.N, repmat(uint8(a), varargin{:})); + end + + function b = reshape(a, varargin) + b = ZN(a.N, reshape(uint8(a), varargin{:})); + end + + function R = Rsymbol(a, b, c, ~) + R = double(Nsymbol(a, b, c)); + end + + function varargout = sort(a, varargin) + [varargout{1:nargout}] = sort(uint8(a), varargin{:}); + varargout{1} = ZN(a.N, varargout{1}); + end + + function s = string(a) + s = compose("%d", uint8(a)); + end + + function varargout = subsref(a, s) + switch s(1).type + case '()' + b = ZN(a.N, subsref(uint8(a), s(1))); + if length(s) == 1 + varargout = {b}; + else + [varargout{1:nargout}] = subsref(b, s(2:end)); + end + case '{}' + error('Cannot use {} to index.'); + case '.' + [varargout{1:nargout}] = builtin('subsref', a, s); + otherwise + error('Undefined behaviour.'); + end + end + + function a = subsasgn(a, s, b) + switch s(1).type + case '()' + assert(lenght(s) == 1, 'Undefined assignment syntax.'); + if isempty(a.N) + N = b.N + else + assert(a.N == b.N) + N = b.N; + end + a = ZN(N, subsasgn(uint8(a), s, b)); + case '{}' + error('Cannot use {} to assign.'); + case '.' + a = builtin('subsasgn', a, s, b); + otherwise + error('Undefined behaviour.'); + end + end + + function b = transpose(a) + b = ZN(a.N, transpose(uint8(a))); + end + + function c = vertcat(varargin) + c = cat(1, varargin{:}); + end + + function charge = ZN(N, varargin) + if nargin < 2 + labels = []; + else + labels = horzcat(varargin{:}); + end + charge@uint8(labels); + if nargin > 0 + charge.N = N; + end + end + end +end + diff --git a/test/TestCharge.m b/test/TestCharge.m index b949d72..abe878b 100644 --- a/test/TestCharge.m +++ b/test/TestCharge.m @@ -9,6 +9,7 @@ smallset = struct( ... 'Z1', Z1, ... 'Z2', Z2([false, true]), ... + 'Z4', ZN(4, 0:3), ... 'U1', U1(-2:2), ... 'O2', O2([0 0 1 2], [0 1 2 2]), ... 'SU2', SU2(1:4), ... From eae62c47cee203884e7abe753eacb1b440631dbb Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sun, 4 Sep 2022 13:41:33 +0200 Subject: [PATCH 013/245] Fix orthorgonalize tests. --- src/tensors/spaces/CartesianSpace.m | 9 ++ src/tensors/spaces/ComplexSpace.m | 9 ++ src/tensors/spaces/GradedSpace.m | 155 +++------------------------- src/utility/linalg/leftorth.m | 30 ++++-- test/TestTensor.m | 6 +- 5 files changed, 57 insertions(+), 152 deletions(-) diff --git a/src/tensors/spaces/CartesianSpace.m b/src/tensors/spaces/CartesianSpace.m index e3aaa31..11c3f3d 100644 --- a/src/tensors/spaces/CartesianSpace.m +++ b/src/tensors/spaces/CartesianSpace.m @@ -251,6 +251,15 @@ bools = [spaces1.dimensions] == [spaces2.dimensions]; end + function bool = ge(space1, space2) + bool = le(space2, space1); + end + + function bool = le(space1, space2) + assert(isscalar(space1) && isscalar(space2)); + bool = degeneracies(space1) <= degeneracies(space2); + end + function hashable = GetMD5_helper(spaces) % Helper function for hash algorithm. This converts the space object to a data % structure which can be processed by :function:`GetMD5`. diff --git a/src/tensors/spaces/ComplexSpace.m b/src/tensors/spaces/ComplexSpace.m index 8111481..354751f 100644 --- a/src/tensors/spaces/ComplexSpace.m +++ b/src/tensors/spaces/ComplexSpace.m @@ -230,6 +230,15 @@ [spaces1.isdual] == [spaces2.isdual]; end + function bool = ge(space1, space2) + bool = le(space2, space1); + end + + function bool = le(space1, space2) + assert(isscalar(space1) && isscalar(space2)); + bool = degeneracies(space1) <= degeneracies(space2); + end + function hashable = GetMD5_helper(spaces) % Helper function for hash algorithm. This converts the space object to a data % structure which can be processed by :function:`GetMD5`. diff --git a/src/tensors/spaces/GradedSpace.m b/src/tensors/spaces/GradedSpace.m index a2f4b39..916ca67 100644 --- a/src/tensors/spaces/GradedSpace.m +++ b/src/tensors/spaces/GradedSpace.m @@ -298,146 +298,21 @@ end end -% -% -% -% function engine = update_hash(spaces, engine) -% engine = update_hash({spaces.charges, spaces.bonds, spaces.isdual}, engine); -% end -% -% function s = GetMD5_helper(data) -% s = {data.charges, data.bonds, data.isdual}; -% end -% -% function bool = hassymmetry(codomain, domain) -% if isempty(codomain) -% bool = ~isa(domain(1).charges, 'Z1'); -% return -% end -% bool = ~isa(codomain(1).charges, 'Z1'); -% end -% -% function [engine, digest] = hash(spaces, engine) -% if nargin < 2 || isempty(engine) -% engine = init_hash(); -% end -% -% engine.update(uint8('CartesianSpace')); -% d = dim(spaces); -% if ~isempty(d) -% engine.update(typecast(d, 'uint8')); -% end -% -% if nargout > 1 -% digest = engine.digest; -% end -% end -% -% function d = computedims(codomain, domain, charges) -% assert(length(codomain) + length(domain) == size(charges, 2)); -% -% d = zeros(size(charges)); -% for i = 1:size(d, 2) -% if i <= length(codomain) -% d(:, i) = dims(codomain(i), charges(:, i)); -% else -% d(:, i) = dims(domain(end + 1 - (i - length(codomain))), charges(:, i)); -% end -% end -% if size(d, 2) == 1 -% if isempty(codomain) -% d = [ones(size(d)) d]; -% else -% d = [d ones(size(d))]; -% end -% end -% end -% -% function d = computedegeneracies(codomain, domain, charges) -% assert(length(codomain) + length(domain) == size(charges, 2)); -% -% d = zeros(size(charges)); -% for i = 1:size(d, 2) -% if i <= length(codomain) -% d(:, i) = degeneracy(codomain(i), charges(:, i)); -% else -% d(:, i) = degeneracy(domain(end + 1 - (i - length(codomain))), charges(:, i)); -% end -% end -% if size(d, 2) == 1 -% if isempty(codomain) -% d = [ones(size(d)) d]; -% else -% d = [d ones(size(d))]; -% end -% end -% end -% -% function d = dim(spaces, charges) -% d = zeros(1, length(spaces)); -% if nargin == 1 -% for i = 1:length(spaces) -% d(i) = sum(dims(spaces(i))); -% end -% return -% end -% for i = 1:length(spaces) -% d(i) = sum(dims(spaces(i), charges(:, i))); -% end -% end -% -% function d = degeneracy(space, charges) -% if nargin == 1 || isempty(charges) -% d = space.bonds; -% return -% end -% d = zeros(1, length(charges)); -% [lia, locb] = ismember(charges, space.int_charges); -% d(lia) = space.bonds(locb(lia)); -% end -% % -% % function d = dims(space, charges) -% % if nargin == 1 || isempty(charges) -% % d = qdim(space.charges) .* space.bonds; -% % return -% % end -% % d = zeros(1, length(charges)); -% % [lia, locb] = ismember(charges, space.int_charges); -% % d(lia) = qdim(space.charges(locb(lia))) .* space.bonds(locb(lia)); -% % end -% % -% -% -% -% function bools = eq(A, B) -% if isempty(A) && isempty(B) -% bools = []; -% return -% end -% if isscalar(A) -% bools = false(size(B)); -% for i = 1:numel(B) -% bools(i) = A.isdual == B(i).isdual && ... -% all(A.bonds == B(i).bonds) && ... -% all(A.charges == B(i).charges); -% end -% return -% end -% if isscalar(B) -% bools = B == A; -% return -% end -% -% assert(all(size(A) == size(B))); -% bools = false(size(A)); -% for i = 1:numel(A) -% bools(i) = A(i).isdual == B(i).isdual && ... -% all(A(i).bonds == B(i).bonds) && ... -% all(A(i).charges == B(i).charges); -% end -% end -% - + function bool = ge(space1, space2) + bool = le(space2, space1); + end + + function bool = le(space1, space2) + assert(isscalar(space1) && isscalar(space2)); + [lia, locb] = ismember(charges(space1), charges(space2)); + if ~all(lia) + bool = false; + return + end + d1 = degeneracies(space1); + d2 = degeneracies(space2); + bool = all(d1 <= d2(locb)); + end function style = braidingstyle(codomain, domain) if nargin == 1 || ~isempty(codomain) diff --git a/src/utility/linalg/leftorth.m b/src/utility/linalg/leftorth.m index 2a621db..28bd781 100644 --- a/src/utility/linalg/leftorth.m +++ b/src/utility/linalg/leftorth.m @@ -11,11 +11,16 @@ case 'qrpos' [Q, R] = qr(A, 0); - D = diag(R); - D(abs(D) < 1e-12) = 1; - D = sign(D); - Q = Q .* D'; - R = D .* R; + if isrow(Q) + Q = Q * sign(R(1)); + R = R * sign(R(1)); + else + D = diag(R); + D(abs(D) < 1e-12) = 1; + D = sign(D); + Q = Q .* D'; + R = D .* R; + end case 'ql' [Q, R] = qr(flip(A, 2), 0); @@ -24,11 +29,16 @@ case 'qlpos' [Q, R] = qr(flip(A, 2), 0); - D = diag(R); - D(abs(D) < 1e-12) = 1; - D = sign(D); - Q = Q .* D'; - R = D .* R; + if isrow(Q) + Q = Q * sign(R(1)); + R = R * sign(R(1)); + else + D = diag(R); + D(abs(D) < 1e-12) = 1; + D = sign(D); + Q = Q .* D'; + R = D .* R; + end Q = flip(Q, 2); R = flip(flip(R, 1), 2); diff --git a/test/TestTensor.m b/test/TestTensor.m index 7c1bfc9..f19d84e 100644 --- a/test/TestTensor.m +++ b/test/TestTensor.m @@ -177,8 +177,9 @@ function orthogonalize(tc, spaces) %% Left orthogonalize p1 = [3 4 2]; p2 = [1 5]; + tc.assumeTrue(spaces(3) * spaces(4) * spaces(2) >= spaces(1)' * spaces(5)') - for alg = ["qr", "qrpos", "polar", "svd"] % ql qlpos + for alg = ["qr", "qrpos", "polar", "svd" "ql" "qlpos"] [Q, R] = leftorth(t, p1, p2, alg); assertTrue(tc, ... @@ -205,9 +206,10 @@ function orthogonalize(tc, spaces) %% Right orthogonalize + tc.assumeTrue(spaces(3) * spaces(4) <= spaces(1)' * spaces(2)' * spaces(5)'); p1 = [3 4]; p2 = [2 1 5]; - for alg = ["lq", "lqpos", "polar", "svd"] % rq rqpos + for alg = ["lq", "lqpos", "polar", "svd" "rq" "rqpos"] [L, Q] = rightorth(t, p1, p2, alg); assertTrue(tc, ... From 184b56d3c13379dfdd11b99b0e097642263188e8 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 7 Sep 2022 10:40:57 +0200 Subject: [PATCH 014/245] add SUN charges. --- src/tensors/charges/GtPattern.m | 322 ++++++++++++++++++ src/tensors/charges/ProductCharge.m | 3 +- src/tensors/charges/SUN.m | 490 ++++++++++++++++++++++++++++ test/TestCharge.m | 21 +- 4 files changed, 825 insertions(+), 11 deletions(-) create mode 100644 src/tensors/charges/GtPattern.m create mode 100644 src/tensors/charges/SUN.m diff --git a/src/tensors/charges/GtPattern.m b/src/tensors/charges/GtPattern.m new file mode 100644 index 0000000..a629f73 --- /dev/null +++ b/src/tensors/charges/GtPattern.m @@ -0,0 +1,322 @@ +classdef GtPattern + %GTPATTERN Object that represents a pattern devised by Gelfand and Tsetlin. + % + % /m_{1,N} m_{2,N} ... m_{N,N}\ + % | m_{1,N-1} ... m_{N-1,N-1} | + % M = | ... | + % | m_{1,2} m_{2,2} | + % \ m_{1,1} / + % + % These consist of triangular arrangements of integers M_ij with the + % following property: + % - for 1 <= k < l <= N: + % m_{k,l} >= m_{k,l-1} >= m_{k+1,l} + % + % They will be represented using arrays of size N x N, where the elements + % outside of the triangular region are assumed to be zero. + + + properties % (Access = private) + M double + N + end + + methods + function p = GtPattern(N, M) + if nargin == 0 + return + end + if nargin == 1 + M = []; + else + assert(2 * size(M, 1) == N * (N + 1), ... + 'Wrong size for M: %d %d %d', N, size(M, 1), size(M, 2)); + end + + p.N = N; + p.M = M; + end + + function [rows, cols] = size(p) + sz = [1 size(p.M, 2)]; + if nargout <= 1 + rows = sz; + return + end + rows = sz(1); + cols = sz(2); + end + + function l = length(p) + l = size(p.M, 2); + end + + function a = cat(dim, varargin) + if nargin == 2 + a = varargin{1}; + return + end + + if nargin > 3 + a = cat(dim, cat(dim, varargin{1:floor(end/2)}), ... + cat(dim, varargin{floor(end/2)+1:end})); + return + end + + a = varargin{1}; + b = varargin{2}; + a.M = horzcat(a.M, b.M); + end + + function a = horzcat(varargin) + a = cat(1, varargin{:}); + end + + function a = subsref(a, s) + assert(length(s) == 1) + switch s.type + case '()' + + if length(s.subs) == 1 + a.M = a.M(:, s.subs{1}); + elseif length(s.subs) == 2 + assert(s.subs{1} == 1 || s.subs{1} == ':'); + a.M = a.M(:, s.subs{2}); + else + error('Undefined.'); + end + + case '.' + assert(length(s) == 1) + switch s.subs + case 'M' + a = a.M; + case 'N' + a = a.N; + otherwise + error('Undefined behaviour.'); + end + otherwise + error('Undefined behaviour.'); + end + end + + function bool = checkbounds(pat, k, l) + bool = all(0 < k & k <= l & l <= pat.N); + end + + function m = get(p, k, l) + %GET Access an element in the pattern. + % m = get(pat, k, l) + % gets the element specified by m_kl. + % + % m = get(pat, ks, l) + % gets a row vector of elements in the l'th row. + % + % m = get(pat, k, ls(:) + % gets a col vector of elements in the k'th column. +% assert(isscalar(p)); + m = p.M(k + bitshift(((l + 1 + p.N) .* (p.N - l)), -1)); + end + + function p = set(p, k, l, val) +% assert(isscalar(p)); + assert(checkbounds(p, k, l)); + p.M(k + bitshift(((l + 1 + p.N) * (p.N - l)), -1)) = val; + end + + function bool = isscalar(a) + bool = size(a.M, 2) == 1; + end + + function disp(p) + N = p.N; + M = p.M; + for i = 1:size(M, 2) + %% Special case N == 1 + if N == 1 + fprintf('(%d)', M(:,i)); + return + end + + + %% Other cases + dig = maxdigits(M(:,i)); + space = repmat(' ', 1, dig); + out = strings(N, 2*N+2); + + % matrix delimiters + % left parenthesis: char(40) + % left upper hook: char(9115) + % left extension: char(9116) + % left lower hook: char(9117) + % + % right parenthesis: char(41) + % right upper hook: char(9118) + % right extension: char(9119) + % right lower hook: char(9120) + + out(:,1) = vertcat("\t/ ", repmat("\t| ",N-2,1), "\t\\ "); + out(:,end-1) = vertcat(" \\", repmat(" |", N-2, 1), " /"); + out(:, end) = '\n'; + out(:,2:end-2) = space; + + % fill in data + ctr = 0; + for ii = 1:N + l = N - ii + 1; + jj = 1; + for k = 1:l + out(ii, ii+jj) = sprintf('%*d', dig, M(end-ctr, i)); + ctr = ctr + 1; + jj = jj + 2; + end + end + assert(ctr == size(M, 1)); + out = join(out, ''); + out = join(out, ''); + fprintf(out); + end + end + + function bool = lt(a, b) + assert(isscalar(a) && isscalar(b)); + for l = a.N:-1:1 + for k = 1:l + if get(b, k, l) > get(a, k, l) + bool = true; + return + elseif get(b, k, l) < get(a, k, l) + bool = false; + return + end + end + end + bool = false; + end + + function bool = eq(a, b) + bool = all(a.M == b.M, 1); + bool = reshape(bool, 1, []); + end + + function sigma = rowsum(p, l) + %ROWSUM Compute the sum of row `l`. + % sigma = rowsum(pat, l) + % computes sigma_l = \sum_{k=1:l} m_{k, l} + + sigma = sum(get(p, 1:l, l)); + end + + function w = pWeight(p) + %PWEIGHT Compute the p-weight of a pattern. + % w = pWeight(pat) + % computes the pattern weight W(pat) = (w_1 w_2 ... w_N) where + % w_i = sigma_i - sigma_{i-1}. + % + % This is an alternative weight definition to the z-weight. + % See also GtPattern.zWeight + + M = p.M; + ctr = 0; + sigmas = zeros(p.N + 1, length(p)); + for i = 1:p.N + sigmas(i + 1, :) = sum(M(end - (1:i) - ctr + 1, :), 1); + ctr = ctr + i; + end + w = diff(sigmas).'; +% sigma = [0 arrayfun(@(l) rowsum(p, l), 1:p.N)] +% w = sigma(2:end) - sigma(1:end-1); + end + + function w = zWeight(p) + %ZWEIGHT Compute the z-weight of a pattern + % w = zWeight(pat) + % computes the z-weight L(pat) = (l_1 l_2 ... l_N-1) where + % l_i = sigma_l - 1/2(sigma_{l+1} + sigma_{l-1}) + % + % This is a generalization of the m-quantum number for angular + % momentum. + + sigma = [0 arrayfun(@(l) rowsum(p, l), 1:p.N)]; + w = sigma(2:end-1) - (sigma(1:end-2) + sigma(3:end))/2; + end + + + end + + methods (Static) + function pat = GeneratePatterns(I) + %GENERATEPATTERNS Generate all possible GtPatterns for a given SU(N) charge. + % pat = GeneratePatterns(charge) + % generates all allowed GtPatterns. + + + %% Special case length 1 + N = length(I); + if N == 1 + pat = GtPattern(1, I(:)); + return + end + + + %% Special case length 2 + if N == 2 + i1 = I(2):I(1); + L = length(i1); + M = zeros(2, 2, L); + M(1, :, :) = repmat(I, 1, 1, L); + M(2, 1, :) = i1; + pat = GtPattern(2, M); + return + end + + + %% Special case length 3 + % if N == 3 + % + % + % + % return; + % end + + + %% Generate all possible weight combinations of one level lower + newIs = cell(1, N-1); + for ii = 1:N-1 + newIs{ii} = I(ii+1):I(ii); + end + + + %% Generate patterns + pat = []; + + for newI = CombVec(newIs{N-1:-1:1}) + newPat = GtPattern.GeneratePatterns(flip(newI)); + pat = [pat, appendRow(newPat, I)]; + end + + + end + end +end + + +function d = maxdigits(var) +%MAXDIGITS Compute max amount of digits used by positive integers in an array. +% d = maxdigits(var) + +d = 1; +var = var(var ~= 0); +d = max(max(1, 1+floor(log10(double(var))))); + +end + +function i = lindex(N, k, l) +%LINDEX Give the linear index of element (k,l). +% i = lindex(N, k, l) +% computes the linear index of the k,l'th element in a pattern. + +i = k + idivide((l + 1 + N) * (N-l), int16(2)); + +end diff --git a/src/tensors/charges/ProductCharge.m b/src/tensors/charges/ProductCharge.m index 2f52533..9eed5b8 100644 --- a/src/tensors/charges/ProductCharge.m +++ b/src/tensors/charges/ProductCharge.m @@ -116,8 +116,7 @@ end end end - - + function varargout = subsref(prodcharge, s) % Overload indexing. % diff --git a/src/tensors/charges/SUN.m b/src/tensors/charges/SUN.m new file mode 100644 index 0000000..99eec33 --- /dev/null +++ b/src/tensors/charges/SUN.m @@ -0,0 +1,490 @@ +classdef SUN < AbstractCharge + % Irreducible representations of the special unitary group SU(N). + + properties + I (1,:) uint8 + end + + methods + function style = braidingstyle(~) + style = BraidingStyle.Bosonic; + end + + function a = conj(a) + for i = 1:numel(a) + a(i).I = a(i).I(1) - fliplr(a(i).I); + end + end + + function bools = eq(a, b) + if isscalar(a) + bools = reshape(all(a.I == vertcat(b.I), 2), size(b)); + elseif isscalar(b) + bools = reshape(all(b.I == vertcat(a.I), 2), size(a)); + else + assert(isequal(size(a), size(b))); + bools = reshape(all(vertcat(a.I) == vertcat(b.I), 2), size(a)); + end + end + + function F = Fsymbol(a, b, c, d, e, f) + persistent cache + if isempty(cache) + cache = LRU; + end + + if Options.CacheEnabled() + key = GetMD5([a.I; b.I; c.I; d.I; e.I; f.I], 'Array', 'hex'); + F = get(cache, key); + if isequal(F, []) + F = Fsymbol_(a, b, c, d, e, f); + cache = set(cache, key, F); + end + else + F = Fsymbol_(a, b, c, d, e, f); + end + end + + function style = fusionstyle(~) + style = FusionStyle.Generic; + end + + function C = fusiontensor(a, b, c) + persistent cache + if isempty(cache) + cache = LRU; + end + + if Options.CacheEnabled() + key = GetMD5([a.I; b.I; c.I], 'Array', 'hex'); + C = get(cache, key); + if isempty(C) + C = clebschgordan(a, b, c); + cache = set(cache, key, C); + end + else + C = clebschgordan(a, b, c); + end + end + + function c = mtimes(a, b) + c = unique(directproduct(a, b)); + end + + function bools = ne(a, b) + if isscalar(a) + bools = reshape(any(a.I ~= vertcat(b.I), 2), size(b)); + elseif isscalar(b) + bools = reshape(any(b.I ~= vertcat(a.I), 2), size(a)); + else + assert(isequal(size(a), size(b))); + bools = reshape(any(vertcat(a.I) ~= vertcat(b.I), 2), size(a)); + end + end + + function N = Nsymbol(a, b, c) + if numel(a) > 1 + N = arrayfun(@Nsymbol, a, b, c); + return + end + N = sum(c == directproduct(a, b)); + end + + function e = one(a) + e = SUN(zeros(1, length(a(1).I), 'uint8')); + end + + function d = qdim(a) + I = double(vertcat(a.I)); + d = 1; + for k2 = 2:size(I, 2) + for k1 = 1:k2-1 + d = d .* (k2 - k1 + I(:, k1) - I(:, k2)) / (k2 - k1); + end + end + d = reshape(double(d), size(a)); + end + + function R = Rsymbol(a, b, c, ~) + persistent cache + if isempty(cache) + cache = LRU; + end + + if Options.CacheEnabled() + key = GetMD5([a.I; b.I; c.I], 'Array', 'hex'); + R = get(cache, key); + if isempty(R) + R = Rsymbol_(a, b, c); + cache = set(cache, key, R); + end + else + R = Rsymbol_(a, b, c); + end + end + + function [b, I] = sort(a) + [~, I] = sortrows(vertcat(a.I)); + b = a(I); + end + + function charge = SUN(varargin) + if nargin == 0 + labels = []; + else + labels = vertcat(varargin{:}); + end + for i = size(labels, 1):-1:1 + charge(i).I = labels(i, :); + end + end + end + + methods %(Access = private) + function J = annihilation(a) + J = cellfun(@ctranspose, creation(a), 'UniformOutput', false); + end + + function b = basis(a) + N = length(a.I); + if N == 1 + b = GtPattern(N, a.I); + return + end + + if N == 2 + Is = (a.I(2):a.I(1)); + b = GtPattern(2, [repmat(a.I(:), 1, length(Is)); Is]); + return + end + + if N == 3 + M = []; + I = a.I; + is = I(2):I(1); + js = I(3):I(2); + for i = is + for j = js + M = [M [repmat([i; j], 1, i - j + 1); j:i]]; + end + end + b = GtPattern(N, [repmat(I(:), 1, size(M, 2)); M]); + return + end + + allIs = arrayfun(@(i) a.I(i+1):a.I(i), 1:N-1, 'UniformOutput', false); + M = []; + for v = combvec(allIs{end:-1:1}) + b_ = basis(SUN(v(end:-1:1).')); + M = [M b_.M]; + end + b = GtPattern(N, [repmat(a.I(:), 1, size(M, 2)); M]); + + end + + function C = clebschgordan(a, b, c) + d1 = qdim(a); d2 = qdim(b); d3 = qdim(c); + N = length(a.I); + + Jplus1 = creation(a); + Jplus2 = creation(b); + + eqs = zeros(N-1, d1, d2, d1, d2); + rows = double.empty(0, 3); + cols = double.empty(0, 2); + + basis_a = basis(a); + weigth_a = pWeight(basis_a); + + basis_b = basis(b); + weigth_b = pWeight(basis_b); + + w3 = pWeight(highest_weight(c)); + wshift = (sum(a.I) + sum(b.I) - sum(c.I)) / N; + + for m1 = 1:length(basis_a) + w1 = weigth_a(m1, :); + w2 = w3 - w1 + wshift; + + for m2 = find(all(w2 == weigth_b, 2)).' + cols = [cols; m1 m2]; + for l = 1:length(Jplus1) + m2_ = m2; + [m1_, ~, v] = find(Jplus1{l}(:, m1)); + eqs(l, m1_, m2_, m1, m2) = eqs(l, m1_, m2_, m1, m2) + ... + reshape(v, 1, []); + rows = [rows; [repmat(l, size(m1_)) m1_ repmat(m2_, size(m1_))]]; +% for k = 1:length(v) +% rows = [rows; l m1_(k) m2_]; +% eqs(l, m1_(k), m2_, m1, m2) = eqs(l, m1_(k), m2_, m1, m2) + v(k); +% end + m1_ = m1; + [m2_, ~, v] = find(Jplus2{l}(:, m2)); + eqs(l, m1_, m2_, m1, m2) = eqs(l, m1_, m2_, m1, m2) + ... + reshape(v, 1, 1, []); + rows = [rows; [repmat([l m1_], size(m2_)) m2_]]; +% for k = 1:length(v) +% rows = [rows; l, m1_, m2_(k)]; +% eqs(l, m1_, m2_(k), m1, m2) = eqs(l, m1_, m2_(k), m1, m2) + v(k); +% end + end + end + end + rows = sortrows(unique(rows, 'rows'), size(rows, 2):-1:1); + + rows_lin = rows(:,1) + (rows(:,2) - 1) * (N-1) + (rows(:,3) - 1) * (N-1) * d1; + cols_lin = cols(:,1) + (cols(:,2) - 1) * d1; + eqs = reshape(eqs, (N-1) * d1 * d2, d1 * d2); + reduced_eqs = eqs(rows_lin, cols_lin); + + solutions = null(reduced_eqs); + N123 = size(solutions, 2); + assert(N123 == Nsymbol(a, b, c)); + + solutions = leftorth(rref(solutions', 1e-12)', 'qrpos'); + + C = zeros(d1 * d2, d3, N123); + for alpha = 1:N123 + for i = 1:length(cols_lin) + C(cols_lin(i), d3, alpha) = solutions(i, alpha); + end + end + C = reshape(C, d1, d2, d3, N123); + %% lower weight CGC + + + basis_c = basis(c); + weigth_c = pWeight(basis_c); + + Jmin1 = cellfun(@ctranspose, Jplus1, 'UniformOutput', false); + Jmin2 = cellfun(@ctranspose, Jplus2, 'UniformOutput', false); + Jmin3 = annihilation(c); + + w1list = unique(weigth_a, 'rows'); + w3list = sortrows(unique(weigth_c, 'rows'), 'descend'); + + for alpha = 1:N123 + known = false(1, d3); + known(d3) = true; + + for w3 = w3list(2:end, :).' + m3list = find(all(w3.' == weigth_c, 2)); + jmax = length(m3list); + imax = 0; + for l = 1:N-1 + w3_ = w3.'; w3_(l:l+1) = w3_(l:l+1) + [1 -1]; + imax = imax + sum(all(w3_ == weigth_c, 2)); + end + eqs = zeros(imax, jmax); + rhs = zeros(imax, d1, d2); + + i = 0; + rows = false(d1, d2); + for l = 1:length(Jmin1) + w3_ = w3.'; w3_(l:l+1) = w3_(l:l+1) + [1 -1]; + m3_list = find(all(w3_ == weigth_c, 2)); + for m3_ = m3_list.' + i = i + 1; + eqs(i, :) = reshape(Jmin3{l}(m3list, m3_), 1, jmax); +% for j = 1:length(m3list) +% m3 = m3list(j); +% eqs(i, j) = Jmin3{l}(m3, m3_); +% end + assert(known(m3_)); + for w1_ = w1list.' + m1_list = find(all(w1_.' == weigth_a, 2)).'; + w1 = w1_.'; w1(l:l+1) = w1(l:l+1) + [-1 1]; + m1list = find(all(w1 == weigth_a, 2)).'; + + w2_ = w3_ - w1_.' + wshift; + m2_list = find(all(w2_ == weigth_b, 2)).'; + w2 = w2_; w2(l:l+1) = w2(l:l+1) + [-1 1]; + m2list = find(all(w2 == weigth_b, 2)).'; + for m2_ = m2_list + for m1_ = m1_list + CGCcoeff = C(m1_, m2_, m3_, alpha); +% rhs(i, m1list, m2_) = CGCcoeff * ... +% reshape(full(Jmin1{l}(m1list, m1_)), 1, [], 1); +% rows = [rows; m1list(:) repmat(m2_, length(m1list), 1)]; + for m1 = m1list + m2 = m2_; + Jm1coeff = Jmin1{l}(m1, m1_); + rhs(i, m1, m2) = rhs(i, m1, m2) + ... + Jm1coeff * CGCcoeff; +% rows = [rows; m1 m2]; + end +% rhs(i, m1_, m2list) = CGCcoeff * ... +% reshape(full(Jmin2{l}(m2list, m2_)), 1, 1, []); +% rows = [rows; repmat(m1_, length(m2list), 1) m2list(:)]; + for m2 = m2list + m1 = m1_; + Jm2coeff = Jmin2{l}(m2, m2_); + rhs(i, m1, m2) = rhs(i, m1, m2) + ... + Jm2coeff * CGCcoeff; +% rows = [rows; m1 m2]; + end +% rows = [rows; combvec(m1list, m2list).']; + end + end + rows(m1_list, m2list) = true; + rows(m1list, m2_list) = true; + end + end + end + ieqs = pinv(eqs); + [rows1, rows2] = find(rows); +% inds = rows(:,1) + (rows(:,2)-1) * d2; + +% C = reshape(C, d1 * d2, d3, N123); +% rhs = reshape(rhs, imax, d1 * d2); +% C(inds, m3list, alpha) = (ieqs * rhs(:, inds)).'; +% C = reshape(C, d1, d2, d3, N123); +% known(m3list) = true; + for j = 1:length(m3list) + m3 = m3list(j); + assert(~known(m3)); + for k = 1:length(rows1) + C(rows1(k), rows2(k), m3, alpha) = ... + ieqs(j, :) * rhs(:, rows1(k), rows2(k)); + end +% for ind = rows.' +% C(ind(1), ind(2), m3, alpha) = ... +% ieqs(j, :) * rhs(:, ind(1), ind(2)); +% % for i = 1:imax +% % C(ind(1), ind(2), m3, alpha) = C(ind(1), ind(2), m3, alpha) + ... +% % ieqs(j, i) * rhs(i, ind(1), ind(2)); +% % end +% end + known(m3) = true; + end + end + end + end + + function J = creation(a) + N = length(a.I); + + J = cell(1, N-1); + for i = 1:length(J) + J{i} = zeros(qdim(a)); + end + + b = basis(a); + for i = 1:length(b) + m = b(i); + + for l = 1:N-1 + n = [reshape(get(m, 1:l+1, l+1), 1, l+1) - (1:l+1) ... + reshape(get(m, 1:l-1, l-1), 1, l-1) - (1:l-1) - 1]; + d = reshape(get(m, 1:l, l), 1, l) - (1:l); + mkl = get(m, 1:l, l); + for k = 1:l + numerator = -prod(n - mkl(k) + k); + if numerator == 0, continue; end + d2 = d; + d2(k) = []; + denominator = prod((d2 - mkl(k) + k) .* (d2 - mkl(k) + k - 1)); + if denominator == 0, continue; end + m2 = set(m, k, l, mkl(k) + 1); + j = m2 == b; + J{l}(j, i) = sqrt(numerator / denominator); + end + end + end + J = cellfun(@sparse, J, 'UniformOutput', false); + end + + function c = directproduct(a, b) + if qdim(a) > qdim(b) + c = directproduct(b, a); + return + end + + newI = []; + N = size(a.I, 2); + + for p = basis(a) + t = b.I; + bad_pattern = false; + for k = 1:N + if bad_pattern, break; end + for l = N:-1:k + if bad_pattern, break; end + + b_kl = get(p, k, l); + if checkbounds(p, k, l-1) + b_kl = b_kl - get(p, k, l-1); + end + t(l) = t(l) + b_kl; + if l > 1 + bad_pattern = t(l-1) < t(l); + end + end + end + + if ~bad_pattern + newI = [newI; t]; + end + end + c = SUN(newI - newI(:, end)); + end + + function F = Fsymbol_(a, b, c, d, e, f) + N1 = Nsymbol(a, b, e); + N2 = Nsymbol(e, c, d); + N3 = Nsymbol(b, c, f); + N4 = Nsymbol(a, f, d); + + if N1 == 0 || N2 == 0 || N3 == 0 || N4 == 0 + F = zeros(N1, N2, N3, N4); + return + end + + A = fusiontensor(a, b, e); + B = fusiontensor(e, c, d); + B = reshape(B(:, :, 1, :), size(B, [1 2 4])); + C = fusiontensor(b, c, f); + D = fusiontensor(a, f, d); + D = reshape(D(:, :, 1, :), size(D, [1 2 4])); + + F = contract(conj(D), [1 5 -4], conj(C), [2 4 5 -3], ... + A, [1 2 3 -1], B, [3 4 -2]); + end + + function p = highest_weight(a) + N = length(a.I); + if N == 1 + p = GtPattern(1, a.I); + return + end + + mask = fliplr(triu(true(N))); + M = repmat(a.I(:), 1, N); + p = GtPattern(N, reshape(M(mask), [], 1)); +% p_ = highest_weight(SUN(a.I(1:end-1))); +% p = GtPattern(N, [a.I(:); p_.M]); + end + + function s = normalize(s) + for i = 1:numel(s) + s(i).I = s(i).I - s(i).I(end); + end + end + + function R = Rsymbol_(a, b, c) + N1 = Nsymbol(a, b, c); + N2 = Nsymbol(b, a, c); + + if N1 == 0 || N2 == 0 + R = zeros(N1, N2); + return + end + + A = fusiontensor(a, b, c); + B = fusiontensor(b, a, c); + + R = contract(conj(B(:, :, 1, :)), [1 2 -2], A(:, :, 1, :), [2 1 -1]); + end + end +end + diff --git a/test/TestCharge.m b/test/TestCharge.m index abe878b..751d105 100644 --- a/test/TestCharge.m +++ b/test/TestCharge.m @@ -2,7 +2,7 @@ % TestCharge - Unit tests for charges. properties - tol = 1e-14 + tol = 1e-13 end properties (TestParameter) @@ -14,6 +14,7 @@ 'O2', O2([0 0 1 2], [0 1 2 2]), ... 'SU2', SU2(1:4), ... 'A4', A4(1:4), ... + 'SU3', SUN([1 0 0], [2 0 0], [3 0 0]), ... 'Z2xU1', ProductCharge(Z2(0, 0, 0, 1, 1, 1), U1(-1, 0, 1, -1, 0, 1))... ) end @@ -53,10 +54,12 @@ function Fmove(testCase, smallset) for d = intersect(e * c, a * f) F1 = conj(fusiontensor(a, f, d)); F4 = fusiontensor(e, c, d); - testCase.assertEqual(contract(F1, [1 6 4 -4], ... - F2, [2 3 6 -3], F3, [1 2 5 -1], F4, [5 3 4 -2]), ... - Fsymbol(a, b, c, d, e, f) * qdim(d), ... - 'AbsTol', testCase.tol, ... + actual = contract(F1, [1 6 4 -4], ... + F2, [2 3 6 -3], F3, [1 2 5 -1], F4, [5 3 4 -2]); + expected = Fsymbol(a, b, c, d, e, f) * qdim(d); + testCase.assertEqual(actual, ... + expected, ... + 'AbsTol', testCase.tol, 'RelTol', testCase.tol, ... 'Fsymbol incompatible with fusiontensor.'); end end @@ -80,10 +83,10 @@ function Funitary(testCase, smallset) for a = smallset, for b = smallset, for c = smallset for d = prod([a, b, c]) Fmat = Fmatrix(a, b, c, d); - testCase.assertEqual(Fmat' * Fmat, eye(size(Fmat)), ... - 'AbsTol', testCase.tol); - testCase.assertEqual(Fmat * Fmat', eye(size(Fmat)), ... - 'AbsTol', testCase.tol); + testCase.assertTrue(isapprox(Fmat' * Fmat, eye(size(Fmat)), ... + 'AbsTol', testCase.tol)); + testCase.assertTrue(isapprox(Fmat * Fmat', eye(size(Fmat)), ... + 'AbsTol', testCase.tol)); end end, end, end end From 5dcb649142f455c73b8d838ae07948754b33d2fe Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 7 Sep 2022 11:19:58 +0200 Subject: [PATCH 015/245] add fermionic charges. --- src/tensors/charges/AbstractCharge.m | 10 ++++++ src/tensors/charges/fZ2.m | 47 ++++++++++++++++++++++++++++ test/TestCharge.m | 17 ++++++---- 3 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 src/tensors/charges/fZ2.m diff --git a/src/tensors/charges/AbstractCharge.m b/src/tensors/charges/AbstractCharge.m index 34ec7ed..cca4185 100644 --- a/src/tensors/charges/AbstractCharge.m +++ b/src/tensors/charges/AbstractCharge.m @@ -435,6 +435,16 @@ theta = theta + qdim(b) / qdim(a) * trace(Rsymbol(a, a, b)); end end + + function p = parity(a) + if braidingstyle(a) == BraidingStyle.Bosonic || ... + braidingstyle(a) == BraidingStyle.Abelian + p = false(size(a)); + return + end + + error('Non-bosonic charges should implement a parity.') + end end %% Utility functions diff --git a/src/tensors/charges/fZ2.m b/src/tensors/charges/fZ2.m new file mode 100644 index 0000000..901acff --- /dev/null +++ b/src/tensors/charges/fZ2.m @@ -0,0 +1,47 @@ +classdef fZ2 < Z2 + % Fermionic charges. + % + % See also AbstractCharge + + methods + function style = braidingstyle(~) + style = BraidingStyle.Fermionic; + end + + function [charges, vertices] = cumprod(a) + charges = fZ2(mod(cumsum(a), 2)); + vertices = []; + end + + function c = mtimes(a, b) + c = fZ2(xor(a, b)); + end + + function e = one(~) + e = fZ2(false); + end + + function p = parity(a) + p = logical(a); + end + + function [d, N] = prod(a, dim) + if nargin < 2 || isempty(dim) + dim = find(size(a) ~= 1, 1); + end + d = fZ2(mod(sum(a, dim), 2)); + if nargout > 1 + N = ones(size(d)); + end + end + + function R = Rsymbol(a, b, c, ~) + R = (-2 .* double(a & b) + 1) .* Nsymbol(a, b, c); + end + + function theta = twist(a) + theta = -2 * double(a) + 1; + end + end +end + diff --git a/test/TestCharge.m b/test/TestCharge.m index 751d105..2dda531 100644 --- a/test/TestCharge.m +++ b/test/TestCharge.m @@ -9,6 +9,7 @@ smallset = struct( ... 'Z1', Z1, ... 'Z2', Z2([false, true]), ... + 'fZ2', fZ2([false true]), ... 'Z4', ZN(4, 0:3), ... 'U1', U1(-2:2), ... 'O2', O2([0 0 1 2], [0 1 2 2]), ... @@ -136,14 +137,18 @@ function Rmove(testCase, smallset) % Rmove(testCase, smallset) % verifies if an R-move on the fusion tensors is compatible with % the Fsymbol. - for a = smallset, for b = smallset + for a = smallset + for b = smallset for c = a * b - testCase.verifyEqual(fusiontensor(a, b, c), ... - contract(fusiontensor(b, a, c), [-2 -1 -3 1], ... - Rsymbol(a, b, c), [-4 1]), 'AbsTol', testCase.tol); + expected = fusiontensor(a, b, c); + actual = contract(fusiontensor(b, a, c), [-2 -1 -3 1], ... + Rsymbol(a, b, c), [-4 1]) * ... + (-2 * double(parity(a) & parity(b)) + 1); + testCase.verifyTrue(isapprox(actual, expected, ... + 'AbsTol', testCase.tol, 'RelTol', testCase.tol)); end - end, end - % TODO non-symmetric braiding tests + end + end end function hexagon(testCase, smallset) From b649264218ea8cc13a0886444e715a367ba1c06f Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 7 Sep 2022 11:32:22 +0200 Subject: [PATCH 016/245] fermionic tensors. --- src/tensors/charges/AbstractCharge.m | 3 +- src/tensors/charges/BraidingStyle.m | 7 +- test/TestFusionTree.m | 196 ++++++--------------------- test/TestTensor.m | 3 + 4 files changed, 49 insertions(+), 160 deletions(-) diff --git a/src/tensors/charges/AbstractCharge.m b/src/tensors/charges/AbstractCharge.m index cca4185..0cfaf9c 100644 --- a/src/tensors/charges/AbstractCharge.m +++ b/src/tensors/charges/AbstractCharge.m @@ -437,8 +437,7 @@ end function p = parity(a) - if braidingstyle(a) == BraidingStyle.Bosonic || ... - braidingstyle(a) == BraidingStyle.Abelian + if istwistless(braidingstyle(a)) p = false(size(a)); return end diff --git a/src/tensors/charges/BraidingStyle.m b/src/tensors/charges/BraidingStyle.m index 0efbe90..5aed581 100644 --- a/src/tensors/charges/BraidingStyle.m +++ b/src/tensors/charges/BraidingStyle.m @@ -17,10 +17,15 @@ end methods - function bool = issymmetric(style) + function bool = istwistless(style) bool = style == BraidingStyle.Abelian || style == BraidingStyle.Bosonic; end + function bool = issymmetric(style) + bool = style == BraidingStyle.Abelian || style == BraidingStyle.Bosonic || ... + style == BraidingStyle.Fermionic; + end + function c = and(a, b) if any([a b] == BraidingStyle.None) c = BraidingStyle.None; diff --git a/test/TestFusionTree.m b/test/TestFusionTree.m index 3544b2c..b2a41cc 100644 --- a/test/TestFusionTree.m +++ b/test/TestFusionTree.m @@ -5,7 +5,7 @@ properties (ClassSetupParameter) weight = {'small', 'medium'}%, 'large'} - charge = {'A4', 'Z1', 'Z2', 'U1', 'O2', 'SU2', 'Z2xU1'} + charge = {'A4', 'Z1', 'Z2', 'fZ2', 'U1', 'O2', 'SU2', 'Z2xU1'} end methods (TestClassSetup) @@ -18,13 +18,13 @@ function generateTrees(tc, charge, weight) chargeset = Z1; case 'Z2' chargeset = Z2([0 1]); + case 'fZ2' + chargeset = fZ2([0 1]); case 'U1' switch weight case 'small' chargeset = U1(-1:1); - case 'medium' - chargeset = U1(-2:2); - case 'large' + case {'medium', 'large'} chargeset = U1(-2:2); end case 'O2' @@ -60,9 +60,7 @@ function generateTrees(tc, charge, weight) switch weight case 'small' chargeset = A4([1 4]); - case 'medium' - chargeset = A4(1:4); - case 'large' + case {'medium', 'large'} chargeset = A4(1:4); end @@ -237,163 +235,47 @@ function traces(tc) end end end - function repartitioning(tc) - for i = 1:numel(tc.trees) - if isempty(tc.trees{i}) || tc.trees{i}.legs < 2 - continue; - end - if rand < tc.testWeight - f = tc.trees{i}; - for n = 0:f.legs - [c1, f1] = repartition(f, [n f.legs-n]); - - assertEqual(tc, size(c1), [length(f) length(f1)], ... - 'Coefficients have the wrong size.'); - assertEqual(tc, full(abs(c1).^2 * qdim(f1.coupled)), ... - qdim(f.coupled), 'AbsTol', tc.tol, 'RelTol', tc.tol, ... - 'Repartition must preserve centernorm.'); - assertEqual(tc, isallowed(f1), true(length(f1), 1), ... - 'Output trees are not allowed.'); - [c2, f2] = repartition(f1, f.rank); - assertEqual(tc, c1 * c2, speye(length(f)), 'AbsTol', tc.tol, ... - 'RelTol', tc.tol, 'Repartition should be invertible.'); - assertEqual(tc, f, f2, 'Repartition should be invertible.'); - - if issymmetric(braidingstyle(f)) - a1_cell = fusiontensor(f); - a2_cell = fusiontensor(f1); - for j = 1:length(f) + function repartitioning(tc) + for i = 1:numel(tc.trees) + if isempty(tc.trees{i}) || tc.trees{i}.legs < 2 + continue; + end + if rand < tc.testWeight + f = tc.trees{i}; + for n = 0:f.legs + [c1, f1] = repartition(f, [n f.legs-n]); + + assertEqual(tc, size(c1), [length(f) length(f1)], ... + 'Coefficients have the wrong size.'); + assertEqual(tc, full(abs(c1).^2 * qdim(f1.coupled)), ... + qdim(f.coupled), 'AbsTol', tc.tol, 'RelTol', tc.tol, ... + 'Repartition must preserve centernorm.'); + assertEqual(tc, isallowed(f1), true(length(f1), 1), ... + 'Output trees are not allowed.'); + + [c2, f2] = repartition(f1, f.rank); + assertEqual(tc, c1 * c2, speye(length(f)), 'AbsTol', tc.tol, ... + 'RelTol', tc.tol, 'Repartition should be invertible.'); + assertEqual(tc, f, f2, 'Repartition should be invertible.'); + + if issymmetric(braidingstyle(f)) + a1_cell = fusiontensor(f); + a2_cell = fusiontensor(f1); + for j = 1:length(f) a1 = a1_cell{j}; a2 = zeros(size(a1)); [~, col, val] = find(c1(j, :)); for k = 1:length(val) - a2 = a2 + val(k) * a2_cell{col(k)}; + a2 = a2 + val(k) * a2_cell{col(k)}; end assertEqual(tc, a2, a1, 'AbsTol', tc.tol, ... - 'Repartition should be compatible with fusiontensors.'); - end - end - end + 'Repartition should be compatible with fusiontensors.'); + end + end end - end - end - - % end - % - % function permute_fusiontensor(testCase, smallset, legs) - % trees = generateFusionTrees(testCase, smallset, legs); - % ps = perms(1:legs); - % for p = ps(randperm(size(ps, 2), min(size(ps, 2), 5)), :).' - % [t1, c1] = permute(trees, p.'); - % for j = 1:length(trees) - % array1 = permute(cast(slice(trees, j), 'double'), ... - % [p.' legs+1]); - % array2 = zeros(size(array1)); - % [~, col, val] = find(c1(j,:)); - % for k = 1:length(val) - % array2 = array2 + val(k) * cast(slice(t1, col(k)), 'double'); - % end - % verifyEqual(testCase, array1, array2, 'AbsTol', testCase.tol, ... - % 'Permute should be compatible with fusiontensors.'); - % end - % end - % end - % - % function permute_properties(testCase, smallset, legs) - % trees = generateFusionTrees(testCase, smallset, legs); - % - % treeargs = cell(2, legs); - % for i = 1:legs - % treeargs{1,i} = unique(trees.uncoupled(:, i)); - % treeargs{2,i} = trees.arrows(i); - % end - % coupled = unique(trees.coupled); - % - % ps = perms(1:legs); - % for p = ps(randperm(size(ps, 2), min(size(ps, 2), 5)), :).' - % [t1, c1] = permute(trees, p.'); - % - % verifyEqual(testCase, size(c1), [length(trees) length(t1)], ... - % 'Coefficients have the wrong size.'); - % verifyEqual(testCase, full(vecnorm(c1)), ones(1, length(trees)), ... - % 'AbsTol', testCase.tol, 'Norm of coefficients should be 1.'); - % verifyEqual(testCase, c1 * c1', speye(length(trees)), ... - % 'AbsTol', testCase.tol, 'Permute should be unitary.'); - % verifyEqual(testCase, c1' * c1, speye(length(t1)), ... - % 'AbsTol', testCase.tol, 'Permute should be unitary.'); - % - % verifyTrue(testCase, issorted(t1), ... - % 'Permute should return sorted trees.'); - % - % tempargs = treeargs(:, p.'); - % t3 = FusionTree.new(tempargs{:}); - % lia = ismember(t3.coupled, coupled); - % t3.charges = t3.charges(lia, :); - % if ~isempty(t3.vertices) - % t3.vertices = t3.vertices(lia,:); - % end - % verifyEqual(testCase, t1, t3, ... - % 'Permute should transform full basis to full basis.'); - % - % [t2, c2] = permute(t1, invperm(p)); - % verifyEqual(testCase, c2, c1', 'AbsTol', testCase.tol, ... - % 'RelTol', testCase.tol, 'Permute should be invertible.'); - % verifyEqual(testCase, t2, trees, ... - % 'Permute should be invertible.'); - % end - % end - % - % function yangbaxter(testCase, smallset, legs) - % trees = generateFusionTrees(testCase, smallset, legs); - % for i = 1:legs-2 - % [t1, c1] = artinbraid(trees, i); - % [t1, c2] = artinbraid(t1, i+1); - % [t1, c3] = artinbraid(t1, i); - % - % [t2, c4] = artinbraid(trees, i+1); - % [t2, c5] = artinbraid(t2, i); - % [t2, c6] = artinbraid(t2, i+1); - % - % verifyEqual(testCase, ... - % c1 * c2 * c3, c4 * c5 * c6, 'AbsTol', testCase.tol, ... - % 'Yang-Baxter equation not satisfied.'); - % verifyEqual(testCase, t1, t2, ... - % 'Yang-Baxter equation not satisfied.'); - % end - % end + end + end + end end - - % methods - % function trees = generateFusionTrees(testCase, smallset, legs) - % % generateFusionTrees - Generate some trees for testing. - % % trees = generateFusionTrees(testCase, smallset, legs) - % - % rng(123); - % args = cell(2, legs); - % for i = 1:legs - % args{1, i} = smallset(randperm(length(smallset), ... - % randi([1 length(smallset)]))); - % args{2, i} = randi([0 1]); - % end - % trees = FusionTree.new(args{:}); - % - % % get reasonable amount of trees: - % while length(trees) > testCase.maxTrees - % coupleds = trees.coupled; - % coupled = unique(trees.coupled); - % coupled = coupled(randi([1 length(coupled)])); - % lia = ismember(coupleds, coupled); - % if all(lia) - % break - % end - % trees.charges = trees.charges(~lia,:); - % if ~isempty(trees.vertices) - % trees.vertices = trees.vertices(~lia,:); - % end - % trees = sort(trees); - % end - % end - % end - end diff --git a/test/TestTensor.m b/test/TestTensor.m index f19d84e..245716c 100644 --- a/test/TestTensor.m +++ b/test/TestTensor.m @@ -19,6 +19,8 @@ function classSetup(tc) 'complex', ComplexSpace(3, false, 4, true, 5, false, 6, false, 7, true), ... 'Z2', GradedSpace.new(Z2(0, 1), [1 1], false, Z2(0, 1), [1 2], true, ... Z2(0, 1), [3 2], true, Z2(0, 1), [2 3], false, Z2(0, 1), [2 5], false), ... + 'fZ2', GradedSpace.new(fZ2(0, 1), [1 1], false, fZ2(0, 1), [1 2], true, ... + fZ2(0, 1), [3 2], true, fZ2(0, 1), [2 3], false, fZ2(0, 1), [2 5], false), ... 'U1', GradedSpace.new(U1(0, 1, -1), [1 2 2], false, U1(0, 1, -1), [3 1 1], false, ... U1(0, 1, -1), [2 2 1], true, U1(0, 1, -1), [1, 2, 3], false, ... U1(0, 1, -1), [1 3 3], true), ... @@ -116,6 +118,7 @@ function permute_via_inner(tc, spaces) end function permute_via_conversion(tc, spaces) + tc.assumeTrue(istwistless(braidingstyle(spaces))); t = Tensor.rand(spaces, []); a = double(t); rng(123); From 689d44899db720f8f6bacf3785663baf5fde590b Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 13 Sep 2022 11:55:29 +0200 Subject: [PATCH 017/245] optimisations SUN --- src/caches/LRU.m | 19 ++++++---- src/tensors/charges/SUN.m | 75 ++++++++++++--------------------------- src/utility/memsize.m | 57 +++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 59 deletions(-) create mode 100644 src/utility/memsize.m diff --git a/src/caches/LRU.m b/src/caches/LRU.m index e5c3ab6..fc586ee 100644 --- a/src/caches/LRU.m +++ b/src/caches/LRU.m @@ -92,7 +92,7 @@ if isKey(cache.map, key) dll_old = cache.map(key); pop(dll_old); - cache.mem = cache.mem - memsize(dll_old.val{2}); + cache.mem = cache.mem - memsize(dll_old.val{2}, 'B'); cache.map.remove(key); end @@ -102,17 +102,24 @@ cache.map(key) = dll_new; % update memory - cache.mem = cache.mem + memsize(val); + cache.mem = cache.mem + memsize(val, 'B'); while cache.mem > cache.memlimit || length(cache.map) > cache.itemlimit dll_oldest = -cache.sentinel; pop(dll_oldest); - cache.mem = cache.mem - memsize(dll_oldest.val{2}); + cache.mem = cache.mem - memsize(dll_oldest.val{2}, 'B'); cache.map.remove(key); end end + + function disp(cache) + fprintf('LRU cache:\n'); + fprintf('\tbytes = %.2f / %.2f %s\n', ... + cache.mem / 1024^2, cache.memlimit / 1024^2, 'MB'); + fprintf('\titems = %d / %d\n', cache.map.Count, cache.itemlimit); + end end end -function b = memsize(x) -b = numel(getByteStreamFromArray(x)); -end \ No newline at end of file +% function b = memsize(x) +% b = numel(getByteStreamFromArray(x)); +% end \ No newline at end of file diff --git a/src/tensors/charges/SUN.m b/src/tensors/charges/SUN.m index 99eec33..6e888ab 100644 --- a/src/tensors/charges/SUN.m +++ b/src/tensors/charges/SUN.m @@ -183,6 +183,8 @@ end function C = clebschgordan(a, b, c) + DISPLAY = false; + t_elapsed = tic; d1 = qdim(a); d2 = qdim(b); d3 = qdim(c); N = length(a.I); @@ -190,8 +192,8 @@ Jplus2 = creation(b); eqs = zeros(N-1, d1, d2, d1, d2); - rows = double.empty(0, 3); - cols = double.empty(0, 2); + rows = false(N-1, d1, d2); + cols = false(d1, d2); basis_a = basis(a); weigth_a = pWeight(basis_a); @@ -206,36 +208,26 @@ w1 = weigth_a(m1, :); w2 = w3 - w1 + wshift; - for m2 = find(all(w2 == weigth_b, 2)).' - cols = [cols; m1 m2]; + m2list = all(w2 == weigth_b, 2); + cols(m1, m2list) = true; + for m2 = find(m2list).' for l = 1:length(Jplus1) m2_ = m2; [m1_, ~, v] = find(Jplus1{l}(:, m1)); eqs(l, m1_, m2_, m1, m2) = eqs(l, m1_, m2_, m1, m2) + ... reshape(v, 1, []); - rows = [rows; [repmat(l, size(m1_)) m1_ repmat(m2_, size(m1_))]]; -% for k = 1:length(v) -% rows = [rows; l m1_(k) m2_]; -% eqs(l, m1_(k), m2_, m1, m2) = eqs(l, m1_(k), m2_, m1, m2) + v(k); -% end + rows(l, m1_, m2_) = true; m1_ = m1; [m2_, ~, v] = find(Jplus2{l}(:, m2)); eqs(l, m1_, m2_, m1, m2) = eqs(l, m1_, m2_, m1, m2) + ... reshape(v, 1, 1, []); - rows = [rows; [repmat([l m1_], size(m2_)) m2_]]; -% for k = 1:length(v) -% rows = [rows; l, m1_, m2_(k)]; -% eqs(l, m1_, m2_(k), m1, m2) = eqs(l, m1_, m2_(k), m1, m2) + v(k); -% end + rows(l, m1_, m2_) = true; end end end - rows = sortrows(unique(rows, 'rows'), size(rows, 2):-1:1); - rows_lin = rows(:,1) + (rows(:,2) - 1) * (N-1) + (rows(:,3) - 1) * (N-1) * d1; - cols_lin = cols(:,1) + (cols(:,2) - 1) * d1; eqs = reshape(eqs, (N-1) * d1 * d2, d1 * d2); - reduced_eqs = eqs(rows_lin, cols_lin); + reduced_eqs = eqs(rows(:), cols(:)); solutions = null(reduced_eqs); N123 = size(solutions, 2); @@ -244,15 +236,10 @@ solutions = leftorth(rref(solutions', 1e-12)', 'qrpos'); C = zeros(d1 * d2, d3, N123); - for alpha = 1:N123 - for i = 1:length(cols_lin) - C(cols_lin(i), d3, alpha) = solutions(i, alpha); - end - end + C(cols, d3, :) = reshape(solutions, [], 1, N123); C = reshape(C, d1, d2, d3, N123); - %% lower weight CGC - + %% lower weight CGC basis_c = basis(c); weigth_c = pWeight(basis_c); @@ -286,10 +273,6 @@ for m3_ = m3_list.' i = i + 1; eqs(i, :) = reshape(Jmin3{l}(m3list, m3_), 1, jmax); -% for j = 1:length(m3list) -% m3 = m3list(j); -% eqs(i, j) = Jmin3{l}(m3, m3_); -% end assert(known(m3_)); for w1_ = w1list.' m1_list = find(all(w1_.' == weigth_a, 2)).'; @@ -303,27 +286,18 @@ for m2_ = m2_list for m1_ = m1_list CGCcoeff = C(m1_, m2_, m3_, alpha); -% rhs(i, m1list, m2_) = CGCcoeff * ... -% reshape(full(Jmin1{l}(m1list, m1_)), 1, [], 1); -% rows = [rows; m1list(:) repmat(m2_, length(m1list), 1)]; for m1 = m1list m2 = m2_; Jm1coeff = Jmin1{l}(m1, m1_); rhs(i, m1, m2) = rhs(i, m1, m2) + ... Jm1coeff * CGCcoeff; -% rows = [rows; m1 m2]; end -% rhs(i, m1_, m2list) = CGCcoeff * ... -% reshape(full(Jmin2{l}(m2list, m2_)), 1, 1, []); -% rows = [rows; repmat(m1_, length(m2list), 1) m2list(:)]; for m2 = m2list m1 = m1_; Jm2coeff = Jmin2{l}(m2, m2_); rhs(i, m1, m2) = rhs(i, m1, m2) + ... Jm2coeff * CGCcoeff; -% rows = [rows; m1 m2]; end -% rows = [rows; combvec(m1list, m2list).']; end end rows(m1_list, m2list) = true; @@ -333,13 +307,7 @@ end ieqs = pinv(eqs); [rows1, rows2] = find(rows); -% inds = rows(:,1) + (rows(:,2)-1) * d2; -% C = reshape(C, d1 * d2, d3, N123); -% rhs = reshape(rhs, imax, d1 * d2); -% C(inds, m3list, alpha) = (ieqs * rhs(:, inds)).'; -% C = reshape(C, d1, d2, d3, N123); -% known(m3list) = true; for j = 1:length(m3list) m3 = m3list(j); assert(~known(m3)); @@ -347,18 +315,20 @@ C(rows1(k), rows2(k), m3, alpha) = ... ieqs(j, :) * rhs(:, rows1(k), rows2(k)); end -% for ind = rows.' -% C(ind(1), ind(2), m3, alpha) = ... -% ieqs(j, :) * rhs(:, ind(1), ind(2)); -% % for i = 1:imax -% % C(ind(1), ind(2), m3, alpha) = C(ind(1), ind(2), m3, alpha) + ... -% % ieqs(j, i) * rhs(i, ind(1), ind(2)); -% % end -% end known(m3) = true; end end end + + t_elapsed = toc(t_elapsed); + if DISPLAY + fprintf('Generated SU(%d) Clebsch-Gordan Coefficient:\n', N); + fprintf('\t(%s) x (%s) -> (%s)\n', num2str(a.I), num2str(b.I), num2str(c.I)); + fprintf('\ttime = %.2fs\n', t_elapsed); + [bytes, unit] = memsize(C); + fprintf('\tsize = %.2f%s\n', bytes, unit); + fprintf('\n'); + end end function J = creation(a) @@ -487,4 +457,3 @@ end end end - diff --git a/src/utility/memsize.m b/src/utility/memsize.m new file mode 100644 index 0000000..ebdc278 --- /dev/null +++ b/src/utility/memsize.m @@ -0,0 +1,57 @@ +function [bytes, unit] = memsize(in, unit) + +if isa(in, 'containers.Map') + warning('off', 'MATLAB:structOnObject'); + in = struct(in); + warning('on', 'MATLAB:structOnObject'); +end + +if ~isobject(in) + s = whos('in'); + bytes = s.bytes; +else + props = properties(in); + bytes = 0; + for ii = 1:length(props) + bytes = bytes + memsize(in.(thisprop), 'B'); + end +end + +if nargin == 1 || isempty(unit) + if bytes > 1024^3 +% bytes = bytes / 1024^3; + unit = 'GB'; + elseif bytes > 1024^2 +% bytes = bytes / 1024^2; + unit = 'MB'; + elseif bytes > 1024 +% bytes = bytes / 1024; + unit = 'KB'; + else + unit = 'B'; + end +end + +if strcmpi(unit, 'B') + return; +end + +if strcmpi(unit, 'KB') + bytes = bytes / 1024; + return; +end + +if strcmpi(unit, 'MB') + bytes = bytes / 1024^2; + return; +end + +if strcmpi(unit, 'GB') + bytes = bytes / 1024^3; + return +end + +warning('Unrecognised unit for bytes, using B instead.'); +unit = 'B'; + +end From 049877610df037300f86eb9b691e1d0cda845b44 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 13 Sep 2022 15:36:48 +0200 Subject: [PATCH 018/245] update docs --- README.md | 6 +- docs/src/index.rst | 3 +- docs/src/lib/tensors.rst | 11 +- docs/src/man/{tensors.rst => tensor.rst} | 8 + src/tensors/FusionTree.m | 95 +++++----- src/tensors/Tensor.m | 214 +++++++++++++++-------- src/tensors/spaces/AbstractSpace.m | 4 +- src/tensors/spaces/CartesianSpace.m | 6 +- src/tensors/spaces/ComplexSpace.m | 4 +- src/utility/wigner/Wigner3j.m | 17 +- src/utility/wigner/Wigner6j.m | 21 +-- 11 files changed, 242 insertions(+), 147 deletions(-) rename docs/src/man/{tensors.rst => tensor.rst} (88%) diff --git a/README.md b/README.md index 0ece692..7f55a59 100644 --- a/README.md +++ b/README.md @@ -69,10 +69,12 @@ This is a package which aims to efficiently implement the various elementary alg Additionally, these tensors support a general global symmetries, in which case both memory and CPU usage are optimized. The framework is able to support both Abelian and non-Abelian symmetries, as well as symmetry groups with multiplicities, which can have bosonic or fermionic braiding rules. The design of the algorithms is chosen such that the inclusion of symmetries should not alter the code after the creation of the tensors. Currently, the following symmetries are implemented: -* Z2 +* Z(N) * U1 -* SU2 +* A4 +* SU(N) * O2 +* Fermionic Z2 * Direct product groups

(back to top)

diff --git a/docs/src/index.rst b/docs/src/index.rst index 4caff35..41126a0 100644 --- a/docs/src/index.rst +++ b/docs/src/index.rst @@ -17,7 +17,7 @@ Additionally, for tensors which are invariant under general global symmetries, v :maxdepth: 2 man/intro - man/tensors + man/tensor man/symmetries @@ -35,4 +35,3 @@ Additionally, for tensors which are invariant under general global symmetries, v lib/tensors lib/utility lib/caches - diff --git a/docs/src/lib/tensors.rst b/docs/src/lib/tensors.rst index 3c1e106..085d0ef 100644 --- a/docs/src/lib/tensors.rst +++ b/docs/src/lib/tensors.rst @@ -18,7 +18,7 @@ Symmetry sectors Fusion trees ------------ -.. automodule:: src.tensors.trees +.. autoclass:: src.tensors.FusionTree Spaces @@ -36,11 +36,6 @@ Kernels Tensors ------- -.. automodule:: src.tensors.tensors - - -Arrays ------- - -.. automodule:: src.tensors.arrays +.. autoclass:: src.tensors.Tensor +.. autoclass:: src.tensors.SpTensor \ No newline at end of file diff --git a/docs/src/man/tensors.rst b/docs/src/man/tensor.rst similarity index 88% rename from docs/src/man/tensors.rst rename to docs/src/man/tensor.rst index 945be55..de533d0 100644 --- a/docs/src/man/tensors.rst +++ b/docs/src/man/tensor.rst @@ -38,11 +38,19 @@ In summary, we consider a tensor to be an object which can be represented in eit These definitions are equivalent, and it is always possible to go from one to the other. The main reason for introducing both is that for some algorithms, one is more convenient than the other, and thus this conversion will prove useful. +Creating and accessing tensors +------------------------------ + +There are several options provided for creating tensors, which are designed to either mimic the existing MATLAB constructors, or to be conveniently generalisable when dealing with symmetries. Almost each of these methods is a wrapper of some sorts around the general static constructor ``Tensor.new``: + +.. automethod:: src.tensors.Tensor.new + :noindex: Index manipulations ------------------- + Contractions ------------ diff --git a/src/tensors/FusionTree.m b/src/tensors/FusionTree.m index 0070dc3..5613e51 100644 --- a/src/tensors/FusionTree.m +++ b/src/tensors/FusionTree.m @@ -3,7 +3,7 @@ % % Properties % ---------- - % charges : AbstractCharge + % charges : :class:`AbstractCharge` % labels for the edges of the fusion trees % % vertices : int @@ -33,11 +33,11 @@ function f = FusionTree(charges, vertices, isdual, rank) % Usage % ----- - % ```f = FusionTree(charges, vertices, isdual, rank)``` + % :code:`f = FusionTree(charges, vertices, isdual, rank)` % % Arguments % --------- - % charges : AbstractCharge + % charges : :class:`AbstractCharge` % array of charges, where each row represents an allowed fusion channel. % % vertices : int = [] @@ -50,6 +50,7 @@ % % rank : int % number of splitting and fusing legs. + if nargin == 0 return; end @@ -63,7 +64,6 @@ error('tensors:tree:argError', 'Incompatible input sizes.'); end - %% General tree N = length(isdual); if N ~= sum(rank) || ... @@ -93,11 +93,12 @@ % % Repeating Arguments % ------------------- - % charges : AbstractCharge + % charges : :class:`AbstractCharge` % set of charges for a given leg % % isdual : logical % presence of a duality transform + arguments rank (1, 2) {mustBeNonnegative, mustBeInteger} end @@ -237,7 +238,7 @@ % % Arguments % --------- - % f : FusionTree + % f : :class:`FusionTree` % tree to swap. % % i : int @@ -253,8 +254,9 @@ % matrix of coefficients that transform input to output trees. % f(i) --> c(i,j) * f(j) % - % f : FusionTree + % f : :class:`FusionTree` % braided trees in canonical form. + if nargin < 3, inv = false; end assert(f.rank(2) == 0, 'Only defined for splitting trees.'); @@ -354,7 +356,7 @@ [order, diagcharges, diagvertices] = ... simulsortrows(charges(:, mask1), vertices(:, mask2)); charges = charges(order, :); vertices = vertices(order, :); - [~, ia1, ic1] = simulunique(diagcharges, diagvertices, 'rows'); + [~, ia1] = simulunique(diagcharges, diagvertices, 'rows'); charges1 = charges(ia1, :); vertices1 = vertices(ia1, :); @@ -407,7 +409,7 @@ % % Arguments % --------- - % f : FusionTree + % f : :class:`FusionTree` % tree to bend. % % Returns @@ -416,8 +418,9 @@ % matrix of coefficients that transform input to output trees. % f(i) --> c(i,j) * f(j) % - % f : FusionTree + % f : :class:`FusionTree` % bent trees in canonical form. + f = flip(f); [c, f] = bendright(f); f = flip(f); @@ -430,16 +433,16 @@ % % Arguments % --------- - % f : FusionTree + % f : :class:`FusionTree` % tree to bend. % % Returns % ------- % c : sparse double % matrix of coefficients that transform input to output trees. - % f(i) --> c(i,j) * f(j) + % `f(i) --> c(i,j) * f(j)` % - % f : FusionTree + % f : :class:`FusionTree` % bent trees in canonical form. if ~hasmultiplicity(fusionstyle(f)) @@ -545,7 +548,7 @@ % % Arguments % --------- - % f : FusionTree + % f : :class:`FusionTree` % tree to braid. % % p : int @@ -563,15 +566,16 @@ % matrix of coefficients that transform input to output trees. % f(i) --> c(i,j) * f(j) % - % f : FusionTree + % f : :class:`FusionTree` % braided trees in canonical form. % % Todo % ---- % Currently this is done by first bending all legs to the splitting tree, this - % step is not necessary when no splitting legs are braided with fusing legs. + % step is not necessary when no splitting legs are braided with fusing legs. % Additionally this can be sped up significantly for charges with Abelian - % braiding style. + % braiding style. + arguments f p (1, :) = 1:f.legs @@ -606,14 +610,11 @@ assert(f.isdual(i) ~= f.isdual(mod1(i+1, f.legs)), 'tensors:trees:ArgError', ... 'Tracing only defined between space and its dual.'); - - L1 = length(f); if i == 1 mask = find(f.charges(:, 1) == conj(f.charges(:, 2)) & ... f.charges(:, 3) == one(f.charges)); - b = f.charges(mask, 1); vals = sqrt(qdim(b)); if f.isdual(i), vals = frobeniusschur(b) .* vals; end @@ -693,7 +694,7 @@ % % Arguments % --------- - % f : FusionTree + % f : :class:`FusionTree` % tree to permute. % % p : int @@ -706,10 +707,11 @@ % ------- % c : sparse double % matrix of coefficients that transform input to output trees. - % f(i) --> c(i,j) * f(j) + % `f(i) --> c(i,j) * f(j)` % - % f : FusionTree + % f : :class:`FusionTree` % permuted trees in canonical form. + arguments f p (1,:) @@ -727,7 +729,7 @@ % % Arguments % --------- - % f : FusionTree + % f : :class:`FusionTree` % tree to repartition. % % newrank : int @@ -737,10 +739,11 @@ % ------- % c : sparse double % matrix of coefficients that transform input to output trees. - % f(i) --> c(i,j) * f(j) + % `f(i) --> c(i,j) * f(j)` % - % f : FusionTree + % f : :class:`FusionTree` % repartitioned trees in canonical form. + arguments f newrank (1, 2) int16 @@ -910,12 +913,15 @@ function bool = isempty(f) bool = size(f, 2) == 0; end + function l = legs(f) l = sum(f.rank); end + function l = length(f) l = size(f.charges, 1); end + function varargout = size(f, varargin) if nargin == 1 if nargout < 2 @@ -939,17 +945,14 @@ varargout{i} = sz(varargin{i}); end end + function n = numArgumentsFromSubscript(~, ~, ~) n = 1; end + function varargout = subsref(f, s) - % subsref - Custom indexing behavior. - % varargout = subsref(f, s) - % overloads the syntax f(i), f{i} or f.i switch s(1).type case '()' - % assert(isscalar(s(1).subs), 'tensors:tree:indexing', ... - % 'Not a valid indexing expression.'); if length(s(1).subs) == 1 i = s(1).subs{1}; elseif length(s(1).subs) == 2 && strcmp(s(1).subs{1}, ':') @@ -990,15 +993,18 @@ [varargout{1:nargout}] = builtin('subsref', f, s); end end + function n = numel(f) n = size(f.charges, 1); end + function bool = ne(f1, f2) bool = any(f1.charges ~= f2.charges, 2); if hasmultiplicity(fusionstyle(f1)) bool = bool | any(f1.vertices ~= f2.vertices, 2); end end + function bool = eq(f1, f2) bool = all(f1.charges == f2.charges, 2); if hasmultiplicity(fusionstyle(f1)) @@ -1044,8 +1050,17 @@ function displayNonScalarObject(f) %% Constructors and converters methods function A = double(f) - % double - Return the tensor representation of a fusion tree. - % A = double(f) + % Construct a numeric representation of a fusion tree. + % + % Arguments + % --------- + % f : :class:`FusionTree` + % + % Returns + % ------- + % A : double + % array representation of a fusion tree. + assert(issymmetric(braidingstyle(f)), ... 'tensors:tree:invalidCharge', ... 'Tensor representation is only defined for charges with bosonic braiding.'); @@ -1071,12 +1086,13 @@ function displayNonScalarObject(f) A = cell2mat(A_cell); end + function C = fusiontensor(f) % Construct an array representation of a fusion tree. % % Arguments % --------- - % f : FusionTree + % f : :class:`FusionTree` % an array of fusion trees to convert. % % Returns @@ -1089,16 +1105,15 @@ function displayNonScalarObject(f) return; end - if length(f) > 1 - C = arrayfun(@fusiontensor, f); - % C = cell(size(f)); - % for i = 1:length(f) - % C(i) = fusiontensor(f(i)); - % end + C = cell(size(f)); + for i = 1:numel(f) + C(i) = fusiontensor(f(i)); + end return end + multi = hasmultiplicity(fusionstyle(f)); switch f.rank(1) case 0 diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 8ab9ce0..41dd7a9 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -126,7 +126,7 @@ % fun : :class:`function_handle` % function of signature :code:`fun(dims, trees)` to fill with. % - % trees : :class`FusionTree` + % trees : :class:`FusionTree` % optional list of fusion trees to identify the tensor blocks. % % Returns @@ -245,11 +245,11 @@ % % :code:`Tensor.new(fun, dims, arrows)` % - % :code:`Tensor.new(fun, charges, degeneracies, arrow)` TODO + % :code:`Tensor.new(fun, codomain, domain)` % % :code:`Tensor.new(fun, tensor)` % - % :code:'Tensor.new(..., 'Rank', r, 'Mode', mode) + % :code:`Tensor.new(..., 'Rank', r, 'Mode', mode)` % % Arguments % --------- @@ -484,8 +484,8 @@ % % Usage % ----- - % t = ctranspose(t) - % t = t' + % :code:`t = ctranspose(t)` + % :code:`t = t'` % % Arguments % --------- @@ -502,9 +502,9 @@ end function d = dot(t1, t2) - % Compute the scalar dot product of two tensors. - % This is defined as the overlap of the two tensors, which therefore must have - % equal domain and codomain. This function is sesquilinear in its arguments. + % Compute the scalar dot product of two tensors. This is defined as the overlap + % of the two tensors, which therefore must have equal domain and codomain. This + % function is sesquilinear in its arguments. % % Arguments % --------- @@ -530,6 +530,18 @@ end function t1 = minus(t1, t2) + % Compute the difference between two tensors. + % + % Arguments + % --------- + % t1, t2 : :class:`Tensor` or numeric + % input tensors, scalars are interpreted as scalar * eye. + % + % Returns + % ------- + % t1 : :class:`Tensor` + % output tensor + if isnumeric(t1), t1 = t1 + (-t2); return; end assert(isequal(size(t1), size(t2)), 'Incompatible sizes for vectorized minus.'); @@ -548,33 +560,49 @@ end end - function t = mrdivide(t, a) - % Scalar division of a tensors. - % - % .. todo:: - % Implement for tensors. + function t = mldivide(t1, t2) + % Left division of tensors. % % Usage % ----- - % t = rdivide(t, a) + % :code:`t = mldivide(t1, t2)` % - % t = t ./ a + % :code:`t = t1 \ t2` % % Arguments % --------- + % t1, t2 : :class:`Tensor` or numeric + % input tensor or scalar. + % + % Returns + % ------- % t : :class:`Tensor` - % input tensor. - % - % a : numeric - % input scalar. + % output tensor. + + t = inv(t1) * t2; + end + + + function t = mrdivide(t1, t2) + % Right division of tensors. + % + % Usage + % ----- + % :code:`t = mrdivide(t1, t2)` + % + % :code:`t1 = t1 / t2` + % + % Arguments + % --------- + % t1, t2 : :class:`Tensor` or numeric + % input tensor or scalar. % % Returns % ------- % t : :class:`Tensor` % output tensor. - assert(isnumeric(a) && isscalar(a), 'Only implemented for scalar numeric a.'); - t = t ./ a; + t = t1 * inv(t2); end function C = mtimes(A, B) @@ -582,7 +610,9 @@ % % Usage % ----- - % A = mtimes(A, B) + % :code:`A = mtimes(A, B)` + % + % :code:`A = A * B` % % Arguments % --------- @@ -692,7 +722,7 @@ else assert(isequal(t1(i).domain, t2(i).domain) && ... isequal(t1(i).codomain, t2(i).codomain), 'tensors:SpaceMismatch', ... - 'Cannot subtract tensors of different structures.'); + 'Cannot add tensors of different structures.'); t1(i).var = t1(i).var + t2(i).var; end end @@ -729,7 +759,7 @@ assert(length(p) == nspaces(t), 'Invalid permutation.'); assert(length(p) == sum(r), 'Invalid new rank.'); - if (all(p == 1:nspaces(t)) && all(rank(t) == r)), return; end + if (all(p == 1:nspaces(t)) && all(rank(t) == r)), return; end persistent cache if isempty(cache), cache = LRU; end @@ -757,7 +787,7 @@ end function t = repartition(t, r) - % Permute the spaces of a tensor. + % Change the rank of a tensor. % % Arguments % --------- @@ -785,9 +815,9 @@ % % Usage % ----- - % t = rdivide(t, a) + % :code:`t = rdivide(t, a)` % - % t = t ./ a + % :code:`t = t ./ a` % % Arguments % --------- @@ -921,9 +951,9 @@ % % Usage % ----- - % t = times(t, a) - % t = t .* a - % t = a .* t + % :code:`t = times(t, a)` + % :code:`t = t .* a` + % :code:`t = a .* t` % % Arguments % --------- @@ -968,6 +998,7 @@ function t = transpose(t, p, r) % Compute the transpose of a tensor. This is defined as rotating the domain to % the codomain and vice versa, while cyclicly permuting the tensor blocks. + % Currently not implemented. % % Usage % ----- @@ -990,11 +1021,11 @@ % t : :class:`Tensor` % transposed output tensor. - error('TBA'); + error('tensors:TBA', 'This method has not been implemented.'); end function t = uplus(t) - + end function t = uminus(t) @@ -1010,11 +1041,11 @@ % % Usage % ----- - % D = eig(A) + % :code:`D = eig(A)` % - % [V, D] = eig(A) + % :code:`[V, D] = eig(A)` % - % [V, D, W] = eig(A) + % :code:`[V, D, W] = eig(A)` % % Arguments % --------- @@ -1081,11 +1112,11 @@ function [Q, R] = leftorth(t, p1, p2, alg) % Factorize a tensor into an orthonormal basis `Q` and remainder `R`, such that - % permute(t, [p1 p2], [length(p1) length(p2)]) = Q * R. + % :code:`permute(t, [p1 p2], [length(p1) length(p2)]) = Q * R`. % % Usage % ----- - % [Q, R] = leftorth(t, p1, p2, alg) + % :code:`[Q, R] = leftorth(t, p1, p2, alg)` % % Arguments % --------- @@ -1152,20 +1183,20 @@ W = V; end - Q = Tensor.eye(t.codomain, W); + Q = t.eye(t.codomain, W); Q.var = fill_matrix_data(Q.var, Qs, dims.charges); - R = Tensor.zeros(W, t.domain); + R = t.zeros(W, t.domain); R.var = fill_matrix_data(R.var, Rs, dims.charges); end function [R, Q] = rightorth(t, p1, p2, alg) % Factorize a tensor into an orthonormal basis `Q` and remainder `L`, such that - % permute(t, [p1 p2], [length(p1) length(p2)]) = L * Q. + % :code:`permute(t, [p1 p2], [length(p1) length(p2)]) = L * Q`. % % Usage % ----- - % [R, Q] = rightorth(t, p1, p2, alg) + % :code:`[R, Q] = rightorth(t, p1, p2, alg)` % % Arguments % --------- @@ -1231,25 +1262,47 @@ W = V; end - Q = Tensor.eye(W, t.domain); + Q = t.eye(W, t.domain); Q.var = fill_matrix_data(Q.var, Qs, dims.charges); - R = Tensor.zeros(t.codomain, W); + R = t.zeros(t.codomain, W); R.var = fill_matrix_data(R.var, Rs, dims.charges); end function N = leftnull(t, p1, p2, alg, atol) + % Compute the left nullspace of a tensor, such that + % :code:`N' * permute(t, [p1 p2], [length(p1) length(p2)]) = 0`. + % + % Arguments + % --------- + % t : :class:`Tensor` + % input tensor to compute the nullspace. + % + % p1, p2 : int + % partition of left and right indices, by default this is the partition of the + % input tensor. + % + % alg : char or string + % selection of algorithms for the nullspace: + % + % - 'svd' + % - 'qr' + % + % Returns + % ------- + % N : :class:`Tensor` + % orthogonal basis for the left nullspace. arguments t p1 = 1:t.rank(1) p2 = t.rank(1) + (1:t.rank(2)) - alg = 'svd' + alg {MustBeMember(alg, {'svd', 'qr'})} = 'svd' atol = norm(t) * eps(underlyingType(t)) end if isempty(p1), p1 = 1:rank(t, 1); end - if isempty(p2), p2 = rank(t, 1) + (1:rank(t,2)); end + if isempty(p2), p2 = rank(t, 1) + (1:rank(t, 2)); end t = permute(t, [p1 p2], [length(p1) length(p2)]); @@ -1263,22 +1316,44 @@ dims.degeneracies(i) = size(Ns{i}, 2); end - N = Tensor.eye(t.codomain, t.codomain.new(dims, false)); + N = t.eye(t.codomain, t.codomain.new(dims, false)); N.var = fill_matrix_data(N.var, Ns, dims.charges); end function N = rightnull(t, p1, p2, alg, atol) + % Compute the right nullspace of a tensor, such that + % :code:`permute(t, [p1 p2], [length(p1) length(p2)]) * N = 0`. + % + % Arguments + % --------- + % t : :class:`Tensor` + % input tensor to compute the nullspace. + % + % p1, p2 : int + % partition of left and right indices, by default this is the partition of the + % input tensor. + % + % alg : char or string + % selection of algorithms for the nullspace: + % + % - 'svd' + % - 'lq' + % + % Returns + % ------- + % N : :class:`Tensor` + % orthogonal basis for the right nullspace. arguments t p1 = 1:t.rank(1) p2 = t.rank(1) + (1:t.rank(2)) - alg = 'svd' + alg {mustBeMember(alg, {'svd', 'lq'})} = 'svd' atol = norm(t) * eps(underlyingType(t)) end if isempty(p1), p1 = 1:rank(t, 1); end - if isempty(p2), p2 = rank(t, 1) + (1:rank(t,2)); end + if isempty(p2), p2 = rank(t, 1) + (1:rank(t, 2)); end t = permute(t, [p1 p2], [length(p1) length(p2)]); @@ -1357,6 +1432,7 @@ dims.degeneracies = zeros(size(mblocks)); doTrunc = ~isempty(fieldnames(trunc)); + if doTrunc, eta = 0; end for i = 1:length(mblocks) if doTrunc [Us{i}, Ss{i}, Vs{i}] = svd(mblocks{i}, 'econ'); @@ -1369,19 +1445,21 @@ if isfield(trunc, 'TruncBelow') for i = 1:length(mblocks) - dims.degeneracies(i) = sum(diag(Ss{i}) > trunc.TruncBelow); - + s = diag(Ss{i}); + dims.degeneracies(i) = sum(s > trunc.TruncBelow); + eta = eta + sum(s(dims.degeneracies(i) + 1:end)); Us{i} = Us{i}(:, 1:dims.degeneracies(i)); - Ss{i} = Ss{i}(1:dims.degeneracies(i), 1:dims.degeneracies(i)); + Ss{i} = diag(s(1:dims.degeneracies(i))); Vs{i} = Vs{i}(1:dims.degeneracies(i), :); end end if isfield(trunc, 'TruncDim') for i = 1:length(mblocks) dims.degeneracies(i) = min(dims.degeneracies(i), trunc.TruncDim); - + s = diag(Ss{i}); + eta = eta + sum(s(dims.degeneracies(i) + 1:end)); Us{i} = Us{i}(:, 1:dims.degeneracies(i)); - Ss{i} = Ss{i}(1:1:dims.degeneracies(i), 1:1:dims.degeneracies(i)); + Ss{i} = diag(s(1:dims.degeneracies(i))); Vs{i} = Vs{i}(1:1:dims.degeneracies(i), :); end end @@ -1465,9 +1543,9 @@ % % Usage % ----- - % A = x^Y + % :class:`A = x^Y` % - % A = X^y + % :class:`A = X^y` % % Arguments % --------- @@ -1554,14 +1632,14 @@ %% methods function bool = isposdef(t) - % Test if a tensor is a positive-definite map. Generally, a Hermitian matrix M - % is positive-definite if the real number z' M z is positive for every nonzero - % complex column vector z. + % Test if a tensor is a positive-definite map. Generally, a Hermitian matrix `M` + % is positive-definite if the real number `z' * M * z` is positive for every + % nonzero complex column vector `z`. % This is equivalent to any of the following conditions: % % - M is Hermitian and all eigenvalues are real and positive. % - M is congruent with a diagonal matrix with positive real entries. - % - There exists an invertible B such that M = B' * B. + % - There exists an invertible B such that `M = B' * B`. % % Arguments % --------- @@ -1571,7 +1649,7 @@ % Returns % ------- % bool : logical - % true if t is positive definite. + % true if `t` is positive definite. mblocks = matrixblocks(t); for i = 1:length(mblocks) @@ -1584,8 +1662,8 @@ end function bool = isisometry(t, side, tol) - % Test if a tensor is an isometric map. Generally, a matrix M is left or right - % isometric if M' * M = I or M * M' = I. + % Test if a tensor is an isometric map. Generally, a matrix `M` is left or right + % isometric if `M' * M = I` or `M * M' = I`. % % Arguments % --------- @@ -1598,8 +1676,8 @@ % Keyword Arguments % ----------------- % AbsTol, RelTol : numeric - % norm(M * M' - eye(size(M))) < max(AbsTol, RelTol * norm(M)). - % By default AbsTol = 0 and RelTol = eps. + % `norm(M * M' - eye(size(M))) < max(AbsTol, RelTol * norm(M))`. + % By default `AbsTol = 0` and `RelTol = eps`. % % Returns % ------- @@ -1698,7 +1776,7 @@ %% Solvers methods function varargout = linsolve(A, b, x0, M1, M2, options) - % Find a solution for a linear system A(x) = b or A * x = b. + % Find a solution for a linear system `A(x) = b` or `A * x = b`. % % Arguments % --------- @@ -1851,8 +1929,8 @@ % % Usage % ----- - % [V, D, flag] = eigsolve(A, x0, howmany, sigma, kwargs) - % D = eigsolve(A, x0, ...) + % :code:`[V, D, flag] = eigsolve(A, x0, howmany, sigma, kwargs)` + % :code:`D = eigsolve(A, x0, ...)` % % Arguments % --------- @@ -2053,7 +2131,6 @@ % Locate non-empty blocks [lia, locb] = ismember(trees.uncoupled, ... charges(s).', 'rows'); - assert(all(lia)); % Fill output if fusionstyle(trees) == FusionStyle.Unique @@ -2064,7 +2141,6 @@ tree_double = tree_array{i}; tree_size = size(tree_double, 1:nspaces(t)); block_size = size(blocks{i}, 1:nspaces(t)); - assert(isequal(tree_size .* block_size, size(a_cell{locb(i)}, 1:nspaces(t)))); a_cell{locb(i)} = a_cell{locb(i)} + ... reshape(contract(tree_double, -(1:2:2*nspaces(t)), ... blocks{i}, -(2:2:2*nspaces(t))), tree_size .* block_size); diff --git a/src/tensors/spaces/AbstractSpace.m b/src/tensors/spaces/AbstractSpace.m index 1bc7cab..881f97d 100644 --- a/src/tensors/spaces/AbstractSpace.m +++ b/src/tensors/spaces/AbstractSpace.m @@ -289,7 +289,7 @@ function hashable = GetMD5_helper(spaces) % Helper function for hash algorithm. This converts the space object to a data - % structure which can be processed by :function:`GetMD5`. + % structure which can be processed by :func:`GetMD5`. % % Arguments % --------- @@ -299,7 +299,7 @@ % Returns % ------- % hashable : cell - % data which can be accepted by :function:`GetMD5`. + % data which can be accepted by :func:`GetMD5`. hashable = {spaces.dimensions, spaces.isdual}; end diff --git a/src/tensors/spaces/CartesianSpace.m b/src/tensors/spaces/CartesianSpace.m index 11c3f3d..1c40aa1 100644 --- a/src/tensors/spaces/CartesianSpace.m +++ b/src/tensors/spaces/CartesianSpace.m @@ -262,7 +262,7 @@ function hashable = GetMD5_helper(spaces) % Helper function for hash algorithm. This converts the space object to a data - % structure which can be processed by :function:`GetMD5`. + % structure which can be processed by :func:`GetMD5`. % % Arguments % --------- @@ -272,14 +272,12 @@ % Returns % ------- % hashable : (1, :) int - % data which can be accepted by :function:`GetMD5`. + % data which can be accepted by :func:`GetMD5`. hashable = [spaces.dimensions]; end function disp(spaces) - % Custom display of spaces. - if isscalar(spaces) fprintf('CartesianSpace of dimension %d:\n', spaces.dimensions); return diff --git a/src/tensors/spaces/ComplexSpace.m b/src/tensors/spaces/ComplexSpace.m index 354751f..04b7da2 100644 --- a/src/tensors/spaces/ComplexSpace.m +++ b/src/tensors/spaces/ComplexSpace.m @@ -241,7 +241,7 @@ function hashable = GetMD5_helper(spaces) % Helper function for hash algorithm. This converts the space object to a data - % structure which can be processed by :function:`GetMD5`. + % structure which can be processed by :func:`GetMD5`. % % Arguments % --------- @@ -251,7 +251,7 @@ % Returns % ------- % hashable : (1, :) int - % data which can be accepted by :function:`GetMD5`. + % data which can be accepted by :func:`GetMD5`. hashable = [spaces.dimensions spaces.isdual]; end diff --git a/src/utility/wigner/Wigner3j.m b/src/utility/wigner/Wigner3j.m index b2373fd..0a49dda 100644 --- a/src/utility/wigner/Wigner3j.m +++ b/src/utility/wigner/Wigner3j.m @@ -7,7 +7,7 @@ % j_1 & j_2 & j_3 \\ % m_1 & m_2 & m_3 % \end{pmatrix} -% +% % or the Clebsch-Gordan coefficient % % .. math:: @@ -18,8 +18,9 @@ % % Usage % ----- -% :code:`W = Wigner3j(j1,j2,j3,m1,m2,m3,ifs,ifcb)` -% :code:`Wigner3j(j1,j2,j3,m1,m2,m3)` +% :code:`W = Wigner3j(j1, j2, j3, m1, m2, m3, ifs, ifcb)` +% +% :code:`Wigner3j(j1, j2, j3, m1, m2, m3)` % % Arguments % --------- @@ -36,18 +37,18 @@ % although the central part of all the computations is based on the Racah formula, some % details differ: % -% - 0: the symbolic (presumably accurate) computation with the double-precision output -% - -1: the same symbolic computation as ifs=0 but with the symbolic output (accurate +% - 0: the symbolic (presumably accurate) computation with the double-precision output +% - -1: the same symbolic computation as ifs=0 but with the symbolic output (accurate % square root/rational-type equation, simplified) -% - -2: the same as :code:`ifs=-1` but without a final simplification of the symbolic +% - -2: the same as :code:`ifs=-1` but without a final simplification of the symbolic % expression, which can be time-consuming sometimes -% - 1: (default, recommended), 2, 3 - numeric double-precision algorithms (see Notes below) +% - 1: (default, recommended), 2, 3 - numeric double-precision algorithms (see Notes below) % - 4: the symbolic computation with the double precision output, but cached. % % all other input values of ifs are set to the closest from the above list. % % ifcb -% if exists and is true, switches to computing the Clebsch-Gordan coefficients instead of +% if exists and is true, switches to computing the Clebsch-Gordan coefficients instead of % the $3j$-symbols (default :code:`false`) % % Returns diff --git a/src/utility/wigner/Wigner6j.m b/src/utility/wigner/Wigner6j.m index 6d526cc..a14d7dc 100644 --- a/src/utility/wigner/Wigner6j.m +++ b/src/utility/wigner/Wigner6j.m @@ -1,5 +1,5 @@ function wig = Wigner6j(j1,j2,j3,j4,j5,j6,ifs,ifcb) -% Computes the Wigner $6j$ symbols +% Computes the Wigner $$6j$$ symbols % % .. math:: % @@ -7,7 +7,7 @@ % j_1 & j_2 & j_3 \\ % j_4 & j_5 & j_6 % \end{Bmatrix} -% +% % or the recoupling matrix element % % .. math:: @@ -22,31 +22,32 @@ % % Usage % ----- -% :code:`W = Wigner6j(j1,j2,j3,j4,j5,j6,ifs,ifcb)` -% :code:`Wigner6j(j1,j2,j3,j4,j5,j6)` +% :code:`W = Wigner6j(j1, j2, j3, j4, j5, j6, ifs, ifcb)` +% +% :code:`Wigner6j(j1, j2, j3, j4, j5, j6)` % % Arguments % --------- % j1, j2, j3, j4, j5, j6 % set of six angular momenta, must be arrays of the same size -% +% % Optional Arguments % ------------------ % ifs % the switch of the computational methods; % although the central part of all the computations is based on the Racah formula, some details differ: % -% - 0 - the symbolic (presumably accurate) computation with the double-precision output -% - -1 - the same symbolic computation as ifs=0 but with the symbolic output (accurate +% - 0 - the symbolic (presumably accurate) computation with the double-precision output +% - -1 - the same symbolic computation as ifs=0 but with the symbolic output (accurate % square root/rational-type equation, simplified) -% - -2 - the same as :code:`ifs=-1` but without a final simplification of the symbolic +% - -2 - the same as :code:`ifs=-1` but without a final simplification of the symbolic % expression, which can be time-consuming sometimes -% - 1, 2 (default, recommended), 3 - numeric double-precision algorithms (see Notes below) +% - 1, 2 (default, recommended), 3 - numeric double-precision algorithms (see Notes below) % % all other input values of ifs are set to the closest from the above list. % % ifcb -% if exists and is true, switches to computing the coupling matrix elements instead of the +% if exists and is true, switches to computing the coupling matrix elements instead of the % $6j$-symbols (default :code:`false`) % % Returns From bf0ccea9aa1f77d0e04b65be3cfd21f1fc7c671a Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sat, 17 Sep 2022 15:14:01 +0200 Subject: [PATCH 019/245] Docstring GetMD5 --- src/caches/GetMD5/GetMD5.m | 119 ++++++++++++++----------------------- 1 file changed, 45 insertions(+), 74 deletions(-) diff --git a/src/caches/GetMD5/GetMD5.m b/src/caches/GetMD5/GetMD5.m index a7bb749..2174349 100644 --- a/src/caches/GetMD5/GetMD5.m +++ b/src/caches/GetMD5/GetMD5.m @@ -1,93 +1,64 @@ function GetMD5(varargin) -% GetMD5 - 128 bit MD5 checksum: file, string, array, byte stream -% This function calculates a 128 bit checksum for arrays or files. -% Digest = GetMD5(Data, Mode, Format) -% INPUT: -% Data: File name or array. -% Mode: CHAR to declare the type of the 1st input. Not case-sensitive. -% 'File': Data is a file name as CHAR. -% '8Bit': If Data is a CHAR array, only the 8 bit ASCII part is -% used. Then the digest is the same as for a ASCII text -% file e.g. created by: FWRITE(FID, Data, 'uchar'). -% This is ignored if Data is not of type CHAR. -% 'Binary': The MD5 sum is obtained for the contents of Data. -% This works for numerical, CHAR and LOGICAL arrays. -% 'Array': Include the class and dimensions of Data in the MD5 -% sum. This can be applied for (nested) structs, cells -% and sparse arrays also. -% Optional. Default: '8Bit' for CHAR, 'Binary' otherwise. -% Format: CHAR, format of the output. Only the first character matters. -% The upper/lower case matters for 'hex' only. -% 'hex': [1 x 32] lowercase hexadecimal CHAR. -% 'HEX': [1 x 32] uppercase hexadecimal CHAR. -% 'double': [1 x 16] double vector with UINT8 values. -% 'uint8': [1 x 16] uint8 vector. -% 'base64': [1 x 22] CHAR, encoded to base 64 (A:Z,a:z,0:9,+,/). -% The CHAR is not padded to keep it short. -% Optional, default: 'hex'. +% Calculates a 128 bit MD5 checksum for arrays or files. % -% OUTPUT: -% Digest: A 128 bit number is replied in the specified format. +% Usage +% ----- +% :code:`digest = GetMD5(Data, Mode, Format)` % -% NOTE: -% * For sparse arrays, function handles, java and user-defined objects the -% M-file GetMD5_helper is called. -% * This replies the same MD5 hash as DataHash, but much faster. +% Arguments +% --------- +% Data +% input data on which the checksum is computed. % -% EXAMPLES: -% Three methods to get the MD5 of a file: -% 1. Direct file access (recommended): -% MD5 = GetMD5(which('GetMD5.m'), 'File') -% 2. Import the file to a CHAR array (no text mode for exact line breaks!): -% FID = fopen(which('GetMD5.m'), 'r'); -% S = fread(FID, inf, 'uchar=>char'); -% fclose(FID); -% MD5 = GetMD5(S, '8bit') -% 3. Import file as a byte stream: -% FID = fopen(which('GetMD5.m'), 'r'); -% S = fread(FID, inf, 'uint8=>uint8'); -% fclose(FID); -% MD5 = GetMD5(S, 'bin'); % 'bin' is optional +% Mode : char +% optional declaration of the type of the input data. % -% Test data: -% GetMD5(char(0:511), '8bit', 'HEX') -% % => F5C8E3C31C044BAE0E65569560B54332 -% GetMD5(char(0:511), 'bin') -% % => 3484769D4F7EBB88BBE942BB924834CD -% GetMD5(char(0:511), 'array') -% % => b9a955ae730b25330d4f4ebb0a51e8f0 -% GetMD5('abc') % implicit '8bit' for CHAR -% % => 900150983cd24fb0d6963f7d28e17f72 +% - 'File' : `data` is a file name as a `char`. +% - '8Bit' : If `data` is a `char` array, only the 8 bit ASCII part is used. Then the +% digest is the same as for a ASCII text file e.g. created by `fwrite(fid, data, +% 'uchar')`. +% - 'Binary' : The MD5 sum is obtained for the contents of `data`. This works for +% numerical, char and logical arrays. +% - 'Array' : Include the class and size information of `data` in the MD5 sum. This can be +% applied for (nested) structs, objects, cells and sparse arrays also. % -% COMPILATION: -% The C-Mex-file is compiled automatically, when this function is called the -% first time. -% See GetMD5.c for details or a manual compilation. +% Format : char +% Format of the output, default value is 'hex'. % -% Tested: Matlab/64 7.8, 7.13, 8.6, 9.1, Win7/64 +% - 'hex' : (1, 32) lowercase hexadecimal char. +% - 'HEX' : (1, 32) uppercase hexadecimal char. +% - 'double' : (1, 16) double vector with uint8 values. +% - 'uint8' : (1, 16) uint8 vector. +% - 'base64' : (1, 22) char vector, encoded in base 64 (A:Z, a:z, 0:9, +, /), unpadded. +% +% Returns +% ------- +% digest +% A 128 bit number in the specified format. +% +% Notes +% ----- +% For sparse arrays, function handles, java and user-defined objects :func:`GetMD5_helper` +% is called to convert into a data format that can be handled. +% +% The C-Mex-file is compiled automatically when this function is called for the first time. +% See :file:`GetMD5.c` for details or a manual installation. +% +% References +% ---------- % Author: Jan Simon, Heidelberg, (C) 2009-2019 matlab.2010(a)n(MINUS)simon.de +% % License: This program is derived from the RSA Data Security, Inc. % MD5 Message Digest Algorithm, RFC 1321, R. Rivest, April 1992 % This implementation is published under the BSD license. % -% See also CalcCRC32, DataHash. +% See also +% -------- +% Other methods for checksums can be found: :code:`CalcCRC32`, :code:`DataHash`, ... % % For more checksum methods see: % http://www.mathworks.com/matlabcentral/fileexchange/31272-datahash -% $JRev: R5G V:038 Sum:yFie32x5VgiF Date:04-Mar-2019 16:20:44 $ -% $License: BSD (use/copy/change/redistribute on own risk, mention the author) $ -% $UnitTest: uTest_GetMD5 $ -% $File: Tools\GLFile\GetMD5.m $ -% History: -% 015: 15-Dec-2009 16:53, BUGFIX: UINT32 has 32 bits on 64 bit systems now. -% Thanks to Sebastiaan Breedveld! -% 026: 30-Jan-2015 00:12, 64 bit arrays larger than 2.1GB accepted. -% Successor of "CalcMD5". -% 031: 04-Jun-2016 22:26, Structs and cells are handled. -% 033: 03-May-2018 20:37, BUGFIX: Null pointer in data array. -% 035: 27-Aug-2018 00:52, Consider string class. - % Dummy code, which calls the auto-compilation only: --------------------------- % This M-function is not called, if the compiled MEX function is in the path. From 826e7fac0b61820741aa7ef9e14b77acb64b6907 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 19 Sep 2022 19:26:27 +0200 Subject: [PATCH 020/245] Bugfixes and docstrings. --- src/tensors/FusionTree.m | 11 ++++++----- src/tensors/Tensor.m | 22 +++++++++++++++------- src/tensors/kernels/AbstractBlock.m | 8 ++++---- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/tensors/FusionTree.m b/src/tensors/FusionTree.m index 5613e51..ba9fb75 100644 --- a/src/tensors/FusionTree.m +++ b/src/tensors/FusionTree.m @@ -841,7 +841,11 @@ end function style = fusionstyle(f) - style = f.charges.fusionstyle; + try + style = fusionstyle(f.charges); + catch + bla + end end @@ -1106,10 +1110,7 @@ function displayNonScalarObject(f) end if length(f) > 1 - C = cell(size(f)); - for i = 1:numel(f) - C(i) = fusiontensor(f(i)); - end + C = arrayfun(@fusiontensor, f); return end diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 41dd7a9..038909f 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -37,6 +37,14 @@ if nargin == 0, return; end + % t = Tensor(tensor) + if nargin == 1 && isa(varargin{1}, 'Tensor') + t.codomain = varargin{1}.codomain; + t.domain = varargin{1}.domain; + t.var = varargin{1}.var; + return + end + % t = Tensor(array) if nargin == 1 && isnumeric(varargin{1}) t.domain = []; @@ -823,7 +831,7 @@ % --------- % t : :class:`Tensor` % input tensor. - % + % uminus % a : numeric % input scalar. % @@ -952,7 +960,7 @@ % Usage % ----- % :code:`t = times(t, a)` - % :code:`t = t .* a` + % :code:`t uminus= t .* a` % :code:`t = a .* t` % % Arguments @@ -1002,8 +1010,8 @@ % % Usage % ----- - % t = transpose(t, p, rank) - % t = t.' + % :code:`t = transpose(t, p, rank)` + % :code:`t = t.'` % % Arguments % --------- @@ -1297,7 +1305,7 @@ t p1 = 1:t.rank(1) p2 = t.rank(1) + (1:t.rank(2)) - alg {MustBeMember(alg, {'svd', 'qr'})} = 'svd' + alg {mustBeMember(alg, {'svd', 'qr'})} = 'svd' atol = norm(t) * eps(underlyingType(t)) end @@ -2129,7 +2137,7 @@ a_cell = mat2cell(a, dimsizes{:}); % Locate non-empty blocks - [lia, locb] = ismember(trees.uncoupled, ... + [~, locb] = ismember(trees.uncoupled, ... charges(s).', 'rows'); % Fill output @@ -2165,7 +2173,7 @@ function disp(t) if isscalar(t) r = t.rank; - fprintf('Rank (%d, %d) Tensor:\n\n', r(1), r(2)); + fprintf('Rank (%d, %d) %s:\n\n', r(1), r(2), class(t)); s = space(t); for i = 1:length(s) fprintf('%d.\t', i); diff --git a/src/tensors/kernels/AbstractBlock.m b/src/tensors/kernels/AbstractBlock.m index bd1355e..128b1b5 100644 --- a/src/tensors/kernels/AbstractBlock.m +++ b/src/tensors/kernels/AbstractBlock.m @@ -51,7 +51,7 @@ % (Abstract) Compute ```Y = permute(X, p) .* a + Y .* b```. % This method is the computationally critical method of this class, thus has % special cases for scalar multiplication (a == 0), addition (nargin == 4), and - % various optimisations when a == 1, b == 0 | b == 1. Additionally, this method + % various optimizations when a == 1, b == 0 | b == 1. Additionally, this method % should not be called directly as it should not perform any error checks. % % Arguments @@ -59,13 +59,13 @@ % a : double % scalar to multiply with X. % - % X : AbstractBlock + % X : :class:`AbstractBlock` % list of source blocks. % % b : double % scalar to multiply with Y. % - % Y : AbstractBlock + % Y : :class:`AbstractBlock` % list of destination blocks. % % p : int @@ -76,7 +76,7 @@ % % Returns % ------- - % Y : AbstractBlock + % Y : :class:`AbstractBlock` % Result of computing Y = permute(X, p) .* a + Y .* b. error('This method should be overloaded.'); From 07ef097046337c8ac33d66c76cea8a86b63e398e Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 22 Sep 2022 12:58:51 +0200 Subject: [PATCH 021/245] Fix fermionic tests. --- test/TestFusionTree.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/TestFusionTree.m b/test/TestFusionTree.m index b2a41cc..94e1b94 100644 --- a/test/TestFusionTree.m +++ b/test/TestFusionTree.m @@ -168,7 +168,7 @@ function braiding(tc) 'Output trees are not allowed.'); % compatible with fusiontensor - if issymmetric(braidingstyle(f)) + if istwistless(braidingstyle(f)) a1_cell = fusiontensor(f); a2_cell = fusiontensor(f1); for j = 1:length(f) From 103564a3177b01c61ce3ea9485704d32f566987e Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 27 Sep 2022 21:29:51 +0200 Subject: [PATCH 022/245] bugfixes, performance updates and improvements --- src/tensors/FusionTree.m | 14 ++- src/tensors/Tensor.m | 141 ++++++++++++++++++++++----- src/tensors/charges/AbstractCharge.m | 49 ++++++++++ src/tensors/kernels/MatrixBlock.m | 81 +++++++++++---- src/tensors/spaces/AbstractSpace.m | 20 ++-- src/tensors/spaces/GradedSpace.m | 68 ++++++++++--- 6 files changed, 306 insertions(+), 67 deletions(-) diff --git a/src/tensors/FusionTree.m b/src/tensors/FusionTree.m index ba9fb75..82f9130 100644 --- a/src/tensors/FusionTree.m +++ b/src/tensors/FusionTree.m @@ -841,13 +841,16 @@ end function style = fusionstyle(f) - try style = fusionstyle(f.charges); - catch - bla - end end + function [lia, locb] = ismember(f1, f2) + if hasmultiplicity(fusionstyle(f1)) + error('TBA'); + end + + [lia, locb] = ismember(f1.charges, f2.charges, 'rows'); + end function [f, p] = sort(f) % sort - Sort the fusion trees into canonical order. @@ -929,7 +932,8 @@ function varargout = size(f, varargin) if nargin == 1 if nargout < 2 - varargout{1} = [1, size(f.charges, 1)]; + c = f.charges; + varargout{1} = [1, size(c, 1)]; elseif nargout == 2 varargout = {1, size(f.charges, 1)}; else diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 038909f..54c2d8f 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -108,9 +108,17 @@ if isnumeric(data), data = {data}; end if iscell(data) - t.var = fill_matrix_data(t.var, data, charges); + if isempty(charges) + t.var = fill_matrix_data(t.var, data); + else + t.var = fill_matrix_data(t.var, data, charges); + end else - t.var = fill_matrix_fun(t.var, data, charges); + if isempty(charges) + t.var = fill_matrix_fun(t.var, data); + else + t.var = fill_matrix_fun(t.var, data, charges); + end end end @@ -310,7 +318,8 @@ arguments kwargs.Rank - kwargs.Mode = 'matrix' + kwargs.Mode {mustBeMember(kwargs.Mode, {'matrix', 'tensor'})} ... + = 'matrix' end % Parse special constructors @@ -355,38 +364,37 @@ % Fill tensor if ~isempty(fun) - if strcmp(kwargs.Mode, 'matrix') - t = fill_matrix(t, fun); - elseif strcmp(kwargs.Mode, 'tensor') - t = fill_tensor(t, fun); - else - error('tensors:ArgumentError', 'Unknown options for Mode'); + switch kwargs.Mode + case 'matrix' + t = fill_matrix(t, fun); + case 'tensor' + t = fill_tensor(t, fun); end end end function t = zeros(varargin) - t = Tensor.new(@(dims, charge) zeros(dims), varargin{:}); + t = Tensor.new(@zeros, varargin{:}); end function t = ones(varargin) - t = Tensor.new(@(dims, charge) ones(dims), varargin{:}); + t = Tensor.new(@ones, varargin{:}); end function t = eye(varargin) - t = Tensor.new(@(dims, charge) eye(dims), varargin{:}); + t = Tensor.new(@eye, varargin{:}); end function t = rand(varargin) - t = Tensor.new(@(dims, charge) rand(dims), varargin{:}); + t = Tensor.new(@rand, varargin{:}); end function t = randc(varargin) - t = Tensor.new(@(dims, charge) randc(dims), varargin{:}); + t = Tensor.new(@randc, varargin{:}); end function t = randnc(varargin) - t = Tensor.new(@(dims, charge) randnc(dims), varargin{:}); + t = Tensor.new(@randnc, varargin{:}); end end @@ -437,6 +445,53 @@ function varargout = matrixblocks(t) [varargout{1:nargout}] = matrixblocks(t.var); end + + function style = braidingstyle(t) + style = braidingstyle(t.codomain, t.domain); + end + + function style = fusionstyle(t) + style = fusionstyle(t.codomain, t.domain); + end + + function tdst = insert_onespace(tsrc, i, dual) + arguments + tsrc + i = nspaces(tsrc) + dual = false + end + + spaces = insertone(space(tsrc), i, dual); + data = matrixblocks(tsrc); + + r = rank(tsrc); + if i <= r(1) + r(1) = r(1) + 1; + else + r(2) = r(2) + 1; + end + tdst = Tensor(spaces(1:r(1)), spaces(r(1)+1:end)'); + tdst.var = fill_matrix_data(tdst.var, data); + end + + function tdst = embed(tsrc, tdst) + + bsrc = tensorblocks(tsrc); + fsrc = fusiontrees(tsrc); + bdst = tensorblocks(tdst); + fdst = fusiontrees(tdst); + + [lia, locb] = ismember(fsrc, fdst); + nsp = nspaces(tdst); + + for i = find(lia).' + sz = min(size(bsrc{i}, 1:nsp), size(bdst{locb(i)}, 1:nsp)); + inds = arrayfun(@(x) 1:x, sz, 'UniformOutput', false); + bdst{locb(i)}(inds{:}) = bsrc{i}(inds{:}); + end + + tdst.var = fill_tensor_data(tdst.var, bdst); + end end @@ -559,6 +614,10 @@ 'Subtraction with scalars only defined for square tensors.'); I = t1(i).eye(t1(i).codomain, t1(i).domain); t1(i).var = t1(i).var - I.var .* t2(i); + elseif iszero(t2(i)) + continue; + elseif iszero(t1(i)) + t1(i) = -t2(i); else assert(isequal(t1(i).domain, t2(i).domain) && ... isequal(t1(i).codomain, t2(i).codomain), 'tensors:SpaceMismatch', ... @@ -590,7 +649,6 @@ t = inv(t1) * t2; end - function t = mrdivide(t1, t2) % Right division of tensors. % @@ -689,6 +747,7 @@ case {1, 2, Inf} n = 0; for i = 1:numel(t) + if iszero(t(i)), continue; end mblocks = matrixblocks(t(i).var); for j = 1:length(mblocks) n = max(norm(mblocks{j}, p), n); @@ -698,6 +757,7 @@ case 'fro' n = 0; for i = 1:numel(t) + if iszero(t(i)), continue; end [mblocks, mcharges] = matrixblocks(t(i).var); qdims = qdim(mcharges); for j = 1:length(mblocks) @@ -727,6 +787,10 @@ 'Addition with scalars only defined for square tensors.'); I = t1(i).eye(t1(i).codomain, t1(i).domain); t1(i).var = t1(i).var + I.var .* t2(i); + elseif iszero(t2(i)) + continue; + elseif iszero(t1(i)) + t1(i) = t2(i); else assert(isequal(t1(i).domain, t2(i).domain) && ... isequal(t1(i).codomain, t2(i).codomain), 'tensors:SpaceMismatch', ... @@ -915,7 +979,7 @@ assert(isequal(A_.domain, B_.codomain), 'tensors:SpaceMismatch', ... 'Contracted spaces incompatible.'); if ~isempty(A_.codomain) || ~isempty(B_.domain) - med.C = Tensor(A_.codomain, B_.domain); + med.C = Tensor.zeros(A_.codomain, B_.domain); else med.C = []; end @@ -932,6 +996,7 @@ else C.var = mul(C.var, varA, varB); end + assert(~isnan(norm(C))); return end @@ -1032,6 +1097,12 @@ error('tensors:TBA', 'This method has not been implemented.'); end + function t = twist(t, i) + if ~istwistless(braidingstyle(t)) + error('TBA'); + end + end + function t = uplus(t) end @@ -1493,6 +1564,10 @@ U.var = fill_matrix_data(U.var, Us, dims.charges); S.var = fill_matrix_data(S.var, Ss, dims.charges); V.var = fill_matrix_data(V.var, Vs, dims.charges); + + if nargout <= 1 + U = S; + end end end @@ -1752,6 +1827,10 @@ end end + function bool = iszero(t) + bool = isempty(t.var); + end + function r = cond(t, p) % Condition number with respect to inversion. This is defined as % :math:`||t|| * ||t^{-1}||` in the p-norm. For well conditioned @@ -1874,10 +1953,10 @@ b_vec = vectorize(b); b_sz = size(b_vec); - if ~isempty(x0) + if ~isempty(x0) && ~iszero(x0) x0_vec = vectorize(x0); else - x0_vec = []; + x0_vec = zeros(b_sz); end % Convert input operators to handle vectors @@ -2038,15 +2117,29 @@ options.KrylovDim = min(sz(1), options.KrylovDim); - [varargout{1:nargout}] = eigs(A_fun, sz(1), howmany, sigma, ... + [V, D, flag] = eigs(A_fun, sz(1), howmany, sigma, ... 'Tolerance', options.Tol, 'MaxIterations', options.MaxIter, ... 'SubspaceDimension', options.KrylovDim, 'IsFunctionSymmetric', ... - options.IsSymmetric, 'StartVector', x0_vec); - if nargout > 1 + options.IsSymmetric, 'StartVector', x0_vec, ... + 'Display', options.Verbosity == 3); + + if nargout <= 1 + varargout = {D}; + elseif nargout == 2 + varargout{2} = D; for i = howmany:-1:1 - V(:, i) = devectorize(varargout{1}(:, i), x0); + varargout{1}(:, i) = devectorize(V(:, i), x0); end - varargout{1} = V; + end + + if flag && options.Verbosity > 0 + warning('eigsolve did not converge.'); + end + + if options.Verbosity > 1 + if flag + fprintf('eigsolve converged.\n'); + end end end diff --git a/src/tensors/charges/AbstractCharge.m b/src/tensors/charges/AbstractCharge.m index 0cfaf9c..246d74b 100644 --- a/src/tensors/charges/AbstractCharge.m +++ b/src/tensors/charges/AbstractCharge.m @@ -686,6 +686,55 @@ reshape(repmat(X{i}, size(Y, 2), 1), size(X{i}, 1), [])]; end end + + function [lia, locb] = ismember_sorted(a, b) + if isempty(a) || isempty(b) + lia = false(size(a)); + locb = zeros(size(a)); + return + end + + if isscalar(a) || isscalar(b) + if any(size(a) == numel(a)) && any(size(b) == numel(b)) + lia = a == b; + else + lia = a(:) == b(:); + end + + if ~any(lia) + lia = false(size(a)); + locb = zeros(size(a)); + return + end + if ~isscalar(b) + locb = find(lia); + locb = locb(1); + lia = any(lia); + else + locb = double(lia); + end + return + end + + [sortab, indsortab] = sort([a(:); b(:)]); + d = sortab(1:end-1) == sortab(2:end); + ndx1 = indsortab(d); + + if nargout <= 1 + lia = ismember(1:length(a), ndx1); + else + szuA = length(a); + d = find(d); + [lia, locb] = ismember(1:szuA, ndx1); + newd = d(locb(lia)); + where = indsortab(newd+1) - szuA; + locb(lia) = where; + end + lia = reshape(lia, size(a)); + if nargout > 1 + locb = reshape(locb, size(a)); + end + end end methods diff --git a/src/tensors/kernels/MatrixBlock.m b/src/tensors/kernels/MatrixBlock.m index a23ce38..fdde9f4 100644 --- a/src/tensors/kernels/MatrixBlock.m +++ b/src/tensors/kernels/MatrixBlock.m @@ -129,24 +129,33 @@ %% General case: addition with permutation % tensor indexing to matrix indexing - rrx = rankrange(X(1).rank); - rry = rankrange(Y(1).rank); + rx = X(1).rank; + ry = Y(1).rank; + rrx = rankrange(rx); + rry = rankrange(ry); p_eff(rry) = rrx(p); + p_eff_1 = p_eff(1:ry(1)); + p_eff_2 = p_eff((1:ry(2)) + ry(1)); % extract small tensor blocks doA = a ~= 1; vars_in = cell(size(map, 1), 1); offset = 0; + + Xrowsizes = {X.rowsizes}; + Xcolsizes = {X.colsizes}; + Xtdims = {X.tdims}; + Xvar = {X.var}; for i = 1:length(X) - rowsz = X(i).rowsizes; - colsz = X(i).colsizes; + rowsz = Xrowsizes{i}; + colsz = Xcolsizes{i}; - var_in = X(i).var; + var_in = Xvar{i}; if doA, var_in = a .* var_in; end - oldtdims = X(i).tdims(:, rrx); - newmdims = [prod(X(i).tdims(:, p_eff(1:Y(1).rank(1))), 2) ... - prod(X(i).tdims(:, p_eff((1:Y(1).rank(2)) + Y(1).rank(1))), 2)]; + oldtdims = Xtdims{i}(:, rrx); + newmdims = [prod(oldtdims(:, p_eff_1), 2) ... + prod(oldtdims(:, p_eff_2), 2)]; for k = 1:length(colsz) - 1 for j = 1:length(rowsz) - 1 @@ -173,11 +182,13 @@ end % inject small tensor blocks + Yrowsizes = {Y.rowsizes}; + Ycolsizes = {Y.colsizes}; if b == 0 offset = 0; for i = 1:length(Y) - rows = length(Y(i).rowsizes) - 1; - cols = length(Y(i).colsizes) - 1; + rows = length(Yrowsizes{i}) - 1; + cols = length(Ycolsizes{i}) - 1; if rows < cols m = cell(rows, 1); for n = 1:rows @@ -199,8 +210,8 @@ if b == 1 offset = 0; for i = 1:length(Y) - rows = length(Y(i).rowsizes) - 1; - cols = length(Y(i).colsizes) - 1; + rows = length(Yrowsizes{i}) - 1; + cols = length(Ycolsizes{i}) - 1; if rows < cols m = cell(rows, 1); for n = 1:rows @@ -221,8 +232,8 @@ offset = 0; for i = 1:length(Y) - rows = length(Y(i).rowsizes) - 1; - cols = length(Y(i).colsizes) - 1; + rows = length(Yrowsizes{i}) - 1; + cols = length(Ycolsizes{i}) - 1; if rows < cols m = cell(rows, 1); for n = 1:rows @@ -265,8 +276,22 @@ % C : MatrixBlock % Result of computing C = (A .* a) * (B .* b) - [indA, locA] = ismember([C.charge], [A.charge]); - [indB, locB] = ismember([C.charge], [B.charge]); + Acharge = [A.charge]; + Bcharge = [B.charge]; + Ccharge = [C.charge]; + + if isequal(Acharge, Ccharge) + indA = true(size(A)); + locA = 1:length(A); + else + [indA, locA] = ismember_sorted(Ccharge, Acharge); + end + if isequal(Bcharge, Ccharge) + indB = true(size(B)); + locB = 1:length(B); + else + [indB, locB] = ismember_sorted(Ccharge, Bcharge); + end if nargin == 3 || (a == 1 && b == 1) for i = find(indA & indB) @@ -372,7 +397,7 @@ function b = fill_matrix_fun(b, fun, charges) if nargin < 3 || isempty(charges) for i = 1:length(b) - b(i).var = fun(size(b(i).var), b(i).charge); + b(i).var = fun(size(b(i).var)); end else [lia, locb] = ismember(charges, [b.charge]); @@ -382,6 +407,28 @@ end end + function b = fill_tensor_data(b, data) + ctr = 0; + p = rankrange(b(1).rank); + + for i = 1:length(b) + rowsz = b(i).rowsizes; + colsz = b(i).colsizes; + for k = 1:length(colsz) - 1 + for j = 1:length(rowsz) - 1 + ctr = ctr + 1; + b(i).var(rowsz(j)+1:rowsz(j+1), colsz(k)+1:colsz(k+1)) = ... + reshape(permute(data{ctr}, p), ... + rowsz(j+1)-rowsz(j), colsz(k+1)-colsz(k)); + end + end + end + end + + function b = fill_tensor_fun(b, fun, trees) + + end + function Y = minus(X, Y) % Subtraction of X and Y. % diff --git a/src/tensors/spaces/AbstractSpace.m b/src/tensors/spaces/AbstractSpace.m index 881f97d..c6c071d 100644 --- a/src/tensors/spaces/AbstractSpace.m +++ b/src/tensors/spaces/AbstractSpace.m @@ -3,12 +3,12 @@ properties (Access = protected) dimensions % Specification of the internal dimensions - isdual (1,1) logical = false % Flag to indicate if the space is a dual space + dual (1,1) logical = false % Flag to indicate if the space is a dual space end %% Constructors methods - function spaces = AbstractSpace(dimensions, isdual) + function spaces = AbstractSpace(dimensions, dual) % Construct an array of vector spaces. % % Repeating Arguments @@ -26,20 +26,20 @@ arguments (Repeating) dimensions - isdual + dual end if nargin == 0, return; end for i = length(dimensions):-1:1 spaces(i).dimensions = dimensions{i}; - spaces(i).isdual = isdual{i}; + spaces(i).dual = dual{i}; end end end methods (Static) - function spaces = new(dimensions, isdual) + function spaces = new(dimensions, dual) % Construct a vector space. This is a utility method to be able to access the % constructor of a subclass. % @@ -48,7 +48,7 @@ % dimensions : int or struct % a variable which represents the internal dimension of the space. % - % isdual : logical + % dual : logical % a variable which indicates if a space is dual. % % Returns @@ -63,6 +63,10 @@ %% Structure methods + function f = isdual(space) + f = [space.dual]; + end + function c = charges(spaces) % Compute all charge combinations of a space. If no internal structure is % present, this yields an empty result. @@ -179,7 +183,7 @@ % dual spaces. for i = 1:length(spaces) - spaces(i).isdual = ~spaces(i).isdual; + spaces(i).dual = ~spaces(i).dual; end end @@ -301,7 +305,7 @@ % hashable : cell % data which can be accepted by :func:`GetMD5`. - hashable = {spaces.dimensions, spaces.isdual}; + hashable = {spaces.dimensions, spaces.dual}; end end end diff --git a/src/tensors/spaces/GradedSpace.m b/src/tensors/spaces/GradedSpace.m index 916ca67..ca27ff4 100644 --- a/src/tensors/spaces/GradedSpace.m +++ b/src/tensors/spaces/GradedSpace.m @@ -3,7 +3,7 @@ %% Constructors methods - function spaces = GradedSpace(dimensions, isdual) + function spaces = GradedSpace(dimensions, dual) % Construct an array of vector spaces. % % Repeating Arguments @@ -12,7 +12,7 @@ % internal structure of the vector space, with fields 'charges' and % 'degeneracies'. % - % isdual : (1, 1) logical + % dual : (1, 1) logical % flag to denote dual spaces. % % Returns @@ -22,7 +22,7 @@ arguments (Repeating) dimensions (1, 1) struct - isdual (1, 1) logical + dual (1, 1) logical end if nargin == 0 @@ -44,11 +44,19 @@ dimensions{i}.degeneracies = dimensions{i}.degeneracies(I); end - args = [dimensions; isdual]; + args = [dimensions; dual]; end spaces = spaces@AbstractSpace(args{:}); end + + function space = one(spaces) + space = GradedSpace(... + struct('charges', one(spaces(1).dimensions.charges), 'degeneracies', 1), ... + false); + end + + end methods (Static) @@ -58,9 +66,9 @@ % % Usage % ----- - % spaces = GradedSpace.new(charges, degeneracies, isdual, ...) + % spaces = GradedSpace.new(charges, degeneracies, dual, ...) % - % spaces = GradedSpace.new(dimensions, isdual, ...) + % spaces = GradedSpace.new(dimensions, dual, ...) % % Repeating Arguments % ------------------- @@ -73,7 +81,7 @@ % degeneracies : int % degeneracies for the internal structure of the space. % - % isdual : logical + % dual : logical % a variable which indicates if a space is dual. % % Returns @@ -119,14 +127,14 @@ % c : (:, :) :class:`AbstractCharge` % list of charge combinations, where each row is an entry. - if spaces(1).isdual + if isdual(spaces(1)) c = conj(spaces(1).dimensions.charges); else c = spaces(1).dimensions.charges; end for i = 2:length(spaces) - if spaces(i).isdual + if isdual(spaces(i)) c = combvec(c, conj(spaces(i).dimensions.charges)); else c = combvec(c, spaces(i).dimensions.charges); @@ -193,7 +201,7 @@ args = cell(2, sum(rank)); for i = 1:size(args, 2) args{1, i} = charges(spaces(i)); - args{2, i} = spaces(i).isdual; + args{2, i} = spaces(i).dual; end trees = FusionTree.new(rank, args{:}); @@ -257,6 +265,18 @@ space = GradedSpace(newdimensions, false); end + function spaces = insertone(spaces, i, dual) + arguments + spaces + i = length(spaces) + dual = false + end + + trivialspace = one(spaces); + if dual, trivialspace = conj(trivialspace); end + spaces = [spaces(1:i) trivialspace spaces(i+1:end)]; + end + %% Utility function bools = eq(spaces1, spaces2) @@ -277,7 +297,7 @@ return end - bools = [spaces1.isdual] == [spaces2.isdual]; + bools = [spaces1.dual] == [spaces2.dual]; if isscalar(spaces2) for i = 1:length(spaces1) @@ -298,6 +318,10 @@ end end + function bools = ne(spaces1, spaces2) + bools = ~(spaces1 == spaces2); + end + function bool = ge(space1, space2) bool = le(space2, space1); end @@ -378,6 +402,24 @@ end end + + function space = plus(space, space2) + assert(isscalar(space) && isscalar(space2)); + assert(space.dual == space2.dual); + + c = charges(space2); + d = degeneracies(space2); + [lia, locb] = ismember(c, charges(space)); + for i = find(lia)' + space.dimensions.degeneracies(locb(i)) = d(i) + ... + space.dimensions.degeneracies(locb(i)); + end + [space.dimensions.charges, p] = sort([space.dimensions.charges, ... + c(~lia)]); + space.dimensions.degeneracies = [space.dimensions.degeneracies, ... + d(~lia)]; + space.dimensions.degeneracies = space.dimensions.degeneracies(p); + end end methods @@ -386,10 +428,10 @@ function disp(spaces) if isscalar(spaces) fprintf('%s GradedSpace of dimension %d:\n', ... class(spaces.dimensions.charges), dims(spaces)); - title_str = strjust(pad(["isdual", "charges", "degeneracies"]), 'right'); + title_str = strjust(pad(["dual", "charges", "degeneracies"]), 'right'); charge_str = strjust(pad([string(spaces.dimensions.charges) string(spaces.dimensions.degeneracies)]), 'center'); - fprintf('\t%s:\t%s\n', title_str(1), string(spaces.isdual)); + fprintf('\t%s:\t%s\n', title_str(1), string(spaces.dual)); fprintf('\t%s:\t%s\n', title_str(2), join(charge_str(1, :), char(9))); fprintf('\t%s:\t%s\n', title_str(3), join(charge_str(2, :), char(9))); return From 01893c9dadfd37c6daec3f5992b239b45cfd42be Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 27 Sep 2022 21:30:27 +0200 Subject: [PATCH 023/245] extra test --- test/TestTensor.m | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/TestTensor.m b/test/TestTensor.m index 245716c..11ad198 100644 --- a/test/TestTensor.m +++ b/test/TestTensor.m @@ -8,7 +8,7 @@ methods (TestClassSetup) function classSetup(tc) orig = Options.CacheEnabled; - Options.CacheEnabled(false); + Options.CacheEnabled(true); tc.addTeardown(@Options.CacheEnabled, orig); end end @@ -172,6 +172,12 @@ function tensorprod_via_conversion(tc, spaces) tc.assertTrue(isapprox(tensorprod(t1, t2, [1 2], [2 1]), ... tensorprod(double(t1), double(t2), [1 2], [2 1]), ... 'AbsTol', tc.tol, 'RelTol', tc.tol)); + + A = Tensor.randnc(spaces(1), spaces(1:2)); + C = Tensor.randnc(spaces(1), spaces(1)); + AC = contract(A, [-1 -2 1], C, [1 -3]); + + tc.assertTrue(isapprox(double(AC), contract(double(A), [-1 -2 1], double(C), [1 -3]))); end function orthogonalize(tc, spaces) From 5212171fe00254567910041a27515d3fd50e2be8 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 28 Sep 2022 10:36:31 +0200 Subject: [PATCH 024/245] bugfix memsize for objects. --- src/utility/memsize.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utility/memsize.m b/src/utility/memsize.m index ebdc278..c266585 100644 --- a/src/utility/memsize.m +++ b/src/utility/memsize.m @@ -13,7 +13,7 @@ props = properties(in); bytes = 0; for ii = 1:length(props) - bytes = bytes + memsize(in.(thisprop), 'B'); + bytes = bytes + memsize({in.(props{ii})}, 'B'); end end From b352da62c9d7c624537661d6debf5909d02d1462 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 28 Sep 2022 10:36:59 +0200 Subject: [PATCH 025/245] Performance updates --- src/tensors/Tensor.m | 1 - src/tensors/kernels/AbstractBlock.m | 29 +++++++++++++++++++++++------ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 54c2d8f..95f9900 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -996,7 +996,6 @@ else C.var = mul(C.var, varA, varB); end - assert(~isnan(norm(C))); return end diff --git a/src/tensors/kernels/AbstractBlock.m b/src/tensors/kernels/AbstractBlock.m index 128b1b5..5511b03 100644 --- a/src/tensors/kernels/AbstractBlock.m +++ b/src/tensors/kernels/AbstractBlock.m @@ -34,13 +34,30 @@ return end - if braidingstyle(codomain, domain) == BraidingStyle.Abelian && ... - fusionstyle(codomain, domain) == FusionStyle.Unique - X = AbelianBlock(codomain, domain); - return - end + persistent cache + if isempty(cache), cache = LRU; end - X = MatrixBlock(codomain, domain); + if Options.CacheEnabled() + key = GetMD5({codomain, domain}, 'Array', 'hex'); + med = get(cache, key); + if isempty(med) + if braidingstyle(codomain, domain) == BraidingStyle.Abelian && ... + fusionstyle(codomain, domain) == FusionStyle.Unique + med = AbelianBlock(codomain, domain); + else + med = MatrixBlock(codomain, domain); + end + cache = set(cache, key, med); + end + else + if braidingstyle(codomain, domain) == BraidingStyle.Abelian && ... + fusionstyle(codomain, domain) == FusionStyle.Unique + med = AbelianBlock(codomain, domain); + else + med = MatrixBlock(codomain, domain); + end + end + X = med; end end From 01838fc9de191eeff8a11374cf172d3054fc5eab Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 28 Sep 2022 11:02:44 +0200 Subject: [PATCH 026/245] merge main --- test/TestTensor.m | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/TestTensor.m b/test/TestTensor.m index 11ad198..197f89f 100644 --- a/test/TestTensor.m +++ b/test/TestTensor.m @@ -8,7 +8,7 @@ methods (TestClassSetup) function classSetup(tc) orig = Options.CacheEnabled; - Options.CacheEnabled(true); + Options.CacheEnabled(false); tc.addTeardown(@Options.CacheEnabled, orig); end end @@ -188,7 +188,7 @@ function orthogonalize(tc, spaces) p2 = [1 5]; tc.assumeTrue(spaces(3) * spaces(4) * spaces(2) >= spaces(1)' * spaces(5)') - for alg = ["qr", "qrpos", "polar", "svd" "ql" "qlpos"] + for alg = ["qr", "qrpos", "polar", "svd", "ql", "qlpos"] [Q, R] = leftorth(t, p1, p2, alg); assertTrue(tc, ... @@ -218,7 +218,7 @@ function orthogonalize(tc, spaces) tc.assumeTrue(spaces(3) * spaces(4) <= spaces(1)' * spaces(2)' * spaces(5)'); p1 = [3 4]; p2 = [2 1 5]; - for alg = ["lq", "lqpos", "polar", "svd" "rq" "rqpos"] + for alg = ["lq", "lqpos", "polar", "svd", "rq", "rqpos"] [L, Q] = rightorth(t, p1, p2, alg); assertTrue(tc, ... @@ -297,4 +297,3 @@ function eigenvalues(tc, spaces) end end end - From c3faca53087a7c1debdb3d3d8d8e94897fbc5b0a Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 28 Sep 2022 16:24:30 +0200 Subject: [PATCH 027/245] Twists and fermions. --- src/tensors/FusionTree.m | 40 ++++++++ src/tensors/Tensor.m | 102 ++++++++++++++----- src/tensors/kernels/AbelianBlock.m | 158 ++++++----------------------- src/tensors/kernels/MatrixBlock.m | 5 +- test/TestTensor.m | 17 ++++ 5 files changed, 166 insertions(+), 156 deletions(-) diff --git a/src/tensors/FusionTree.m b/src/tensors/FusionTree.m index f0b4ac2..176be11 100644 --- a/src/tensors/FusionTree.m +++ b/src/tensors/FusionTree.m @@ -774,6 +774,45 @@ [f, p] = sort(f); c = c(:, p); end + + function [c, f] = twist(f, i, inv) + % Compute the coefficients that twist legs. + % + % Arguments + % --------- + % f : :class:`FusionTree` + % tree to repartition. + % + % i : int or logical + % indices of legs to twist. + % + % inv : logical + % flag to determine inverse twisting. + % + % Returns + % ------- + % c : sparse double + % matrix of coefficients that transform input to output trees. + % `f(i) --> c(i,j) * f(j)` + % + % f : :class:`FusionTree` + % twisted trees in canonical form. + + arguments + f + i + inv = false + end + + if istwistless(braidingstyle(f)) + c = speye(length(f)); + return + end + + theta = prod(twist(f.uncoupled(:, i)), 2); + if inv, theta = conj(theta); end + c = spdiags(theta, 0, length(f), length(f)); + end end @@ -782,6 +821,7 @@ function style = braidingstyle(f) style = f.charges.braidingstyle; end + function bool = isallowed(f) if ~hasmultiplicity(fusionstyle(f)) if f.rank(1) == 0 diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index e0c67e7..8be780f 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -698,35 +698,37 @@ % C : :class:`Tensor` % output tensor. - szA = size(A); - szB = size(B); - assert(szA(2) == szB(1)); - - % Scalar multiplications - if isnumeric(A) || isnumeric(B) + if ~isscalar(A) || ~isscalar(B) + szA = size(A); + szB = size(B); + assert(szA(2) == szB(1)); + + % Tensor multiplications for i = szA(1):-1:1 for j = szB(2):-1:1 - C(i,j) = A(i, 1) .* B(1, j); + C(i,j) = A(i, 1) * B(1, j); for k = 2:szA(2) - C(i,j) = C(i,j) + A(i, k) .* B(k, j); + C(i,j) = C(i,j) + A(i, k) * B(k, j); end end end return end - % Tensor multiplications - for i = szA(1):-1:1 - for j = szB(2):-1:1 - C(i,j) = tensorprod(A(i, 1), B(1, j), ... - rank(A(i, 1), 1) + (1:rank(A(i, 1), 2)), ... - rank(B(1, j), 1):-1:1); - for k = 2:szA(2) - C(i,j) = C(i,j) + tensorprod(A(i, k), B(k, j), ... - rank(A(i, k), 1) + (1:rank(A(i, k), 2)), ... - rank(B(k, j), 1):-1:1); - end - end + if isnumeric(A) || isnumeric(B) + C = A .* B; + return + end + + assert(isequal(A.domain, B.codomain), 'tensors:SpaceMismatch', ... + 'Multiplied spaces incompatible.'); + if ~isempty(A.codomain) || ~isempty(B.domain) + C = Tensor.zeros(A.codomain, B.domain); + C.var = mul(C.var, A.var, B.var); + else + Ablocks = matrixblocks(A.var); + Bblocks = matrixblocks(B.var); + C = horzcat(Ablocks{:}) * vertcat(Bblocks{:}); end end @@ -976,9 +978,16 @@ med = get(cache, key); if isempty(med) med = struct; + A_ = similar(@(x,charge) uninit(x), A, iA, 'Rank', rA); med.varA = A_.var; - med.mapA = permute(fusiontrees(A), iA, rA); + [med.mapA, f] = permute(fusiontrees(A), iA, rA); + for i = rA(1) + (1:rA(2)) + if ~isdual(space(A_, i)) + [c2, f] = twist(f, i); + med.mapA = med.mapA * c2; + end + end B_ = similar(@(x,charge) uninit(x), B, iB, 'Rank', rB); med.varB = B_.var; @@ -1008,6 +1017,10 @@ end A = permute(A, iA, rA); + for i = rA(1) + (1:rA(2)) + if ~isdual(space(A, i)), A = twist(A, i); end + end +% A = twist(A, [false(1, length(A.codomain)) ~isdual(A.domain')]); B = permute(B, iB, rB); assert(isequal(A.domain, B.codomain), 'tensors:SpaceMismatch', ... @@ -1104,10 +1117,51 @@ error('tensors:TBA', 'This method has not been implemented.'); end - function t = twist(t, i) - if ~istwistless(braidingstyle(t)) - error('TBA'); + function t = twist(t, i, inv) + % Twist the spaces of a tensor. + % + % Arguments + % --------- + % t : :class:`Tensor` + % input tensor. + % + % i : (1, :) int or logical + % indices to twist. + % + % inv : logical + % flag to indicate inverse twisting. + % + % Returns + % ------- + % t : :class:`Tensor` + % permuted tensor with desired rank. + + arguments + t + i + inv = false + end + + if isempty(i) || ~any(i) || istwistless(braidingstyle(t)) + return + end + + persistent cache + if isempty(cache), cache = LRU; end + + if Options.CacheEnabled() + key = GetMD5({GetMD5_helper(t.codomain), GetMD5_helper(t.domain), i, inv}, ... + 'Array', 'hex'); + med = get(cache, key); + if isempty(med) + med = twist(fusiontrees(t), i, inv); + cache = set(cache, key, med); + end + else + med = twist(fusiontrees(t), i, inv); end + + t.var = axpby(1, t.var, 0, t.var, 1:nspaces(t), med); end function t = uplus(t) diff --git a/src/tensors/kernels/AbelianBlock.m b/src/tensors/kernels/AbelianBlock.m index 509a6e4..17a162c 100644 --- a/src/tensors/kernels/AbelianBlock.m +++ b/src/tensors/kernels/AbelianBlock.m @@ -19,31 +19,38 @@ %% General case: addition with permutation % tensor indexing to matrix indexing - rrx = rankrange(X(1).rank); - rry = rankrange(Y(1).rank); + rx = X(1).rank; + ry = Y(1).rank; + rrx = rankrange(rx); + rry = rankrange(ry); p_eff(rry) = rrx(p); + p_eff_1 = p_eff(1:ry(1)); + p_eff_2 = p_eff((1:ry(2)) + ry(1)); % extract small tensor blocks doA = a ~= 1; - vars = cell(size(map, 1), 1); + vars_in = cell(size(map, 1), 1); offset = 0; + + Xrowsizes = {X.rowsizes}; + Xcolsizes = {X.colsizes}; + Xtdims = {X.tdims}; + Xvar = {X.var}; for i = 1:length(X) - rowsz = X(i).rowsizes; - colsz = X(i).colsizes; + rowsz = Xrowsizes{i}; + colsz = Xcolsizes{i}; - var_in = X(i).var; + var_in = Xvar{i}; if doA, var_in = a .* var_in; end - oldtdims = X(i).tdims(:, rrx); - newmdims = [prod(X(i).tdims(:, p_eff(1:Y(1).rank(1))), 2) ... - prod(X(i).tdims(:, p_eff((1:Y(1).rank(2)) + Y(1).rank(1))), 2)]; + oldtdims = Xtdims{i}(:, rrx); + newmdims = [prod(oldtdims(:, p_eff_1), 2) prod(oldtdims(:, p_eff_2), 2)]; for k = 1:length(colsz) - 1 for j = 1:length(rowsz) - 1 ind = j + (k-1) * (length(rowsz) - 1); offset = offset + 1; - - vars{offset} = reshape(permute(reshape(... + vars_in{offset} = reshape(permute(reshape(... var_in(rowsz(j)+1:rowsz(j+1), colsz(k)+1:colsz(k+1)), ... oldtdims(ind, :)), ... p_eff), ... @@ -53,8 +60,11 @@ end % apply map - [row, col] = find(map); - vars(col) = vars(row); + vars_out = cell(size(map, 2), 1); + [rows, cols, vals] = find(map); + for i = 1:length(vals) + vars_out{cols(i)} = vals(i) * vars_in{rows(i)}; + end % inject small tensor blocks if b == 0 @@ -65,13 +75,13 @@ if rows < cols m = cell(rows, 1); for n = 1:rows - m{n} = cat(2, vars{offset + n + ((1:cols)-1) * rows}); + m{n} = cat(2, vars_out{offset + n + ((1:cols)-1) * rows}); end Y(i).var = cat(1, m{:}); else m = cell(cols, 1); for n = 1:cols - m{n} = cat(1, vars{offset + (n-1) * rows + (1:rows)}); + m{n} = cat(1, vars_out{offset + (n-1) * rows + (1:rows)}); end Y(i).var = cat(2, m{:}); end @@ -88,13 +98,13 @@ if rows < cols m = cell(rows, 1); for n = 1:rows - m{n} = cat(2, vars{offset + n + ((1:cols)-1) * rows}); + m{n} = cat(2, vars_out{offset + n + ((1:cols)-1) * rows}); end Y(i).var = Y(i).var + cat(1, m{:}); else m = cell(cols, 1); for n = 1:cols - m{n} = cat(1, vars{offset + (n-1) * rows + (1:rows)}); + m{n} = cat(1, vars_out{offset + (n-1) * rows + (1:rows)}); end Y(i).var = Y(i).var + cat(2, m{:}); end @@ -110,128 +120,18 @@ if rows < cols m = cell(rows, 1); for n = 1:rows - m{n} = cat(2, vars{offset + n + ((1:cols)-1) * rows}); + m{n} = cat(2, vars_out{offset + n + ((1:cols)-1) * rows}); end Y(i).var = b .* Y(i).var + cat(1, m{:}); else m = cell(cols, 1); for n = 1:cols - m{n} = cat(1, vars{offset + (n-1) * rows + (1:rows)}); + m{n} = cat(1, vars_out{offset + (n-1) * rows + (1:rows)}); end Y(i).var = b .* Y(i).var + cat(2, m{:}); end offset = offset + rows * cols; end - -% rrx = rankrange(x(1).rank); -% rry = rankrange(y(1).rank); -% p_eff(rry) = rrx(p); - -% %% extract blocks -% doA = a ~= 1; -% vars = cell(1, size(map, 1)); -% offset = 0; -% for block = x -% rowsz = block.rowsizes; -% colsz = block.colsizes; -% -% if doA -% varA = a .* block.var; -% else -% varA = block.var; -% end -% -% olddims = block.tdims(:, rrx); -% newdims = [prod(olddims(:, p_eff(1:y(1).rank(1))), 2) ... -% prod(olddims(:, p_eff(y(1).rank(1) + (1:y(1).rank(2)))), 2)]; -% for k = 1:length(colsz) - 1 -% for j = 1:length(rowsz) - 1 -% ind = j + (k-1) * (length(rowsz) - 1); -% offset = offset + 1; -% % vars{offset} = varA(rowsz(j)+1:rowsz(j+1), colsz(k)+1:colsz(k+1)); -% % vars{offset} = reshape(vars{offset}, olddims(ind, :)); -% % vars{offset} = permute(vars{offset}, p_eff); -% % vars{offset} = reshape(vars{offset}, newdims(ind, :)); -% -% % less readible but faster -% vars{offset} = reshape(permute(reshape(... -% varA(rowsz(j)+1:rowsz(j+1), colsz(k)+1:colsz(k+1)), ... -% olddims(ind, :)), p_eff), ... -% newdims(ind, :)); -% end -% end -% end -% -% %% apply map -% [row, col] = find(map); -% vars(col) = vars(row); -% -% %% inject blocks -% if b == 0 -% offset = 0; -% for i = 1:length(y) -% rows = length(y(i).rowsizes) - 1; -% cols = length(y(i).colsizes) - 1; -% if rows < cols -% m = cell(rows, 1); -% for n = 1:rows -% m{n} = cat(2, vars{offset + n + ((1:cols)-1) * rows}); -% end -% y(i).var = cat(1, m{:}); -% else -% m = cell(cols, 1); -% for n = 1:cols -% m{n} = cat(1, vars{offset + (n-1) * rows + (1:rows)}); -% end -% y(i).var = cat(2, m{:}); -% end -% offset = offset + rows * cols; -% end -% return -% end -% -% if b == 1 -% offset = 0; -% for i = 1:length(y) -% rows = length(y(i).rowsizes) - 1; -% cols = length(y(i).colsizes) - 1; -% if rows < cols -% m = cell(rows, 1); -% for n = 1:rows -% m{n} = cat(2, vars{offset + n + ((1:cols)-1) * rows}); -% end -% y(i).var = y(i).var + cat(1, m{:}); -% else -% m = cell(cols, 1); -% for n = 1:cols -% m{n} = cat(1, vars{offset + (n-1) * rows + (1:rows)}); -% end -% y(i).var = y(i).var + cat(2, m{:}); -% end -% offset = offset + rows * cols; -% end -% return -% end -% -% offset = 0; -% for i = 1:length(y) -% rows = length(y(i).rowsizes) - 1; -% cols = length(y(i).colsizes) - 1; -% if rows < cols -% m = cell(rows, 1); -% for n = 1:rows -% m{n} = cat(2, vars{offset + n + ((1:cols)-1) * rows}); -% end -% y(i).var = b .* y(i).var + cat(1, m{:}); -% else -% m = cell(cols, 1); -% for n = 1:cols -% m{n} = cat(1, vars{offset + (n-1) * rows + (1:rows)}); -% end -% y(i).var = b .* y(i).var + cat(2, m{:}); -% end -% offset = offset + rows * cols; -% end end function typename = underlyingType(b) diff --git a/src/tensors/kernels/MatrixBlock.m b/src/tensors/kernels/MatrixBlock.m index fdde9f4..d74859b 100644 --- a/src/tensors/kernels/MatrixBlock.m +++ b/src/tensors/kernels/MatrixBlock.m @@ -92,7 +92,7 @@ %% Special case 2: addition without permutation if nargin == 4 || (isempty(p) && isempty(map)) || ... - (all(p == 1:length(p)) && all(X(1).rank == Y(1).rank)) + (all(p == 1:length(p)) && isequal(map, speye(size(map)))) % reduce to scalar multiplication if b == 0 % a ~= 0 -> case 1 Y = X .* a; @@ -154,8 +154,7 @@ if doA, var_in = a .* var_in; end oldtdims = Xtdims{i}(:, rrx); - newmdims = [prod(oldtdims(:, p_eff_1), 2) ... - prod(oldtdims(:, p_eff_2), 2)]; + newmdims = [prod(oldtdims(:, p_eff_1), 2) prod(oldtdims(:, p_eff_2), 2)]; for k = 1:length(colsz) - 1 for j = 1:length(rowsz) - 1 diff --git a/test/TestTensor.m b/test/TestTensor.m index 197f89f..21180bb 100644 --- a/test/TestTensor.m +++ b/test/TestTensor.m @@ -138,6 +138,7 @@ function permute_via_conversion(tc, spaces) end function multiplication_via_conversion(tc, spaces) + tc.assumeTrue(istwistless(braidingstyle(spaces))); t1 = Tensor.randnc(spaces(1), spaces(2)); t2 = Tensor.randnc(spaces(2), spaces(3)); @@ -166,6 +167,7 @@ function multiplication_via_conversion(tc, spaces) end function tensorprod_via_conversion(tc, spaces) + tc.assumeTrue(istwistless(braidingstyle(spaces))); t1 = Tensor.randnc([], spaces(1:2)); t2 = Tensor.randnc(spaces(1:2), []); @@ -180,6 +182,21 @@ function tensorprod_via_conversion(tc, spaces) tc.assertTrue(isapprox(double(AC), contract(double(A), [-1 -2 1], double(C), [1 -3]))); end + function contract_order(tc, spaces) + A = Tensor.randnc(spaces(1:2), spaces(1)'); + r = Tensor.randnc(spaces(1)', spaces(1)'); + + args = {A, r, conj(A); [-1 2 1], [1 3], [-2 2 3]}; + + r1 = contract(args{:}, 'Rank', rank(r)); + for p = perms(1:3)' + args2 = args(:, p); + r2 = contract(args2{:}, 'Rank', rank(r)); + tc.assertTrue(isapprox(r1, r2), ... + 'Contraction order should leave result invariant.'); + end + end + function orthogonalize(tc, spaces) t = Tensor.randnc(spaces, []); From 4f6c5998bd7f322d2e143d4009db648980c7d081 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 28 Sep 2022 17:07:54 +0200 Subject: [PATCH 028/245] string conversion for spaces --- src/tensors/spaces/GradedSpace.m | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/tensors/spaces/GradedSpace.m b/src/tensors/spaces/GradedSpace.m index ca27ff4..6e7aad2 100644 --- a/src/tensors/spaces/GradedSpace.m +++ b/src/tensors/spaces/GradedSpace.m @@ -426,14 +426,15 @@ function disp(spaces) % Custom display of spaces. if isscalar(spaces) - fprintf('%s GradedSpace of dimension %d:\n', ... - class(spaces.dimensions.charges), dims(spaces)); - title_str = strjust(pad(["dual", "charges", "degeneracies"]), 'right'); - charge_str = strjust(pad([string(spaces.dimensions.charges) - string(spaces.dimensions.degeneracies)]), 'center'); - fprintf('\t%s:\t%s\n', title_str(1), string(spaces.dual)); - fprintf('\t%s:\t%s\n', title_str(2), join(charge_str(1, :), char(9))); - fprintf('\t%s:\t%s\n', title_str(3), join(charge_str(2, :), char(9))); + fprintf('%s', string(spaces)); +% fprintf('%s GradedSpace of dimension %d:\n', ... +% class(spaces.dimensions.charges), dims(spaces)); +% title_str = strjust(pad(["dual", "charges", "degeneracies"]), 'right'); +% charge_str = strjust(pad([string(spaces.dimensions.charges) +% string(spaces.dimensions.degeneracies)]), 'center'); +% fprintf('\t%s:\t%s\n', title_str(1), string(spaces.dual)); +% fprintf('\t%s:\t%s\n', title_str(2), join(charge_str(1, :), char(9))); +% fprintf('\t%s:\t%s\n', title_str(3), join(charge_str(2, :), char(9))); return end @@ -447,6 +448,18 @@ function disp(spaces) fprintf('\n'); end end + + function s = string(spaces) + title_str = strjust(pad(["dual", "charges", "degeneracies"]), 'right'); + charge_str = strjust(pad([string(spaces.dimensions.charges) + string(spaces.dimensions.degeneracies)]), 'center'); + s = sprintf(... + '%s GradedSpace of dimension %d:\n\t%s:\t%s\n\t%s:\t%s\n\t%s:\t%s\n', ... + class(spaces.dimensions.charges), dims(spaces), ... + title_str(1), string(spaces.dual), ... + title_str(2), join(charge_str(1, :), char(9)), ... + title_str(3), join(charge_str(2, :), char(9))); + end end From ab12dfc05b3515c4e3067a64e1acf8f7e804839e Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 29 Sep 2022 11:18:10 +0200 Subject: [PATCH 029/245] bugfixes --- src/tensors/Tensor.m | 7 +++++++ src/tensors/spaces/CartesianSpace.m | 14 +++++++++++++- src/tensors/spaces/ComplexSpace.m | 2 +- src/utility/Options.m | 11 +++++++++++ src/utility/linalg/contract.m | 27 +++++++++++++++++++++++++++ 5 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 8be780f..a105233 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -2166,6 +2166,7 @@ 'largestreal', 'smallestreal', 'bothendsreal', ... 'largestimag', 'smallestimag', 'bothendsimag'}), ... 'tensors:ArgumentError', 'Invalid choice of eigenvalue selector.'); + nargoutchk(0, 3); x0_vec = vectorize(x0); sz = size(x0_vec); @@ -2187,10 +2188,16 @@ if nargout <= 1 varargout = {D}; elseif nargout == 2 + for i = howmany:-1:1 + varargout{1}(:, i) = devectorize(V(:, i), x0); + end + varargout{2} = D; + else varargout{2} = D; for i = howmany:-1:1 varargout{1}(:, i) = devectorize(V(:, i), x0); end + varargout{3} = flag; end if flag && options.Verbosity > 0 diff --git a/src/tensors/spaces/CartesianSpace.m b/src/tensors/spaces/CartesianSpace.m index 1c40aa1..af7ddfc 100644 --- a/src/tensors/spaces/CartesianSpace.m +++ b/src/tensors/spaces/CartesianSpace.m @@ -279,7 +279,7 @@ function disp(spaces) if isscalar(spaces) - fprintf('CartesianSpace of dimension %d:\n', spaces.dimensions); + fprintf('CartesianSpace of dimension %d\n', spaces.dimensions); return end @@ -291,5 +291,17 @@ function disp(spaces) fprintf('\n'); end end + + function s = string(spaces) + if numel(spaces) > 1 + s = strings(size(spaces)); + for i = 1:numel(spaces) + s(i) = string(spaces); + end + return + end + + s = sprintf('CartesianSpace of dimension %d', spaces.dimensions); + end end end diff --git a/src/tensors/spaces/ComplexSpace.m b/src/tensors/spaces/ComplexSpace.m index 04b7da2..4b88c6b 100644 --- a/src/tensors/spaces/ComplexSpace.m +++ b/src/tensors/spaces/ComplexSpace.m @@ -190,7 +190,7 @@ % fused space. space.dimensions = space.dimensions * space2.dimensions; - space.isdual = false; + space.dual = false; end function space = prod(spaces) diff --git a/src/utility/Options.m b/src/utility/Options.m index 2a65273..753dc5c 100644 --- a/src/utility/Options.m +++ b/src/utility/Options.m @@ -13,6 +13,17 @@ if isempty(isenabled), isenabled = true; end bool = isenabled; end + + function bool = Debug(bool) + persistent dodebug + if nargin > 0 + dodebug = bool; + return + end + + if isempty(dodebug), dodebug = false; end + bool = dodebug; + end end end diff --git a/src/utility/linalg/contract.m b/src/utility/linalg/contract.m index 7deb1e0..71a227d 100644 --- a/src/utility/linalg/contract.m +++ b/src/utility/linalg/contract.m @@ -81,6 +81,11 @@ % contract last pair [dimA, dimB] = contractinds(ia, ib); + +if Options.Debug + contractcheck(A, ia, ca, B, ib, cb); +end + C = tensorprod(A, B, dimA, dimB, ca, cb, 'NumDimensionsA', length(ia)); ia(dimA) = []; ib(dimB) = []; ic = [ia ib]; @@ -135,6 +140,11 @@ [A, ia, ca] = contracttree(tensors, indices, conjlist, tree{1}); [B, ib, cb] = contracttree(tensors, indices, conjlist, tree{2}); [dimA, dimB] = contractinds(ia, ib); + +if Options.Debug + contractcheck(A, ia, ca, B, ib, cb); +end + C = tensorprod(A, B, dimA, dimB, ca, cb, 'NumDimensionsA', length(ia)); ia(dimA) = []; @@ -142,3 +152,20 @@ ic = [ia ib]; cc = false; end + +function contractcheck(A, ia, ca, B, ib, cb) + +Aspaces = space(A); +if ca, Aspaces = conj(Aspaces); end +Bspaces = space(B); +if cb, Bspaces = conj(Bspaces); end + +[dimA, dimB] = contractinds(ia, ib); + +for i = 1:length(dimA) + assert(Aspaces(dimA(i)) == conj(Bspaces(dimB(i))), 'tensors:SpaceMismatch', ... + 'Invalid index %d:\n\t%s\n\tis incompatible with\n\t%s', ... + ia(dimA(i)), string(Aspaces(dimA(i))), string(Bspaces(dimB(i)))); +end + +end From ee87b1a3f0d29a401bdd25c49d55f14e8280d5dd Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sat, 1 Oct 2022 09:30:47 +0200 Subject: [PATCH 030/245] desymmetrize --- src/tensors/Tensor.m | 21 +++++++++++++-------- src/tensors/spaces/GradedSpace.m | 14 ++++++++++++-- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 8be780f..1e416c4 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -45,14 +45,6 @@ return end - % t = Tensor(tensor) - if nargin == 1 && isa(varargin{1}, 'Tensor') - t.codomain = varargin{1}.codomain; - t.domain = varargin{1}.domain; - t.var = varargin{1}.var; - return - end - % t = Tensor(array) if nargin == 1 && isnumeric(varargin{1}) t.domain = []; @@ -2311,6 +2303,19 @@ a = cell2mat(a_cell); end + + function tdst = desymmetrize(tsrc, mode) + arguments + tsrc + mode {mustBeMember(mode, {'Cartesian', 'Complex'})} = 'Complex' + end + + tdst = repartition(Tensor(double(tsrc)), rank(tsrc)); + if strcmp(mode, 'Complex') + if ~isempty(tsrc.domain), tdst.domain = ComplexSpace(tsrc.domain); end + if ~isempty(tsrc.codomain), tdst.codomain = ComplexSpace(tsrc.codomain); end + end + end end diff --git a/src/tensors/spaces/GradedSpace.m b/src/tensors/spaces/GradedSpace.m index 6e7aad2..de1d7ff 100644 --- a/src/tensors/spaces/GradedSpace.m +++ b/src/tensors/spaces/GradedSpace.m @@ -55,8 +55,6 @@ struct('charges', one(spaces(1).dimensions.charges), 'degeneracies', 1), ... false); end - - end methods (Static) @@ -460,6 +458,18 @@ function disp(spaces) title_str(2), join(charge_str(1, :), char(9)), ... title_str(3), join(charge_str(2, :), char(9))); end + + function complexspaces = ComplexSpace(gradedspaces) + d = num2cell(dims(gradedspaces)); + isdual = num2cell([gradedspaces.dual]); + args = [d; isdual]; + complexspaces = ComplexSpace(args{:}); + end + + function cartesianspaces = CartesianSpace(gradedspaces) + d = num2cell(dims(gradedspaces)); + cartesianspaces = CartesianSpace(d{:}); + end end From dd786cfe191e105b436fe3a6925e5f350cdee818 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sat, 1 Oct 2022 19:55:15 +0200 Subject: [PATCH 031/245] Documentation update. --- docs/requirements.txt | 2 +- docs/src/conf.py | 4 +- docs/src/man/tensor.rst | 206 +++++++++++++++++++++++++++++++++++++++- src/tensors/Tensor.m | 7 +- 4 files changed, 213 insertions(+), 6 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 20914e8..dbf71cb 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -6,4 +6,4 @@ nbsphinx==0.8.9 sphinx-gallery==0.10.1 myst-parser==0.17.2 linkify-it-py==2.0.0 - +jinja2==3.0.3 diff --git a/docs/src/conf.py b/docs/src/conf.py index 72377ee..c760c53 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -11,7 +11,7 @@ docs_src = os.path.abspath(os.path.dirname(__file__)) # docs source folder docs_root = os.path.abspath(os.path.join(docs_src, '..')) # docs root folder repo_root = os.path.abspath(os.path.join(docs_src, '..', '..')) # repo root folder -GITHUBBASE = 'https://github.com/lkdvos/TensorTrack' # repo link +GITHUBBASE = 'https://github.com/QuantumGhent/TensorTrack' # repo link # -- Project information @@ -67,7 +67,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = 'en' # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'default' diff --git a/docs/src/man/tensor.rst b/docs/src/man/tensor.rst index de533d0..e5bcc1e 100644 --- a/docs/src/man/tensor.rst +++ b/docs/src/man/tensor.rst @@ -7,7 +7,9 @@ Definitions We start with the definition of a tensor. According to the `Link Wikipedia page ` on tensors, there are 3 equivalent ways of describing a tensor object: * As a multidimensional array. + * As a multilinear map. + * Using tensor products. From a MATLAB perspective, the former approach is the most natural, as such tensors are easily created with several builtin methods. We thus say a tensor of size ``sz`` can be thought of as the array that is created by calling any of the standard functions ``t = zeros(sz)``, ``t = ones(sz)``, ``t = rand(sz)``, ... @@ -38,18 +40,218 @@ In summary, we consider a tensor to be an object which can be represented in eit These definitions are equivalent, and it is always possible to go from one to the other. The main reason for introducing both is that for some algorithms, one is more convenient than the other, and thus this conversion will prove useful. -Creating and accessing tensors ------------------------------- +Creating tensors +---------------- There are several options provided for creating tensors, which are designed to either mimic the existing MATLAB constructors, or to be conveniently generalisable when dealing with symmetries. Almost each of these methods is a wrapper of some sorts around the general static constructor ``Tensor.new``: .. automethod:: src.tensors.Tensor.new :noindex: +In summary, for tensors without any internal symmetry, the easiest way of constructing tensors is by using the form :code:`Tensor.new(fun, [dims...])`, where optionally it is possible to specify arrows for each of the dimensions with :code:`Tensor.new(fun, [dims...], [arrows...])`. +Additionally, it is possible to make the partition of domain and codomain by supplying a rank for the tensor :code:`Tensor.new(fun, [dims...], [arrows...], 'Rank', [r1 r2])`. +While the latter two options do not alter any of the results in the non symmetric case, adding arrows is often useful from a debugging point of view, as it will then become less likely to accidentaly connect tensors that are not compatible. Additionally, the rank determines how the tensor acts as a linear map, which becomes important for decompositions, eigenvalues, etc. +Finally, the most general form is found by explicitly constructing the vector spaces upon which the tensor will act. +For tensors without symmetries, :class:`ComplexSpace` and :class:`CartesianSpace` represent spaces with or without arrows respectively, while :class:`GradedSpace` is used for tensors which have internal symmetries. +These allow for the final form :code:`Tensor.new(fun, codomain, domain)`. +On top of this, several convenience methods exist for commonly used initializers, such as: + +* :code:`Tensor.zeros` +* :code:`Tensor.ones` +* :code:`Tensor.eye` +* :code:`Tensor.rand` +* :code:`Tensor.randn` +* :code:`Tensor.randc` +* :code:`Tensor.randnc` + +Often, it can prove useful to create new tensors based on existing ones, either to ensure that they can be added, subtracted, or contracted. +In these cases, :code:`Tensor.similar` will be the method of choice, which acts as a wrapper to avoid manually having to select spaces from the tensors. + +.. automethod:: src.tensors.Tensor.similar + :noindex: + + +Accessing tensor data +--------------------- + +In general, :class:`Tensor` objects do not allow indexing or slicing operations, as this is not compatible with their internal structure. +Nevertheless, in accordance with the two ways in which a tensor can be represented, it is possible to access the tensor data in two ways. + +First off, when we consider a tensor as a linear operator which maps the domain to the codomain, we can ask for the matrix representation of this map. +For tensors with internal symmetries this will in general be a list of matrices, that represent the block-diagonal part of the tensor, along with the corresponding charge through :code:`matrixblocks(t)`. + +Secondly, when we want to think of a tensor more in terms of an N-dimensional array, we can access these as :code:`tensorblocks(t)`. +For tensors with internal symmetries, this will generate a list of all channels that are not explicitly zero by virtue of the symmetry, along with a representation of these channels, which is called a :class:`FusionTree`. + +Additional details for the symmetric tensors can be found in the :ref:`Symmetries` section. + +As an example, it could prove educational to understand the sizes of the lists and the sizes of the blocks generated by the example code below: + +.. code-block:: matlab + + >> A = Tensor.zeros([2 2 2], 'Rank', [2 1]); + >> Ablocks = matrixblocks(A) + + Ablocks = + + 1×1 cell array + + {4×2 double} + + >> Atensors = tensorblocks(A) + + Atensors = + + 1×1 cell array + + {2×2×2 double} + + >> z2space = GradedSpace.new(Z2(0, 1), [1 1], false); + >> B = Tensor.zeros([z2space z2space], z2space); + >> [Bblocks, Bcharges] = matrixblocks(B) + + Bblocks = + + 1×2 cell array + + {2×1 double} {2×1 double} + + + Bcharges = + + 1×2 Z2: + + logical data: + 0 1 + + >> [Btensors, Btrees] = tensorblocks(B) + + Btensors = + + 4×1 cell array + + {[0]} + {[0]} + {[0]} + {[0]} + + + Btrees = + + (2, 1) Z2 FusionTree array: + + isdual: + 0 0 0 + charges: + + + | + | + + - - | + | + + - + | - | - + + - | - | - + +In the very same way, in order to write data into a tensor, the same two formats can be used. + +First off, :code:`t = fill_matrix(t, blocks)` will take a list of blocks and fill these into the tensor. +This requires the list to be full, and sorted according to the charge, or in other words it has to be of the same shape and order as the output of :code:`matrixblocks`. +If it is only necessary to change some of the blocks, :code:`t = fill_matrix(t, blocks, charges)` additionally passes on an array of charges which specifies which block will be filled. + +Similarly, :code:`t = fill_tensor(t, tensors)` will take a list of N-dimensional arrays and fill these into the tensor, in the same order and shape of the output of :code:`tensorblocks`. +If it is required to only change some of the tensors, an array of :class:`FusionTree` s can be passed in as :code:`t = fill_tensor(t, tensors, trees)` to specify which tensors should be changed. + +.. code-block:: matlab + + >> Ablocks{1} = ones(size(Ablocks{1})); + >> A = fill_matrix(A, Ablocks); + >> Atensors{1} = rand(size(Atensors{1})); + >> A = fill_matrix(A, Atensors); + + >> Bblocks = cellfun(@(x) ones(size(x)), Bblocks, 'UniformOutput', false); + >> B = fill_matrix(B, Bblocks); + >> Bblocks{1} = Bblocks{1} + 1; + >> B = fill_matrix(B, Bblocks(1), Bcharges(1)); + +Additionally, it is also possible to use initializers instead of a list of data. +These initializers should have signature :code:`fun(dims, identifier)`. +For non symmetric tensors, ``identifier`` will be omitted, but for symmetric tensors the matrix case uses charges as ``identifier``, while the tensor case uses fusion trees as ``identifier``. +Again, it is possible to select only some of the blocks through the third argument. + +.. code-block:: matlab + + >> f = @(dims, identifier) ones(dims); + >> A = fill_matrix(A, f); + >> A = fill_tensor(A, f); + + >> g = @(dims, charge) qdim(charge) * ones(dims); + >> B = fill_matrix(B, g); + >> h = @(dims, tree) qdim(f.coupled) * ones(dims); + >> B = fill_tensor(B, h); + +Finally, we mention that for most tensors, it is possible to generate an N-dimensional array representation, at the cost of losing all information about the symmetries. +This can sometimes be useful as a tool for debugging, and can be accessed through :code:`a = double(t)`. + + Index manipulations ------------------- +Once a tensor has been created, it is possible to manipulate the order and partition of the indices through the use of :code:`permute(t, p, r)`. +This methods works similarly to :code:`permute` for arrays, as it requires a permutation vector ``p`` for determining the new order of the tensor legs. +Additionally and optionally, one may specify a rank `r` to determine the partition of the resulting tensor. +In order to only change the partition without permuting indices, :code:`repartition(t, r)` also is implemented. + +.. code-block:: matlab + + >> A = Tensor.rand([1 2 3]) + + A = + + Rank (3, 0) Tensor: + + 1. CartesianSpace of dimension 1 + + 2. CartesianSpace of dimension 2 + + 3. CartesianSpace of dimension 3 + + >> A2 = repartition(A, [1 2]) + + A2 = + + Rank (1, 2) Tensor: + + 1. CartesianSpace of dimension 1 + + 2. CartesianSpace of dimension 2 + + 3. CartesianSpace of dimension 3 + + >> A3 = permute(A, [3 2 1]) + + A3 = + + Rank (3, 0) Tensor: + + 1. CartesianSpace of dimension 3 + + 2. CartesianSpace of dimension 2 + + 3. CartesianSpace of dimension 1 + + >> A3 = permute(A, [3 2 1], [2 1]) + + A3 = + + Rank (2, 1) Tensor: + + 1. CartesianSpace of dimension 3 + + 2. CartesianSpace of dimension 2 + + 3. CartesianSpace of dimension 1 + +.. note:: + + While the partition of tensor indices might seem of no importance for tensors without internal structure, it can still have non-trivial consequences. + This is demonstrated by comparing the ``matrixblocks`` and the ``tensorblocks`` before and after repartitioning. Contractions ------------ diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 451673b..5045da2 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -389,6 +389,10 @@ t = Tensor.new(@rand, varargin{:}); end + function t = randn(varargin) + t = Tensor.new(@randn, varargin{:}); + end + function t = randc(varargin) t = Tensor.new(@randc, varargin{:}); end @@ -438,8 +442,9 @@ f = fusiontrees(t.codomain, t.domain); end - function b = tensorblocks(t) + function [b, f] = tensorblocks(t) b = tensorblocks(t.var); + if nargout > 1, f = fusiontrees(t); end end function varargout = matrixblocks(t) From 8b961bcfa0f07eccfaf9310877069e7cd8d73f81 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sat, 1 Oct 2022 20:45:14 +0200 Subject: [PATCH 032/245] Small changes. --- docs/src/man/tensor.rst | 6 ++-- src/tensors/charges/GtPattern.m | 55 ------------------------------ src/tensors/spaces/AbstractSpace.m | 17 +++++++++ src/tensors/spaces/GradedSpace.m | 49 -------------------------- 4 files changed, 20 insertions(+), 107 deletions(-) diff --git a/docs/src/man/tensor.rst b/docs/src/man/tensor.rst index e5bcc1e..56299d1 100644 --- a/docs/src/man/tensor.rst +++ b/docs/src/man/tensor.rst @@ -17,11 +17,11 @@ We will use a graphical representation of such a tensor as an object with severa Nevertheless, for our purpose it will often be useful to be able to think of such an abstract object as a map, or thus as a matrix. For this, we additionally specify the rank of the tensor ``[n1 n2]``, which results in a partition of the indices into ``n1`` left and ``n2`` right indices, which we will call **codomain** and **domain** respectively. -If we then reshape the original array into a matrix. ``m = reshape(t, prod(sz(1:n1)), prod(sz((1:n2)+n1)))``. +If we then reshape the original array into a matrix. :code:`m = reshape(t, prod(sz(1:n1)), prod(sz((1:n2)+n1)))`. With this definition however, we still run into issues when we want to perform something like matrix multiplication. As MATLAB uses column-major ordering of array elements, ``reshape`` effectively groups indices together from left to right, where the left index is changing the fastest. If we then multiply two tensors, we connect the domain (right indices) of the first tensor with the codomain (left indices) of the second, and note that the counter-clockwise ordering of our indices now causes the domain indices to be grouped bottom-to-top, while the codomain indices are grouped top-to-bottom. -Thus, in order for our matrix multiplication to be consistent, we reverse the order of the domain indices, and define the tensor map as ``m = reshape(permute(t, [1:n1 n1+flip(1:n2)]), prod(sz(1:n1)), prod(sz(n1+(1:n2))``. +Thus, in order for our matrix multiplication to be consistent, we reverse the order of the domain indices, and define the tensor map as :code:`m = reshape(permute(t, [1:n1 n1+flip(1:n2)]), prod(sz(1:n1)), prod(sz(n1+(1:n2))`. The mathematical origin of this unfortunate permutation is found when considering the dual of a tensor product of spaces, which is isomorphic to the tensor product of the dual spaces, but reversed. In other words, we start out with a tensor as follows: @@ -250,7 +250,7 @@ In order to only change the partition without permuting indices, :code:`repartit .. note:: - While the partition of tensor indices might seem of no importance for tensors without internal structure, it can still have non-trivial consequences. + While the partition of tensor indices might seem of little importance for tensors without internal structure, it can still have non-trivial consequences. This is demonstrated by comparing the ``matrixblocks`` and the ``tensorblocks`` before and after repartitioning. Contractions diff --git a/src/tensors/charges/GtPattern.m b/src/tensors/charges/GtPattern.m index a629f73..a71a96e 100644 --- a/src/tensors/charges/GtPattern.m +++ b/src/tensors/charges/GtPattern.m @@ -244,61 +244,6 @@ function disp(p) end - - methods (Static) - function pat = GeneratePatterns(I) - %GENERATEPATTERNS Generate all possible GtPatterns for a given SU(N) charge. - % pat = GeneratePatterns(charge) - % generates all allowed GtPatterns. - - - %% Special case length 1 - N = length(I); - if N == 1 - pat = GtPattern(1, I(:)); - return - end - - - %% Special case length 2 - if N == 2 - i1 = I(2):I(1); - L = length(i1); - M = zeros(2, 2, L); - M(1, :, :) = repmat(I, 1, 1, L); - M(2, 1, :) = i1; - pat = GtPattern(2, M); - return - end - - - %% Special case length 3 - % if N == 3 - % - % - % - % return; - % end - - - %% Generate all possible weight combinations of one level lower - newIs = cell(1, N-1); - for ii = 1:N-1 - newIs{ii} = I(ii+1):I(ii); - end - - - %% Generate patterns - pat = []; - - for newI = CombVec(newIs{N-1:-1:1}) - newPat = GtPattern.GeneratePatterns(flip(newI)); - pat = [pat, appendRow(newPat, I)]; - end - - - end - end end diff --git a/src/tensors/spaces/AbstractSpace.m b/src/tensors/spaces/AbstractSpace.m index c6c071d..3b6a163 100644 --- a/src/tensors/spaces/AbstractSpace.m +++ b/src/tensors/spaces/AbstractSpace.m @@ -220,6 +220,23 @@ error('tensors:AbstractMethod', 'This method should be overloaded.'); end + function space = plus(space1, space2) + % Find the direct sum of two spaces. + % + % Arguments + % --------- + % space1, space2 : (1,1) :class:`AbstractSpace` + % input spaces. + % + % Returns + % ------- + % space : (1,1) :class:`AbstractSpace` + % direct sum space. + + + error('tensors:AbstractMethod', 'This method should be overloaded.'); + end + function space = prod(spaces) % Fuse a product space to a single space. % diff --git a/src/tensors/spaces/GradedSpace.m b/src/tensors/spaces/GradedSpace.m index de1d7ff..be64aa1 100644 --- a/src/tensors/spaces/GradedSpace.m +++ b/src/tensors/spaces/GradedSpace.m @@ -352,55 +352,6 @@ end end - function bool = hascharge(spaces, charges) - mustBeEqualLength(spaces, charges); - bool = false(1, length(space)); - for i = 1:length(space) - bool(i) = any(space.charges{1} == charge(i)); - end - end - - function space = fuse(spaces) - space = spaces(1); - if length(spaces) == 1 - return - end - - allcharges = combcharges(spaces, []).'; - degeneracies = computedegeneracies(spaces, [], allcharges); - - switch fusionstyle(allcharges) - case FusionStyle.Unique - fusedcharges = prod(allcharges, 2); - fusedbonds = prod(degeneracies, 2); - - [newcharges, ~, ic] = unique(fusedcharges); - newbonds = zeros(size(newcharges)); - for i = 1:length(newcharges) - newbonds(i) = sum(fusedbonds(ic == i)); - end - space = GradedSpace(newcharges, newbonds, false); - - otherwise - fusedbonds = prod(degeneracies, 2); - fusedcharges_cell = cell(size(allcharges, 1), 1); - fusedbonds_cell = cell(size(fusedcharges_cell)); - for i = 1:size(allcharges, 1) - [fusedcharges_cell{i}, N] = prod(allcharges(i, :)); - fusedbonds_cell{i} = fusedbonds(i) .* N; - end - - fusedbonds = horzcat(fusedbonds_cell{:}); - [newcharges, ~, ic] = unique(horzcat(fusedcharges_cell{:})); - newbonds = zeros(size(newcharges)); - for i = 1:length(newcharges) - newbonds(i) = sum(fusedbonds(ic == i)); - end - space = GradedSpace(newcharges, newbonds, false); - - end - end - function space = plus(space, space2) assert(isscalar(space) && isscalar(space2)); assert(space.dual == space2.dual); From e079c740f05455cb8f2b19d1473c868203b231ff Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 4 Oct 2022 09:46:59 +0200 Subject: [PATCH 033/245] Vectorize ctranspose --- src/tensors/Tensor.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 5045da2..2efa6ec 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -565,8 +565,10 @@ % t : :class:`Tensor` % adjoint tensor. - [t.codomain, t.domain] = swapvars(t.codomain, t.domain); - t.var = t.var'; + for i = 1:numel(t) + [t(i).codomain, t(i).domain] = swapvars(t(i).codomain, t(i).domain); + t(i).var = t(i).var'; + end end function d = dot(t1, t2) From dffed6912235d2c4ecde0fbb37f8c03a937b7b7d Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 4 Oct 2022 10:59:17 +0200 Subject: [PATCH 034/245] Debug contractions, vectorizations --- src/tensors/Tensor.m | 17 +++++++++++------ src/tensors/spaces/GradedSpace.m | 2 +- src/utility/linalg/contract.m | 17 ++++++++++------- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 2efa6ec..311ce49 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -39,9 +39,12 @@ % t = Tensor(tensor) if nargin == 1 && isa(varargin{1}, 'Tensor') - t.codomain = varargin{1}.codomain; - t.domain = varargin{1}.domain; - t.var = varargin{1}.var; + for i = numel(varargin{1}):-1:1 + t(i).codomain = varargin{1}(i).codomain; + t(i).domain = varargin{1}(i).domain; + t(i).var = varargin{1}(i).var; + end + t = reshape(t, size(varargin{1})); return end @@ -159,9 +162,9 @@ if isnumeric(data), data = {data}; end if iscell(data) - t.var = fill_tensor_data(t.var, data, trees); + t.var = fill_tensor_data(t.var, data); else - t.var = fill_tensor_fun(t.var, data, trees); + t.var = fill_tensor_fun(t.var, data); end end @@ -543,7 +546,9 @@ % t : :class:`Tensor` % conjugate tensor. - t = permute(t', nspaces(t):-1:1, rank(t)); + for i = 1:numel(t) + t(i) = permute(t(i)', nspaces(t(i)):-1:1, rank(t(i))); + end end function t = ctranspose(t) diff --git a/src/tensors/spaces/GradedSpace.m b/src/tensors/spaces/GradedSpace.m index be64aa1..b8cda96 100644 --- a/src/tensors/spaces/GradedSpace.m +++ b/src/tensors/spaces/GradedSpace.m @@ -272,7 +272,7 @@ trivialspace = one(spaces); if dual, trivialspace = conj(trivialspace); end - spaces = [spaces(1:i) trivialspace spaces(i+1:end)]; + spaces = [spaces(1:i-1) trivialspace spaces(i:end)]; end diff --git a/src/utility/linalg/contract.m b/src/utility/linalg/contract.m index 71a227d..3b8ecd8 100644 --- a/src/utility/linalg/contract.m +++ b/src/utility/linalg/contract.m @@ -40,6 +40,7 @@ arguments kwargs.Conj (1, :) logical = false(size(tensors)) kwargs.Rank = [] + kwargs.Debug = false end assert(length(kwargs.Conj) == length(tensors)); @@ -51,6 +52,8 @@ end end +debug = kwargs.Debug; + % Special case for single input tensor if nargin == 2 [~, order] = sort(indices{1}, 'descend'); @@ -76,13 +79,13 @@ tree = generatetree(partialtrees, contractindices); % contract all subtrees -[A, ia, ca] = contracttree(tensors, indices, kwargs.Conj, tree{1}); -[B, ib, cb] = contracttree(tensors, indices, kwargs.Conj, tree{2}); +[A, ia, ca] = contracttree(tensors, indices, kwargs.Conj, tree{1}, debug); +[B, ib, cb] = contracttree(tensors, indices, kwargs.Conj, tree{2}, debug); % contract last pair [dimA, dimB] = contractinds(ia, ib); -if Options.Debug +if debug contractcheck(A, ia, ca, B, ib, cb); end @@ -128,7 +131,7 @@ tree = generatetree(partialtrees, contractindices); end -function [C, ic, cc] = contracttree(tensors, indices, conjlist, tree) +function [C, ic, cc] = contracttree(tensors, indices, conjlist, tree, debug) if isnumeric(tree) C = tensors{tree}; @@ -137,11 +140,11 @@ return end -[A, ia, ca] = contracttree(tensors, indices, conjlist, tree{1}); -[B, ib, cb] = contracttree(tensors, indices, conjlist, tree{2}); +[A, ia, ca] = contracttree(tensors, indices, conjlist, tree{1}, debug); +[B, ib, cb] = contracttree(tensors, indices, conjlist, tree{2}, debug); [dimA, dimB] = contractinds(ia, ib); -if Options.Debug +if debug contractcheck(A, ia, ca, B, ib, cb); end From 37bb5ce43847e09f6da0becd8635ced1ac4b95d6 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 6 Oct 2022 09:26:21 +0200 Subject: [PATCH 035/245] permute -> tpermute, SparseTensor --- src/sparse/SparseTensor.m | 354 +++++++++++++++++++++++++++++ src/sparse/ind2sub_.m | 36 +++ src/sparse/sub2ind_.m | 28 +++ src/sparse/sub2sub.m | 31 +++ src/tensors/FusionTree.m | 2 + src/tensors/Tensor.m | 197 ++++++++++++++-- src/utility/linalg/contract.m | 92 +------- src/utility/linalg/contractcheck.m | 16 ++ src/utility/linalg/contracttree.m | 25 ++ src/utility/linalg/generatetree.m | 24 ++ test/TestTensor.m | 18 +- 11 files changed, 705 insertions(+), 118 deletions(-) create mode 100644 src/sparse/SparseTensor.m create mode 100644 src/sparse/ind2sub_.m create mode 100644 src/sparse/sub2ind_.m create mode 100644 src/sparse/sub2sub.m create mode 100644 src/utility/linalg/contractcheck.m create mode 100644 src/utility/linalg/contracttree.m create mode 100644 src/utility/linalg/generatetree.m diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m new file mode 100644 index 0000000..aaeea7e --- /dev/null +++ b/src/sparse/SparseTensor.m @@ -0,0 +1,354 @@ +classdef (InferiorClasses = {?Tensor}) SparseTensor + % Class for multi-dimensional sparse objects. + + properties (Access = private) + ind = [] + sz = [] + var (:, 1) + end + + methods + function t = SparseTensor(varargin) + + if nargin == 0 || (nargin == 1 && isempty(varargin{1})) + return; + + elseif nargin == 1 % cast from existing object + source = varargin{1}; + switch class(source) + case 'SparseTensor' + t.ind = source.ind; + t.sz = source.sz; + t.var = source.var; + + case 'Tensor' + t.sz = ones(1, nspaces(source(1))); + t.sz(1:ndims(source)) = size(source); + + t.ind = ind2sub_(t.sz, 1:numel(source)); + t.var = source(:); + + otherwise + error('sparse:ArgError', 'Unknown syntax.'); + end + + elseif nargin == 2 % indices and values + ind = varargin{1}; + var = reshape(varargin{2}, [], 1); + if isscalar(var), var = repmat(var, size(ind, 1), 1); end + assert(size(ind, 1) == size(var, 1), 'sparse:argerror', ... + 'indices and values must be the same size.'); + t.ind = ind; + t.var = var; + t.sz = max(ind, [], 1); + + elseif nargin == 3 % indices, values and size + ind = varargin{1}; + var = reshape(varargin{2}, [], 1); + if isscalar(var), var = repmat(var, size(ind, 1), 1); end + assert(size(ind, 1) == size(var, 1), 'sparse:argerror', ... + 'indices and values must be the same size.'); + sz = reshape(varargin{3}, 1, []); + assert(isempty(ind) || size(ind, 2) == length(sz), 'sparse:argerror', ... + 'number of indices does not match size vector.'); + assert(isempty(ind) || all(max(ind, [], 1) <= sz), 'sparse:argerror', ... + 'indices must not exceed size vector.'); + t.ind = ind; + t.var = var; + t.sz = sz; + + else + error('sparse:argerror', 'unknown syntax.'); + end + end + + function t = permute(t, p) + if ~isempty(t.ind) + t.ind = t.ind(:, p); + end + t.sz = t.sz(p); + end + + function t = tpermute(t, p, r) + for i = 1:numel(t.var) + t.var(i) = tpermute(t.var(i), p, r); + end + t = permute(t, p); + end + + function t = reshape(t, sz) + assert(prod(sz) == prod(t.sz), ... + 'sparse:argerror', 'To reshape the number of elements must not change.'); + + t.ind = sub2sub(sz, t.sz, t.ind); + t.sz = sz; + end + + function C = tensorprod(A, B, dimA, dimB, ca, cb, options) + arguments + A SparseTensor + B SparseTensor + dimA + dimB + ca = false + cb = false + options.NumDimensionsA = ndims(A) + end + + szA = size(A, 1:options.NumDimensionsA); + szB = size(B, 1:max(ndims(B), max(dimB))); + + assert(length(dimA) == length(dimB) && all(szA(dimA) == szB(dimB)), ... + 'sparse:dimerror', 'incompatible contracted dimensions.'); + + uncA = 1:length(szA); uncA(dimA) = []; + uncB = 1:length(szB); uncB(dimB) = []; + + if isempty(uncA) + if isempty(uncB) + szC = [1 1]; + elseif length(uncB) == 1 + szC = [1 szB(uncB)]; + else + szC = szB(uncB); + end + elseif isempty(uncB) + if length(uncA) == 1 + szC = [szA(uncA) 1]; + else + szC = szA(uncA); + end + else + szC = [szA(uncA) szB(uncB)]; + end + + Cvar = A.var.empty(0, 1); + Cind = double.empty(0, length(uncA) + length(uncB)); + + ks = unique([A.ind(:, dimA); B.ind(:, dimB)], 'rows'); + + for k = ks.' + rowlinds = all(A.ind(:, dimA) == k.', 2); + if ~any(rowlinds), continue; end + + collinds = all(B.ind(:, dimB) == k.', 2); + if ~any(collinds), continue; end + + rowinds = find(rowlinds); + colinds = find(collinds); + + for i = rowinds.' + av = A.var(i); + ai = A.ind(i, uncA); + for j = colinds.' + bv = B.var(j); + bj = B.ind(j, uncB); + + mask = all([ai bj] == Cind, 2); + if any(mask) + Cvar(mask) = Cvar(mask) + ... + tensorprod(av, bv, dimA, dimB, ... + 'NumDimensionsA', options.NumDimensionsA); + else + Cvar(end+1) = tensorprod(av, bv, dimA, dimB, ... + 'NumDimensionsA', options.NumDimensionsA); + Cind = [Cind; ai bj]; + end + end + end + end + + C = SparseTensor(Cind, Cvar, szC); + if size(Cind, 1) == prod(szC), C = full(C); end + end + + function B = full(A) + inds = ind2sub_(A.sz, 1:prod(A.sz)); + + [lia, locb] = ismember(inds, A.ind, 'rows'); + B(lia) = A.var(locb(lia)); + + if ~all(lia) + s = arrayfun(@(i) space(A, i), 1:ndims(A), 'UniformOutput', false); + r = rank(A.var(1)); + for i = find(~lia).' + allspace = arrayfun(@(j) s{j}(inds(i, j)), 1:length(s)); + B(i) = Tensor.zeros(allspace(1:r(1)), allspace(r(1)+1:end)'); + end + end + B = reshape(B, A.sz); + end + + function s = space(t, i) + assert(isscalar(i), 'sparse:argerror', ... + 'Can only obtain spaces for single index.'); + for j = size(t, i):-1:1 + el = t.var(find(t.ind(:, i), 1)); + if isempty(el) + warning('cannot deduce space.'); + continue; + end + s(j) = space(t.var(find(t.ind(:, i) == j, 1)), i); + end + end + + function n = ndims(A) + n = length(A.sz); + end + + function c = mtimes(a, b) + szA = a.sz; + szB = b.sz; + assert(length(szA) == 2 && length(szB) == 2, 'sparse:argerror', ... + 'mtimes only defined for matrices.'); + assert(szA(2) == szB(1), 'sparse:dimerror', ... + 'incompatible sizes for mtimes.'); + + cvar = []; + cind = double.empty(0, 2); + + for k = 1:size(a, 2) + rowlinds = a.ind(:, 2) == k; + if ~any(rowlinds), continue; end + + collinds = b.ind(:, 1) == k; + if ~any(collinds), continue; end + + rowinds = find(rowlinds); + colinds = find(collinds); + + for i = rowinds.' + av = a.var(i); + ai = a.ind(i, 1); + for j = colinds.' + bv = b.var(j); + bj = b.ind(j, 2); + + + mask = all([ai bj] == cind, 2); + if any(mask) + cvar(mask) = cvar(mask) + av * bv; + else + cvar(end+1) = av * bv; + cind = [cind; ai bj]; + end + end + end + end + c = SparseTensor(cind, cvar, [szA(1) szB(2)]); + end + + function sz = size(a, i) + if nargin == 1 + sz = a.sz; + return + end + + sz = ones(1, max(i)); + sz(1:length(a.sz)) = a.sz; + sz = sz(i); + end + + function disp(t) + nz = size(t.var, 1); + if nz == 0 + fprintf('all-zero %s of size %s\n', class(t), ... + regexprep(mat2str(t.sz), {'\[', '\]', '\s+'}, {'', '', 'x'})); + return + end + + fprintf('%s of size %s with %d nonzeros:\n', class(t), ... + regexprep(mat2str(t.sz), {'\[', '\]', '\s+'}, {'', '', 'x'}), nz); + + spc = floor(log10(max(double(t.ind), [], 1))) + 1; + if numel(spc) == 1 + fmt = strcat("\t(%", num2str(spc(1)), "u)"); + else + fmt = strcat("\t(%", num2str(spc(1)), "u,"); + for i = 2:numel(spc) - 1 + fmt = strcat(fmt, "%", num2str(spc(i)), "u,"); + end + fmt = strcat(fmt, "%", num2str(spc(end)), "u)"); + end + + for i = 1:nz + fprintf('%s\t\t', compose(fmt, t.ind(i,:))); + disp(t.var(i)); + fprintf('\n'); + end + end + end + + %% Indexing + methods + function t = subsref(t, s) + assert(length(s) == 1, 'sparse:index', 'only single level indexing allowed'); + assert(strcmp(s.type, '()'), 'sparse:index', 'only () indexing allowed'); + + n = size(s.subs, 2); + if n == 1 % linear indexing + [s.subs{1:size(t.sz, 2)}] = ind2sub(t.sz, s.subs{1}); + else + assert(n == size(t.sz, 2), 'sparse:index', ... + 'number of indexing indices must match tensor size.'); + end + f = true(size(t.ind, 1), 1); + newsz = zeros(1, size(s.subs, 2)); + + for i = 1:size(s.subs, 2) + A = s.subs{i}; + if strcmp(A, ':') + newsz(i) = t.size(i); + continue; + end + nA = length(A); + if nA ~= length(unique(A)) + error("Repeated index in position %i",i); + end + if ~isempty(t.ind) + B = t.ind(:, i); + P = false(max(max(A), max(B)) + 1, 1); + P(A + 1) = true; + f = and(f, P(B + 1)); + [~, ~, temp] = unique([A(:); t.ind(f, i)], 'stable'); + t.ind(f, i) = temp(nA+1:end); + newsz(i) = nA; + end + end + t.sz = newsz; + if ~isempty(t.ind) + t.ind = t.ind(f, :); + t.var = t.var(f); + end + end + + function t = subsasgn(t, s, v) + assert(strcmp(s(1).type, '()'), 'sparse:index', 'only () indexing allowed'); + assert(length(s(1).subs) == size(t.sz, 2), 'sparse:index', ... + 'number of indexing indices must match tensor size.'); + + subsize = zeros(1, size(s(1).subs, 2)); + for i = 1:length(subsize) + if strcmp(s(1).subs{i}, ':') + s(1).subs{i} = 1:t.sz(i); + elseif islogical(s(1).subs{i}) + s(1).subs{i} = find(s(1).subs{i}).'; + end + subsize(i) = length(s(1).subs{i}); + end + if isscalar(v), v = repmat(v, subsize); end + subs = combvec(s(1).subs{:}); + for i = 1:size(subs, 1) + idx = find(all(t.ind == subs(i, :), 2)); + if isempty(idx) + t.ind = [t.ind; subs]; + t.var = [t.var; v(i)]; + else + t.var(idx) = v(i); + end + end + t.sz = max(t.sz, cellfun(@max, s(1).subs)); + end + end +end + diff --git a/src/sparse/ind2sub_.m b/src/sparse/ind2sub_.m new file mode 100644 index 0000000..40e2fdd --- /dev/null +++ b/src/sparse/ind2sub_.m @@ -0,0 +1,36 @@ +function I = ind2sub_(sz, ind, perm) +% Faster implementation of builtin ind2sub + +if isempty(ind) + I = []; + return; +elseif size(sz, 2) == 1 + I = ind; + return; +end + +if nargin < 3 + perm = 1:numel(sz); +else + perm(perm) = 1:numel(sz); +end + +nout = numel(sz); +I = zeros(numel(ind), numel(sz)); + +if nout > 2 + k = cumprod(sz); + for i = nout:-1:3 + I(:, perm(i)) = floor((ind-1) / k(i-1)) + 1; + ind = rem(ind-1, k(i-1)) + 1; + end +end + +if nout >= 2 + I(:, perm(2)) = floor((ind-1)/sz(1)) + 1; + I(:, perm(1)) = rem(ind-1, sz(1)) + 1; +else + I(:, perm(1)) = ind; +end + +end \ No newline at end of file diff --git a/src/sparse/sub2ind_.m b/src/sparse/sub2ind_.m new file mode 100644 index 0000000..eb66711 --- /dev/null +++ b/src/sparse/sub2ind_.m @@ -0,0 +1,28 @@ +function ind = sub2ind_(sz, I) +%sub2ind_ faster implementation of builtin sub2ind. + +if isempty(I) + ind = []; + return; +elseif size(I,2) == 1 + ind = I; + return; +end + +numOfIndInput = size(I,2); + +ind = I(:,1); +if numOfIndInput >= 2 + %Compute linear indices + ind = ind + (I(:,2) - 1).*sz(1); +end + +if numOfIndInput > 2 + %Compute linear indices + k = cumprod(sz); + for i = 3:numOfIndInput + ind = ind + (I(:,i)-1)*k(i-1); + end +end + +end \ No newline at end of file diff --git a/src/sparse/sub2sub.m b/src/sparse/sub2sub.m new file mode 100644 index 0000000..29c7338 --- /dev/null +++ b/src/sparse/sub2sub.m @@ -0,0 +1,31 @@ +function sub2 = sub2sub(sz2, sz1, sub1) +%SUB2SUB Convert subscripts to subscripts corresponding to different array +%dimensions. + +if isempty(sub1) || isequal(sz1, sz2) + sub2 = sub1; + return; +end +sub2 = zeros(size(sub1, 1), numel(sz2)); % preallocate new subs +nto1 = sum(cumsum(flip(sz1-1), 2) == 0, 2); % extract number of trailing ones in both sizes +nto2 = sum(cumsum(flip(sz2-1), 2) == 0, 2); +pos1_prev = 0; pos2_prev = 0; +flag = true; +while flag + [pos1, pos2] = find(cumprod(sz1(pos1_prev+1:end)).' == cumprod(sz2(pos2_prev+1:end)), 1); + pos1 = pos1 + pos1_prev; + pos2 = pos2 + pos2_prev; + if prod(sz1(pos1_prev+1:pos1)) > 2^48-1 + error('Cannot map subscripts to new size as intermediate index exceeds MAXSIZE') + end + sub2(:, pos2_prev+1:pos2) = ind2sub_(sz2(pos2_prev+1:pos2), sub2ind_(sz1(pos1_prev+1:pos1), sub1(:, pos1_prev+1:pos1))); + if pos2 == numel(sz2) - nto2 || pos1 == numel(sz1) - nto1 + flag = false; + else + pos1_prev = pos1; + pos2_prev = pos2; + end +end +sub2(:, end-nto2+1:end) = 1; + +end diff --git a/src/tensors/FusionTree.m b/src/tensors/FusionTree.m index 176be11..bb8bfda 100644 --- a/src/tensors/FusionTree.m +++ b/src/tensors/FusionTree.m @@ -804,6 +804,8 @@ inv = false end + if isempty(f), c = []; return; end + if istwistless(braidingstyle(f)) c = speye(length(f)); return diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 311ce49..75c4dd8 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -462,6 +462,36 @@ style = fusionstyle(t.codomain, t.domain); end +% function t = horzcat(varargin) +% sp = space(varargin{1}, [1, 3:nspaces(varargin{1})]); +% for i = 2:length(varargin) +% sp2 = space(varargin{2}, [1, 3:nspaces(varargin{1})]); +% assert(isequal(sp, sp2), 'tensors:SpaceMismatch', ... +% 'hozcat tensors may only differ in the second space.'); +% end +% t = builtin('horzcat', varargin{:}); +% end +% +% function t = vertcat(varargin) +% sp = space(varargin{1}, 2:nspaces(varargin{1})); +% for i = 2:length(varargin) +% sp2 = space(varargin{2}, 2:nspaces(varargin{1})); +% assert(isequal(sp, sp2), 'tensors:SpaceMismatch', ... +% 'vertcat tensors may only differ in the first space.'); +% end +% t = builtin('vertcat', varargin{:}); +% end +% +% function t = cat(dim, varargin) +% sp = space(varargin{1}, [1:dim-1 dim+1:nspaces(varargin{1})]); +% for i = 2:length(varargin) +% sp2 = space(varargin{2}, [1:dim-1 dim+1:nspaces(varargin{1})]); +% assert(isequal(sp, sp2), 'tensors:SpaceMismatch', ... +% 'cat tensors may only differ in the concatenated dimension.'); +% end +% t = builtin('cat', dim, varargin{:}); +% end + function tdst = insert_onespace(tsrc, i, dual) arguments tsrc @@ -547,7 +577,70 @@ % conjugate tensor. for i = 1:numel(t) - t(i) = permute(t(i)', nspaces(t(i)):-1:1, rank(t(i))); + t(i) = tpermute(t(i)', nspaces(t(i)):-1:1, rank(t(i))); + end + end + + function C = contract(tensors, indices, kwargs) + arguments (Repeating) + tensors + indices (1, :) {mustBeInteger} + end + + arguments + kwargs.Conj (1, :) logical = false(size(tensors)) + kwargs.Rank = [] + kwargs.Debug = false + end + + assert(length(kwargs.Conj) == length(tensors)); + + for i = 1:length(tensors) + if length(indices{i}) > 1 + assert(length(unique(indices{i})) == length(indices{i}), ... + 'Tensors:TBA', 'Traces not implemented.'); + end + end + + debug = kwargs.Debug; + + % Special case for single input tensor + if nargin == 2 + [~, order] = sort(indices{1}, 'descend'); + C = tensors{1}; + if kwargs.Conj + C = tpermute(C', order(length(order):-1:1), kwargs.Rank); + else + C = tpermute(C, order, kwargs.Rank); + end + return + end + + % Generate trees + contractindices = cellfun(@(x) x(x > 0), indices, 'UniformOutput', false); + partialtrees = num2cell(1:length(tensors)); + tree = generatetree(partialtrees, contractindices); + + % contract all subtrees + [A, ia, ca] = contracttree(tensors, indices, kwargs.Conj, tree{1}, debug); + [B, ib, cb] = contracttree(tensors, indices, kwargs.Conj, tree{2}, debug); + + % contract last pair + [dimA, dimB] = contractinds(ia, ib); + + if debug, contractcheck(A, ia, ca, B, ib, cb); end + + C = tensorprod(A, B, dimA, dimB, ca, cb, 'NumDimensionsA', length(ia)); + ia(dimA) = []; ib(dimB) = []; + ic = [ia ib]; + + % permute last tensor + if ~isempty(ic) && length(ic) > 1 + [~, order] = sort(ic, 'descend'); + if isempty(kwargs.Rank) + kwargs.Rank = [length(order) 0]; + end + C = tpermute(C, order, kwargs.Rank); end end @@ -814,7 +907,7 @@ end end - function t = permute(t, p, r) + function t = tpermute(t, p, r) % Permute the spaces of a tensor. % % Arguments @@ -835,8 +928,16 @@ arguments t - p = 1:nspaces(t) - r = rank(t) + p = [] + r = [] + end + + if ~isscalar(t) + for i = 1:numel(t) + t(i) = tpermute(t(i), p, r); + end + t = permute(t, p); + return end if isempty(p), p = 1:nspaces(t); end @@ -893,7 +994,7 @@ end assert(sum(r) == sum(rank(t)), 'tensors:ValueError', 'Invalid new rank.'); - t = permute(t, 1:nspaces(t), r); + t = tpermute(t, 1:nspaces(t), r); end function t = rdivide(t, a) @@ -951,7 +1052,53 @@ dimB ca = false cb = false - options.NumDimensionsA + options.NumDimensionsA = ndims(A) + end + + if ~isscalar(A) || ~isscalar(B) + szA = size(A, 1:options.NumDimensionsA); + szB = size(B, 1:max(ndims(B), max(dimB))); + + assert(all(szA(dimA) == szB(dimB)), 'tensors:SizeMismatch', ... + 'Invalid contraction sizes.'); + + uncA = 1:length(szA); uncA(dimA) = []; + uncB = 1:length(szB); uncB(dimB) = []; + + if isempty(uncA) + if isempty(uncB) + szC = [1 1]; + elseif length(uncB) == 1 + szC = [1 szB(uncB)]; + else + szC = szB(uncB); + end + elseif isempty(uncB) + if length(uncA) == 1 + szC = [szA(uncA) 1]; + else + szC = szA(uncA); + end + else + szC = [szA(uncA) szB(uncB)]; + end + + A = reshape(permute(A, [uncA dimA]), prod(szA(uncA)), prod(szA(dimA))); + B = reshape(permute(B, [dimB, uncB]), prod(szB(dimB)), prod(szB(uncB))); + + for i = prod(szA(uncA)):-1:1 + for j = prod(szB(uncB)):-1:1 + C(i,j) = tensorprod(A(i,1), B(1,j), dimA, dimB, ca, cb, ... + 'NumDimensionsA', options.NumDimensionsA); + for k = 2:prod(szA(dimA)) + C(i,j) = C(i,j) + ... + tensorprod(A(i,k), B(k,j), dimA, dimB, ca, cb, ... + 'NumDimensionsA', options.NumDimensionsA); + end + end + end + C = reshape(C, szC); + return end uncA = 1:nspaces(A); uncA(dimA) = []; @@ -1020,12 +1167,12 @@ return end - A = permute(A, iA, rA); + A = tpermute(A, iA, rA); for i = rA(1) + (1:rA(2)) if ~isdual(space(A, i)), A = twist(A, i); end end % A = twist(A, [false(1, length(A.codomain)) ~isdual(A.domain')]); - B = permute(B, iB, rB); + B = tpermute(B, iB, rB); assert(isequal(A.domain, B.codomain), 'tensors:SpaceMismatch', ... 'Contracted spaces incompatible.'); @@ -1138,7 +1285,7 @@ % Returns % ------- % t : :class:`Tensor` - % permuted tensor with desired rank. + % twisted tensor with desired rank. arguments t @@ -1256,7 +1403,7 @@ function [Q, R] = leftorth(t, p1, p2, alg) % Factorize a tensor into an orthonormal basis `Q` and remainder `R`, such that - % :code:`permute(t, [p1 p2], [length(p1) length(p2)]) = Q * R`. + % :code:`tpermute(t, [p1 p2], [length(p1) length(p2)]) = Q * R`. % % Usage % ----- @@ -1300,7 +1447,7 @@ if isempty(p1), p1 = 1:rank(t, 1); end if isempty(p2), p2 = rank(t, 1) + (1:rank(t,2)); end - t = permute(t, [p1 p2], [length(p1) length(p2)]); + t = tpermute(t, [p1 p2], [length(p1) length(p2)]); dims = struct; [mblocks, dims.charges] = matrixblocks(t); @@ -1336,7 +1483,7 @@ function [R, Q] = rightorth(t, p1, p2, alg) % Factorize a tensor into an orthonormal basis `Q` and remainder `L`, such that - % :code:`permute(t, [p1 p2], [length(p1) length(p2)]) = L * Q`. + % :code:`tpermute(t, [p1 p2], [length(p1) length(p2)]) = L * Q`. % % Usage % ----- @@ -1380,7 +1527,7 @@ if isempty(p1), p1 = 1:rank(t, 1); end if isempty(p2), p2 = rank(t, 1) + (1:rank(t,2)); end - t = permute(t, [p1 p2], [length(p1) length(p2)]); + t = tpermute(t, [p1 p2], [length(p1) length(p2)]); dims = struct; [mblocks, dims.charges] = matrixblocks(t); @@ -1415,7 +1562,7 @@ function N = leftnull(t, p1, p2, alg, atol) % Compute the left nullspace of a tensor, such that - % :code:`N' * permute(t, [p1 p2], [length(p1) length(p2)]) = 0`. + % :code:`N' * tpermute(t, [p1 p2], [length(p1) length(p2)]) = 0`. % % Arguments % --------- @@ -1448,7 +1595,7 @@ if isempty(p1), p1 = 1:rank(t, 1); end if isempty(p2), p2 = rank(t, 1) + (1:rank(t, 2)); end - t = permute(t, [p1 p2], [length(p1) length(p2)]); + t = tpermute(t, [p1 p2], [length(p1) length(p2)]); dims = struct; [mblocks, dims.charges] = matrixblocks(t); @@ -1466,7 +1613,7 @@ function N = rightnull(t, p1, p2, alg, atol) % Compute the right nullspace of a tensor, such that - % :code:`permute(t, [p1 p2], [length(p1) length(p2)]) * N = 0`. + % :code:`tpermute(t, [p1 p2], [length(p1) length(p2)]) * N = 0`. % % Arguments % --------- @@ -1499,7 +1646,7 @@ if isempty(p1), p1 = 1:rank(t, 1); end if isempty(p2), p2 = rank(t, 1) + (1:rank(t, 2)); end - t = permute(t, [p1 p2], [length(p1) length(p2)]); + t = tpermute(t, [p1 p2], [length(p1) length(p2)]); dims = struct; [mblocks, dims.charges] = matrixblocks(t); @@ -1518,7 +1665,7 @@ function [U, S, V, eta] = tsvd(t, p1, p2, trunc) % Compute the singular value decomposition of a tensor. This computes left and % right isometries U and V, and a non-negative diagonal tensor S such that - % norm(permute(t, [p1 p2], [length(p1) length(p2)]) - U * S * V) = 0 + % :code:`norm(tpermute(t, [p1 p2], [length(p1) length(p2)]) - U * S * V) = 0` % Additionally, the dimension of S can be truncated in such a way to minimize % this norm, which gives the truncation error eta. % @@ -1552,7 +1699,7 @@ % ------- % U, S, V : :class:`Tensor` % left isometry U, non-negative diagonal S and right isometry V that satisfy - % U * S * V = permute(t, [p1 p2], [length(p1) length(p2)]). + % :code:`U * S * V = tpermute(t, [p1 p2], [length(p1) length(p2)])`. % % eta : numeric % truncation error. @@ -1566,7 +1713,7 @@ trunc.TruncSpace end - t = permute(t, [p1 p2], [length(p1) length(p2)]); + t = tpermute(t, [p1 p2], [length(p1) length(p2)]); dims = struct; [mblocks, dims.charges] = matrixblocks(t); @@ -2356,7 +2503,15 @@ function disp(t) for i = 1:length(s) fprintf('%d.\t', i); disp(s(i)); - fprintf('\n'); + end + fprintf('\n'); + + [blocks, charges] = matrixblocks(t); + for i = 1:length(blocks) + if ~isempty(blocks) + fprintf('charge %s:\n', string(charges(i))); + end + disp(blocks{i}); end else builtin('disp', t); diff --git a/src/utility/linalg/contract.m b/src/utility/linalg/contract.m index 3b8ecd8..fe121aa 100644 --- a/src/utility/linalg/contract.m +++ b/src/utility/linalg/contract.m @@ -58,18 +58,8 @@ if nargin == 2 [~, order] = sort(indices{1}, 'descend'); C = tensors{1}; - if isnumeric(C) - C = permute(C, order); - if kwargs.Conj - C = conj(C); - end - else - if kwargs.Conj - C = permute(C', order(length(order):-1:1), kwargs.Rank); - else - C = permute(C, order, kwargs.Rank); - end - end + C = permute(C, order); + if kwargs.Conj, C = conj(C); end return end @@ -85,9 +75,7 @@ % contract last pair [dimA, dimB] = contractinds(ia, ib); -if debug - contractcheck(A, ia, ca, B, ib, cb); -end +if debug, contractcheck(A, ia, ca, B, ib, cb); end C = tensorprod(A, B, dimA, dimB, ca, cb, 'NumDimensionsA', length(ia)); ia(dimA) = []; ib(dimB) = []; @@ -96,79 +84,7 @@ % permute last tensor if ~isempty(ic) && length(ic) > 1 [~, order] = sort(ic, 'descend'); - if isnumeric(C) - C = permute(C, order); - else - if isempty(kwargs.Rank) - kwargs.Rank = [length(order) 0]; - end - C = permute(C, order, kwargs.Rank); - end -end - -end - -function tree = generatetree(partialtrees, contractindices) -if length(partialtrees) == 1 - tree = partialtrees{1}; - return -end - -if all(cellfun('isempty', contractindices)) % disconnected network - partialtrees{end - 1} = partialtrees(end - 1:end); - partialtrees(end) = []; - contractindices(end) = []; -else - tocontract = min(horzcat(contractindices{:})); - tinds = find(cellfun(@(x) any(tocontract == x), contractindices)); - assert(length(tinds) == 2); - partialtrees{tinds(1)} = partialtrees(tinds); - partialtrees(tinds(2)) = []; - contractindices{tinds(1)} = unique1(horzcat(contractindices{tinds})); - contractindices(tinds(2)) = []; -end - -tree = generatetree(partialtrees, contractindices); -end - -function [C, ic, cc] = contracttree(tensors, indices, conjlist, tree, debug) - -if isnumeric(tree) - C = tensors{tree}; - ic = indices{tree}; - cc = conjlist(tree); - return -end - -[A, ia, ca] = contracttree(tensors, indices, conjlist, tree{1}, debug); -[B, ib, cb] = contracttree(tensors, indices, conjlist, tree{2}, debug); -[dimA, dimB] = contractinds(ia, ib); - -if debug - contractcheck(A, ia, ca, B, ib, cb); -end - -C = tensorprod(A, B, dimA, dimB, ca, cb, 'NumDimensionsA', length(ia)); - -ia(dimA) = []; -ib(dimB) = []; -ic = [ia ib]; -cc = false; -end - -function contractcheck(A, ia, ca, B, ib, cb) - -Aspaces = space(A); -if ca, Aspaces = conj(Aspaces); end -Bspaces = space(B); -if cb, Bspaces = conj(Bspaces); end - -[dimA, dimB] = contractinds(ia, ib); - -for i = 1:length(dimA) - assert(Aspaces(dimA(i)) == conj(Bspaces(dimB(i))), 'tensors:SpaceMismatch', ... - 'Invalid index %d:\n\t%s\n\tis incompatible with\n\t%s', ... - ia(dimA(i)), string(Aspaces(dimA(i))), string(Bspaces(dimB(i)))); + C = permute(C, order); end end diff --git a/src/utility/linalg/contractcheck.m b/src/utility/linalg/contractcheck.m new file mode 100644 index 0000000..67d6608 --- /dev/null +++ b/src/utility/linalg/contractcheck.m @@ -0,0 +1,16 @@ +function contractcheck(A, ia, ca, B, ib, cb) + +Aspaces = space(A); +if ca, Aspaces = conj(Aspaces); end +Bspaces = space(B); +if cb, Bspaces = conj(Bspaces); end + +[dimA, dimB] = contractinds(ia, ib); + +for i = 1:length(dimA) + assert(Aspaces(dimA(i)) == conj(Bspaces(dimB(i))), 'tensors:SpaceMismatch', ... + 'Invalid index %d:\n\t%s\n\tis incompatible with\n\t%s', ... + ia(dimA(i)), string(Aspaces(dimA(i))), string(Bspaces(dimB(i)))); +end + +end \ No newline at end of file diff --git a/src/utility/linalg/contracttree.m b/src/utility/linalg/contracttree.m new file mode 100644 index 0000000..7163271 --- /dev/null +++ b/src/utility/linalg/contracttree.m @@ -0,0 +1,25 @@ +function [C, ic, cc] = contracttree(tensors, indices, conjlist, tree, debug) + +if isnumeric(tree) + C = tensors{tree}; + ic = indices{tree}; + cc = conjlist(tree); + return +end + +[A, ia, ca] = contracttree(tensors, indices, conjlist, tree{1}, debug); +[B, ib, cb] = contracttree(tensors, indices, conjlist, tree{2}, debug); +[dimA, dimB] = contractinds(ia, ib); + +if debug + contractcheck(A, ia, ca, B, ib, cb); +end + +C = tensorprod(A, B, dimA, dimB, ca, cb, 'NumDimensionsA', length(ia)); + +ia(dimA) = []; +ib(dimB) = []; +ic = [ia ib]; +cc = false; + +end diff --git a/src/utility/linalg/generatetree.m b/src/utility/linalg/generatetree.m new file mode 100644 index 0000000..c60d761 --- /dev/null +++ b/src/utility/linalg/generatetree.m @@ -0,0 +1,24 @@ +function tree = generatetree(partialtrees, contractindices) + +if length(partialtrees) == 1 + tree = partialtrees{1}; + return +end + +if all(cellfun('isempty', contractindices)) % disconnected network + partialtrees{end - 1} = partialtrees(end - 1:end); + partialtrees(end) = []; + contractindices(end) = []; +else + tocontract = min(horzcat(contractindices{:})); + tinds = find(cellfun(@(x) any(tocontract == x), contractindices)); + assert(length(tinds) == 2); + partialtrees{tinds(1)} = partialtrees(tinds); + partialtrees(tinds(2)) = []; + contractindices{tinds(1)} = unique1(horzcat(contractindices{tinds})); + contractindices(tinds(2)) = []; +end + +tree = generatetree(partialtrees, contractindices); + +end diff --git a/test/TestTensor.m b/test/TestTensor.m index 21180bb..487073d 100644 --- a/test/TestTensor.m +++ b/test/TestTensor.m @@ -8,7 +8,7 @@ methods (TestClassSetup) function classSetup(tc) orig = Options.CacheEnabled; - Options.CacheEnabled(false); + Options.CacheEnabled(true); tc.addTeardown(@Options.CacheEnabled, orig); end end @@ -100,14 +100,14 @@ function permute_via_inner(tc, spaces) for i = 0:5 ps = perms(1:nspaces(t1)).'; for p = ps(:, randperm(size(ps, 2), min(size(ps, 2), 20))) - t3 = permute(t1, p.', [i 5-i]); + t3 = tpermute(t1, p.', [i 5-i]); tc.assertTrue(all(dims(t1, p.') == dims(t3)), ... 'Incorrect size after permutation.'); tc.assertTrue(... isapprox(norm(t1), norm(t3), 'AbsTol', tc.tol, 'RelTol', tc.tol), ... 'Permute should preserve norms.') - t4 = permute(t2, p.', [i 5-i]); + t4 = tpermute(t2, p.', [i 5-i]); tc.assertTrue(all(dims(t2, p.') == dims(t4)), ... 'Incorrect size after permutation.'); tc.assertTrue(... @@ -126,7 +126,7 @@ function permute_via_conversion(tc, spaces) for k = 0:nspaces(t) ps = perms(1:nspaces(t)).'; for p = ps(:, randperm(size(ps, 2), min(size(ps, 2), 10))) - t2 = permute(t, p.', [k nspaces(t)-k]); + t2 = tpermute(t, p.', [k nspaces(t)-k]); a2 = double(t2); tc.assertTrue(all(dims(t2) == size(a2, 1:nspaces(t)))); tc.assertTrue(all(dims(t2) == size(a, p.'))); @@ -209,7 +209,7 @@ function orthogonalize(tc, spaces) [Q, R] = leftorth(t, p1, p2, alg); assertTrue(tc, ... - isapprox(Q * R, permute(t, [p1 p2], [length(p1) length(p2)]), ... + isapprox(Q * R, tpermute(t, [p1 p2], [length(p1) length(p2)]), ... 'AbsTol', tc.tol, 'RelTol', tc.tol), ... sprintf('Q and R not a valid %s factorization.', alg)); @@ -239,7 +239,7 @@ function orthogonalize(tc, spaces) [L, Q] = rightorth(t, p1, p2, alg); assertTrue(tc, ... - isapprox(L * Q, permute(t, [p1 p2], [length(p1) length(p2)]), ... + isapprox(L * Q, tpermute(t, [p1 p2], [length(p1) length(p2)]), ... 'AbsTol', tc.tol, 'RelTol', tc.tol), ... sprintf('Q and R not a valid %s factorization.', alg)); @@ -268,7 +268,7 @@ function nullspace(tc, spaces) for alg = ["qr", "svd"] N = leftnull(t, [3 4 2], [1 5], alg); - assertTrue(tc, norm(N' * permute(t, [3 4 2 1 5], [3 2])) < ... + assertTrue(tc, norm(N' * tpermute(t, [3 4 2 1 5], [3 2])) < ... 100 * eps(norm(t)), ... 'N should be a left nullspace.'); assertTrue(tc, isisometry(N, 'left', ... @@ -280,7 +280,7 @@ function nullspace(tc, spaces) %% Right nullspace for alg = ["lq", "svd"] N = rightnull(t, [3 4], [2 1 5], alg); - assertTrue(tc, norm(permute(t, [3 4 2 1 5], [2 3]) * N') < ... + assertTrue(tc, norm(tpermute(t, [3 4 2 1 5], [2 3]) * N') < ... 100 * eps(norm(t)), ... 'N should be a right nullspace.'); assertTrue(tc, isisometry(N, 'right', ... @@ -292,7 +292,7 @@ function nullspace(tc, spaces) function singularvalues(tc, spaces) t = Tensor.randnc(spaces, []); [U, S, V] = tsvd(t, [3 4 2], [1 5]); - assertTrue(tc, isapprox(permute(t, [3 4 2 1 5], [3 2]), U * S * V), ... + assertTrue(tc, isapprox(tpermute(t, [3 4 2 1 5], [3 2]), U * S * V), ... 'USV should be a factorization.'); assertTrue(tc, isisometry(U), ... 'U should be an isometry.'); From 8ef8ee6fe9ababff0132190130341c5aeb2af054 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Fri, 7 Oct 2022 12:37:58 +0200 Subject: [PATCH 036/245] SparseTensor updates --- src/sparse/SparseTensor.m | 354 ++++++++++++++++++++++++++------------ src/tensors/Tensor.m | 45 +++-- test/TestSparseTensor.m | 94 ++++++++++ test/TestTensor.m | 1 - 4 files changed, 372 insertions(+), 122 deletions(-) create mode 100644 test/TestSparseTensor.m diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index aaeea7e..7bb8345 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -9,7 +9,6 @@ methods function t = SparseTensor(varargin) - if nargin == 0 || (nargin == 1 && isempty(varargin{1})) return; @@ -69,13 +68,6 @@ t.sz = t.sz(p); end - function t = tpermute(t, p, r) - for i = 1:numel(t.var) - t.var(i) = tpermute(t.var(i), p, r); - end - t = permute(t, p); - end - function t = reshape(t, sz) assert(prod(sz) == prod(t.sz), ... 'sparse:argerror', 'To reshape the number of elements must not change.'); @@ -84,6 +76,196 @@ t.sz = sz; end + function B = full(A) + inds = ind2sub_(A.sz, 1:prod(A.sz)); + + [lia, locb] = ismember(inds, A.ind, 'rows'); + B(lia) = A.var(locb(lia)); + + if ~all(lia) + s = arrayfun(@(i) space(A, i), 1:ndims(A), 'UniformOutput', false); + r = rank(A.var(1)); + for i = find(~lia).' + allspace = arrayfun(@(j) s{j}(inds(i, j)), 1:length(s)); + B(i) = Tensor.zeros(allspace(1:r(1)), allspace(r(1)+1:end)'); + end + end + B = reshape(B, A.sz); + end + + function s = space(t, i) + assert(isscalar(i), 'sparse:argerror', ... + 'Can only obtain spaces for single index.'); + for j = size(t, i):-1:1 + el = t.var(find(t.ind(:, i), 1)); + if isempty(el) + warning('cannot deduce space.'); + continue; + end + s(j) = space(t.var(find(t.ind(:, i) == j, 1)), i); + end + end + + function n = ndims(A) + n = length(A.sz); + end + + function sz = size(a, i) + if nargin == 1 + sz = a.sz; + return + end + + sz = ones(1, max(i)); + sz(1:length(a.sz)) = a.sz; + sz = sz(i); + end + + function disp(t) + nz = nnz(t); + if nz == 0 + fprintf('all-zero %s of size %s\n', class(t), ... + regexprep(mat2str(t.sz), {'\[', '\]', '\s+'}, {'', '', 'x'})); + return + end + + fprintf('%s of size %s with %d nonzeros:\n', class(t), ... + regexprep(mat2str(t.sz), {'\[', '\]', '\s+'}, {'', '', 'x'}), nz); + + spc = floor(log10(max(double(t.ind), [], 1))) + 1; + if numel(spc) == 1 + fmt = strcat("\t(%", num2str(spc(1)), "u)"); + else + fmt = strcat("\t(%", num2str(spc(1)), "u,"); + for i = 2:numel(spc) - 1 + fmt = strcat(fmt, "%", num2str(spc(i)), "u,"); + end + fmt = strcat(fmt, "%", num2str(spc(end)), "u)"); + end + + for i = 1:nz + fprintf('%s\t\t', compose(fmt, t.ind(i,:))); + disp(t.var(i)); + fprintf('\n'); + end + end + + function type = underlyingType(a) + if isempty(a.var) + type = 'double'; + else + type = underlyingType(a.var); + end + end + + function bool = issparse(~) + bool = true; + end + + function n = nnz(t) + n = length(t.var); + end + end + + %% Linear Algebra + methods + function a = conj(a) + if ~isempty(a.var) + a.var = conj(a.var); + end + end + + function d = dot(a, b) + [~, ia, ib] = intersect(a.ind, b.ind, 'rows'); + if isempty(ia), d = 0; return; end + d = dot(a.var(ia), b.var(ib)); + end + + function a = minus(a, b) + assert(isequal(size(a), size(b)), ... + 'sparse:dimerror', 'input dimensions incompatible.'); + if isempty(b.ind), return; end + if isempty(a.ind), a = -b; return; end + + [lia, locb] = ismember(b.ind, a.ind, 'rows'); + a.var(locb(lia)) = a.var(locb(lia)) - b.var(lia); + if ~all(lia) + a.var = [a.var; -b.var(~lia)]; + a.ind = [a.ind; b.ind(~lia, :)]; + end + end + + function c = mtimes(a, b) + szA = a.sz; + szB = b.sz; + assert(length(szA) == 2 && length(szB) == 2, 'sparse:argerror', ... + 'mtimes only defined for matrices.'); + assert(szA(2) == szB(1), 'sparse:dimerror', ... + 'incompatible sizes for mtimes.'); + + cvar = []; + cind = double.empty(0, 2); + + for k = 1:size(a, 2) + rowlinds = a.ind(:, 2) == k; + if ~any(rowlinds), continue; end + + collinds = b.ind(:, 1) == k; + if ~any(collinds), continue; end + + rowinds = find(rowlinds); + colinds = find(collinds); + + for i = rowinds.' + av = a.var(i); + ai = a.ind(i, 1); + for j = colinds.' + bv = b.var(j); + bj = b.ind(j, 2); + + + mask = all([ai bj] == cind, 2); + if any(mask) + cvar(mask) = cvar(mask) + av * bv; + else + cvar(end+1) = av * bv; + cind = [cind; ai bj]; + end + end + end + end + c = SparseTensor(cind, cvar, [szA(1) szB(2)]); + end + + function n = norm(t, p) + arguments + t + p = 'fro' + end + + if isempty(t.var), n = 0; return; end + n = norm(t.var); + end + + function t = normalize(t) + if isempty(t.var) + warning('sparse:empty', 'cannot normalize an empty tensor.'); + end + t = t .* (1 / norm(t)); + end + + function a = plus(a, b) + assert(isequal(size(a), size(b)), ... + 'sparse:dimerror', 'input dimensions incompatible.'); + if isempty(b.ind), return; end + if isempty(a.ind), a = b; return; end + + [lia, locb] = ismember(b.ind, a.ind, 'rows'); + a.var(locb(lia)) = a.var(locb(lia)) + b.var(lia); + a.var = [a.var; b.var(~lia)]; + a.ind = [a.ind; b.ind(~lia, :)]; + end + function C = tensorprod(A, B, dimA, dimB, ca, cb, options) arguments A SparseTensor @@ -162,129 +344,81 @@ if size(Cind, 1) == prod(szC), C = full(C); end end - function B = full(A) - inds = ind2sub_(A.sz, 1:prod(A.sz)); - - [lia, locb] = ismember(inds, A.ind, 'rows'); - B(lia) = A.var(locb(lia)); - - if ~all(lia) - s = arrayfun(@(i) space(A, i), 1:ndims(A), 'UniformOutput', false); - r = rank(A.var(1)); - for i = find(~lia).' - allspace = arrayfun(@(j) s{j}(inds(i, j)), 1:length(s)); - B(i) = Tensor.zeros(allspace(1:r(1)), allspace(r(1)+1:end)'); + function t = times(t1, t2) + if isnumeric(t1) + if isempty(t2.var) + t = t2; + return end + t = t2; + t.var = t1 .* t.var; + return end - B = reshape(B, A.sz); - end - - function s = space(t, i) - assert(isscalar(i), 'sparse:argerror', ... - 'Can only obtain spaces for single index.'); - for j = size(t, i):-1:1 - el = t.var(find(t.ind(:, i), 1)); - if isempty(el) - warning('cannot deduce space.'); - continue; - end - s(j) = space(t.var(find(t.ind(:, i) == j, 1)), i); + + if isnumeric(t2) + t = t2 .* t1; + return end - end - - function n = ndims(A) - n = length(A.sz); - end - - function c = mtimes(a, b) - szA = a.sz; - szB = b.sz; - assert(length(szA) == 2 && length(szB) == 2, 'sparse:argerror', ... - 'mtimes only defined for matrices.'); - assert(szA(2) == szB(1), 'sparse:dimerror', ... - 'incompatible sizes for mtimes.'); - cvar = []; - cind = double.empty(0, 2); + if isscalar(t1) && ~isscalar(t2) + t1 = repmat(t1, size(t2)); + elseif isscalar(t2) && ~isscalar(t1) + t2 = repmat(t2, size(t1)); + end - for k = 1:size(a, 2) - rowlinds = a.ind(:, 2) == k; - if ~any(rowlinds), continue; end - - collinds = b.ind(:, 1) == k; - if ~any(collinds), continue; end - - rowinds = find(rowlinds); - colinds = find(collinds); - - for i = rowinds.' - av = a.var(i); - ai = a.ind(i, 1); - for j = colinds.' - bv = b.var(j); - bj = b.ind(j, 2); - - - mask = all([ai bj] == cind, 2); - if any(mask) - cvar(mask) = cvar(mask) + av * bv; - else - cvar(end+1) = av * bv; - cind = [cind; ai bj]; - end - end + assert(isequal(size(t1), size(t2)), 'sparse:dimerror', ... + 'incompatible input sizes.'); + + if ~issparse(t1) + if isempty(t2.var) + t = t2; + return end - end - c = SparseTensor(cind, cvar, [szA(1) szB(2)]); - end - - function sz = size(a, i) - if nargin == 1 - sz = a.sz; + + idx = sub2ind_(t2.sz, t2.ind); + t = t2; + t.var = t1(idx) .* t.var; return end - sz = ones(1, max(i)); - sz(1:length(a.sz)) = a.sz; - sz = sz(i); - end - - function disp(t) - nz = size(t.var, 1); - if nz == 0 - fprintf('all-zero %s of size %s\n', class(t), ... - regexprep(mat2str(t.sz), {'\[', '\]', '\s+'}, {'', '', 'x'})); + if ~issparse(t2) + if isempty(t1.var) + t = t1; + return + end + + idx = sub2ind_(t1.sz, t1.ind); + t = t1; + t.var = t.var .* t2(idx); return end - fprintf('%s of size %s with %d nonzeros:\n', class(t), ... - regexprep(mat2str(t.sz), {'\[', '\]', '\s+'}, {'', '', 'x'}), nz); - - spc = floor(log10(max(double(t.ind), [], 1))) + 1; - if numel(spc) == 1 - fmt = strcat("\t(%", num2str(spc(1)), "u)"); - else - fmt = strcat("\t(%", num2str(spc(1)), "u,"); - for i = 2:numel(spc) - 1 - fmt = strcat(fmt, "%", num2str(spc(i)), "u,"); - end - fmt = strcat(fmt, "%", num2str(spc(end)), "u)"); + [inds, ia, ib] = intersect(t1.ind, t2.ind, 'rows'); + t = SparseTensor(inds, t1.var(ia) .* t2.var(ib), t1.sz); + end + + function t = tpermute(t, p, r) + for i = 1:numel(t.var) + t.var(i) = tpermute(t.var(i), p, r); end - - for i = 1:nz - fprintf('%s\t\t', compose(fmt, t.ind(i,:))); - disp(t.var(i)); - fprintf('\n'); - end + t = permute(t, p); + end + + function a = uminus(a) + if ~isempty(a.var), a.var = -a.var; end + end + + function a = uplus(a) end end + %% Indexing methods function t = subsref(t, s) assert(length(s) == 1, 'sparse:index', 'only single level indexing allowed'); assert(strcmp(s.type, '()'), 'sparse:index', 'only () indexing allowed'); - + n = size(s.subs, 2); if n == 1 % linear indexing [s.subs{1:size(t.sz, 2)}] = ind2sub(t.sz, s.subs{1}); diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 75c4dd8..9c38224 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -684,16 +684,25 @@ % d : double % scalar dot product of the two tensors. - assert(isequal(t1.domain, t2.domain) && isequal(t1.codomain, t2.codomain), ... - 'tensors:SpaceMismatch', ... - 'Dot product only defined for tensors of equal structure.'); - + assert(isequal(size(t1), size(t2)), 'tensors:dimerror', ... + 'input tensors must have the same size.'); + d = 0; - [mblocks1, mcharges] = matrixblocks(t1.var); - mblocks2 = matrixblocks(t2.var); - qdims = qdim(mcharges); - for i = 1:length(t1.var) - d = d + qdims(i) * sum(conj(mblocks1{i}) .* mblocks2{i}, 'all'); + for i = 1:numel(t1) + assert(isequal(t1(i).domain, t2(i).domain) && ... + isequal(t1(i).codomain, t2(i).codomain), ... + 'tensors:SpaceMismatch', ... + 'dot product only defined for tensors of equal structure.'); + [mblocks1, mcharges] = matrixblocks(t1(i).var); + mblocks2 = matrixblocks(t2(i).var); + qdims = qdim(mcharges); + for j = 1:length(mblocks1) + try + d = d + qdims(j) * sum(conj(mblocks1{j}) .* mblocks2{j}, 'all'); + catch + bla + end + end end end @@ -1213,7 +1222,15 @@ % output tensor. if isnumeric(t), [t, a] = swapvars(t, a); end - t.var = times(t.var, a); + if isscalar(a) && ~isscalar(t) + a = repmat(a, size(t)); + else + assert(isequal(size(a), size(t)), 'tensors:dimerror', ... + 'input sizes incompatible.'); + end + for i = 1:numel(t) + t(i).var = times(t(i).var, a(i)); + end end function tr = trace(t) @@ -1320,7 +1337,9 @@ end function t = uminus(t) - t.var = -t.var; + for i = 1:numel(t) + t(i).var = -t(i).var; + end end end @@ -2470,6 +2489,10 @@ a = cell2mat(a_cell); end + function a = sparse(t) + a = SparseTensor(t); + end + function tdst = desymmetrize(tsrc, mode) arguments tsrc diff --git a/test/TestSparseTensor.m b/test/TestSparseTensor.m new file mode 100644 index 0000000..2137997 --- /dev/null +++ b/test/TestSparseTensor.m @@ -0,0 +1,94 @@ +classdef TestSparseTensor < matlab.unittest.TestCase + % Unit tests for sparse arrays of tensors. + + properties + tol = 1e-12 + end + + methods (Test) + function basic_linear_algebra(tc) + spaces = CartesianSpace.new([2 3 4 5]); + szs = cell(1, 4); + for i = 1:length(szs) + szs{i} = spaces(randperm(length(spaces), randi([2 3]))); + end + t1 = generatesparsetensor([2 2], szs, 0.3); + a = rand(); + + tc.verifyTrue(isapprox(norm(t1)^2, dot(t1, t1), ... + 'AbsTol', tc.tol, 'RelTol', tc.tol), 'norm and dot incompatible.'); + + tc.verifyTrue(isapprox(norm(a .* t1), abs(a) * norm(t1), ... + 'AbsTol', tc.tol, 'RelTol', tc.tol), ... + 'norm and scalar multiplication incompatible.'); + tc.verifyTrue(isapprox(t1 + t1, 2 .* t1, ... + 'AbsTol', tc.tol, 'RelTol', tc.tol), ... + '2t and t+t incompatible.'); + + tc.verifyTrue(isapprox(-t1, t1 .* (-1), 'AbsTol', tc.tol), ... + '=t and t * (-1) incompatible.'); + tc.verifyTrue(isapprox(norm(normalize(t1)), 1), ... + 'normalize should result in unit norm.'); + + t2 = generatesparsetensor([2 2], szs, 0.3); + b = rand(); + + tc.verifyTrue(isapprox(dot(b .* t2, a .* t1), conj(b) * a * dot(t2, t1), ... + 'AbsTol', tc.tol, 'RelTol', tc.tol) && ... + isapprox(dot(t2, t1), conj(dot(t1, t2)), ... + 'AbsTol', tc.tol, 'RelTol', tc.tol), ... + 'Dot should be sesquilinear.'); + end + + function permute_via_inner(tc) + spaces = CartesianSpace.new([2 3 4 5]); + szs = cell(1, 4); + for i = 1:length(szs) + szs{i} = spaces(randperm(length(spaces), randi([2 3]))); + end + t1 = generatesparsetensor([2 2], szs, 0.3); + t2 = generatesparsetensor([2 2], szs, 0.3); + + inner = dot(t1, t2); + for i = 0:4 + ps = perms(1:ndims(t1)).'; + for p = ps(:, randperm(size(ps, 2), min(size(ps, 2), 20))) + t3 = tpermute(t1, p.', [i 4-i]); + for j = 1:ndims(t1) + tc.assertTrue(isequal(space(t1, p(j)), space(t3, j)), ... + 'incorrect spaces after permutation.'); + end + tc.assertTrue(... + isapprox(norm(t1), norm(t3), 'AbsTol', tc.tol, 'RelTol', tc.tol), ... + 'Permute should preserve norms.') + + t4 = tpermute(t2, p.', [i 4-i]); + for j = 1:ndims(t1) + tc.assertTrue(isequal(space(t2, p(j)), space(t4, j)), ... + 'incorrect spaces after permutation.'); + end + tc.assertTrue(... + isapprox(dot(t3, t4), inner, 'AbsTol', tc.tol, 'RelTol', tc.tol), ... + 'Permute should preserve inner products.'); + end + end + end + end +end + +function t = generatesparsetensor(rank, spaces, sparsity) + +sz = cellfun(@length, spaces); +ind = ind2sub_(sz, 1:prod(sz)); + +for i = prod(sz):-1:1 + for j = length(sz):-1:1 + localsz(j) = spaces{j}(ind(i,j)); + end + var(i) = Tensor.rand(localsz(1:rank(1)), localsz(rank(1)+1:end)', 'Rank', rank); +end + +li1 = rand([1, length(var)]) < sparsity; +t = SparseTensor(ind(li1, :), var(li1), sz); + +end \ No newline at end of file diff --git a/test/TestTensor.m b/test/TestTensor.m index 487073d..eef895e 100644 --- a/test/TestTensor.m +++ b/test/TestTensor.m @@ -89,7 +89,6 @@ function matrix_functions(tc, spaces) end end - function permute_via_inner(tc, spaces) rng(213); t1 = Tensor.rand(spaces, []); From 7b66c6f2c40ad87e0befb9d7c2d8d3e6ddf46b9d Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sun, 9 Oct 2022 12:04:50 +0200 Subject: [PATCH 037/245] utility things for SparseTensor --- src/sparse/SparseTensor.m | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index 7bb8345..6a3f1ad 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -162,6 +162,10 @@ function disp(t) bool = true; end + function bool = isscalar(t) + bool = prod(t.sz) == 1; + end + function n = nnz(t) n = length(t.var); end @@ -415,22 +419,31 @@ function disp(t) %% Indexing methods + function i = end(t, k, n) + if n == 1 + i = prod(t.sz); + return + end + + assert(n == length(t.sz), 'sparse:index', 'invalid amount of indices.') + i = t.sz(k); + end + function t = subsref(t, s) - assert(length(s) == 1, 'sparse:index', 'only single level indexing allowed'); - assert(strcmp(s.type, '()'), 'sparse:index', 'only () indexing allowed'); + assert(strcmp(s(1).type, '()'), 'sparse:index', 'only () indexing allowed'); - n = size(s.subs, 2); + n = size(s(1).subs, 2); if n == 1 % linear indexing - [s.subs{1:size(t.sz, 2)}] = ind2sub(t.sz, s.subs{1}); + [s(1).subs{1:size(t.sz, 2)}] = ind2sub_(t.sz, s(1).subs{1}); else assert(n == size(t.sz, 2), 'sparse:index', ... 'number of indexing indices must match tensor size.'); end f = true(size(t.ind, 1), 1); - newsz = zeros(1, size(s.subs, 2)); + newsz = zeros(1, size(s(1).subs, 2)); - for i = 1:size(s.subs, 2) - A = s.subs{i}; + for i = 1:size(s(1).subs, 2) + A = s(1).subs{i}; if strcmp(A, ':') newsz(i) = t.size(i); continue; @@ -454,6 +467,10 @@ function disp(t) t.ind = t.ind(f, :); t.var = t.var(f); end + if length(s) > 1 + assert(isscalar(t)) + t = subsref(t.var, s(2:end)); + end end function t = subsasgn(t, s, v) From 649f52954bab1e757cbe07e754e6492b21a5fe06 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sun, 9 Oct 2022 12:05:17 +0200 Subject: [PATCH 038/245] better error message for empty tensors. --- src/tensors/kernels/MatrixBlock.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tensors/kernels/MatrixBlock.m b/src/tensors/kernels/MatrixBlock.m index d74859b..f4b12de 100644 --- a/src/tensors/kernels/MatrixBlock.m +++ b/src/tensors/kernels/MatrixBlock.m @@ -19,6 +19,9 @@ rank = [length(codomain) length(domain)]; trees = fusiontrees(codomain, domain); + assert(~isempty(trees), 'tensors:empty', ... + 'no fusion channels available for the given spaces.'); + [c, ~, ic] = unique(trees.coupled); uncoupled = trees.uncoupled; From 186c75ef7b331c7a0600102bb89de1afff76641c Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sun, 9 Oct 2022 12:05:47 +0200 Subject: [PATCH 039/245] ProductCharge fixes for non-Abelian groups --- src/tensors/charges/AbstractCharge.m | 32 ++++-- src/tensors/charges/ProductCharge.m | 163 ++++++++++++++++++++++++--- test/TestCharge.m | 3 +- 3 files changed, 172 insertions(+), 26 deletions(-) diff --git a/src/tensors/charges/AbstractCharge.m b/src/tensors/charges/AbstractCharge.m index 246d74b..eda412a 100644 --- a/src/tensors/charges/AbstractCharge.m +++ b/src/tensors/charges/AbstractCharge.m @@ -590,7 +590,8 @@ charges = []; vertices = []; for i = 1:size(a, 2) - [chargepart, vertexpart] = cumprod(a(:, i)); + [chargepart, vertexpart] = cumprod(... + subsref(a, substruct('()', {':', i}))); charges = horzcat(charges, chargepart); vertices = horzcat(vertices, vertexpart); end @@ -605,17 +606,20 @@ end if length(a) == 2 - f = a(1) * a(2); - charges = [repmat(a(1), 1, length(f)); f]; + f = subsref(a, substruct('()', {1})) * subsref(a, substruct('()', {2})); + charges = [repmat(subsref(a, substruct('()', {1})), 1, length(f)); f]; vertices = []; return end - part = cumprod(a(1:end-1)); + part = cumprod(subsref(a, substruct('()', {1:length(a)-1}))); charges = []; for i = 1:size(part, 2) - f = part(end, i) * a(end); - charges = [charges [repmat(part(:, i), 1, length(f)); f]]; + f = subsref(part, substruct('()', {size(part, 1), i})) * ... + subsref(a, substruct('()', {length(a)})); + charges = [charges ... + [repmat(subsref(part, substruct('()', {size(part, 1), i})), ... + 1, length(f)); f]]; end vertices = []; return @@ -630,9 +634,10 @@ if length(a) == 2 charges = []; vertices = []; - for f = a(1) * a(2) - N = Nsymbol(a(1), a(2), f); - charges = [charges repmat([a(1); f], 1, N)]; + for f = subsref(a, substruct('()', {1})) * subsref(a, substruct('()', {2})) + N = Nsymbol(subsref(a, substruct('()', {1})), ... + subsref(a, substruct('()', {2})), f); + charges = [charges repmat([subsref(a, substruct('()', {1})); f], 1, N)]; vertices = [vertices 1:N]; end return @@ -642,9 +647,12 @@ charges = []; vertices = []; for i = 1:size(chargepart, 2) - for f = chargepart(end, i) * a(end) - N = Nsymbol(chargepart(end, i), a(end), f); - charges = [charges, repmat([chargepart(:, i); f], 1, N)]; + for f = subsref(chargepart, substruct('()', {size(chargepart, 1), i})) * ... + subsref(a, substruct('()', {length(a)})) + N = Nsymbol(subsref(chargepart, substruct('()', {size(chargepart, 1), i})), ... + subsref(a, substruct('()', {length(a)})), f); + charges = [charges, ... + repmat([subsref(chargepart, substruct('()', {':', i})); f], 1, N)]; vertices = [vertices [repmat(vertexpart(:, i), 1, N); 1:N]]; end end diff --git a/src/tensors/charges/ProductCharge.m b/src/tensors/charges/ProductCharge.m index 9eed5b8..3f73e0f 100644 --- a/src/tensors/charges/ProductCharge.m +++ b/src/tensors/charges/ProductCharge.m @@ -108,11 +108,68 @@ N = ones(size(d)); end - otherwise - if nargout > 1 - [d, N] = prod@AbstractCharge(a, dim); - else - d = prod@AbstractCharge(a, dim); + case FusionStyle.Simple + d = subsref(a, substruct('()', {1})); + N = 1; + for i = 2:length(a) + a_i = subsref(a, substruct('()', {i})); + d_ = subsref(d, substruct('()', {1})) * ... + a_i; + if nargout > 1 + N_(1:length(d_)) = N(1); + end + + for j = 2:length(d) + c = subsref(d, substruct('()', {j})) * ... + a_i; + d_ = [d_ c]; + if nargout > 1 + N_(end + (1:length(c))) = N(j); + end + end + + if nargout < 2 + d = unique(d_); + else + [d, ~, ic] = unique(d_); + N = zeros(size(d)); + for i = 1:length(N) + N(i) = sum(N_(ic == i)); + end + end + end + + case FusionStyle.Generic + error('TBA'); + d = a(1); + N = 1; + for i = 2:length(a) + d_ = d(1) * a(i); + if nargout > 1 + N_ = N(1) .* ... + Nsymbol(repmat(d(1), 1, length(d_)), ... + repmat(a(i), 1, length(d_)), d_); + end + + for j = 2:length(d) + c = d(j) * a(i); + d_(end + (1:length(c))) = c; + if nargout > 1 + N_(end + (1:length(c))) = N(j) .* ... + Nsymbol(repmat(d(j), 1, length(c)), ... + repmat(a(i), 1, length(c)), c); + end + end + + if nargout < 2 + d = unique(d_); + else + [d, ~, ic] = unique(d_); + N = zeros(size(d)); + for i = 1:length(N) + N(i) = sum(N_(ic == i)); + end + end end end end @@ -291,6 +348,11 @@ end end + function c = intersect(a, b) + c = subsref(a, substruct('()', ... + {mod(find(reshape(a, [], 1) == reshape(b, 1, [])) - 1, length(a)) + 1})); + end + function F = Fsymbol(a, b, c, d, e, f) if hasmultiplicity(fusionstyle(a)) error('Not implemented yet.'); @@ -304,6 +366,74 @@ end end + function F = Fmatrix(a, b, c, d, e, f) + % Compute the full recoupling matrix from ``e`` to ``f``. + % + % .. todo:: + % Add proper definition? + % + % Usage + % ----- + % :code:`F = Fmatrix(a, b, c, d, e, f)` computes the matrix between all allowed + % channels. + % + % Arguments + % --------- + % a, b, c : :class:`.AbstractCharge` + % charges being fused + % d : :class:`.AbstractCharge` + % total charges + % e : :class:`.AbstractCharge` (1, \*) + % intermediate charges before recoupling + % f : :class:`.AbstractCharge` (1, \*) + % intermediate charge after recoupling + % + % Returns + % ------- + % F : :class:`double` (\*, \*, \*, \*) + % recoupling matrix between all allowed channels + if a.fusionstyle == FusionStyle.Unique + if nargin < 5, e = a * b; end + if nargin < 6, f = b * c; end + F = Fsymbol(a, b, c, d, e, f); + return + end + + if nargin < 5, e = intersect(a * b, conj(c * conj(d))); end + if nargin < 6, f = intersect(b * c, conj(conj(d) * a)); end + + if hasmultiplicity(a.fusionstyle) + Fblocks = cell(length(f), length(e)); + for i = 1:length(e) + for j = 1:length(f) + Fblocks{j, i} = Fsymbol(a, b, c, d, ... + subsref(e, substruct('()', {i})), ... + subsref(f, substruct('()', {j}))); + sz = size(Fblocks{j, i}, 1:4); + Fblocks{j, i} = reshape(Fblocks{j, i}, ... + sz(1) * sz(2), sz(3) * sz(4)).'; + end + end + F = cell2mat(Fblocks); + return + end + + F = zeros(length(f), length(e)); + for i = 1:length(e) + for j = 1:length(f) + F(j, i) = Fsymbol(a, b, c, d, ... + subsref(e, substruct('()', {i})), ... + subsref(f, substruct('()', {j}))); + end + end + end + + function disp(a) + for i = 1:length(a.charges) + disp(a.charges{i}); + end + end + function N = Nsymbol(a, b, c) N = Nsymbol(a.charges{1}, b.charges{1}, c.charges{1}); for i = 2:length(a.charges) @@ -323,17 +453,17 @@ assert(isscalar(a) && isscalar(b)) charges = cell(size(a.charges)); - charges{1} = a.charges{1} * b.charges{1}; + ctr = 1; for i = 1:length(charges) charges{i} = a.charges{i} * b.charges{i}; + n = numel(charges{i}); + charges{i} = reshape(repmat(reshape(charges{i}, 1, []), ctr, 1), 1, []); + for j = 1:i-1 + charges{j} = repmat(charges{j}, 1, n); + end + ctr = ctr * n; end - - for i = 2:length(charges) - n1 = length(charges{i-1}); - n2 = length(charges{i}); - charges{i-1} = repmat(charges{i}, 1, n2); - charges{i} = reshape(repmat(charges{i}, n1, 1), 1, []); - end + c = ProductCharge(charges{:}); end function bool = ne(a, b) @@ -343,6 +473,13 @@ end end + function d = qdim(a) + d = qdim(a.charges{1}); + for i = 2:length(a.charges) + d = d .* qdim(a.charges{i}); + end + end + function R = Rsymbol(a, b, c, inv) if nargin < 4, inv = []; end if hasmultiplicity(fusionstyle(a)) diff --git a/test/TestCharge.m b/test/TestCharge.m index 2dda531..3e0d5b4 100644 --- a/test/TestCharge.m +++ b/test/TestCharge.m @@ -16,7 +16,8 @@ 'SU2', SU2(1:4), ... 'A4', A4(1:4), ... 'SU3', SUN([1 0 0], [2 0 0], [3 0 0]), ... - 'Z2xU1', ProductCharge(Z2(0, 0, 0, 1, 1, 1), U1(-1, 0, 1, -1, 0, 1))... + 'Z2xU1', ProductCharge(Z2(0, 0, 0, 1, 1, 1), U1(-1, 0, 1, -1, 0, 1)), ... + 'U1xSU2', ProductCharge(U1(-1, -1, 0, 0, 1, 1), SU2(1, 2, 1, 2, 1 ,2))... ) end From e6e0e63be665217612708e87842a4d60f58e2d4c Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 10 Oct 2022 10:20:50 +0200 Subject: [PATCH 040/245] bugfix typo --- src/tensors/charges/AbstractCharge.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tensors/charges/AbstractCharge.m b/src/tensors/charges/AbstractCharge.m index eda412a..bec1d32 100644 --- a/src/tensors/charges/AbstractCharge.m +++ b/src/tensors/charges/AbstractCharge.m @@ -572,6 +572,7 @@ % explanation % vertices : type % explanation + if a.fusionstyle == FusionStyle.Unique charges = a; for i = 2:size(charges, 1) @@ -618,7 +619,7 @@ f = subsref(part, substruct('()', {size(part, 1), i})) * ... subsref(a, substruct('()', {length(a)})); charges = [charges ... - [repmat(subsref(part, substruct('()', {size(part, 1), i})), ... + [repmat(subsref(part, substruct('()', {1:size(part, 1), i})), ... 1, length(f)); f]]; end vertices = []; From 970caf7b7f498ddcba4326dbd740553b35143c2c Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 10 Oct 2022 10:21:05 +0200 Subject: [PATCH 041/245] add custom eigsolve --- src/utility/linalg/eigsolve.m | 157 ++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 src/utility/linalg/eigsolve.m diff --git a/src/utility/linalg/eigsolve.m b/src/utility/linalg/eigsolve.m new file mode 100644 index 0000000..aaa6d9e --- /dev/null +++ b/src/utility/linalg/eigsolve.m @@ -0,0 +1,157 @@ +function [V, D, flag] = eigsolve(A, v, howmany, which, kwargs) +%EIGSOLVE Summary of this function goes here +% Detailed explanation goes here + +arguments + A + v + howmany = 1 + which {mustBeMember(which, {'lm', 'largestabs', 'lr', 'largestreal', ... + 'li', 'largestimag', 'sm', 'smallestabs', 'sr', 'smallestreal', ... + 'si', 'smallestimag'})} = 'lm' + kwargs.Tol = eps(underlyingType(v))^(3/4) + kwargs.Algorithm = 'Arnoldi' + kwargs.MaxIter = 100 + kwargs.KrylovDim {mustBeGreaterThan(kwargs.KrylovDim, howmany)} = 20 + kwargs.DeflateDim + kwargs.ReOrth = 2 + kwargs.NoBuild +end + +% Input validations +if ~isfield(kwargs, 'NoBuild') + kwargs.NoBuild = ceil(kwargs.KrylovDim / 10); +end +if ~isfield(kwargs, 'DeflateDim') + kwargs.DeflateDim = max(round(3/5 * kwargs.KrylovDim), howmany); +end +if ~isa(A, 'function_handle') + A = @(x) A * x; +end +if norm(v) < eps(underlyingType(v))^(3/4) + error('eigsolve:inputnorm', 'starting vector should not have zero norm.'); +end + +% Arnoldi storage for Krylov subspace basis +V = zeros(0, kwargs.KrylovDim, 'like', v); + +% Arnoldi storage for Hessenberg matrix +H = zeros(kwargs.KrylovDim, kwargs.KrylovDim, underlyingType(v)); + +ctr_outer = 0; +ctr_inner = 0; +flag = 0; + +while ctr_outer < kwargs.MaxIter + ctr_outer = ctr_outer + 1; + + while ctr_inner < kwargs.KrylovDim % build Krylov subspace + ctr_inner = ctr_inner + 1; + + V(1:length(v), ctr_inner) = v; + v = A(v); + H(:, ctr_inner) = V' * v; + v = v - V * H(:, ctr_inner); + + % reorthogonalize new vector + if ctr_inner >= kwargs.ReOrth + c = V' * v; + H(:, ctr_inner) = H(:, ctr_inner) + c; + v = v - V * c; + end + + % normalize + beta = norm(v, 'fro'); + v = v / beta; + + if ctr_inner >= howmany + invariantsubspace = beta < eps(underlyingType(beta))^(3/4); + if invariantsubspace || ctr_inner == kwargs.KrylovDim + break; + end + + % check for convergence during subspace build + if ~mod(ctr_inner, kwargs.NoBuild) + [U, lambda] = eig(H(1:ctr_inner, 1:ctr_inner), 'vector'); + select = selecteigvals(lambda, howmany, which); + conv = beta * norm(U(ctr_inner, select), Inf); + + if conv < kwargs.Tol + V = V(:, 1:ctr_inner) * U(:, select); + D = diag(lambda(select)); + return + end + end + end + + H(ctr_inner + 1, ctr_inner) = beta; + end + + % stopping criterium reached - irrespective of convergence + if ctr_outer == kwargs.MaxIter || ctr_inner ~= kwargs.KrylovDim + [U, lambda] = eig(H(1:ctr_inner, 1:ctr_inner), 'vector'); + select = selecteigvals(lambda, howmany, which); + conv = beta * norm(U(ctr_inner, select), Inf); + V = V(:, 1:ctr_inner) * U(:, select); + D = diag(lambda(select)); + + if conv > kwargs.Tol + if invariantsubspace + flag = 1; + else + flag = 2; + end + end + return + end + + % deflate Krylov subspace + [U1, T] = schur(H, 'real'); + E = ordeig(T); + select1 = false(size(E)); + select1(selecteigvals(E, kwargs.DeflateDim, which)) = true; + [U1, T] = ordschur(U1, T, select1); + + V = V * U1; + [U, lambda] = eig(T(1:kwargs.DeflateDim, 1:kwargs.DeflateDim), 'vector'); + select = selecteigvals(lambda, howmany, which); + conv = beta * norm(U1(kwargs.KrylovDim, 1:kwargs.DeflateDim) * U(:, select), 'Inf'); + + % check for convergence + if conv < kwargs.Tol + V = V(:, 1:kwargs.DeflateDim) * U(:, select); + D = diag(lambda(select)); + return + end + + % deflate Krylov subspace + H(1:kwargs.DeflateDim, 1:kwargs.DeflateDim) = ... + T(1:kwargs.DeflateDim, 1:kwargs.DeflateDim); + H(kwargs.DeflateDim + 1, 1:kwargs.DeflateDim) = ... + beta * U1(kwargs.KrylovDim, 1:kwargs.DeflateDim); + V(:, kwargs.DeflateDim + 1:end) = 0 * V(:, kwargs.DeflateDim + 1:end); + ctr_inner = kwargs.DeflateDim; +end + +end + +function select = selecteigvals(lambda, howmany, which) + +switch which + case {'largestabs', 'lm'} + [~, p] = sort(abs(lambda), 'descend'); + case {'largestreal', 'lr'} + [~, p] = sort(real(lambda), 'descend'); + case {'largestimag', 'li'} + [~, p] = sort(imag(lambda), 'descend'); + case {'smallestabs', 'sm'} + [~, p] = sort(abs(lambda), 'ascend'); + case {'smallestreal', 'sr'} + [~, p] = sort(real(lambda), 'ascend'); + case {'smallestimag', 'si'} + [~, p] = sort(imag(lambda), 'ascend'); +end + +select = p(1:howmany); + +end From ceff9b3ebe295ed27e10352c047bbbed3ee90993 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 11 Oct 2022 14:27:50 +0200 Subject: [PATCH 042/245] bugfix simulsort --- src/utility/simulsort.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utility/simulsort.m b/src/utility/simulsort.m index 107354e..09b6e89 100644 --- a/src/utility/simulsort.m +++ b/src/utility/simulsort.m @@ -77,7 +77,7 @@ end else for k = length(arrays)-1:-1:1 - for i = 1:size(I, 2) + for i = 1:size(I, 1) varargout{k}(i, :) = arrays{k}(i, I(i, :)); end end @@ -113,7 +113,7 @@ % permute index for i = 1:size(I, 1) - I(i, :) = I(i, I_(:, i)); + I(i, :) = I(i, I_(i, :)); end end end From 37f5e666513edfb61f19f8a6bda6ec52c95f6c51 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 11 Oct 2022 14:28:46 +0200 Subject: [PATCH 043/245] bugfix ProductCharge sorting --- src/tensors/charges/ProductCharge.m | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/tensors/charges/ProductCharge.m b/src/tensors/charges/ProductCharge.m index 3f73e0f..bfb4715 100644 --- a/src/tensors/charges/ProductCharge.m +++ b/src/tensors/charges/ProductCharge.m @@ -58,6 +58,10 @@ a = cat(1, a, varargin{:}); end + function s = GetMD5_helper(a) + s = cellfun(@GetMD5_helper, a.charges, 'UniformOutput', false); + end + function varargout = size(prodcharge, varargin) [varargout{1:nargout}] = size(prodcharge.charges{1}, varargin{:}); end @@ -492,6 +496,16 @@ function disp(a) end end + function bool = issorted(a) + [a, p] = sort(a); + bool = isequal(p, (1:length(a)).'); + end + + function bool = issortedrows(a) + [p, ~] = simulsortrows(a.charges{:}); + bool = isequal(p, (1:size(a.charges{1}, 1)).'); + end + function [a, I] = sort(a, varargin) [I, a.charges{:}] = simulsort(a.charges{:}, varargin{:}); end @@ -509,9 +523,9 @@ function disp(a) return end - newcol = size(a.charges, 2) * (col(:) - 1) + (1:size(a.charges, 2)); + newcol = reshape(col + (((1:length(a.charges)).' - 1) .* size(a, 2)), 1, []); [I, a.charges{:}] = simulsortrows(a.charges{:}, ... - 'Col', reshape(newcol, 1, []), ... + 'Col', newcol, ... 'Direction', direction); end From a257df3aa625830846edde140532b31fb03f517a Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 17 Oct 2022 14:55:01 +0200 Subject: [PATCH 044/245] Huge update, no clue if still working... --- src/caches/LRU.m | 4 +- src/mps/FiniteMpo.m | 138 +++++++++ src/mps/InfiniteMpo.m | 144 +++++++++ src/mps/MpoTensor.m | 387 ++++++++++++++++++++++++ src/mps/MpsTensor.m | 372 +++++++++++++++++++++++ src/mps/UniformMps.m | 361 +++++++++++++++++++++++ src/sparse/SparseTensor.m | 314 ++++++++++++++------ src/sparse/sub2sub.m | 4 +- src/tensors/AbstractTensor.m | 362 +++++++++++++++++++++++ src/tensors/FusionTree.m | 16 +- src/tensors/Tensor.m | 443 +++++----------------------- src/tensors/charges/O2.m | 4 + src/tensors/charges/ProductCharge.m | 19 +- src/tensors/charges/Z1.m | 4 + src/tensors/kernels/MatrixBlock.m | 27 +- src/tensors/spaces/CartesianSpace.m | 2 +- src/tensors/spaces/ComplexSpace.m | 29 ++ src/tensors/spaces/GradedSpace.m | 37 ++- src/utility/Verbosity.m | 13 + src/utility/linalg/contracttree.m | 2 +- src/utility/linalg/eigsolve.m | 43 ++- src/utility/linalg/generatetree.m | 8 +- test/TestFiniteMpo.m | 33 +++ test/TestFusionTree.m | 21 +- test/TestInfiniteMpo.m | 39 +++ test/TestTensor.m | 21 +- test/TestUniformMps.m | 69 +++++ 27 files changed, 2416 insertions(+), 500 deletions(-) create mode 100644 src/mps/FiniteMpo.m create mode 100644 src/mps/InfiniteMpo.m create mode 100644 src/mps/MpoTensor.m create mode 100644 src/mps/MpsTensor.m create mode 100644 src/mps/UniformMps.m create mode 100644 src/tensors/AbstractTensor.m create mode 100644 src/utility/Verbosity.m create mode 100644 test/TestFiniteMpo.m create mode 100644 test/TestInfiniteMpo.m create mode 100644 test/TestUniformMps.m diff --git a/src/caches/LRU.m b/src/caches/LRU.m index fc586ee..4daaf1a 100644 --- a/src/caches/LRU.m +++ b/src/caches/LRU.m @@ -57,14 +57,14 @@ % val : any % value that is stored with a key, or empty if key not in cache. - try + if isKey(cache.map, key) dll = cache.map(key); val = dll.val{2}; % Re-insert dll to the front of the list pop(dll); append(cache.sentinel, dll); - catch + else val = []; end end diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m new file mode 100644 index 0000000..179b1bf --- /dev/null +++ b/src/mps/FiniteMpo.m @@ -0,0 +1,138 @@ +classdef FiniteMpo + % Finite Matrix product operators + + properties + L + O + R + end + + methods + function mpo = FiniteMpo(L, O, R) + if nargin == 0, return; end + if ~iscell(O), O = {O}; end + mpo.L = L; + mpo.O = O; + mpo.R = R; + end + + function v = apply(mpo, v) + N = length(mpo); + assert(nspaces(v) - 2 == N, 'incompatible vector.'); + + inds = arrayfun(@(x) [2*x -(x+1) 2*(x+1) 2*x+1], 1:N, ... + 'UniformOutput', false); + for d = depth(mpo):-1:1 + args = [mpo(d).O; inds]; + v = contract(v, 1:2:2*N+3, ... + mpo(d).L, [-1 2 1], args{:}, mpo(d).R, [2*N+3 2*N+2 -(N+2)], ... + 'Rank', rank(v)); + end + end + + function [V, D, flag] = eigsolve(mpo, v0, howmany, sigma, options) + arguments + mpo + v0 = [] + howmany = 1 + sigma = 'largestabs' + + options.Tol = eps(underlyingType(mpo))^(3/4) + options.Algorithm = 'eigs' + options.MaxIter = 100 + options.KrylovDim = 20 + options.IsSymmetric logical = false + options.DeflateDim + options.ReOrth = 2 + options.NoBuild + options.Verbosity = 0 + end + + if isempty(v0), v0 = initialize_fixedpoint(mpo(1)); end + + kwargs = [fieldnames(options).'; struct2cell(options)]; + [V, D, flag] = eigsolve(@(x) mpo.apply(x), v0, howmany, sigma, kwargs{:}); + end + + function v = initialize_fixedpoint(mpo) + v = Tensor.randnc(domain(mpo), []); + end + + function s = domain(mpo) + s = conj(... + [space(mpo(1).L, 3), cellfun(@(x) space(x, 4), mpo.O), space(mpo.R, 1)]); + end + + function s = codomain(mpo) + s = [space(mpo(1).L, 1), cellfun(@(x) space(x, 2), mpo.O), space(mpo.R, 3)]; + end + + function d = depth(mpo) + d = size(mpo, 1); + end + + function l = length(mpo) + l = length(mpo(1).O); + end + + function w = width(mpo) + w = size(mpo.O, 2); + end + + function v = applyleft(mpo, v) + + end + + function mpo_d = ctranspose(mpo) + mpo_d = mpo; + mpo_d.L = tpermute(conj(mpo.L), [3 2 1], rank(mpo.L)); + mpo_d.R = tpermute(conj(mpo.R), [3 2 1], rank(mpo.R)); + mpo_d.O = cellfun(@(x) tpermute(conj(x), [3 2 1 4], rank(x)), mpo.O, ... + 'UniformOutput', false); +% mpo_d = FiniteMpo(conj(mpo.L), conj(mpo.O), conj(mpo.R)); + end + + function mpo1 = plus(mpo1, mpo2) + + end + + function v = applyright(mpo, v) + arguments + mpo + v MpsTensor + end + + assert(depth(mpo) == 1, 'mps:TBA', 'Not implemented yet.'); + assert(v.plegs == width(mpo), 'mps:ArgError', 'Incompatible sizes.'); + + w = width(mpo); + + mpopart = cell(2, w); + for i = 1:w + mpopart{1, i} = mpo.O{i}; + mpopart{2, i} = [2 * i, 2 * (i + 1) + 1, -(1 + i), 2 * i + 1]; + end + + v = MpsTensor(contract(... + v, [1, 2:2:(2 * w), 2 * (w + 1), -(1:v.alegs) - (w + 2)], ... + mpo.L, [-1 3 1], ... + mpo.R, [-(w + 2), 2 * (w + 1) + 1, 2 * (w + 1)], ... + mpopart{:}, 'Rank', rank(v))); + end + + function t = Tensor(mpo) + assert(depth(mpo) == 1, 'not implemented for 1 < depth'); + N = length(mpo); + inds = arrayfun(@(x) [x -(x+1) (x+1) -(N+x+3)], 1:N, ... + 'UniformOutput', false); + args = [mpo.O; inds]; + t = contract(mpo.L, [-1 1 -(N+3)], args{:}, mpo.R, [-(2*N+4) N+1 -(N+2)], ... + 'Rank', [N+2 N+2]); + end + end + + methods (Static) + + end +end + diff --git a/src/mps/InfiniteMpo.m b/src/mps/InfiniteMpo.m new file mode 100644 index 0000000..c7e3f3b --- /dev/null +++ b/src/mps/InfiniteMpo.m @@ -0,0 +1,144 @@ +classdef InfiniteMpo + % Infinite translation invariant matrix product operator. + + properties + O + end + + methods + function mpo = InfiniteMpo(varargin) + if nargin == 0, return; end + + if nargin == 1 + O = varargin{1}; + + if isa(O, 'InfiniteMpo') + for i = numel(O):1:1 + mpo(i).O = O(i).O; + end + mpo = reshape(mpo, size(O)); + + elseif isa(O, 'MpoTensor') + mpo.O = {O}; + + elseif isa(O, 'AbstractTensor') + for i = height(O):-1:1 + mpo(i).O = arrayfun(@MpoTensor, O(i, :), 'UniformOutput', false); + end + + elseif iscell(O) + mpo.O = O; + end + return + end + end + end + + methods + function [GL, lambda] = leftenvironment(mpo, mps1, mps2, GL, eigopts) + arguments + mpo + mps1 + mps2 + GL = [] + eigopts.KrylovDim = 30 + eigopts.MaxIter = 1000 + eigopts.ReOrth = 2 + eigopts.Tol = eps(underlyingType(mps1))^(3/4) + end + + if isempty(GL) + GL = cell(1, period(mps1)); + for i = size(mpo.O{1}, 1):-1:1 + GL{1}(1, i, 1) = Tensor.randnc(... + [leftvspace(mps2, 1) leftvspace(mpo.O{1}, i)'], leftvspace(mps1, 1)); + end + end + + [GL{1}, lambda] = eigsolve(@(x) transferleft(mpo, mps1, mps2, x), ... + GL{1}, 1, 'largestabs', 'KrylovDim', eigopts.KrylovDim, 'Tol', eigopts.Tol, ... + 'ReOrth', eigopts.ReOrth, 'MaxIter', eigopts.MaxIter); + + n = lambda^(1/period(mps1)); + for w = 2:period(mps1) + GL{w} = applyleft(mpo.O{w}, mps1.AL(w), conj(mps2.AL(w)), GL{w-1}) / n; + end + end + + function [GR, lambda] = rightenvironment(mpo, mps1, mps2, GR, eigopts) + arguments + mpo + mps1 + mps2 + GR = [] + eigopts.KrylovDim = 30 + eigopts.MaxIter = 1000 + eigopts.ReOrth = 2 + eigopts.Tol = eps(underlyingType(mps1))^(3/4) + end + + N = period(mps1); + if isempty(GR) + GR = cell(1, N); + for i = size(mpo.O{end}, 3):-1:1 + GR{1}(1, i, 1) = Tensor.randnc(... + [rightvspace(mps1, N)' rightvspace(mpo.O{end}, i)'], ... + rightvspace(mps2, N)'); + end + end + + [GR{1}, lambda] = eigsolve(@(x) transferright(mpo, mps1, mps2, x), ... + GR{1}, 1, 'largestabs', 'KrylovDim', eigopts.KrylovDim, 'Tol', eigopts.Tol, ... + 'ReOrth', eigopts.ReOrth, 'MaxIter', eigopts.MaxIter); + + n = lambda^(1/N); + for w = N:-1:1 + GR{w} = applyright(mpo.O{w}, mps1.AR(w), conj(mps2.AR(w)), ... + GR{next(w, N)}) / n; + end + end + + function x = transferleft(mpo, mps1, mps2, x) + for w = 1:period(mps1) + x = applyleft(mpo.O{w}, mps1.AL(w), conj(mps2.AL(w)), x); + end + end + + function x = transferright(mpo, mps1, mps2, x) + for w = period(mps1):-1:1 + x = applyright(mpo.O{w}, mps1.AR(w), conj(mps2.AR(w)), x); + end + end + end + + methods (Static) + function mpo = Ising(beta, kwargs) + arguments + beta = log(1 + sqrt(2)) / 2; + kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'Z2'})} = 'Z1' + end + + if strcmp(kwargs.Symmetry, 'Z1') + t = [exp(beta) exp(-beta); exp(-beta) exp(beta)]; + [v, d] = eig(t); + t = v * sqrt(d) * v; + + o = zeros(2, 2, 2, 2); + o(1, 1, 1, 1) = 1; + o(2, 2, 2, 2) = 1; + + o = contract(o, 1:4, t, [-1 1], t, [-2 2], t, [-3 3], t, [-4 4]); + + O = fill_tensor(Tensor.zeros([2 2 2 2]), o); + + else + s = GradedSpace.new(Z2(0, 1), [1 1], false); + O = fill_tensor(Tensor([s s], [s s]), ... + @(~,f) sqrt(prod(logical(f.uncoupled) .* sinh(beta) + ... + ~logical(f.uncoupled) .* cosh(beta)))); + end + + mpo = InfiniteMpo(O); + end + end +end diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m new file mode 100644 index 0000000..ff90714 --- /dev/null +++ b/src/mps/MpoTensor.m @@ -0,0 +1,387 @@ +classdef (InferiorClasses = {?Tensor, ?MpsTensor, ?SparseTensor}) MpoTensor < AbstractTensor + % Matrix product operator building block + % This object represents the MPO tensor at a single site as the sum of rank (2,2) + % (sparse) tensors and some scalars, which will be implicitly used as unit tensors. + % + % 4 + % ^ + % | + % 1 ->-- O -->- 3 + % | + % ^ + % 2 + + properties + tensors = [] + scalars = [] + end + + methods + function n = nspaces(~) + n = 4; + end + end + + methods + function t = MpoTensor(varargin) + if nargin == 0, return; end + + if nargin == 1 + if isnumeric(varargin{1}) + t.scalars = varargin{1}; + t.tensors = SparseTensor([], [], size(t.scalars)); + elseif isa(varargin{1}, 'MpoTensor') + t.scalars = varargin{1}.scalars; + t.tensors = varargin{1}.tensors; + elseif isa(varargin{1}, 'Tensor') || isa(varargin{1}, 'SparseTensor') + t.tensors = varargin{1}; + t.scalars = zeros(size(t.tensors)); + end + return + end + + if nargin == 2 + t.tensors = varargin{1}; + t.scalars = varargin{2}; + N = max(ndims(t.tensors), ndims(t.scalars)); + assert(isequal(size(t.tensors, 1:N), size(t.scalars, 1:N)), 'mpotensors:dimerror', ... + 'scalars and tensors should have the same size.'); + if any(ismember(find(t.tensors), find(t.scalars))) + warning('mpotensor contains both scalar and tensor at the same site.'); + end + end + end + + function t = minus(t, t2) + t.tensors = t.tensors - t2.tensors; + t.scalars = t.scalars - t2.scalars; + end + + function t = plus(t, t2) + t.tensors = t.tensors + t2.tensors; + t.scalars = t.scalars + t2.scalars; + end + + function t = times(t, a) + if isnumeric(t) + t = a .* t; + return + end + + assert(isnumeric(a), 'mpotensor:argerror', 'invalid input types.'); + + t.tensors = t.tensors .* a; + t.scalars = t.scalars .* a; + end + + function t = rdivide(t, a) + assert(isnumeric(a), 'mpotensor:argerror', 'invalid input types.'); + t.tensors = t.tensors ./ a; + t.scalars = t.scalars ./ a; + end + + function t = mrdivide(t, a) + assert(isnumeric(a), 'mpotensor:argerror', 'invalid input types.'); + t.tensors = t.tensors / a; + t.scalars = t.scalars / a; + end + + function y = applyleft(O, T, B, x) + arguments + O MpoTensor + T MpsTensor + B MpsTensor + x + end + + switch num2str([nspaces(x) nspaces(T) nspaces(B)]) + case '3 3 3' + if isdual(space(B, 2)), twist(B, 2); end + + y = contract(x, [4 2 1], O.tensors, [2 5 -2 3], ... + T, [1 3 -3], B, [4 5 -1], ... + 'Rank', [2 1]); + scals = reshape(O.scalars, size(O, 1), size(O, 3)); + for j = 1:size(O, 1) + cols = find(scals(j, :)); + if isempty(cols), continue; end + y_ = contract(x(j), [3 -2 1], T, [1 2 -3], B, [3 2 -1], ... + 'Rank', [2 1]); + for i = cols + y(i) = y(i) + scals(j, i) * y_; + end + end + y2 = contract(x, [4 2 1], O, [2 5 -2 3], ... + T, [1 3 -3], B, [4 5 -1], ... + 'Rank', [2 1]); + try + assert(isapprox(y, y2)) + catch + bla + end + + otherwise + error('not implemented.'); + end + end + + function y = applyright(O, T, B, x) + arguments + O MpoTensor + T MpsTensor + B MpsTensor + x + end + + switch num2str([nspaces(x) nspaces(T) nspaces(B)]) + case '3 3 3' + if isdual(space(B, 2)), twist(B, 2); end + + y = contract(x, [1 2 4], O.tensors, [-2 5 2 3], ... + T, [-1 3 1], B, [-3 5 4], ... + 'Rank', [2 1]); + + scals = reshape(O.scalars, size(O, 1), size(O, 3)); + for i = 1:size(O, 3) + rows = find(scals(:, i)); + if isempty(rows), continue; end + y_ = contract(x(i), [1 -2 4], T, [-1 2 1], B, [-3 2 4], ... + 'Rank', [2 1]); + for j = rows + y(j) = y(j) + scals(j, i) * y_; + end + end + + otherwise + error('not implemented.'); + end + end + + function C = tensorprod(A, B, dimA, dimB, ca, cb, options) + arguments + A + B + dimA + dimB + ca = false + cb = false + options.NumDimensionsA = ndims(A) + end + + assert(~isa(A, 'MpoTensor') || ~isa(B, 'MpoTensor')); + if isa(A, 'MpoTensor') + assert(sum(dimA == 1 | dimA == 3, 'all') == 1, ... + 'Cannot deduce output space unless leg 1 xor leg 3 is connected.'); + assert(sum(dimA == 2 | dimA == 4, 'all') == 1, ... + 'Cannot deduce output space unless leg 2 xor leg 4 is connected.'); + + C1 = tensorprod(A.tensors, B, dimA, dimB, ca, cb); + + % action of braiding tensor, always flip connected indices + uncA = 1:nspaces(A); uncA(dimA) = []; + uncB = 1:nspaces(B); uncB(dimB) = []; + + C2 = reshape(permute(A.scalars, [uncA flip(dimA)]), ... + [prod(size(A, uncA)) prod(size(A, dimA))]) * ... + tpermute(B, [dimB uncB], [length(dimB) length(uncB)]); + C = C1 + C2; + else + assert(sum(dimB == 1 | dimB == 3, 'all') == 1, ... + 'Cannot deduce output space unless leg 1 xor leg 3 is connected.'); + assert(sum(dimB == 2 | dimB == 4, 'all') == 1, ... + 'Cannot deduce output space unless leg 2 xor leg 4 is connected.'); + + C1 = tensorprod(A, B.tensors, dimA, dimB, ca, cb); + + % action of braiding tensor, always flip connected indices + uncA = 1:nspaces(A); uncA(dimA) = []; + uncB = 1:nspaces(B); uncB(dimB) = []; + + C2 = tpermute(A, [uncA flip(dimA)], [length(uncA) length(dimA)]) * ... + reshape(permute(B.scalars, [flip(dimB) uncB]), ... + [prod(size(B, dimB)) prod(size(B, uncB))]); + C = C1 + C2; + end + + + end +% +% function C = contract(tensors, indices, kwargs) +% arguments (Repeating) +% tensors +% indices (1, :) {mustBeInteger} +% end +% +% arguments +% kwargs.Conj (1, :) logical = false(size(tensors)) +% kwargs.Rank = [] +% kwargs.Debug = false +% end +% +% % replace mpo with tensor + scalar contribution +% i = find(cellfun(@(x) isa(x, 'MpoTensor'), tensors), 1); +% +% args1 = [tensors; indices]; +% args1{1, i} = args1{1, i}.tensors; +% conj1 = kwargs.Conj; +% +% args2 = [tensors([1:i-1 i+1:end]); indices([1:i-1 i+1:end])]; +% conj2 = kwargs.Conj([1:i-1 i+1:end]); +% +% C = contract(args1{:}, ... +% 'Conj', conj1, 'Rank', kwargs.Rank, 'Debug', kwargs.Debug) + ... +% contract(args2{:}, ... +% 'Conj', conj2, 'Rank', kwargs.Rank, 'Debug', kwargs.Debug); +% +% +% for ii = 1:length(tensors) +% if isa(tensors{ii}, 'MpoTensor') && strcmp(tensors{ii}.info, 'dense') +% tensors{ii} = tensors{ii}.var; +% end +% end +% +% % replace scalar mpo's and correct indices +% scalar = 1; +% mpoInds = cellfun(@(x) isa(x, 'MpoTensor'), tensors); +% +% % collect scalars +% for ii = 1:length(tensors) +% if mpoInds(ii) +% if kwargs.Conj(ii) +% scalar = scalar * conj(tensors{ii}.var); +% else +% scalar = scalar * tensors{ii}.var; +% end +% end +% end +% +% if scalar == 0 +% warning('Endresult is 0, probably could have been more efficient.'); +% end +% +% % remove mpo's from tensorlist +% tensors(mpoInds) = []; +% kwargs.Conj(mpoInds) = []; +% +% % correct indices +% toCorrect = indices(mpoInds); +% indices = indices(~mpoInds); +% +% for ii = 1:length(toCorrect) +% vertical = toCorrect{ii}([1 3]); +% horizont = toCorrect{ii}([2 4]); +% vertMax = max(vertical); +% vertMin = min(vertical); +% horMax = max(horizont); +% horMin = min(horizont); +% +% for jj = 1:length(indices) +% indices{jj}(indices{jj} == vertMax) = vertMin; +% indices{jj}(indices{jj} == horMax ) = horMin; +% end +% for jj = ii+1:length(toCorrect) +% toCorrect{jj}(toCorrect{jj} == vertMax) = vertMin; +% toCorrect{jj}(toCorrect{jj} == horMax ) = horMin; +% end +% end +% +% +% %% Perform contraction +% args = [tensors; indices]; +% C = contract(args{:}, ... +% 'Conj', kwargs.Conj, 'Rank', kwargs.Rank, 'Debug', kwargs.Debug); +% % [output, med] = Contract(tensors, indices, endcenter, med); +% if ~isequal(scalar, 1) +% C = C * scalar; +% end +% end + end + + methods + function s = pspace(O) + s = space(O.tensors, 2); + end + + function s = leftvspace(O, lvls) + if nargin == 1 + s = space(O.tensors, 1); + else + s = space(O.tensors(lvls, :, :, :), 1); + end + end + + function s = rightvspace(O, lvls) + if nargin == 1 + s = space(O.tensors, 3); + else + s = space(O.tensors(:, :, lvls, :), 3); + end + end + end + + methods + function t = subsref(t, s) + assert(length(s) == 1, 'mpotensor:index', ... + 'only a single level of indexing allowed.'); + switch s.type + case '.' + t = builtin('subsref', t, s); + case '()' + t.scalars = subsref(t.scalars, s); + t.tensors = subsref(t.tensors, s); + otherwise + error('mpotensor:index', 'invalid indexing expression'); + end + end + + function t = subsasgn(t, s, v) + assert(length(s) == 1, 'mpotensor:index', ... + 'only a single level of indexing allowed.'); + assert(strcmp(s.type, '()'), 'mpotensor:index', 'only () indexing allowed.'); + + if isnumeric(v) + t.scalars = subsasgn(t.scalars, s, v); + elseif isa(v, 'MpoTensor') + t.scalars = subsasgn(t.scalars, s, v.scalars); + t.tensors = subsasgn(t.tensors, s, v.tensors); + elseif isa(v, 'Tensor') + t.tensors = subsasgn(t.tensors, s, v); + end + end + + function bools = eq(a, b) + arguments + a MpoTensor + b MpoTensor + end + + bools = a.scalars == b.scalars & a.tensors == b.tensors; + end + + function I = find(O) + I = union(find(O.tensors), find(O.scalars)); + end + + function varargout = size(t, varargin) + if nargin > 1 + [varargout{1:nargout}] = size(t.scalars, varargin{:}); + else + [varargout{1:nargout}] = size(t.scalars); + end + end + end + + methods (Static) + function O = zeros(m, n, o, p) + if nargin == 1 + sz = m; + elseif nargin == 4 + sz = [m n o p]; + else + error('mpotensor:argerror', 'invalid amount of inputs.'); + end + O = MpoTensor(SparseTensor([], [], sz), zeros(sz)); + end + end +end + diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m new file mode 100644 index 0000000..f1396fd --- /dev/null +++ b/src/mps/MpsTensor.m @@ -0,0 +1,372 @@ +classdef MpsTensor < Tensor + %MPSTENSOR Summary of this class goes here + % Detailed explanation goes here + + properties + plegs = 1 + alegs = 0 + end + + + %% Constructors + methods + function A = MpsTensor(tensor, alegs) + arguments + tensor = [] + alegs = zeros(size(tensor)) + end + + if isscalar(alegs) && ~isscalar(tensor) + alegs = repmat(alegs, size(tensor)); + else + assert(isequal(size(alegs, 1:ndims(tensor)), size(tensor)), 'mps:ArgError', ... + 'Input sizes incompatible.'); + end + + if isempty(tensor) + args = {}; + else + args = {tensor}; + end + A@Tensor(args{:}); + if ~isempty(tensor) + for i = numel(A):-1:1 + A(i).plegs = nspaces(tensor(i)) - alegs(i) - 2; + A(i).alegs = alegs(i); + end + end + end + end + + + %% Properties + methods + function s = pspace(A) + s = space(A, 1 + (1:A.plegs)); + end + + function s = leftvspace(A) + s = space(A, 1); + end + + function s = rightvspace(A) + s = space(A, nspaces(A) - A.alegs); + end + end + + + %% Linear Algebra + methods + function [AL, L] = leftorth(A, alg) + arguments + A + alg = 'qr' + end + + if A.alegs == 0 + [AL, L] = leftorth@Tensor(A, 1:nspaces(A)-1, nspaces(A), alg); + else + [AL, L] = leftorth@Tensor(A, [1:A.plegs+1 A.plegs+3], A.plegs+2, alg); + AL = permute(AL, [1:A.plegs+1 A.plegs+3 A.plegs+2], rank(A)); + end + AL = MpsTensor(AL, A.alegs); + end + + function [AL, C, lambda, eta] = uniform_leftorth(A, C, kwargs) + arguments + A + C = initializeC(A, circshift(A, 1)) + kwargs.Tol = eps(underlyingType(A))^(3/4) + kwargs.MaxIter = 500 + kwargs.Method = 'polar' + kwargs.Verbosity = 0 + kwargs.Normalize = true + kwargs.EigsInit = 3 + kwargs.EigsFrequence = 2 + end + + % constants + EIG_TOLFACTOR = 1/50; + EIG_MAXTOL = 1e-4; + MINKRYLOVDIM = 8; + MAXKRYLOVDIM = 30; + + % initialization + N = width(A); + if isempty(C), C = initializeC(A, circshift(A, 1)); end + if kwargs.Normalize, C(1) = normalize(C(1)); end + A = arrayfun(@(a) MpsTensor(repartition(a, [nspaces(a)-1 1])), A); + AL = A; + + for ctr = 1:kwargs.MaxIter + if ctr > kwargs.EigsInit && mod(ctr, kwargs.EigsFrequence) == 0 + C_ = repartition(C(end), [2 0]); + [C_, ~] = eigsolve(@(x) applyleft(A, conj(AL), x), C_, ... + 1, 'largestabs', ... + 'Tol', min(eta * EIG_TOLFACTOR, EIG_MAXTOL), ... + 'KrylovDim', between(MINKRYLOVDIM, ... + MAXKRYLOVDIM - ctr / 2 + 4, ... + MAXKRYLOVDIM), ... + 'NoBuild', 4, ... + 'Verbosity', kwargs.Verbosity - 1); + [~, C(end)] = leftorth(C_, 1, 2, kwargs.Method); + end + + C_ = C(end); + lambdas = ones(1, N); + for w = 1:N + ww = prev(w, N); + CA = multiplyleft(A(w), C(ww)); + [AL(w), C(w)] = leftorth(CA, kwargs.Method); + lambdas(w) = norm(C(w)); + if kwargs.Normalize, C(w) = C(w) ./ lambdas(w); end + end + eta = norm(C_ - C(end), Inf); + + if eta < kwargs.Tol + if kwargs.Verbosity >= Verbosity.conv + fprintf('Conv %2d:\terror = %0.4e\n', ctr, eta); + end + break; + else + if kwargs.Verbosity >= Verbosity.iter + fprintf('Iter %2d:\terror = %0.4e\n', ctr, eta); + end + end + end + + if kwargs.Verbosity >= Verbosity.warn && eta > kwargs.Tol + fprintf('Not converged %2d:\terror = %0.4e\n', ctr, eta); + end + + if nargout > 2, lambda = prod(lambdas); end + end + + function [R, AR] = rightorth(A, alg) + arguments + A + alg = 'lq' + end + + [R, AR] = rightorth@Tensor(A, 1, 2:nspaces(A), alg); + AR = MpsTensor(repartition(AR, rank(A)), A.alegs); + end + + function [AR, C, lambda, eta] = uniform_rightorth(A, C, kwargs) + arguments + A + C = initializeC(A, circshift(A, 1)) + kwargs.Tol = eps(underlyingType(A))^(3/4) + kwargs.MaxIter = 500 + kwargs.Method = 'polar' + kwargs.Verbosity = 0 + kwargs.Normalize = true + kwargs.EigsInit = 3 + kwargs.EigsFrequence = 2 + end + + % constants + EIG_TOLFACTOR = 1/50; + EIG_MAXTOL = 1e-4; + MINKRYLOVDIM = 8; + MAXKRYLOVDIM = 30; + + % initialization + N = width(A); + if isempty(C), C = initializeC(A, circshift(A, 1)); end + if kwargs.Normalize, C(1) = normalize(C(1)); end + A = arrayfun(@(a) MpsTensor(repartition(a, [1 nspaces(a)-1])), A); + AR = A; + + for ctr = 1:kwargs.MaxIter + if ctr > kwargs.EigsInit && mod(ctr, kwargs.EigsFrequence) == 0 + C_ = repartition(C(end), [nspaces(C(end)) 0]); + [C_, ~] = eigsolve(@(x) applyright(A, conj(AR), x), C_, ... + 1, 'largestabs', ... + 'Tol', min(eta * EIG_TOLFACTOR, EIG_MAXTOL), ... + 'KrylovDim', between(MINKRYLOVDIM, ... + MAXKRYLOVDIM - ctr / 2 + 4, ... + MAXKRYLOVDIM), ... + 'NoBuild', 4, ... + 'Verbosity', kwargs.Verbosity - 1); + [C(end), ~] = rightorth(C_, 1, 2, kwargs.Method); + end + + C_ = C(end); + lambdas = ones(1, N); + for w = N:-1:1 + ww = prev(w, N); + AC = multiplyright(A(w), C(w)); + [C(ww), AR(w)] = rightorth(AC, kwargs.Method); + lambdas(w) = norm(C(ww)); + if kwargs.Normalize, C(ww) = C(ww) ./ lambdas(w); end + end + eta = norm(C_ - C(end), Inf); + + if eta < kwargs.Tol + if kwargs.Verbosity >= Verbosity.conv + fprintf('Conv %2d:\terror = %0.4e\n', ctr, eta); + end + break; + else + if kwargs.Verbosity >= Verbosity.iter + fprintf('Iter %2d:\terror = %0.4e\n', ctr, eta); + end + end + end + + if kwargs.Verbosity >= Verbosity.warn && eta > kwargs.Tol + fprintf('Not converged %2d:\terror = %0.4e\n', ctr, eta); + end + + if nargout > 2, lambda = prod(lambdas); end + end + + function rho = applyleft(T, B, rho) + if nargin == 2 + rho = []; + return + end + assert(isequal(size(T), size(B)), 'mpstensor:dimagree', ... + 'dimensions should agree.'); + if length(T) > 1 + for i = 1:length(T) + rho = applyleft(T(i), B(i), rho); + end + return + end + + if isempty(rho) + switch num2str([T1.plegs T1.alegs T2.alegs]) + case '1 0 0' + indices = {[1 2 -2] [1 2 -1]}; + + case '2 0 0' + indices = {[1 2 3 -2] [1 2 3 -1]}; + case '1 1 0' + indices = {[1 2 -2 -3] [1 2 -1]}; + case '1 0 1' + indices = {[1 2 -2] [1 2 -1 -3]}; + case '1 1 1' + indices = {[1 2 -2 3] [1 2 -1 3]}; + otherwise + error('mps:ArgError', 'leftapply ill-defined.'); + end + rho = contract(T1, indices{1}, T2, indices{2}); + return + end + + switch num2str([nspaces(rho) T.plegs T.alegs B.alegs]) + case '2 1 0 0' + tmp = repartition(rho, [1 1]) * repartition(T, [1 2]); + tmp = tpermute(B, [3 2 1], [1 2]) * repartition(tmp, [2 1]); + rho = repartition(tmp, rank(rho)); +% indices = {[1 2] [2 3 -2] [1 3 -1]}; +% r = rank(rho); +% T = twist(T, [isdual(space(T, [1 2])) false]); + case '3 1 0 0' + tmp = tpermute(rho, [1 3 2], [2 1]) * repartition(T, [1 2]); + tmp = tpermute(B, [3 2 1], [1 2]) * tpermute(tmp, [1 3 4 2], [2 2]); + rho = repartition(tmp, rank(rho)); + otherwise + error('mps:ArgError', 'applyleft ill-defined'); + end +% rho = contract(rho, indices{1}, T, indices{2}, B, indices{3}, ... +% 'Rank', r); + end + + function rho = applyright(T, B, rho) + if nargin == 2 + rho = []; + return + end + assert(isequal(size(T), size(B)), 'mpstensor:dimagree', ... + 'dimensions should agree.'); + if length(T) > 1 + for i = length(T):-1:1 + rho = applyright(T(i), B(i), rho); + end + return + end + if isempty(rho) + switch num2str([T.plegs T.alegs B.alegs]) + case '1 0 0' + rho = contract(T, [-1 2 1], B, [-2 2 1]); +% rhoR=Contract({T1,T2},{[-1,2,1],[-2,2,1]}); + case '2 0 0' + rhoR=Contract({T1,T2},{[-1,2,3,1],[-2,2,3,1]}); + case '1 1 0' + rhoR=Contract({T1,T2},{[-1,2,1,-3],[-2,2,1]}); + case '1 0 1' + rhoR=Contract({T1,T2},{[-1,2,1],[-2,2,1,-3]}); + case '1 1 1' + rhoR=Contract({T1,T2},{[-1,2,1,3],[-2,2,1,3]}); + otherwise + error('mps:ArgError', 'applyright ill-defined.'); + end + return + end + + switch num2str([nspaces(rho) T.plegs T.alegs B.alegs]) + case '2 1 0 0' + tmp = repartition(T, [2 1]) * repartition(rho, [1 1]); + rho = repartition(... + repartition(tmp, [1 2]) * tpermute(B, [3 2 1], [2 1]), rank(rho)); + +% T = twist(T, [false ~isdual(space(T, 2:3))]); +% rho = contract(rho, [1 2], T, [-1 3 1], B, [-2 3 2], ... +% 'Rank', rank(rho)); + otherwise + error('mps:ArgError', 'applyright ill-defined.'); + end + end + + function A = multiplyleft(A, C) + A = MpsTensor(repartition(... + repartition(C, [1 1]) * repartition(A, [1 nspaces(A)-1]), ... + rank(A)), A.alegs); +% if ~isdual(space(C, 2)) +% C = twist(C, 2); +% end +% A = MpsTensor(contract(C, [-1 1], A, [1 -(2:nspaces(A))], 'Rank', rank(A))); + end + + function A = multiplyright(A, C) + if A.alegs == 0 + A = MpsTensor(repartition(... + repartition(A, [nspaces(A)-1 1]) * repartition(C, [1 1]), ... + rank(A)), 0); + return + end + if isdual(space(C, 1)), C = twist(C, 1); end + Alegs = nspaces(A); + if A.alegs == 0 + A = contract(A, [-(1:Alegs-1) 1], C, [1 -Alegs], 'Rank', rank(A)); + else + A = contract(A, [-(1:Alegs-1-A.alegs) 1 -(Alegs-A.legs+1:Alegs)], ... + C, [1 Alegs - A.alegs], 'Rank', rank(A)); + end + A = MpsTensor(A); + end + + function C = initializeC(AL, AR) + for i = length(AL):-1:1 + C(i) = AL.eye(rightvspace(AL(i))', leftvspace(AR(i))); + end + end + + function A = expand(A, addspace) + NOISE_FACTOR = 1e-3; + for i = length(A):-1:1 + spaces = space(A(i)); + spaces(1) = addspace(i); + spaces(nspaces(A(i)) - A(i).alegs) = conj(addspace(next(i, length(A)))); + r = rank(A(i)); + A(i) = embed(A(i), ... + NOISE_FACTOR * ... + normalize(A.randnc(spaces(1:r(1)), spaces(r(1)+1:end)'))); + end + end + end +end + diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m new file mode 100644 index 0000000..15710d7 --- /dev/null +++ b/src/mps/UniformMps.m @@ -0,0 +1,361 @@ +classdef UniformMps + %UNIFORMMPS Implementation of infinite translation invariant MPS + % MPS is stored in center gauge, where + % AL(w) * C(w) = AC(w) = C(w-1) * AR(w) + + properties + AL (1,:) MpsTensor = MpsTensor.empty(1, 0) + AR (1,:) MpsTensor = MpsTensor.empty(1, 0) + C (1,:) Tensor = Tensor.empty(1, 0) + AC (1,:) MpsTensor = MpsTensor.empty(1, 0) + end + + + %% Constructors + methods + function mps = UniformMps(varargin) + + if nargin == 0, return; end % default empty constructor + + if nargin == 1 + if isa(varargin{1}, 'UniformMps') % copy constructor + for i = numel(varargin{1}):-1:1 + mps(i).AL = varargin{1}(i).AL; + mps(i).AR = varargin{1}(i).AR; + mps(i).C = varargin{1}(i).C; + mps(i).AC = varargin{1}(i).AC; + end + mps = reshape(mps, size(mps)); + + elseif isa(varargin{1}, 'Tensor') + % by default we take the input as AR, as canonicalize right + % orthogonalizes before left orthogonalization + for i = height(varargin{1}):-1:1 + mps(i).AR = varargin{1}(i, :); + mps(i).AL = varargin{1}(i, :); + end + mps = canonicalize(mps); + + else + error('Invalid constructor for UniformMps.') + end + + elseif nargin == 4 + mps.AL = varargin{1}; + mps.AR = varargin{2}; + mps.C = varargin{3}; + mps.AC = varargin{4}; + + else + error('Invalid constructor for UniformMps.') + end + end + end + + methods (Static) + function mps = new(fun, pspaces, vspaces) + if isempty(fun), fun = @randnc; end + + if isscalar(pspaces) && ~isscalar(vspaces) + pspaces = repmat(pspaces, size(vspaces)); + elseif isscalar(vspaces) && ~isscalar(pspaces) + vspaces = repmat(vspaces, size(pspaces)); + else + assert(isequal(size(vspaces), size(pspaces)), 'mps:dimagree', ... + 'Invalid sizes of input spaces.'); + end + + for w = length(pspaces):-1:1 + A(w) = Tensor.new(fun, [vspaces(w) pspaces(w)], ... + vspaces(next(w, length(pspaces)))); + end + + mps = UniformMps(A); + end + + function mps = randnc(pspaces, vspaces) + mps = UniformMps.new(@randnc, pspaces, vspaces); + end + end + + + %% Properties + methods + function p = period(mps) + for i = numel(mps):-1:1 + p(i) = length(mps(i).AL); + end + end + + function d = depth(mps) + d = size(mps, 1); + end + + function s = leftvspace(mps, w) + if nargin == 1 || isempty(w), w = 1:period(mps); end + s = arrayfun(@leftvspace, mps.AL(w)); + end + + function s = rightvspace(mps, w) + if nargin == 1 || isempty(w), w = 1:period(mps); end + s = arrayfun(@rightvspace, mps.AL(w)); + end + + function type = underlyingType(mps) + type = underlyingType(mps(1).AR(1)); + end + end + + + %% Methods + methods + function [mps, lambda] = canonicalize(mps, kwargs) + arguments + mps + kwargs.Tol = eps(underlyingType(mps))^(3/4) + kwargs.MaxIter = 1e3 + kwargs.Method {mustBeMember(kwargs.Method, {'polar', 'qr', 'qrpos'})} ... + = 'polar' + kwargs.Verbosity = Verbosity.warn + kwargs.DiagC = false + kwargs.ComputeAC = true + kwargs.Order {mustBeMember(kwargs.Order, {'lr', 'rl'})} = 'lr' + end + + for i = 1:length(mps) + if strcmp(kwargs.Order, 'lr') + [mps(i).AR, ~, ~, eta1] = uniform_rightorth(... + mps(i).AR, [], ... + 'Tol', kwargs.Tol, 'MaxIter', kwargs.MaxIter, ... + 'Method', kwargs.Method, 'Verbosity', kwargs.Verbosity); + [mps(i).AL, mps(i).C, lambda, eta2] = uniform_leftorth(... + mps(i).AR, mps(i).C, ... + 'Tol', kwargs.Tol, 'MaxIter', kwargs.MaxIter, ... + 'Method', kwargs.Method, 'Verbosity', kwargs.Verbosity); + else + [mps(i).AL, ~, ~, eta1] = uniform_leftorth(... + mps(i).AL, [], ... + 'Tol', kwargs.Tol, 'MaxIter', kwargs.MaxIter, ... + 'Method', kwargs.Method, 'Verbosity', kwargs.Verbosity); + [mps(i).AR, mps(i).C, lambda, eta2] = uniform_rightorth(... + mps(i).AL, mps(i).C, ... + 'Tol', kwargs.Tol, 'MaxIter', kwargs.MaxIter, ... + 'Method', kwargs.Method, 'Verbosity', kwargs.Verbosity); + end + end + + if kwargs.DiagC + mps = diagonalizeC(mps); + end + if kwargs.ComputeAC + for i = 1:height(mps) + for w = period(mps(i)):-1:1 + mps(i).AC(w) = multiplyleft(mps(i).AR(w), ... + mps(i).C(prev(w, period(mps(i))))); + end + end + end + end + + function mps = diagonalizeC(mps) + for i = 1:height(mps) + for w = 1:period(mps(i)) + C_iw = mps(i).C(w); + [U, S, V] = tsvd(C_iw, 1, 2); + + if isdual(C_iw.codomain) ~= isdual(S.codomain) + U.domain = conj(U.domain); + S.codomain = conj(S.codomain); + S = twist(S, 1); + end + + if isdual(C_iw.domain) ~= isdual(S.domain) + V.codomain = conj(V.codomain); + S.domain = conj(S.domain); + S = twist(S, 2); + end + + ww = next(w, period(mps(i))); + mps(i).C(w) = S; + + mps(i).AL(ww) = multiplyleft(mps(i).AL(ww), U'); + mps(i).AL(w) = multiplyright(mps(i).AL(w), U); + + mps(i).AR(ww) = multiplyleft(mps(i).AR(ww), V); + mps(i).AR(w) = multiplyright(mps(i).AR(w), V'); + end + end + end + + function [V, D] = transfereigs(mps1, mps2, howmany, which, eigopts, kwargs) + arguments + mps1 + mps2 = mps1 + howmany = min(20, dim(leftvspace(mps1, 1) * leftvspace(mps2, 1)')) + which = 'largestabs' + eigopts.KrylovDim = 100 + eigopts.MaxIter = 1000 + eigopts.ReOrth = 2 + eigopts.Tol = eps(underlyingType(mps1))^(3/4) + kwargs.Verbosity = 0 + kwargs.Type {mustBeMember(kwargs.Type, ... + {'l_LL' 'l_LR' 'l_RL' 'l_RR' 'r_LL' 'r_LR' 'r_RL' 'r_RR'})} = 'l_LL' + kwargs.Charge = [] + end + + % extract tensors + if strcmp(kwargs.Type(3), 'L'), T = mps1.AL; else, T = mps1.AR; end + if strcmp(kwargs.Type(4), 'L'), B = mps2.AL; else, B = mps2.AR; end + + % generate initial guess + if strcmp(kwargs.Type(1), 'l') + vspace1 = leftvspace(mps2, 1); + vspace2 = leftvspace(mps1, 1); + else + vspace1 = rightvspace(mps1, period(mps)); + vspace2 = rightvspace(mps2, period(mps)); + end + if isempty(kwargs.Charge) || isequal(kwargs.Charge, one(kwargs.Charge)) + v0 = Tensor.randnc([vspace1 vspace2'], []); + else + dims = struct('charges', kwargs.Charge, ... + 'degeneracies', ones(size(kwargs.Charge))); + auxspace = vspace1.new(dims, false); + v0 = Tensor.randnc([vspace1 vspace2' auxspace], []); + end + + % find eigenvectors + eigkwargs = [fieldnames(eigopts).'; struct2cell(eigopts).']; + if strcmp(kwargs.Type(1), 'l') + [V, D] = eigsolve(@(x) applyleft(T, conj(B), x), v0, howmany, which, ... + eigkwargs{:}, 'Verbosity', kwargs.Verbosity); + else + [V, D] = eigsolve(@(x) applyright(T, conj(B), x), v0, howmany, which, ... + eigkwargs{:}, 'Verbosity', kwargs.Verbosity); + end + + if nargout < 2, V = D; end + end + + function f = fidelity(mps1, mps2, kwargs) + arguments + mps1 + mps2 + kwargs.KrylovDim = 30 + kwargs.MaxIter = 500 + kwargs.ReOrth = 2 + kwargs.Tol = eps(underlyingType(mps1))^(3/4) + kwargs.Verbosity = 0 + end + + eigkwargs = [fieldnames(kwargs).'; struct2cell(kwargs).']; + f = transfereigs(mps1, mps2, 1, 'largestabs', eigkwargs{:}); + end + + function rho = fixedpoint(mps, type, w) + arguments + mps + type {mustBeMember(type, ... + {'l_LL' 'l_LR' 'l_RL' 'l_RR' 'r_LL' 'r_LR' 'r_RL' 'r_RR'})} + w = strcmp(type(1), 'l') * 1 + strcmp(type(1), 'r') * period(mps) + end + + ww = prev(w, period(mps)); + switch type + case 'l_RR' + rho = mps.C(ww)' * mps.C(ww); + case 'l_RL' + rho = mps.C(ww); + case 'l_LR' + rho = mps.C(ww)'; + case 'l_LL' + rho = mps.C.eye(leftvspace(mps, w), leftvspace(mps, w)); + case 'r_RR' + rho = mps.C.eye(rightvspace(mps, w)', rightvspace(mps, w)'); + case 'r_RL' + rho = mps.C(w)'; + case 'r_LR' + rho = mps.C(w); + case 'r_LL' + rho = mps.C(w) * mps.C(w)'; + end + end + + function mps = desymmetrize(mps) + if numel(mps) > 1 + mps = arrayfun(@desymmetrize, mps); + return + end + mps.AL = desymmetrize(mps.AL); + mps.AR = desymmetrize(mps.AR); + mps.C = desymmetrize(mps.C); + mps.AC = desymmetrize(mps.AC); + end + + % essential + [mps, lambda] = Canonical(mps, options); + [f, rho] = TransferEigs(mps1, mps2, x0, num, charge, choice, options); + + % utility + PlotTransferEigs(mps1, mps2, x0, num, charges); + [schmidt, charges] = SchmidtValues(mps, loc) + PlotEntSpectrum(mps, svalue, opts) + xi = CorrelationLength(mps, charge); + [epsilon, delta, spectrum] = MarekGap(mps, charge, angle, num) + S = EntanglementEntropy(mps, loc); + S = RenyiEntropy(mps,n, loc); + E = ExpectationValue(mps, W, GL, GR) + rho = LeftFixedPoint(mps1, mps2, w, choice) + rho = RightFixedPoint(mps1, mps2, w, choice) + sf=StaticStructureFactor(mps,S,k) + + out = Block(mps, opts) + out = Split(mps, varargin) + [out, lambda] = Truncate(mps, control, opts) + + [f, rho] = Fidelity(mps1, mps2, tol) + + mps = Conj(mps) + + mps = mtimes(mps, lambda) + + mps = ShiftUnitCell(mps,dd,dw) + out = Rotate180(mps) + out = Transpose(mps) + + mps = SendToGpu(mps) + mps = GetFromGpu(mps) + + [mps, xi] = Retract(mps, eta, alpha) + n = Inner(x, eta, xi) + + mps = TensorNone(mps) + + % to be done later + + Add; % is this useful? + MultiplyLeft; % no clue + MultiplyRight; % no clue + + end + + methods (Static) + + [AR, C, lambda, error] = OrthRight(A, C, options) + [AL, C, lambda, error] = OrthLeft (A, C, options) + out = Random(virtualLeg, varargin); + + % conversion from legacy datastructure + mps = FromCell(A); + + end + + + methods (Access = protected) + + % anything? + + end + +end + diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index 6a3f1ad..1f280c7 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -1,10 +1,10 @@ -classdef (InferiorClasses = {?Tensor}) SparseTensor +classdef (InferiorClasses = {?Tensor, ?MpsTensor}) SparseTensor < AbstractTensor % Class for multi-dimensional sparse objects. properties (Access = private) ind = [] sz = [] - var (:, 1) + var (:, 1) Tensor = Tensor.empty(0, 1); end methods @@ -14,21 +14,21 @@ elseif nargin == 1 % cast from existing object source = varargin{1}; - switch class(source) - case 'SparseTensor' - t.ind = source.ind; - t.sz = source.sz; - t.var = source.var; - - case 'Tensor' - t.sz = ones(1, nspaces(source(1))); - t.sz(1:ndims(source)) = size(source); - - t.ind = ind2sub_(t.sz, 1:numel(source)); - t.var = source(:); - - otherwise - error('sparse:ArgError', 'Unknown syntax.'); + + if isa(source, 'SparseTensor') + t.ind = source.ind; + t.sz = source.sz; + t.var = source.var; + + elseif isa(source, 'Tensor') + t.sz = ones(1, nspaces(source(1))); + t.sz(1:ndims(source)) = size(source); + + t.ind = ind2sub_(t.sz, 1:numel(source)); + t.var = source(:); + + else + error('sparse:ArgError', 'Unknown syntax.'); end elseif nargin == 2 % indices and values @@ -43,17 +43,21 @@ elseif nargin == 3 % indices, values and size ind = varargin{1}; - var = reshape(varargin{2}, [], 1); - if isscalar(var), var = repmat(var, size(ind, 1), 1); end - assert(size(ind, 1) == size(var, 1), 'sparse:argerror', ... - 'indices and values must be the same size.'); - sz = reshape(varargin{3}, 1, []); - assert(isempty(ind) || size(ind, 2) == length(sz), 'sparse:argerror', ... - 'number of indices does not match size vector.'); - assert(isempty(ind) || all(max(ind, [], 1) <= sz), 'sparse:argerror', ... - 'indices must not exceed size vector.'); + if ~isempty(ind) && ~isempty(varargin{2}) + var = reshape(varargin{2}, [], 1); + if isscalar(var), var = repmat(var, size(ind, 1), 1); end + assert(size(ind, 1) == size(var, 1), 'sparse:argerror', ... + 'indices and values must be the same size.'); + sz = reshape(varargin{3}, 1, []); + assert(isempty(ind) || size(ind, 2) == length(sz), 'sparse:argerror', ... + 'number of indices does not match size vector.'); + assert(isempty(ind) || all(max(ind, [], 1) <= sz), 'sparse:argerror', ... + 'indices must not exceed size vector.'); + t.var = var; + else + sz = reshape(varargin{3}, 1, []); + end t.ind = ind; - t.var = var; t.sz = sz; else @@ -71,9 +75,9 @@ function t = reshape(t, sz) assert(prod(sz) == prod(t.sz), ... 'sparse:argerror', 'To reshape the number of elements must not change.'); - - t.ind = sub2sub(sz, t.sz, t.ind); - t.sz = sz; + idx = sub2ind_(t.sz, t.ind); + t.ind = ind2sub_(sz, idx); + t.sz = sz; end function B = full(A) @@ -106,10 +110,26 @@ end end + function n = nspaces(A) + if nnz(A) == 0 + n = ndims(A); + else + n = nspaces(A.var(1)); + end + end + function n = ndims(A) n = length(A.sz); end - + + function r = rank(A) + if nnz(A) == 0 + r = [ndims(A) 0]; + else + r = rank(A.var(1)); + end + end + function sz = size(a, i) if nargin == 1 sz = a.sz; @@ -169,6 +189,46 @@ function disp(t) function n = nnz(t) n = length(t.var); end + + function bools = eq(a, b) + arguments + a SparseTensor + b SparseTensor + end + + if isscalar(a) && isscalar(b) + bools = (isempty(a.var) && isempty(b.var)) || ... + (~isempty(a.var) && ~isempty(b.var) && a.var == b.var); + return + end + + if isscalar(a) + if nnz(a) == 0 + bools = true(size(b)); + if nnz(b) ~= 0 + bools(sub2ind_(b.sz, b.ind)) = false; + end + else + bools = false(size(b)); + bools(sub2ind_(b.sz, b.ind)) = a.var == b.var; + end + return + end + + if isscalar(b) + bools = b == a; + return + end + + assert(isequal(size(a), size(b)), 'sparse:dimerror', ... + 'input sizes incompatible'); + bools = true(size(a.inds)); + [inds, ia, ib] = intersect(a.ind, b.ind, 'rows'); + + bools(sub2ind_(a.sz, a.ind)) = false; + bools(sub2ind_(b.sz, b.ind)) = false; + bools(sub2ind_(a.sz, inds)) = a.var(ia) == b.var(ib); + end end %% Linear Algebra @@ -186,17 +246,7 @@ function disp(t) end function a = minus(a, b) - assert(isequal(size(a), size(b)), ... - 'sparse:dimerror', 'input dimensions incompatible.'); - if isempty(b.ind), return; end - if isempty(a.ind), a = -b; return; end - - [lia, locb] = ismember(b.ind, a.ind, 'rows'); - a.var(locb(lia)) = a.var(locb(lia)) - b.var(lia); - if ~all(lia) - a.var = [a.var; -b.var(~lia)]; - a.ind = [a.ind; b.ind(~lia, :)]; - end + a = a + (-b); end function c = mtimes(a, b) @@ -259,8 +309,23 @@ function disp(t) end function a = plus(a, b) - assert(isequal(size(a), size(b)), ... + n = max(ndims(a), ndims(b)); + assert(isequal(size(a, 1:n), size(b, 1:n)), ... 'sparse:dimerror', 'input dimensions incompatible.'); + + if ~issparse(a) + if nnz(b) > 0 + idx = sub2ind_(b.sz, b.ind); + a(idx) = a(idx) + b.var; + end + return + end + + if ~issparse(b) + a = b + a; + return + end + if isempty(b.ind), return; end if isempty(a.ind), a = b; return; end @@ -308,44 +373,57 @@ function disp(t) szC = [szA(uncA) szB(uncB)]; end - Cvar = A.var.empty(0, 1); - Cind = double.empty(0, length(uncA) + length(uncB)); + A = reshape(permute(A, [uncA dimA]), [prod(szA(uncA)), prod(szA(dimA))]); + B = reshape(permute(B, [dimB uncB]), [prod(szB(dimB)), prod(szB(uncB))]); - ks = unique([A.ind(:, dimA); B.ind(:, dimB)], 'rows'); - - for k = ks.' - rowlinds = all(A.ind(:, dimA) == k.', 2); - if ~any(rowlinds), continue; end - - collinds = all(B.ind(:, dimB) == k.', 2); - if ~any(collinds), continue; end - - rowinds = find(rowlinds); - colinds = find(collinds); - - for i = rowinds.' - av = A.var(i); - ai = A.ind(i, uncA); - for j = colinds.' - bv = B.var(j); - bj = B.ind(j, uncB); - - mask = all([ai bj] == Cind, 2); - if any(mask) - Cvar(mask) = Cvar(mask) + ... - tensorprod(av, bv, dimA, dimB, ... - 'NumDimensionsA', options.NumDimensionsA); - else - Cvar(end+1) = tensorprod(av, bv, dimA, dimB, ... - 'NumDimensionsA', options.NumDimensionsA); - Cind = [Cind; ai bj]; + if isempty(uncA) && isempty(uncB) + C = 0; + if nnz(A) > 0 && nnz(B) > 0 + for i = 1:size(A, 1) + for j = 1:size(B, 2) + for k = 1:size(A, 2) + Aind = all(A.ind == [i k], 2); + if ~any(Aind), continue; end + Bind = all(B.ind == [k j], 2); + if ~any(Bind), continue; end + + C = C + ... + tensorprod(A.var(Aind), B.var(Bind), dimA, dimB, ... + 'NumDimensionsA', options.NumDimensionsA); + end + end + end + end + else + Cvar = A.var.empty(0, 1); + Cind = double.empty(0, length(uncA) + length(uncB)); + + if nnz(A) > 0 && nnz(B) > 0 + for i = 1:size(A, 1) + for j = 1:size(B, 2) + for k = 1:size(A, 2) + Aind = all(A.ind == [i k], 2); + if ~any(Aind), continue; end + Bind = all(B.ind == [k j], 2); + if ~any(Bind), continue; end + if ~isempty(Cind) && all(Cind(end,:) == [i j], 2) + Cvar(end) = Cvar(end) + ... + tensorprod(A.var(Aind), B.var(Bind), dimA, dimB, ... + 'NumDimensionsA', options.NumDimensionsA); + else + Cvar(end+1) = ... + tensorprod(A.var(Aind), B.var(Bind), dimA, dimB, ... + 'NumDimensionsA', options.NumDimensionsA); + Cind = [Cind; [i j]]; + end + end end end end + + C = reshape(SparseTensor(Cind, Cvar, [size(A,1) size(B,2)]), szC); + if size(Cind, 1) == prod(szC), C = full(C); end end - - C = SparseTensor(Cind, Cvar, szC); - if size(Cind, 1) == prod(szC), C = full(C); end end function t = times(t1, t2) @@ -408,6 +486,12 @@ function disp(t) t = permute(t, p); end + function t = twist(t, i) + if nnz(t) > 0 + t.var = twist(t.var, i); + end + end + function a = uminus(a) if ~isempty(a.var), a.var = -a.var; end end @@ -434,7 +518,8 @@ function disp(t) n = size(s(1).subs, 2); if n == 1 % linear indexing - [s(1).subs{1:size(t.sz, 2)}] = ind2sub_(t.sz, s(1).subs{1}); + I = ind2sub_(t.sz, s(1).subs{1}); + s(1).subs = arrayfun(@(x) I(:,x), 1:width(I), 'UniformOutput',false); else assert(n == size(t.sz, 2), 'sparse:index', ... 'number of indexing indices must match tensor size.'); @@ -459,8 +544,8 @@ function disp(t) f = and(f, P(B + 1)); [~, ~, temp] = unique([A(:); t.ind(f, i)], 'stable'); t.ind(f, i) = temp(nA+1:end); - newsz(i) = nA; end + newsz(i) = nA; end t.sz = newsz; if ~isempty(t.ind) @@ -475,6 +560,11 @@ function disp(t) function t = subsasgn(t, s, v) assert(strcmp(s(1).type, '()'), 'sparse:index', 'only () indexing allowed'); + + if length(s(1).subs) == 1 + I = ind2sub_(t.sz, s(1).subs{1}); + s(1).subs = arrayfun(@(x) I(:,x), 1:width(I), 'UniformOutput',false); + end assert(length(s(1).subs) == size(t.sz, 2), 'sparse:index', ... 'number of indexing indices must match tensor size.'); @@ -488,18 +578,70 @@ function disp(t) subsize(i) = length(s(1).subs{i}); end if isscalar(v), v = repmat(v, subsize); end - subs = combvec(s(1).subs{:}); - for i = 1:size(subs, 1) - idx = find(all(t.ind == subs(i, :), 2)); - if isempty(idx) - t.ind = [t.ind; subs]; - t.var = [t.var; v(i)]; - else - t.var(idx) = v(i); + subs = combvec(s(1).subs{:}).'; + + if isempty(t.ind) + t.ind = subs; + t.var = v(:); + else + for i = 1:size(subs, 1) + idx = find(all(t.ind == subs(i, :), 2)); + if isempty(idx) + t.ind = [t.ind; subs]; + t.var = [t.var; full(v(i))]; + else + t.var(idx) = v(i); + end end end t.sz = max(t.sz, cellfun(@max, s(1).subs)); end + + function [I, J, V] = find(t, k, which) + arguments + t + k = [] + which = 'first' + end + + if isempty(t.ind) + I = []; + J = []; + V = []; + return + end + + [inds, p] = sortrows(t.ind, width(t.ind):-1:1); + + if ~isempty(k) + if strcmp(which, 'first') + inds = inds(1:k, :); + p = p(1:k); + else + inds = inds(end:-1:end-k+1, :); + p = p(end:-1:end-k+1); + end + end + + if nargout < 2 + I = sub2ind_(t.sz, inds); + return + end + + subs = sub2sub([t.sz(1) prod(t.sz(2:end))], t.sz, t.ind); + I = subs(:,1); + J = subs(:,2); + + if nargout > 2 + V = t.var(p); + end + end + + function t = sortinds(t) + if isempty(t), return; end + [t.ind, p] = sortrows(t.ind, width(t.ind):-1:1); + t.var = t.var(p); + end end end diff --git a/src/sparse/sub2sub.m b/src/sparse/sub2sub.m index 29c7338..c5ed718 100644 --- a/src/sparse/sub2sub.m +++ b/src/sparse/sub2sub.m @@ -7,8 +7,8 @@ return; end sub2 = zeros(size(sub1, 1), numel(sz2)); % preallocate new subs -nto1 = sum(cumsum(flip(sz1-1), 2) == 0, 2); % extract number of trailing ones in both sizes -nto2 = sum(cumsum(flip(sz2-1), 2) == 0, 2); +nto1 = sum(cumsum(flip(sz1-1), 2) == 0, 2), length(sz1); % extract number of trailing ones in both sizes +nto2 = sum(cumsum(flip(sz2-1), 2) == 0, 2), length(sz2); pos1_prev = 0; pos2_prev = 0; flag = true; while flag diff --git a/src/tensors/AbstractTensor.m b/src/tensors/AbstractTensor.m new file mode 100644 index 0000000..9ffd288 --- /dev/null +++ b/src/tensors/AbstractTensor.m @@ -0,0 +1,362 @@ +classdef AbstractTensor + + + methods + function varargout = linsolve(A, b, x0, M1, M2, options) + % Find a solution for a linear system `A(x) = b` or `A * x = b`. + % + % Arguments + % --------- + % A : operator + % either a function handle implementing or an object that supports + % right multiplication. + % + % b : :class:`Tensor` + % right-hand side of the equation, interpreted as vector. + % + % x0 : :class:`Tensor` + % optional initial guess for the solution. + % + % M1, M2 : operator + % preconditioner M = M1 or M = M1 * M2 to effectively solve the system A * + % inv(M) * y = b with y = M * x. + % M is either a function handle implementing or an object that supports + % left division. + % + % Keyword Arguments + % ----------------- + % Tol : numeric + % specifies the tolerance of the method, by default this is the square root of + % eps. + % + % Algorithm : char + % specifies the algorithm used. Can be either one of the following: + % + % - 'bicgstab' + % - 'bicgstabl' + % - 'gmres' + % - 'pcg' + % + % MaxIter : int + % Maximum number of iterations. + % + % Restart : int + % For 'gmres', amount of iterations after which to restart. + % + % Verbosity : int + % Level of output information, by default nothing is printed if `flag` is + % returned, otherwise only warnings are given. + % + % - 0 : no information + % - 1 : information at failure + % - 2 : information at convergence + % + % Returns + % ------- + % x : :class:`Tensor` + % solution vector. + % + % flag : int + % a convergence flag: + % + % - 0 : linsolve converged to the desired tolerance. + % - 1 : linsolve reached the maximum iterations without convergence. + % - 2 : linsolve preconditioner was ill-conditioned. + % - 3 : linsolve stagnated. + % - 4 : one of the scalar quantities calculated became too large or too small. + % + % relres : numeric + % relative residual, norm(b - A * x) / norm(b). + % + % iter : int + % iteration number at which x was computed. + % + % resvec : numeric + % vector of estimated residual norms at each part of the iteration. + + arguments + A + b + x0 = [] + M1 = [] + M2 = [] + + options.Tol = eps(underlyingType(b)) ^ (3/4) + options.Algorithm {mustBeMember(options.Algorithm, ... + {'pcg', 'gmres', 'bicgstab', 'bicgstabl'})} = 'gmres' + options.MaxIter = 400 + options.Restart = 30 + options.Verbosity = 0 + end + + if issparse(b), b = full(b); end + + % Convert input objects to vectors + b_vec = vectorize(b); + b_sz = size(b_vec); + + if ~isempty(x0) && ~issparse(x0) + x0_vec = vectorize(x0); + else + x0_vec = zeros(b_sz); + end + + % Convert input operators to handle vectors + if isa(A, 'function_handle') + A_fun = @(x) vectorize(A(devectorize(x, b))); + else + A_fun = @(x) vectorize(A * devectorize(x, b)); + end + + if isempty(M1) + M1_fun = []; + elseif isa(M1, 'function_handle') + M1_fun = @(x) vectorize(M1(devectorize(x, b))); + else + M1_fun = @(x) vectorize(M1 \ devectorize(x, b)); + end + + if isempty(M2) + M2_fun = []; + elseif isa(M2, 'function_handle') + M2_fun = @(x) vectorize(M2(devectorize(x, b))); + else + M2_fun = @(x) vectorize(M2 \ devectorize(x, b)); + end + + % Sanity check on parameters + options.Restart = min(options.Restart, b_sz(1)); + if options.Tol < eps(underlyingType(b))^0.9 + warning('Requested tolerance might be too strict.'); + end + + % Apply MATLAB implementation + switch options.Algorithm + case 'bicgstab' + [varargout{1:nargout}] = bicgstab(A_fun, b_vec, ... + options.Tol, options.MaxIter, M1_fun, M2_fun, x0_vec); + case 'bicgstabl' + [varargout{1:nargout}] = bicgstabl(A_fun, b_vec, ... + options.Tol, options.MaxIter, M1_fun, M2_fun, x0_vec); + case 'gmres' + options.MaxIter = min(b_sz(1), options.MaxIter); + [varargout{1:nargout}] = gmres(A_fun, b_vec, ... + options.Restart, options.Tol, options.MaxIter, ... + M1_fun, M2_fun, x0_vec); + case 'pcg' + [varargout{1:nargout}] = pcg(A_fun, b_vec, ... + options.Tol, options.MaxIter, M1_fun, M2_fun, x0_vec); + end + + + % Convert output + varargout{1} = devectorize(varargout{1}, b); + end + + function varargout = eigsolve(A, x0, howmany, sigma, options) + % Find a few eigenvalues and eigenvectors of an operator. + % + % Usage + % ----- + % :code:`[V, D, flag] = eigsolve(A, x0, howmany, sigma, kwargs)` + % :code:`D = eigsolve(A, x0, ...)` + % + % Arguments + % --------- + % A : :class:`Tensor` or function_handle + % A square tensormap interpreted as matrix. + % A function handle which implements one of the following, depending on sigma: + % + % - A \ x, if `sigma` is 0 or 'smallestabs' + % - (A - sigma * I) \ x, if sigma is a nonzero scalar + % - A * x, for all other cases + % + % x0 : :class:`Tensor` + % initial guess for the eigenvector. If A is a :class:`Tensor`, this defaults + % to a random complex :class:`Tensor`, for function handles this is a required + % argument. + % + % howmany : int + % amount of eigenvalues and eigenvectors that should be computed. By default + % this is 1, and this should not be larger than the total dimension of A. + % + % sigma : 'char' or numeric + % selector for the eigenvalues, should be either one of the following: + % + % - 'largestabs', 'largestreal', 'largestimag' : default, eigenvalues of + % largest magnitude, real part or imaginary part. + % - 'smallestabs', 'smallestreal', 'smallestimag' : eigenvalues of smallest + % magnitude, real part or imaginary part. + % - numeric : eigenvalues closest to sigma. + % + % Keyword Arguments + % ----------------- + % Tol : numeric + % tolerance of the algorithm. + % + % Algorithm : char + % choice of algorithm. Currently only 'eigs' is available, which leverages the + % default Matlab eigs. + % + % MaxIter : int + % maximum number of iterations, 100 by default. + % + % KrylovDim : int + % number of vectors kept in the Krylov subspace. + % + % IsSymmetric : logical + % flag to speed up the algorithm if the operator is symmetric, false by + % default. + % + % Verbosity : int + % Level of output information, by default nothing is printed if `flag` is + % returned, otherwise only warnings are given. + % + % - 0 : no information + % - 1 : information at failure + % - 2 : information at convergence + % - 3 : information at every iteration + % + % Returns + % ------- + % V : (1, howmany) :class:`Tensor` + % vector of eigenvectors. + % + % D : numeric + % vector of eigenvalues if only a single output argument is asked, diagonal + % matrix of eigenvalues otherwise. + % + % flag : int + % if flag = 0 then all eigenvalues are converged, otherwise not. + + arguments + A + x0 = A.randnc(A.domain, []) + howmany = 1 + sigma = 'largestabs' + + options.Tol = eps(underlyingType(x0))^(3/4) + options.Algorithm = 'eigs' + options.MaxIter = 100 + options.KrylovDim = 20 + options.IsSymmetric logical = false + options.DeflateDim + options.ReOrth = 2 + options.NoBuild + options.Verbosity = 0 + end + + assert(isnumeric(sigma) || ismember(sigma, {'largestabs', 'smallestabs', ... + 'largestreal', 'smallestreal', 'bothendsreal', ... + 'largestimag', 'smallestimag', 'bothendsimag'}), ... + 'tensors:ArgumentError', 'Invalid choice of eigenvalue selector.'); + nargoutchk(0, 3); + + x0_vec = vectorize(x0); + sz = size(x0_vec); + + if isa(A, 'function_handle') + A_fun = @(x) vectorize(A(devectorize(x, x0))); + else + A_fun = @(x) vectorize(A * devectorize(x, x0)); + end + + options.KrylovDim = min(sz(1), options.KrylovDim); + + [V, D, flag] = eigs(A_fun, sz(1), howmany, sigma, ... + 'Tolerance', options.Tol, 'MaxIterations', options.MaxIter, ... + 'SubspaceDimension', options.KrylovDim, 'IsFunctionSymmetric', ... + options.IsSymmetric, 'StartVector', x0_vec, ... + 'Display', options.Verbosity == 3); + + if nargout <= 1 + varargout = {D}; + elseif nargout == 2 + for i = howmany:-1:1 + varargout{1}(:, i) = devectorize(V(:, i), x0); + end + varargout{2} = D; + else + varargout{2} = D; + for i = howmany:-1:1 + varargout{1}(:, i) = devectorize(V(:, i), x0); + end + varargout{3} = flag; + end + + if flag && options.Verbosity > 0 + warning('eigsolve did not converge.'); + end + + if options.Verbosity > 1 + if flag + fprintf('eigsolve converged.\n'); + end + end + end + + function C = contract(tensors, indices, kwargs) + arguments (Repeating) + tensors + indices (1, :) {mustBeInteger} + end + + arguments + kwargs.Conj (1, :) logical = false(size(tensors)) + kwargs.Rank = [] + kwargs.Debug = false + end + + assert(length(kwargs.Conj) == length(tensors)); + celldisp(indices); + for i = 1:length(tensors) + if length(indices{i}) > 1 + assert(length(unique(indices{i})) == length(indices{i}), ... + 'Tensors:TBA', 'Traces not implemented.'); + end + end + + debug = kwargs.Debug; + + % Special case for single input tensor + if nargin == 2 + [~, order] = sort(indices{1}, 'descend'); + C = tensors{1}; + if kwargs.Conj + C = tpermute(C', order(length(order):-1:1), kwargs.Rank); + else + C = tpermute(C, order, kwargs.Rank); + end + return + end + + % Generate trees + contractindices = cellfun(@(x) x(x > 0), indices, 'UniformOutput', false); + partialtrees = num2cell(1:length(tensors)); + tree = generatetree(partialtrees, contractindices); + + % contract all subtrees + [A, ia, ca] = contracttree(tensors, indices, kwargs.Conj, tree{1}, debug); + [B, ib, cb] = contracttree(tensors, indices, kwargs.Conj, tree{2}, debug); + + % contract last pair + [dimA, dimB] = contractinds(ia, ib); + + if debug, contractcheck(A, ia, ca, B, ib, cb); end + + C = tensorprod(A, B, dimA, dimB, ca, cb, 'NumDimensionsA', length(ia)); + ia(dimA) = []; ib(dimB) = []; + ic = [ia ib]; + + % permute last tensor + if ~isempty(ic) && length(ic) > 1 + [~, order] = sort(ic, 'descend'); + if isempty(kwargs.Rank) + kwargs.Rank = [length(order) 0]; + end + C = tpermute(C, order, kwargs.Rank); + end + end + end +end + diff --git a/src/tensors/FusionTree.m b/src/tensors/FusionTree.m index bb8bfda..4fe9c0f 100644 --- a/src/tensors/FusionTree.m +++ b/src/tensors/FusionTree.m @@ -806,7 +806,7 @@ if isempty(f), c = []; return; end - if istwistless(braidingstyle(f)) + if istwistless(braidingstyle(f)) || isempty(i) || ~any(i) c = speye(length(f)); return end @@ -920,6 +920,20 @@ end end end + + function bool = issorted(f) + if ~isempty(f.vertices) && hasmultiplicity(fusionstyle(f)) + [~, p] = sort(f); + bool = isequal(p, (1:length(f)).'); + else + bool = issorted(f.coupled); + if ~bool, return; end + cols = [treeindsequence(f.rank(1)) + 1 ... % center charge + (1:treeindsequence(f.rank(2))) + treeindsequence(f.rank(1)) + 1 ... % fuse charges + fliplr(1:treeindsequence(f.rank(1)))]; + bool = issortedrows(f.charges(:, cols)); + end + end end %% Setters and Getters diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 9c38224..26883e9 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -1,4 +1,4 @@ -classdef Tensor +classdef Tensor < AbstractTensor %TENSOR Summary of this class goes here % Detailed explanation goes here @@ -164,7 +164,11 @@ if iscell(data) t.var = fill_tensor_data(t.var, data); else - t.var = fill_tensor_fun(t.var, data); + [tmp, trees] = tensorblocks(t); + for i = 1:length(tmp) + tmp{i} = data(size(tmp{i}), trees(i)); + end + t.var = fill_tensor_data(t.var, tmp); end end @@ -405,6 +409,12 @@ end end + methods (Hidden) + function tdst = zerosLike(t, varargin) + tdst = repmat(0 * t, varargin{:}); + end + end + %% Structure methods @@ -462,6 +472,9 @@ style = fusionstyle(t.codomain, t.domain); end + function t = full(t) + end + % function t = horzcat(varargin) % sp = space(varargin{1}, [1, 3:nspaces(varargin{1})]); % for i = 2:length(varargin) @@ -551,8 +564,8 @@ 'tensors:SizeError', 'Incompatible sizes for vectorized function.'); % make everything a vector - A = arrayfun(@repartition, A); - B = arrayfun(@repartition, B); +% A = arrayfun(@repartition, A); +% B = arrayfun(@repartition, B); d = norm(A - B); end @@ -581,68 +594,7 @@ end end - function C = contract(tensors, indices, kwargs) - arguments (Repeating) - tensors - indices (1, :) {mustBeInteger} - end - - arguments - kwargs.Conj (1, :) logical = false(size(tensors)) - kwargs.Rank = [] - kwargs.Debug = false - end - - assert(length(kwargs.Conj) == length(tensors)); - - for i = 1:length(tensors) - if length(indices{i}) > 1 - assert(length(unique(indices{i})) == length(indices{i}), ... - 'Tensors:TBA', 'Traces not implemented.'); - end - end - - debug = kwargs.Debug; - - % Special case for single input tensor - if nargin == 2 - [~, order] = sort(indices{1}, 'descend'); - C = tensors{1}; - if kwargs.Conj - C = tpermute(C', order(length(order):-1:1), kwargs.Rank); - else - C = tpermute(C, order, kwargs.Rank); - end - return - end - - % Generate trees - contractindices = cellfun(@(x) x(x > 0), indices, 'UniformOutput', false); - partialtrees = num2cell(1:length(tensors)); - tree = generatetree(partialtrees, contractindices); - - % contract all subtrees - [A, ia, ca] = contracttree(tensors, indices, kwargs.Conj, tree{1}, debug); - [B, ib, cb] = contracttree(tensors, indices, kwargs.Conj, tree{2}, debug); - - % contract last pair - [dimA, dimB] = contractinds(ia, ib); - - if debug, contractcheck(A, ia, ca, B, ib, cb); end - - C = tensorprod(A, B, dimA, dimB, ca, cb, 'NumDimensionsA', length(ia)); - ia(dimA) = []; ib(dimB) = []; - ic = [ia ib]; - - % permute last tensor - if ~isempty(ic) && length(ic) > 1 - [~, order] = sort(ic, 'descend'); - if isempty(kwargs.Rank) - kwargs.Rank = [length(order) 0]; - end - C = tpermute(C, order, kwargs.Rank); - end - end + function t = ctranspose(t) % Compute the adjoint of a tensor. This is defined as swapping the codomain and @@ -667,6 +619,8 @@ [t(i).codomain, t(i).domain] = swapvars(t(i).codomain, t(i).domain); t(i).var = t(i).var'; end + + t = permute(t, ndims(t):-1:1); end function d = dot(t1, t2) @@ -824,7 +778,7 @@ if isnumeric(A) || isnumeric(B) C = A .* B; return - end + end assert(isequal(A.domain, B.codomain), 'tensors:SpaceMismatch', ... 'Multiplied spaces incompatible.'); @@ -961,7 +915,7 @@ if isempty(cache), cache = LRU; end if Options.CacheEnabled() - key = GetMD5({GetMD5_helper(t.codomain), GetMD5_helper(t.domain), p, r}, ... + key = GetMD5({t.codomain, t.domain, p, r}, ... 'Array', 'hex'); med = get(cache, key); if isempty(med) @@ -1132,8 +1086,8 @@ if isempty(cache), cache = LRU; end if Options.CacheEnabled() - key = GetMD5({GetMD5_helper(A.codomain), GetMD5_helper(A.domain), ... - GetMD5_helper(B.codomain), GetMD5_helper(B.domain), ... + key = GetMD5({A.codomain, A.domain, ... + B.codomain, B.domain, ... dimA, dimB, ca, cb}, 'Array', 'hex'); med = get(cache, key); if isempty(med) @@ -1153,8 +1107,11 @@ med.varB = B_.var; med.mapB = permute(fusiontrees(B), iB, rB); - assert(isequal(A_.domain, B_.codomain), 'tensors:SpaceMismatch', ... - 'Contracted spaces incompatible.'); + if ~isequal(A_.domain, B_.codomain) + error('tensors:SpaceMismatch', ... + 'Contracted spaces incompatible.\n%s\n%s', ... + string(A_.domain), string(B_.codomain)); + end if ~isempty(A_.codomain) || ~isempty(B_.domain) med.C = Tensor.zeros(A_.codomain, B_.domain); else @@ -1169,7 +1126,7 @@ if isempty(C) Ablocks = matrixblocks(varA); Bblocks = matrixblocks(varB); - C = horzcat(Ablocks{:}) * vertcat(Bblocks{:}); + C = sum(cellfun(@mtimes, Ablocks, Bblocks), 'all'); % horzcat(Ablocks{:}) * vertcat(Bblocks{:}); else C.var = mul(C.var, varA, varB); end @@ -1318,7 +1275,7 @@ if isempty(cache), cache = LRU; end if Options.CacheEnabled() - key = GetMD5({GetMD5_helper(t.codomain), GetMD5_helper(t.domain), i, inv}, ... + key = GetMD5({t.codomain, t.domain, i, inv}, ... 'Array', 'hex'); med = get(cache, key); if isempty(med) @@ -1562,7 +1519,8 @@ V = t.domain.new(dims, false); if strcmp(alg, 'polar') - assert(isequal(V, prod(t.codomain))); + assert(isequal(V, prod(t.codomain)), ... + 'linalg:polar', 'polar decomposition should lead to square R.'); W = t.codomain; elseif length(p1) == 1 && V == t.codomain W = t.codomain; @@ -1785,11 +1743,18 @@ S = Tensor.zeros(W1, W2); V = Tensor.eye(W2, t.domain); else + mask = dims.degeneracies ~= 0; + dims.charges = dims.charges(mask); + dims.degeneracies = dims.degeneracies(mask); W = t.domain.new(dims, false); U = Tensor.eye(t.codomain, W); S = Tensor.zeros(W, W); V = Tensor.eye(W, t.domain); + + Us = Us(mask); + Vs = Vs(mask); + Ss = Ss(mask); end U.var = fill_matrix_data(U.var, Us, dims.charges); @@ -2005,6 +1970,12 @@ tol.AbsTol = 0 end + if numel(t) > 1 + bool = arrayfun(@(x) isisometry(x, side, ... + 'RelTol', tol.RelTol, 'AbsTol', tol.AbsTol), t); + return + end + mblocks = matrixblocks(t); for i = 1:length(mblocks) if ~isisometry(mblocks{i}, side, 'RelTol', tol.RelTol, 'AbsTol', tol.AbsTol) @@ -2093,294 +2064,6 @@ %% Solvers methods - function varargout = linsolve(A, b, x0, M1, M2, options) - % Find a solution for a linear system `A(x) = b` or `A * x = b`. - % - % Arguments - % --------- - % A : operator - % either a function handle implementing or an object that supports - % right multiplication. - % - % b : :class:`Tensor` - % right-hand side of the equation, interpreted as vector. - % - % x0 : :class:`Tensor` - % optional initial guess for the solution. - % - % M1, M2 : operator - % preconditioner M = M1 or M = M1 * M2 to effectively solve the system A * - % inv(M) * y = b with y = M * x. - % M is either a function handle implementing or an object that supports - % left division. - % - % Keyword Arguments - % ----------------- - % Tol : numeric - % specifies the tolerance of the method, by default this is the square root of - % eps. - % - % Algorithm : char - % specifies the algorithm used. Can be either one of the following: - % - % - 'bicgstab' - % - 'bicgstabl' - % - 'gmres' - % - 'pcg' - % - % MaxIter : int - % Maximum number of iterations. - % - % Restart : int - % For 'gmres', amount of iterations after which to restart. - % - % Verbosity : int - % Level of output information, by default nothing is printed if `flag` is - % returned, otherwise only warnings are given. - % - % - 0 : no information - % - 1 : information at failure - % - 2 : information at convergence - % - % Returns - % ------- - % x : :class:`Tensor` - % solution vector. - % - % flag : int - % a convergence flag: - % - % - 0 : linsolve converged to the desired tolerance. - % - 1 : linsolve reached the maximum iterations without convergence. - % - 2 : linsolve preconditioner was ill-conditioned. - % - 3 : linsolve stagnated. - % - 4 : one of the scalar quantities calculated became too large or too small. - % - % relres : numeric - % relative residual, norm(b - A * x) / norm(b). - % - % iter : int - % iteration number at which x was computed. - % - % resvec : numeric - % vector of estimated residual norms at each part of the iteration. - - arguments - A - b - x0 = [] - M1 = [] - M2 = [] - - options.Tol = eps(underlyingType(b)) ^ (3/4) - options.Algorithm {mustBeMember(options.Algorithm, ... - {'pcg', 'gmres', 'bicgstab', 'bicgstabl'})} = 'gmres' - options.MaxIter = 400 - options.Restart = 30 - options.Verbosity = 0 - end - - % Convert input objects to vectors - b_vec = vectorize(b); - b_sz = size(b_vec); - - if ~isempty(x0) && ~iszero(x0) - x0_vec = vectorize(x0); - else - x0_vec = zeros(b_sz); - end - - % Convert input operators to handle vectors - if isa(A, 'function_handle') - A_fun = @(x) vectorize(A(devectorize(x, b))); - else - A_fun = @(x) vectorize(A * devectorize(x, b)); - end - - if isempty(M1) - M1_fun = []; - elseif isa(M1, 'function_handle') - M1_fun = @(x) vectorize(M1(devectorize(x, b))); - else - M1_fun = @(x) vectorize(M1 \ devectorize(x, b)); - end - - if isempty(M2) - M2_fun = []; - elseif isa(M2, 'function_handle') - M2_fun = @(x) vectorize(M2(devectorize(x, b))); - else - M2_fun = @(x) vectorize(M2 \ devectorize(x, b)); - end - - % Sanity check on parameters - options.Restart = min(options.Restart, b_sz(1)); - if options.Tol < eps(underlyingType(b))^0.9 - warning('Requested tolerance might be too strict.'); - end - - % Apply MATLAB implementation - switch options.Algorithm - case 'bicgstab' - [varargout{1:nargout}] = bicgstab(A_fun, b_vec, ... - options.Tol, options.MaxIter, M1_fun, M2_fun, x0_vec); - case 'bicgstabl' - [varargout{1:nargout}] = bicgstabl(A_fun, b_vec, ... - options.Tol, options.MaxIter, M1_fun, M2_fun, x0_vec); - case 'gmres' - options.MaxIter = min(b_sz(1), options.MaxIter); - [varargout{1:nargout}] = gmres(A_fun, b_vec, ... - options.Restart, options.Tol, options.MaxIter, ... - M1_fun, M2_fun, x0_vec); - case 'pcg' - [varargout{1:nargout}] = pcg(A_fun, b_vec, ... - options.Tol, options.MaxIter, M1_fun, M2_fun, x0_vec); - end - - - % Convert output - varargout{1} = devectorize(varargout{1}, b); - end - - function varargout = eigsolve(A, x0, howmany, sigma, options) - % Find a few eigenvalues and eigenvectors of an operator. - % - % Usage - % ----- - % :code:`[V, D, flag] = eigsolve(A, x0, howmany, sigma, kwargs)` - % :code:`D = eigsolve(A, x0, ...)` - % - % Arguments - % --------- - % A : :class:`Tensor` or function_handle - % A square tensormap interpreted as matrix. - % A function handle which implements one of the following, depending on sigma: - % - % - A \ x, if `sigma` is 0 or 'smallestabs' - % - (A - sigma * I) \ x, if sigma is a nonzero scalar - % - A * x, for all other cases - % - % x0 : :class:`Tensor` - % initial guess for the eigenvector. If A is a :class:`Tensor`, this defaults - % to a random complex :class:`Tensor`, for function handles this is a required - % argument. - % - % howmany : int - % amount of eigenvalues and eigenvectors that should be computed. By default - % this is 1, and this should not be larger than the total dimension of A. - % - % sigma : 'char' or numeric - % selector for the eigenvalues, should be either one of the following: - % - % - 'largestabs', 'largestreal', 'largestimag' : default, eigenvalues of - % largest magnitude, real part or imaginary part. - % - 'smallestabs', 'smallestreal', 'smallestimag' : eigenvalues of smallest - % magnitude, real part or imaginary part. - % - numeric : eigenvalues closest to sigma. - % - % Keyword Arguments - % ----------------- - % Tol : numeric - % tolerance of the algorithm. - % - % Algorithm : char - % choice of algorithm. Currently only 'eigs' is available, which leverages the - % default Matlab eigs. - % - % MaxIter : int - % maximum number of iterations, 100 by default. - % - % KrylovDim : int - % number of vectors kept in the Krylov subspace. - % - % IsSymmetric : logical - % flag to speed up the algorithm if the operator is symmetric, false by - % default. - % - % Verbosity : int - % Level of output information, by default nothing is printed if `flag` is - % returned, otherwise only warnings are given. - % - % - 0 : no information - % - 1 : information at failure - % - 2 : information at convergence - % - 3 : information at every iteration - % - % Returns - % ------- - % V : (1, howmany) :class:`Tensor` - % vector of eigenvectors. - % - % D : numeric - % vector of eigenvalues if only a single output argument is asked, diagonal - % matrix of eigenvalues otherwise. - % - % flag : int - % if flag = 0 then all eigenvalues are converged, otherwise not. - - arguments - A - x0 = A.randnc(A.domain, []) - howmany = 1 - sigma = 'largestabs' - - options.Tol = 1e-12 - options.Algorithm = 'eigs' - options.MaxIter = 100 - options.KrylovDim = 20 - options.IsSymmetric logical = false - options.Verbosity = 0 - end - - assert(isnumeric(sigma) || ismember(sigma, {'largestabs', 'smallestabs', ... - 'largestreal', 'smallestreal', 'bothendsreal', ... - 'largestimag', 'smallestimag', 'bothendsimag'}), ... - 'tensors:ArgumentError', 'Invalid choice of eigenvalue selector.'); - nargoutchk(0, 3); - - x0_vec = vectorize(x0); - sz = size(x0_vec); - - if isa(A, 'function_handle') - A_fun = @(x) vectorize(A(devectorize(x, x0))); - else - A_fun = @(x) vectorize(A * devectorize(x, x0)); - end - - options.KrylovDim = min(sz(1), options.KrylovDim); - - [V, D, flag] = eigs(A_fun, sz(1), howmany, sigma, ... - 'Tolerance', options.Tol, 'MaxIterations', options.MaxIter, ... - 'SubspaceDimension', options.KrylovDim, 'IsFunctionSymmetric', ... - options.IsSymmetric, 'StartVector', x0_vec, ... - 'Display', options.Verbosity == 3); - - if nargout <= 1 - varargout = {D}; - elseif nargout == 2 - for i = howmany:-1:1 - varargout{1}(:, i) = devectorize(V(:, i), x0); - end - varargout{2} = D; - else - varargout{2} = D; - for i = howmany:-1:1 - varargout{1}(:, i) = devectorize(V(:, i), x0); - end - varargout{3} = flag; - end - - if flag && options.Verbosity > 0 - warning('eigsolve did not converge.'); - end - - if options.Verbosity > 1 - if flag - fprintf('eigsolve converged.\n'); - end - end - end - function v = vectorize(t, type) % Collect all parameters in a vector, weighted to reproduce the correct % inproduct. @@ -2518,7 +2201,8 @@ type = underlyingType(t(1).var); end - function disp(t) + function disp(t, details) + if nargin == 1 || isempty(details), details = false; end if isscalar(t) r = t.rank; fprintf('Rank (%d, %d) %s:\n\n', r(1), r(2), class(t)); @@ -2528,16 +2212,33 @@ function disp(t) disp(s(i)); end fprintf('\n'); - - [blocks, charges] = matrixblocks(t); - for i = 1:length(blocks) - if ~isempty(blocks) - fprintf('charge %s:\n', string(charges(i))); + if details + [blocks, charges] = matrixblocks(t); + for i = 1:length(blocks) + if ~isempty(blocks) + fprintf('charge %s:\n', string(charges(i))); + end + disp(blocks{i}); end - disp(blocks{i}); end else - builtin('disp', t); + fprintf('%s of size %s:\n', class(t), ... + regexprep(mat2str(size(t)), {'\[', '\]', '\s+'}, {'', '', 'x'})); + subs = ind2sub_(size(t), 1:numel(t)); + spc = floor(log10(max(double(subs), [], 1))) + 1; + if numel(spc) == 1 + fmt = strcat("\t(%", num2str(spc(1)), "u)"); + else + fmt = strcat("\t(%", num2str(spc(1)), "u,"); + for i = 2:numel(spc) - 1 + fmt = strcat(fmt, "%", num2str(spc(i)), "u,"); + end + fmt = strcat(fmt, "%", num2str(spc(end)), "u)"); + end + for i = 1:numel(t) + fprintf('%s\t\t', compose(fmt, subs(i, :))); + disp(t(i), details); + end end end end diff --git a/src/tensors/charges/O2.m b/src/tensors/charges/O2.m index 854bb8d..1525737 100644 --- a/src/tensors/charges/O2.m +++ b/src/tensors/charges/O2.m @@ -225,6 +225,10 @@ bool = issortedrows(reshape([A.j] + [A.s], size(A))); end + function bool = issorted(A) + bool = issorted(reshape([A.j] + [A.s], size(A))); + end + function c = mtimes(a, b) assert(isscalar(a) && isscalar(b), ... 'Simple fusion cannot be vectorised.'); diff --git a/src/tensors/charges/ProductCharge.m b/src/tensors/charges/ProductCharge.m index bfb4715..f80ede4 100644 --- a/src/tensors/charges/ProductCharge.m +++ b/src/tensors/charges/ProductCharge.m @@ -496,14 +496,21 @@ function disp(a) end end + function theta = twist(a) + theta = twist(a.charges{1}); + for i = 2:length(a.charges) + theta = theta .* twist(a.charges{i}); + end + end + function bool = issorted(a) [a, p] = sort(a); bool = isequal(p, (1:length(a)).'); end function bool = issortedrows(a) - [p, ~] = simulsortrows(a.charges{:}); - bool = isequal(p, (1:size(a.charges{1}, 1)).'); + [~, p] = sortrows(a); + bool = isequal(p, (1:size(a, 1)).'); end function [a, I] = sort(a, varargin) @@ -513,16 +520,10 @@ function disp(a) function [a, I] = sortrows(a, col, direction) arguments a - col (1,:) double = [] + col (1,:) double = 1:size(a, 2) direction = 'ascend' end - if nargin == 1 || isempty(col) || isequal(col, 1:size(a, 2)) - [I, a.charges{:}] = simulsortrows(a.charges{:}, ... - 'Direction', direction); - return - end - newcol = reshape(col + (((1:length(a.charges)).' - 1) .* size(a, 2)), 1, []); [I, a.charges{:}] = simulsortrows(a.charges{:}, ... 'Col', newcol, ... diff --git a/src/tensors/charges/Z1.m b/src/tensors/charges/Z1.m index 6c28e1d..bf4e95b 100644 --- a/src/tensors/charges/Z1.m +++ b/src/tensors/charges/Z1.m @@ -40,6 +40,10 @@ bool = true; end + function bool = issorted(~) + bool = true; + end + function a = mtimes(a, ~), end function bools = ne(A, B) diff --git a/src/tensors/kernels/MatrixBlock.m b/src/tensors/kernels/MatrixBlock.m index f4b12de..88370b6 100644 --- a/src/tensors/kernels/MatrixBlock.m +++ b/src/tensors/kernels/MatrixBlock.m @@ -94,8 +94,12 @@ %% Special case 2: addition without permutation + rx = X(1).rank; + ry = Y(1).rank; + if nargin == 4 || (isempty(p) && isempty(map)) || ... - (all(p == 1:length(p)) && isequal(map, speye(size(map)))) + (all(p == 1:length(p)) && isequal(rx, ry) && ... + isequal(map, speye(size(map)))) % reduce to scalar multiplication if b == 0 % a ~= 0 -> case 1 Y = X .* a; @@ -132,8 +136,6 @@ %% General case: addition with permutation % tensor indexing to matrix indexing - rx = X(1).rank; - ry = Y(1).rank; rrx = rankrange(rx); rry = rankrange(ry); p_eff(rry) = rrx(p); @@ -657,4 +659,23 @@ end end end + + methods + function assertBlocksizes(X) + for i = 1:numel(X) + assert(isequal(size(X(i).var), [X(i).rowsizes(end) X(i).colsizes(end)]), ... + 'kernel:dimerror', 'Wrong size of block'); + rows = length(X(i).rowsizes) - 1; + cols = length(X(i).colsizes) - 1; + matdims = [prod(X(i).tdims(:, 1:X(i).rank(1)), 2) ... + prod(X(i).tdims(:, X(i).rank(1)+1:end), 2)]; + for k = 1:cols + for j = 1:rows + assert(matdims(j + (k-1) * rows, 1) == X(i).rowsizes(j+1) - X(i).rowsizes(j)); + assert(matdims(j + (k-1) * rows, 2) == X(i).colsizes(k+1) - X(i).colsizes(k)); + end + end + end + end + end end diff --git a/src/tensors/spaces/CartesianSpace.m b/src/tensors/spaces/CartesianSpace.m index af7ddfc..42cab5e 100644 --- a/src/tensors/spaces/CartesianSpace.m +++ b/src/tensors/spaces/CartesianSpace.m @@ -97,7 +97,7 @@ % c : [] % empty result. - c = []; + c = Z1; end function d = degeneracies(spaces) diff --git a/src/tensors/spaces/ComplexSpace.m b/src/tensors/spaces/ComplexSpace.m index 4b88c6b..2aefc33 100644 --- a/src/tensors/spaces/ComplexSpace.m +++ b/src/tensors/spaces/ComplexSpace.m @@ -255,6 +255,35 @@ hashable = [spaces.dimensions spaces.isdual]; end + + function disp(spaces) + if isscalar(spaces) + fprintf('%s\n', string(spaces)); + return + end + + sz = size(spaces); + dim_str = sprintf('%dx%d', sz(1), sz(2)); + fprintf('%s ProductSpace with elements:\n\n', dim_str); + for i = 1:length(spaces) + fprintf('%d.\t', i); + disp(spaces(i)); + fprintf('\n'); + end + end + + function s = string(spaces) + if numel(spaces) > 1 + s = arrayfun(@string, spaces); + return + end + + if spaces.dual + s = sprintf("Space (%d)*", dims(spaces)); + else + s = sprintf("Space (%d)", dims(spaces)); + end + end end end \ No newline at end of file diff --git a/src/tensors/spaces/GradedSpace.m b/src/tensors/spaces/GradedSpace.m index b8cda96..ec9672f 100644 --- a/src/tensors/spaces/GradedSpace.m +++ b/src/tensors/spaces/GradedSpace.m @@ -40,6 +40,8 @@ 'tensors:ArgumentError', ... 'Charges should be unique.'); + assert(all(dimensions{i}.degeneracies > 0), 'tensors:argerror', ... + 'degeneracies should be strictly positive.'); [dimensions{i}.charges, I] = sort(dimensions{i}.charges); dimensions{i}.degeneracies = dimensions{i}.degeneracies(I); end @@ -375,7 +377,7 @@ function disp(spaces) % Custom display of spaces. if isscalar(spaces) - fprintf('%s', string(spaces)); + fprintf('%s\n', string(spaces)); % fprintf('%s GradedSpace of dimension %d:\n', ... % class(spaces.dimensions.charges), dims(spaces)); % title_str = strjust(pad(["dual", "charges", "degeneracies"]), 'right'); @@ -399,15 +401,30 @@ function disp(spaces) end function s = string(spaces) - title_str = strjust(pad(["dual", "charges", "degeneracies"]), 'right'); - charge_str = strjust(pad([string(spaces.dimensions.charges) - string(spaces.dimensions.degeneracies)]), 'center'); - s = sprintf(... - '%s GradedSpace of dimension %d:\n\t%s:\t%s\n\t%s:\t%s\n\t%s:\t%s\n', ... - class(spaces.dimensions.charges), dims(spaces), ... - title_str(1), string(spaces.dual), ... - title_str(2), join(charge_str(1, :), char(9)), ... - title_str(3), join(charge_str(2, :), char(9))); +% title_str = strjust(pad(["dual", "charges", "degeneracies"]), 'right'); +% charge_str = strjust(pad([string(spaces.dimensions.charges) +% string(spaces.dimensions.degeneracies)]), 'center'); +% s = sprintf(... +% '%s GradedSpace of dimension %d:\n\t%s:\t%s\n\t%s:\t%s\n\t%s:\t%s\n', ... +% class(spaces.dimensions.charges), dims(spaces), ... +% title_str(1), string(spaces.dual), ... +% title_str(2), join(charge_str(1, :), char(9)), ... +% title_str(3), join(charge_str(2, :), char(9))); + if numel(spaces) > 1 + s = arrayfun(@string, spaces); + return + end + + chargestring = join(compose("%s => %d", string(spaces.dimensions.charges).', ... + spaces.dimensions.degeneracies.'), ', '); + + if spaces.dual + s = sprintf('%s Space (%d)*: %s', class(spaces.dimensions.charges), ... + dims(spaces), chargestring); + else + s = sprintf('%s Space (%d): %s', class(spaces.dimensions.charges), ... + dims(spaces), chargestring); + end end function complexspaces = ComplexSpace(gradedspaces) diff --git a/src/utility/Verbosity.m b/src/utility/Verbosity.m new file mode 100644 index 0000000..fb0d412 --- /dev/null +++ b/src/utility/Verbosity.m @@ -0,0 +1,13 @@ +classdef Verbosity < uint8 + % Verbosity level enumeration class. + + enumeration + off (0) % No information + warn (1) % Information on failure + conv (2) % Convergence information + iter (3) % Information about each iteration + detail (4) % Detailed information about each iteration + diagnostics (intmax('uint8')) % all possible information + end +end + diff --git a/src/utility/linalg/contracttree.m b/src/utility/linalg/contracttree.m index 7163271..4ead6a4 100644 --- a/src/utility/linalg/contracttree.m +++ b/src/utility/linalg/contracttree.m @@ -1,7 +1,7 @@ function [C, ic, cc] = contracttree(tensors, indices, conjlist, tree, debug) if isnumeric(tree) - C = tensors{tree}; + C = tensors{tree}; ic = indices{tree}; cc = conjlist(tree); return diff --git a/src/utility/linalg/eigsolve.m b/src/utility/linalg/eigsolve.m index aaa6d9e..71d2e80 100644 --- a/src/utility/linalg/eigsolve.m +++ b/src/utility/linalg/eigsolve.m @@ -16,6 +16,7 @@ kwargs.DeflateDim kwargs.ReOrth = 2 kwargs.NoBuild + kwargs.Verbosity = Verbosity.warn end % Input validations @@ -24,6 +25,9 @@ end if ~isfield(kwargs, 'DeflateDim') kwargs.DeflateDim = max(round(3/5 * kwargs.KrylovDim), howmany); +else + assert(kwargs.DeflateDim < kwargs.KrylovDim, 'eigsolve:argerror', ... + 'Deflate size should be smaller than krylov dimension.') end if ~isa(A, 'function_handle') A = @(x) A * x; @@ -50,20 +54,22 @@ V(1:length(v), ctr_inner) = v; v = A(v); - H(:, ctr_inner) = V' * v; + H(1:ctr_inner, ctr_inner) = V(:, 1:ctr_inner)' * v; v = v - V * H(:, ctr_inner); % reorthogonalize new vector if ctr_inner >= kwargs.ReOrth - c = V' * v; - H(:, ctr_inner) = H(:, ctr_inner) + c; - v = v - V * c; + c = V(:, 1:ctr_inner)' * v; + H(1:ctr_inner, ctr_inner) = H(1:ctr_inner, ctr_inner) + c; + v = v - V(:, 1:ctr_inner) * c; end % normalize beta = norm(v, 'fro'); v = v / beta; + if ctr_inner == kwargs.KrylovDim, break; end + if ctr_inner >= howmany invariantsubspace = beta < eps(underlyingType(beta))^(3/4); if invariantsubspace || ctr_inner == kwargs.KrylovDim @@ -74,13 +80,23 @@ if ~mod(ctr_inner, kwargs.NoBuild) [U, lambda] = eig(H(1:ctr_inner, 1:ctr_inner), 'vector'); select = selecteigvals(lambda, howmany, which); - conv = beta * norm(U(ctr_inner, select), Inf); + conv = max(abs(beta * U(ctr_inner, select))); if conv < kwargs.Tol V = V(:, 1:ctr_inner) * U(:, select); D = diag(lambda(select)); + if kwargs.Verbosity >= Verbosity.conv + fprintf('Conv %2d (%2d/%2d): error = %.5e.\n', ctr_outer, ... + ctr_inner, kwargs.KrylovDim, conv); + end return end + + if kwargs.Verbosity >= Verbosity.detail + fprintf('Iter %2d (%2d/%2d):\tlambda = %.5e + %.5ei;\terror = %.5e\n', ... + ctr_outer, ctr_inner, kwargs.KrylovDim, ... + real(lambda(1)), imag(lambda(1)), conv); + end end end @@ -91,14 +107,16 @@ if ctr_outer == kwargs.MaxIter || ctr_inner ~= kwargs.KrylovDim [U, lambda] = eig(H(1:ctr_inner, 1:ctr_inner), 'vector'); select = selecteigvals(lambda, howmany, which); - conv = beta * norm(U(ctr_inner, select), Inf); + conv = max(abs(beta * U(ctr_inner, select))); V = V(:, 1:ctr_inner) * U(:, select); D = diag(lambda(select)); if conv > kwargs.Tol if invariantsubspace + fprintf('Found invariant subspace.\n'); flag = 1; else + fprintf('Reached maxiter without convergence.\n'); flag = 2; end end @@ -115,21 +133,30 @@ V = V * U1; [U, lambda] = eig(T(1:kwargs.DeflateDim, 1:kwargs.DeflateDim), 'vector'); select = selecteigvals(lambda, howmany, which); - conv = beta * norm(U1(kwargs.KrylovDim, 1:kwargs.DeflateDim) * U(:, select), 'Inf'); + conv = max(abs(beta * U1(kwargs.KrylovDim, 1:kwargs.DeflateDim) * U(:, select))); % check for convergence if conv < kwargs.Tol V = V(:, 1:kwargs.DeflateDim) * U(:, select); D = diag(lambda(select)); + if kwargs.Verbosity >= Verbosity.conv + fprintf('Conv %2d: error = %.5e.\n', ctr_outer, conv); + end return end + if kwargs.Verbosity >= Verbosity.iter + fprintf('Iter %2d:\tlambda = %.5e + %.5ei;\terror = %.5e\n', ... + ctr_outer, real(lambda(1)), imag(lambda(1)), conv); + end + % deflate Krylov subspace + H = zeros(kwargs.KrylovDim, kwargs.KrylovDim, underlyingType(v)); H(1:kwargs.DeflateDim, 1:kwargs.DeflateDim) = ... T(1:kwargs.DeflateDim, 1:kwargs.DeflateDim); H(kwargs.DeflateDim + 1, 1:kwargs.DeflateDim) = ... beta * U1(kwargs.KrylovDim, 1:kwargs.DeflateDim); - V(:, kwargs.DeflateDim + 1:end) = 0 * V(:, kwargs.DeflateDim + 1:end); +% V(:, kwargs.DeflateDim + 1:end) = 0 * V(:, kwargs.DeflateDim + 1:end); ctr_inner = kwargs.DeflateDim; end diff --git a/src/utility/linalg/generatetree.m b/src/utility/linalg/generatetree.m index c60d761..1333de7 100644 --- a/src/utility/linalg/generatetree.m +++ b/src/utility/linalg/generatetree.m @@ -12,7 +12,13 @@ else tocontract = min(horzcat(contractindices{:})); tinds = find(cellfun(@(x) any(tocontract == x), contractindices)); - assert(length(tinds) == 2); + + if length(tinds) ~= 2 + celldisp(contractindices); + error('contract:indices', ... + 'contracted indices should appear exactly twice.\n(%d)', ... + tocontract); + end partialtrees{tinds(1)} = partialtrees(tinds); partialtrees(tinds(2)) = []; contractindices{tinds(1)} = unique1(horzcat(contractindices{tinds})); diff --git a/test/TestFiniteMpo.m b/test/TestFiniteMpo.m new file mode 100644 index 0000000..4027741 --- /dev/null +++ b/test/TestFiniteMpo.m @@ -0,0 +1,33 @@ +classdef TestFiniteMpo < matlab.unittest.TestCase + % Unit tests for finite matrix product operators. + + properties (TestParameter) + mpo = struct('trivial0', ... + FiniteMpo(Tensor.randnc(ComplexSpace.new(2, false, 4, true, 2, false), []), ... + {}, Tensor.randnc(ComplexSpace.new(2, false, 4, false, 2, true), []))..., ... [2 4 2], [false true true]), {}, Tensor.randnc([2 4 2], [true false false])), ... + ...'trivial1', ... + ... FiniteMpo(Tensor.randnc([2 4 2]), {Tensor.randnc([4 2 4 2])}, ... + ... Tensor.randnc([2 4 2])) ... + ) + end + + methods (Test) + function testFixedpoints(tc, mpo) + + end + + function testProperties(tc, mpo) + mpo_tensor = Tensor(mpo); + tc.assertTrue(isequal(mpo.domain, mpo_tensor.domain)); + tc.assertTrue(isequal(mpo.codomain, mpo_tensor.codomain)); + + v = initialize_fixedpoint(mpo); + tc.assertTrue(isapprox(mpo.apply(v), mpo_tensor * v)); + end + + function testTransposes(tc, mpo) + + end + end +end + diff --git a/test/TestFusionTree.m b/test/TestFusionTree.m index 94e1b94..2c63ed3 100644 --- a/test/TestFusionTree.m +++ b/test/TestFusionTree.m @@ -4,8 +4,8 @@ %#ok<*PROPLC> properties (ClassSetupParameter) - weight = {'small', 'medium'}%, 'large'} - charge = {'A4', 'Z1', 'Z2', 'fZ2', 'U1', 'O2', 'SU2', 'Z2xU1'} + weight = {'small', 'medium'} %, 'large'} + charge = {'A4', 'Z1', 'Z2', 'fZ2', 'U1', 'O2', 'SU2', 'Z2xU1', 'U1xSU2'} end methods (TestClassSetup) @@ -64,6 +64,19 @@ function generateTrees(tc, charge, weight) chargeset = A4(1:4); end + case 'U1xSU2' + switch weight + case 'small' + chargeset = ProductCharge(U1(-1:1), SU2(2,1,2)); + case 'medium' + chargeset = ProductCharge(U1(-2:2), SU2(1,2,1,2,1)); + case 'large' + chargeset = ProductCharge(U1(-2:2), SU2(3,2,1,2,3)); + end + + otherwise + error('not implemented'); + end %% Setup weight @@ -75,7 +88,7 @@ function generateTrees(tc, charge, weight) tc.testWeight = 0.5; case 'medium' legs = 0:4; - maxTrees = 100; + maxTrees = 200; maxLegs = 5; tc.testWeight = 0.25; case 'large' @@ -140,6 +153,8 @@ function trees_properties(tc) end assertTrue(tc, all(isallowed(tc.trees{i,j})), ... 'Generated invalid fusion trees.'); + assertTrue(tc, issorted(tc.trees{i,j}), ... + 'FusionTrees are not properly sorted.'); end end end diff --git a/test/TestInfiniteMpo.m b/test/TestInfiniteMpo.m new file mode 100644 index 0000000..72e4737 --- /dev/null +++ b/test/TestInfiniteMpo.m @@ -0,0 +1,39 @@ +classdef TestInfiniteMpo < matlab.unittest.TestCase + % Unit tests for infinite matrix product operators. + + properties (TestParameter) + mpo = struct(... + 'trivial', InfiniteMpo.Ising(), ... + 'Z2', InfiniteMpo.Ising('Symmetry', 'Z2') ... + ) + mps = struct(... + 'trivial', UniformMps.randnc(CartesianSpace.new(2), CartesianSpace.new(4)), ... + 'Z2', UniformMps.randnc(GradedSpace.new(Z2(0,1), [1 1], false), ... + GradedSpace.new(Z2(0,1), [4 4], false)) ... + ) + end + + methods (Test, ParameterCombination='sequential') + function testEnvironments(tc, mpo, mps) + [GL, lambdaL] = leftenvironment(mpo, mps, mps); + tc.assertTrue(isapprox(transferleft(mpo, mps, mps, GL{1}), GL{1} * lambdaL)); + [GR, lambdaR] = rightenvironment(mpo, mps, mps); + tc.assertTrue(isapprox(transferright(mpo, mps, mps, GR{1}), GR{1} * lambdaR)); + + N = period(mps); + for w = 1:N + tc.assertTrue(isapprox(... + applyleft(mpo.O{w}, mps.AL(w), conj(mps.AL(w)), GL{w}), ... + lambdaL^(1/N) * GL{next(w, N)})); + tc.assertTrue(isapprox(... + applyright(mpo.O{w}, mps.AR(w), conj(mps.AR(w)), GR{prev(w, N)}), ... + lambdaR^(1/N) * GR{w})); + end + end + + function testDerivatives(tc, mpo, mps) + + end + end +end + diff --git a/test/TestTensor.m b/test/TestTensor.m index eef895e..4f43f3b 100644 --- a/test/TestTensor.m +++ b/test/TestTensor.m @@ -27,7 +27,19 @@ function classSetup(tc) 'SU2', GradedSpace.new(SU2(1, 2), [3 1], false, SU2(1, 3), [2 1], false, ... SU2(2, 3), [1 1], true, SU2(1, 2), [2 2], false, SU2(1, 2, 4), [1 1 1], true), ... 'A4', GradedSpace.new(A4(1:2), [2 1], false, A4([1 4]), [1 2], false, ... - A4(1:4), [2 1 3 1], true, A4(1:4), [2 2 1 2], false, A4(2:3), [1 2], true) ... + A4(1:4), [2 1 3 1], true, A4(1:4), [2 2 1 2], false, A4(2:3), [1 2], true), ... + 'U1xSU2', GradedSpace.new(... + ProductCharge(U1(-1:1), SU2(2,1,2)), [1 2 1], false, ... + ProductCharge(U1(-2:2), SU2(1,2,1,2,1)), [2 1 2 2 2], true, ... + ProductCharge(U1(-1:1), SU2(2,1,2)), [1 2 2], false, ... + ProductCharge(U1(-1:1), SU2(2,1,2)), [1 2 1], false, ... + ProductCharge(U1(-1:1), SU2(2,1,2)), [1 2 2], false), ... + 'Hubbard', GradedSpace.new(... + ProductCharge(U1(0, 1, 2), SU2(1, 2, 1), fZ2(0, 1, 0)), [1 1 1], false, ... + ProductCharge(U1(0, 1), SU2(1, 2), fZ2(0, 1)), [1 1], true, ... + ProductCharge(U1(-2:2), SU2(1, 2, 1, 2, 1), fZ2(0, 1, 0, 1, 0)), [1 1 3 1 1], false, ... + ProductCharge(U1(0, 1, 2), SU2(1, 2, 1), fZ2(0, 1, 0)), [1 1 1], false, ... + ProductCharge(U1(0, 1), SU2(1, 2), fZ2(0, 1)), [1 1], true) ... ) end @@ -98,7 +110,7 @@ function permute_via_inner(tc, spaces) for i = 0:5 ps = perms(1:nspaces(t1)).'; - for p = ps(:, randperm(size(ps, 2), min(size(ps, 2), 20))) + for p = ps(:, randperm(size(ps, 2), min(size(ps, 2), 5))) t3 = tpermute(t1, p.', [i 5-i]); tc.assertTrue(all(dims(t1, p.') == dims(t3)), ... 'Incorrect size after permutation.'); @@ -150,6 +162,10 @@ function multiplication_via_conversion(tc, spaces) tc.assertTrue(isapprox(double(t1 * t2), ... tensorprod(double(t1), double(t2), [2 3], [2 1], 'NumDimensionsA', 3))); + l1 = contract(t1, [1 2 3], conj(t1), [1 2 3]); + l2 = contract(double(t1), [1 2 3], conj(double(t1)), [1 2 3]); + tc.assertTrue(isapprox(l1, l2)); + W1 = spaces(1:3); W2 = spaces(4:5); @@ -312,4 +328,5 @@ function eigenvalues(tc, spaces) end end end + end diff --git a/test/TestUniformMps.m b/test/TestUniformMps.m new file mode 100644 index 0000000..9025486 --- /dev/null +++ b/test/TestUniformMps.m @@ -0,0 +1,69 @@ +classdef TestUniformMps < matlab.unittest.TestCase + % Unit tests for uniform matrix product states. + + properties (TestParameter) + mps = struct(... + 'trivial', UniformMps.randnc(CartesianSpace.new(2), CartesianSpace.new(4)), ... + 'trivial2', UniformMps.randnc(CartesianSpace.new([2 2]), CartesianSpace.new([12 12])), ... + 'fermion1', UniformMps.randnc(GradedSpace.new(fZ2(0,1), [1 1], false), ... + GradedSpace.new(fZ2(0,1), [2 2], false)), ... + 'fermion2', UniformMps.randnc(GradedSpace.new(fZ2(0,1), [1 1], false), ... + GradedSpace.new(fZ2(0,1), [2 2], true)), ... + 'fermion3', UniformMps.randnc(GradedSpace.new(fZ2(0,1), [1 1], true), ... + GradedSpace.new(fZ2(0,1), [2 2], false)), ... + 'fermion4', UniformMps.randnc(GradedSpace.new(fZ2(0,1), [1 1], true), ... + GradedSpace.new(fZ2(0,1), [2 2], true)), ... + 'haldane', UniformMps.randnc(GradedSpace.new(SU2(2), 1, false, SU2(2), 1, false), ... + GradedSpace.new(SU2(1:2:5), [5 3 2], false, SU2(2:2:6), [5 2 1], false)) ... + ) + end + + methods (Test) + function testCanonical(tc, mps) + mps = canonicalize(mps); + AL = mps.AL; AR = mps.AR; C = mps.C; AC = mps.AC; + tc.assertTrue(all(isisometry(mps.AL, 'left')), ... + 'AL should be a left isometry.'); + tc.assertTrue(all(isisometry(mps.AR, 'right')), ... + 'AR should be a right isometry.'); + + for w = 1:period(mps) + ALC = repartition(multiplyright(AL(w), C(w)), rank(AC(w))); + CAR = multiplyleft(AR(w), C(prev(w, period(mps)))); + tc.assertTrue(isapprox(ALC, AC(w)) && isapprox(AC(w), CAR), ... + 'AL, AR, C and AC should be properly related.'); + end + end + + function testDiagonalC(tc, mps) + mps2 = diagonalizeC(mps); + f = fidelity(mps, mps2, 'Verbosity', Verbosity.diagnostics); + tc.assertTrue(isapprox(f, 1), 'Diagonalizing C should not alter the state.'); + end + + function testFixedpoints(tc, mps) + for top = ["L" "R"] + if strcmp(top, "L") + T = mps.AL; + else + T = mps.AR; + end + + for bot = ["L" "R"] + if strcmp(bot, "L") + B = mps.AL; + else + B = mps.AR; + end + + rhoL = fixedpoint(mps, sprintf('l_%c%c', top, bot)); + tc.assertTrue(isapprox(rhoL, applyleft(T, conj(B), rhoL)), ... + 'rho_left should be a fixed point.'); + rhoR = fixedpoint(mps, sprintf('r_%c%c', top, bot)); + tc.assertTrue(isapprox(rhoR, applyright(T, conj(B), rhoR))); + end + end + end + end +end + From b281d2ef80189fcfa7d70ef1dd2155a43fbcf4e6 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 19 Oct 2022 02:21:09 +0200 Subject: [PATCH 045/245] new vumps with fermions? --- src/algorithms/Vumps.m | 209 ++++++++++++++++++++++++++ src/mps/FiniteMpo.m | 130 +++++++++------- src/mps/InfiniteMpo.m | 160 +++++++++++++++----- src/mps/MpoTensor.m | 115 ++++++++++---- src/mps/MpsTensor.m | 139 +++++++++++++---- src/mps/UniformMps.m | 85 +++++++---- src/tensors/AbstractTensor.m | 4 +- src/tensors/Tensor.m | 63 +++----- src/tensors/charges/Z2.m | 2 +- src/tensors/charges/fZ2.m | 4 + src/tensors/kernels/AbstractBlock.m | 3 + src/tensors/spaces/GradedSpace.m | 6 +- src/utility/linalg/tensorprod.m | 12 +- src/utility/permutations/iscircperm.m | 12 ++ src/utility/time2str.m | 33 ++++ test/TestFiniteMpo.m | 28 ++-- test/TestInfiniteMpo.m | 58 +++++-- test/TestUniformMps.m | 67 +++++---- 18 files changed, 841 insertions(+), 289 deletions(-) create mode 100644 src/algorithms/Vumps.m create mode 100644 src/utility/permutations/iscircperm.m create mode 100644 src/utility/time2str.m diff --git a/src/algorithms/Vumps.m b/src/algorithms/Vumps.m new file mode 100644 index 0000000..e33cd5e --- /dev/null +++ b/src/algorithms/Vumps.m @@ -0,0 +1,209 @@ +classdef Vumps + %UNTITLED Summary of this class goes here + % Detailed explanation goes here + + %% Options + properties + tol = 1e-10 + miniter = 5 + maxiter = 100 + verbosity = Verbosity.iter + which = 'largestabs' + + + KrylovDim = 20 + + dynamical_tols = true + tol_min = 1e-13 + tol_max = 1e-10 + eigs_tolfactor = 1e-4 + canonical_tolfactor = 1e-4 + environments_tolfactor = 1e-4 + end + + properties (Access = private) + alg_eigs = struct('MaxIter', 100, 'KrylovDim', 20) + alg_canonical = struct('Method', 'polar') + alg_environments = struct + end + + + %% + methods + function v = Vumps(kwargs) + arguments + kwargs.?Vumps + end + + fields = fieldnames(kwargs); + if ~isempty(fields) + for field = fields.' + v.(field{1}) = kwargs.(field{1}); + end + end + + if ~isfield('alg_eigs', kwargs) + v.alg_eigs.Tol = sqrt(v.tol_min * v.tol_max); + v.alg_eigs.Verbosity = v.verbosity - 2; + end + + if ~isfield('alg_canonical', kwargs) + v.alg_canonical.Tol = sqrt(v.tol_min * v.tol_max); + v.alg_canonical.Verbosity = v.verbosity - 2; + end + + if ~isfield('alg_environments', kwargs) + v.alg_environments.Tol = sqrt(v.tol_min * v.tol_max); + v.alg_environments.Verbosity = v.verbosity - 2; + end + end + + function [mps, lambda, GL, GR] = fixedpoint(alg, mpo, mps) + t_total = tic; + disp_init(alg); + + mps = canonicalize(mps); + [GL, GR] = environments(mpo, mps); + + for iter = 1:alg.maxiter + t_iter = tic; + AC = updateAC(alg, mpo, mps, GL, GR); + C = updateC (alg, mpo, mps, GL, GR); + mps = updatemps(alg, AC, C); + + [GL, GR, lambda] = environments(mpo, mps, mps, GL, GR); + eta = convergence(alg, mpo, mps, GL, GR); + + if iter > alg.miniter && eta < alg.tol + disp_conv(alg, iter, lambda, eta, toc(t_total)); + return + end + alg = updatetols(alg, iter, eta); + disp_iter(alg, iter, lambda, eta, toc(t_iter)); + end + + warning('vumps:maxiter', ... + 'Reached maximum amount of iterations without converging.'); + disp_maxiter(alg, iter, lambda, eta, toc(t_total)); + end + end + + + %% Subroutines + methods + function AC = updateAC(alg, mpo, mps, GL, GR) + kwargs = namedargs2cell(alg.alg_eigs); + H_AC = AC_hamiltonian(mpo, mps, GL, GR); + [AC, ~] = eigsolve(H_AC, mps.AC, 1, alg.which, kwargs{:}); + end + + function C = updateC(alg, mpo, mps, GL, GR) + kwargs = namedargs2cell(alg.alg_eigs); + H_C = C_hamiltonian(mpo, mps, GL, GR); + [C, ~] = eigsolve(H_C, mps.C, 1, alg.which, kwargs{:}); + end + + function mps = updatemps(alg, AC, C) + [~, Q_AC] = rightorth(AC); + [~, Q_C] = rightorth(C, 1, 2); + + AR = contract(conj(Q_C), [1 -1], Q_AC, [1 -2 -3], 'Rank', [1 2]); + + mps = UniformMps([], AR, C, []); + kwargs = namedargs2cell(alg.alg_canonical); + mps = canonicalize(mps, kwargs{:}); + end + + function [GL, GR, lambda] = environments(alg, mpo, mps, GL, GR) + arguments + alg + mpo + mps + GL = [] + GR = [] + end + + kwargs = namedargs2cell(alg.alg_environments); + [GL, GR, lambda] = environments(mpo, mps, mps, GL, GR, ... + kwargs{:}); + end + + function eta = convergence(alg, mpo, mps, GL, GR) + AC_ = apply(AC_hamiltonian(mpo, mps, GL, GR), mps.AC); + lambda_AC = dot(AC_, mps.AC); + AC_ = normalize(AC_ ./ lambda_AC); + + C_ = apply(C_hamiltonian(mpo, mps, GL, GR), mps.C); + lambda_C = dot(C_, mps.C); + C_ = normalize(C_ ./ lambda_C); + + eta = distance(AC_ , repartition(multiplyleft(mps.AR, C_), rank(AC_))); + end + end + + + %% Option handling + methods + function alg = updatetols(alg, iter, eta) + if ~alg.dynamical_tols, return; end + + alg.alg_eigs.Tol = between(alg.tol_min, eta * alg.eigs_tolfactor, ... + alg.tol_max / iter); + alg.alg_canonical.Tol = between(alg.tol_min, eta * alg.canonical_tolfactor, ... + alg.tol_max / iter); + alg.alg_environments.Tol = between(alg.tol_min, eta * alg.environments_tolfactor, ... + alg.tol_max / iter); + end + end + + %% Display + methods (Access = private) + function disp_init(alg) + if alg.verbosity < Verbosity.conv, return; end + fprintf('---- VUMPS ----\n'); + end + + function disp_iter(alg, iter, lambda, eta, t) + if alg.verbosity < Verbosity.iter, return; end + + s = settings; + if abs(imag(lambda)) < eps(lambda)^(3/4) * abs(lambda) + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('Iter %2d:\tE = %-0.4f\terror = %0.1e\t(%s)\n', ... + iter, real(lambda), eta, time2str(t)); + otherwise + fprintf('Iter %4d:\tE = %-0.15f\terror = %0.4e\t(%s)\n', ... + iter, real(lambda), eta, time2str(t, 's')); + + end + else + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('Iter %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + otherwise + fprintf('Iter %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + + end + end + end + + function disp_conv(alg, iter, lambda, eta, t) + if alg.verbosity < Verbosity.conv, return; end + s = settings; + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('Conv %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + otherwise + fprintf('Conv %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + + end + fprintf('---------------\n'); + end + end +end + diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index 179b1bf..977779c 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -2,9 +2,9 @@ % Finite Matrix product operators properties - L + L MpsTensor O - R + R MpsTensor end methods @@ -18,15 +18,14 @@ function v = apply(mpo, v) N = length(mpo); - assert(nspaces(v) - 2 == N, 'incompatible vector.'); - - inds = arrayfun(@(x) [2*x -(x+1) 2*(x+1) 2*x+1], 1:N, ... - 'UniformOutput', false); - for d = depth(mpo):-1:1 - args = [mpo(d).O; inds]; - v = contract(v, 1:2:2*N+3, ... - mpo(d).L, [-1 2 1], args{:}, mpo(d).R, [2*N+3 2*N+2 -(N+2)], ... - 'Rank', rank(v)); + for d = 1:depth(mpo) + if N == 0 + v = applytransfer(mpo(d).L, mpo(d).R, v); + elseif N == 1 + v = applychannel(mpo(d).O{1}, mpo(d).L, mpo(d).R, v); + else + v = applympo(mpo(d).O{:}, mpo(d).L, mpo(d).R, v); + end end end @@ -50,7 +49,7 @@ if isempty(v0), v0 = initialize_fixedpoint(mpo(1)); end - kwargs = [fieldnames(options).'; struct2cell(options)]; + kwargs = namedargs2cell(options); [V, D, flag] = eigsolve(@(x) mpo.apply(x), v0, howmany, sigma, kwargs{:}); end @@ -60,11 +59,11 @@ function s = domain(mpo) s = conj(... - [space(mpo(1).L, 3), cellfun(@(x) space(x, 4), mpo.O), space(mpo.R, 1)]); + [rightvspace(mpo(1).L) cellfun(@(x) pspace(x)', mpo(1).O) leftvspace(mpo(1).R)]); end function s = codomain(mpo) - s = [space(mpo(1).L, 1), cellfun(@(x) space(x, 2), mpo.O), space(mpo.R, 3)]; + s = [leftvspace(mpo(end).L) cellfun(@pspace, mpo(end).O) rightvspace(mpo(end).R)]; end function d = depth(mpo) @@ -75,64 +74,91 @@ l = length(mpo(1).O); end - function w = width(mpo) - w = size(mpo.O, 2); - end - - function v = applyleft(mpo, v) - - end - - function mpo_d = ctranspose(mpo) - mpo_d = mpo; - mpo_d.L = tpermute(conj(mpo.L), [3 2 1], rank(mpo.L)); - mpo_d.R = tpermute(conj(mpo.R), [3 2 1], rank(mpo.R)); - mpo_d.O = cellfun(@(x) tpermute(conj(x), [3 2 1 4], rank(x)), mpo.O, ... - 'UniformOutput', false); -% mpo_d = FiniteMpo(conj(mpo.L), conj(mpo.O), conj(mpo.R)); + function type = underlyingType(mpo) + type = underlyingType(mpo(1).L); end - function mpo1 = plus(mpo1, mpo2) + function mpo = ctranspose(mpo) + if depth(mpo) > 1 + mpo = flip(mpo, 1); + end + for d = 1:depth(mpo) + mpo(d).L = mpo(d).L'; + mpo(d).O = cellfun(@ctranspose, mpo(d).O, ... + 'UniformOutput', false); + mpo(d).R = mpo(d).R'; + end end - function v = applyright(mpo, v) - arguments - mpo - v MpsTensor + function mpo = transpose(mpo) + if depth(mpo) > 1 + mpo = flip(mpo, 1); end - assert(depth(mpo) == 1, 'mps:TBA', 'Not implemented yet.'); - assert(v.plegs == width(mpo), 'mps:ArgError', 'Incompatible sizes.'); - - w = width(mpo); - - mpopart = cell(2, w); - for i = 1:w - mpopart{1, i} = mpo.O{i}; - mpopart{2, i} = [2 * i, 2 * (i + 1) + 1, -(1 + i), 2 * i + 1]; + for d = 1:depth(mpo) + [mpo(d).L, mpo(d).R] = swapvars(mpo(d).L, mpo(d).R); + mpo(d).O = cellfun(@transpose, ... + fliplr(mpo(d).O), 'UniformOutput', false); end - - v = MpsTensor(contract(... - v, [1, 2:2:(2 * w), 2 * (w + 1), -(1:v.alegs) - (w + 2)], ... - mpo.L, [-1 3 1], ... - mpo.R, [-(w + 2), 2 * (w + 1) + 1, 2 * (w + 1)], ... - mpopart{:}, 'Rank', rank(v))); end - +% +% function v = applyleft(mpo, v) +% +% end +% +% function mpo1 = plus(mpo1, mpo2) +% +% end +% +% function v = applyright(mpo, v) +% arguments +% mpo +% v MpsTensor +% end +% +% assert(depth(mpo) == 1, 'mps:TBA', 'Not implemented yet.'); +% assert(v.plegs == width(mpo), 'mps:ArgError', 'Incompatible sizes.'); +% +% w = width(mpo); +% +% mpopart = cell(2, w); +% for i = 1:w +% mpopart{1, i} = mpo.O{i}; +% mpopart{2, i} = [2 * i, 2 * (i + 1) + 1, -(1 + i), 2 * i + 1]; +% end +% +% v = MpsTensor(contract(... +% v, [1, 2:2:(2 * w), 2 * (w + 1), -(1:v.alegs) - (w + 2)], ... +% mpo.L, [-1 3 1], ... +% mpo.R, [-(w + 2), 2 * (w + 1) + 1, 2 * (w + 1)], ... +% mpopart{:}, 'Rank', rank(v))); +% end +% function t = Tensor(mpo) assert(depth(mpo) == 1, 'not implemented for 1 < depth'); N = length(mpo); inds = arrayfun(@(x) [x -(x+1) (x+1) -(N+x+3)], 1:N, ... 'UniformOutput', false); args = [mpo.O; inds]; - t = contract(mpo.L, [-1 1 -(N+3)], args{:}, mpo.R, [-(2*N+4) N+1 -(N+2)], ... + t = contract(mpo.L, [-1 1 -(2*N+4)], args{:}, mpo.R, [-(N+3) N+1 -(N+2)], ... 'Rank', [N+2 N+2]); end end methods (Static) - + function mpo = randnc(pspaces, vspaces) + assert(length(pspaces) == length(vspaces) + 1); + + L = MpsTensor(Tensor.randnc([pspaces(1) vspaces(1)'], pspaces(1))); + O = cell(1, length(pspaces)-2); + for i = 1:length(O) + O{i} = MpoTensor(Tensor.randnc([vspaces(i) pspaces(i+1)], ... + [pspaces(i+1) vspaces(i+1)])); + end + R = MpsTensor(Tensor.randnc([pspaces(end)' vspaces(end)], pspaces(end)')); + mpo = FiniteMpo(L, O, R); + end end end diff --git a/src/mps/InfiniteMpo.m b/src/mps/InfiniteMpo.m index c7e3f3b..3689906 100644 --- a/src/mps/InfiniteMpo.m +++ b/src/mps/InfiniteMpo.m @@ -14,7 +14,7 @@ if isa(O, 'InfiniteMpo') for i = numel(O):1:1 - mpo(i).O = O(i).O; + mpo(i, 1).O = O(i, 1).O; end mpo = reshape(mpo, size(O)); @@ -23,7 +23,7 @@ elseif isa(O, 'AbstractTensor') for i = height(O):-1:1 - mpo(i).O = arrayfun(@MpoTensor, O(i, :), 'UniformOutput', false); + mpo(i, 1).O = arrayfun(@MpoTensor, O(i, :), 'UniformOutput', false); end elseif iscell(O) @@ -35,6 +35,34 @@ end methods + function p = period(mpo) + p = length(mpo(1).O); + end + + function d = depth(mpo) + d = length(mpo); + end + + function mpo = block(mpo) + if depth(mpo) == 1, return; end + O_ = mpo(1, 1).O; + for d = 2:depth(mpo) + + for w = period(mpo):-1:1 + vspaces = [rightvspace(O_{w})' rightvspace(mpo(d, 1).O{w})']; + fuser(w) = Tensor.eye(vspaces, prod(vspaces)); + end + + for w = 1:period(mpo) + O_{w} = MpoTensor(contract(O_{w}, [1 2 5 -4], mpo(d, 1).O{w}, [3 -2 4 2], ... + fuser(prev(w, period(mpo)))', [-1 3 1], fuser(w), [5 4 -3], ... + 'Rank', [2 2])); + end + end + mpo = mpo(1, 1); + mpo.O = O_; + end + function [GL, lambda] = leftenvironment(mpo, mps1, mps2, GL, eigopts) arguments mpo @@ -47,22 +75,10 @@ eigopts.Tol = eps(underlyingType(mps1))^(3/4) end - if isempty(GL) - GL = cell(1, period(mps1)); - for i = size(mpo.O{1}, 1):-1:1 - GL{1}(1, i, 1) = Tensor.randnc(... - [leftvspace(mps2, 1) leftvspace(mpo.O{1}, i)'], leftvspace(mps1, 1)); - end - end - - [GL{1}, lambda] = eigsolve(@(x) transferleft(mpo, mps1, mps2, x), ... - GL{1}, 1, 'largestabs', 'KrylovDim', eigopts.KrylovDim, 'Tol', eigopts.Tol, ... + T = transfermatrix(mpo, mps1, mps2, 'Type', 'LL'); + [GL, lambda] = eigsolve(T, GL, 1, 'largestabs', ... + 'KrylovDim', eigopts.KrylovDim, 'Tol', eigopts.Tol, ... 'ReOrth', eigopts.ReOrth, 'MaxIter', eigopts.MaxIter); - - n = lambda^(1/period(mps1)); - for w = 2:period(mps1) - GL{w} = applyleft(mpo.O{w}, mps1.AL(w), conj(mps2.AL(w)), GL{w-1}) / n; - end end function [GR, lambda] = rightenvironment(mpo, mps1, mps2, GR, eigopts) @@ -77,37 +93,92 @@ eigopts.Tol = eps(underlyingType(mps1))^(3/4) end - N = period(mps1); - if isempty(GR) - GR = cell(1, N); - for i = size(mpo.O{end}, 3):-1:1 - GR{1}(1, i, 1) = Tensor.randnc(... - [rightvspace(mps1, N)' rightvspace(mpo.O{end}, i)'], ... - rightvspace(mps2, N)'); - end - end + T = transfermatrix(mpo, mps1, mps2, 'Type', 'RR').'; - [GR{1}, lambda] = eigsolve(@(x) transferright(mpo, mps1, mps2, x), ... - GR{1}, 1, 'largestabs', 'KrylovDim', eigopts.KrylovDim, 'Tol', eigopts.Tol, ... + [GR, lambda] = eigsolve(T, GR, 1, 'largestabs', ... + 'KrylovDim', eigopts.KrylovDim, 'Tol', eigopts.Tol, ... 'ReOrth', eigopts.ReOrth, 'MaxIter', eigopts.MaxIter); + end + + function [GL, GR, lambda] = environments(mpo, mps1, mps2, GL, GR, eigopts) + arguments + mpo + mps1 + mps2 = mps1 + GL = [] + GR = [] + eigopts.KrylovDim = 30 + eigopts.MaxIter = 1000 + eigopts.ReOrth = 2 + eigopts.Tol = eps(underlyingType(mps1))^(3/4) + end + + kwargs = [fieldnames(eigopts).'; struct2cell(eigopts).']; + [GL, lambdaL] = leftenvironment(mpo, mps1, mps2, GL, kwargs{:}); + [GR, lambdaR] = rightenvironment(mpo, mps1, mps2, GR, kwargs{:}); + lambda = (lambdaL + lambdaR) / 2; + if abs(lambdaL - lambdaR)/abs(lambda) > eps(lambda)^(1/3) + warning('lambdas disagree'); + end + + overlap = sqrt(contract(GL, [1 3 2], mps1.C, [2 4], mps2.C', [5 1], GR, [4 3 5])); + GL = GL / overlap; + GR = GR / overlap; + end + end + %% Derived operators + methods + function T = transfermatrix(mpo, mps1, mps2, sites, kwargs) + arguments + mpo + mps1 + mps2 = mps1 + sites = 1:period(mpo) + kwargs.Type {mustBeMember(kwargs.Type, {'LL' 'LR' 'RL' 'RR'})} = 'RR' + end - n = lambda^(1/N); - for w = N:-1:1 - GR{w} = applyright(mpo.O{w}, mps1.AR(w), conj(mps2.AR(w)), ... - GR{next(w, N)}) / n; + assert(all(diff(sites) == 1), 'sites must be neighbouring and increasing.'); + + if kwargs.Type(1) == 'L' + A1 = mps1.AL(sites); + else + A1 = mps1.AR(sites); + end + if kwargs.Type(2) == 'L' + A2 = mps2.AL(sites); + else + A2 = mps2.AR(sites); end + + A2 = twist(A2, 1 + find(isdual(space(A1, 2:nspaces(A1)-1)))); + T = FiniteMpo(A2', {rot90(mpo.O{1})}, A1); + +% T = transfermatrix(mps1, mps2, sites, 'Type', kwargs.Type); +% T.O = {rot90(mpo.O{1})}; end - function x = transferleft(mpo, mps1, mps2, x) - for w = 1:period(mps1) - x = applyleft(mpo.O{w}, mps1.AL(w), conj(mps2.AL(w)), x); + function H = AC_hamiltonian(mpo, mps, GL, GR) + arguments + mpo + mps + GL = fixedpoint(transfermatrix(mpo, mps, 'Type', 'LL')) + GR = fixedpoint(transfermatrix(mpo, mps, 'Type', 'RR').') end + GR = twist(GR, find(isdual(space(GR, nspaces(GR)))) + nspaces(GR)-1); + GL = twist(GL, find(isdual(space(GL, 1)))); + H = FiniteMpo(GL, mpo.O, GR); end - function x = transferright(mpo, mps1, mps2, x) - for w = period(mps1):-1:1 - x = applyright(mpo.O{w}, mps1.AR(w), conj(mps2.AR(w)), x); + function H = C_hamiltonian(mpo, mps, GL, GR) + arguments + mpo + mps + GL = fixedpoint(transfermatrix(mpo, mps, 'Type', 'LL')) + GR = fixedpoint(transfermatrix(mpo, mps, 'Type', 'RR').') end + GR = twist(GR, find(isdual(space(GR, nspaces(GR)))) + nspaces(GR)-1); + GL = twist(GL, find(isdual(space(GL, 1)))); + H = FiniteMpo(GL, {}, GR); end end @@ -134,11 +205,22 @@ else s = GradedSpace.new(Z2(0, 1), [1 1], false); O = fill_tensor(Tensor([s s], [s s]), ... - @(~,f) sqrt(prod(logical(f.uncoupled) .* sinh(beta) + ... + @(~, f) 2 * sqrt(prod(logical(f.uncoupled) .* sinh(beta) + ... ~logical(f.uncoupled) .* cosh(beta)))); end mpo = InfiniteMpo(O); end + + function mpo = fDimer() + pspace = GradedSpace.new(fZ2(0, 1), [1 1], false); + O = Tensor([pspace pspace], [pspace pspace]); + O1 = fill_tensor(O, @(~, f) ~any(f.uncoupled) || ... + (f.uncoupled(2) && sum(f.uncoupled) == 2)); + O2 = fill_tensor(O, @(~, f) ~any(f.uncoupled) || ... + (f.uncoupled(4) && sum(f.uncoupled) == 2)); + + mpo = InfiniteMpo([O1; O2]); + end end end diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index ff90714..328025d 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -86,6 +86,46 @@ t.scalars = t.scalars / a; end + function v = applychannel(O, L, R, v) + arguments + O MpoTensor + L MpsTensor + R MpsTensor + v + end + auxlegs_v = nspaces(v) - 3; + auxlegs_l = L.alegs; + auxlegs_r = R.alegs; + auxlegs = auxlegs_v + auxlegs_l + auxlegs_r; + + v = contract(v, [1 3 5 (-(1:auxlegs_v) - 3 - auxlegs_l)], ... + L, [-1 2 1 (-(1:auxlegs_l) - 3)], ... + O, [2 -2 4 3], ... + R, [5 4 -3 (-(1:auxlegs_r) - 3 - auxlegs_l - auxlegs_v)], ... + 'Rank', rank(v) + [0 auxlegs]); + end + + function v = applympo(varargin) + assert(nargin >= 3) + v = varargin{end}; + R = varargin{end-1}; + L = varargin{end-2}; + N = nargin - 1; + + auxlegs_v = nspaces(v) - N; + auxlegs_l = L.alegs; + auxlegs_r = R.alegs; + auxlegs = auxlegs_v + auxlegs_l + auxlegs_r; + + Oinds = cellfun(@(x) [2*x-2 -x 2*x 2*x-1], 2:N-1, 'UniformOutput', false); + O = [varargin(1:end-3); Oinds]; + v = contract(v, [1:2:2*N-1 ((-1:auxlegs_v) - N - auxlegs_l)], ... + L, [-1 2 1 (-(1:auxlegs_l) - N)], ... + O{:}, ... + R, [2*N-1 2*N-2 -N (-(1:auxlegs_r) - N - auxlegs_l - auxlegs_v)], ... + 'Rank', rank(v) + [0 auxlegs]); + end + function y = applyleft(O, T, B, x) arguments O MpoTensor @@ -157,6 +197,11 @@ end end + function O = rot90(O) + O.tensors = tpermute(O.tensors, [2 3 4 1], [2 2]); + O.scalars = permute(O.scalars, [2 3 4 1]); + end + function C = tensorprod(A, B, dimA, dimB, ca, cb, options) arguments A @@ -170,40 +215,50 @@ assert(~isa(A, 'MpoTensor') || ~isa(B, 'MpoTensor')); if isa(A, 'MpoTensor') - assert(sum(dimA == 1 | dimA == 3, 'all') == 1, ... - 'Cannot deduce output space unless leg 1 xor leg 3 is connected.'); - assert(sum(dimA == 2 | dimA == 4, 'all') == 1, ... - 'Cannot deduce output space unless leg 2 xor leg 4 is connected.'); - - C1 = tensorprod(A.tensors, B, dimA, dimB, ca, cb); - - % action of braiding tensor, always flip connected indices - uncA = 1:nspaces(A); uncA(dimA) = []; - uncB = 1:nspaces(B); uncB(dimB) = []; + C = tensorprod(A.tensors, B, dimA, dimB, ca, cb); - C2 = reshape(permute(A.scalars, [uncA flip(dimA)]), ... - [prod(size(A, uncA)) prod(size(A, dimA))]) * ... - tpermute(B, [dimB uncB], [length(dimB) length(uncB)]); - C = C1 + C2; + if nnz(A.scalars) > 0 + assert(sum(dimA == 1 | dimA == 3, 'all') == 1, ... + 'Cannot deduce output space unless leg 1 xor leg 3 is connected.'); + assert(sum(dimA == 2 | dimA == 4, 'all') == 1, ... + 'Cannot deduce output space unless leg 2 xor leg 4 is connected.'); + + uncA = 1:nspaces(A); uncA(dimA) = []; + uncB = 1:nspaces(B); uncB(dimB) = []; + + A = reshape(permute(A.scalars, [uncA flip(dimA)]), ... + [prod(size(A, uncA)) prod(size(A, dimA))]); + B = tpermute(B, [dimB uncB], [length(dimB) length(uncB)]); + C = C + A * B; + end else - assert(sum(dimB == 1 | dimB == 3, 'all') == 1, ... - 'Cannot deduce output space unless leg 1 xor leg 3 is connected.'); - assert(sum(dimB == 2 | dimB == 4, 'all') == 1, ... - 'Cannot deduce output space unless leg 2 xor leg 4 is connected.'); - - C1 = tensorprod(A, B.tensors, dimA, dimB, ca, cb); + C = tensorprod(A, B.tensors, dimA, dimB, ca, cb); - % action of braiding tensor, always flip connected indices - uncA = 1:nspaces(A); uncA(dimA) = []; - uncB = 1:nspaces(B); uncB(dimB) = []; - - C2 = tpermute(A, [uncA flip(dimA)], [length(uncA) length(dimA)]) * ... - reshape(permute(B.scalars, [flip(dimB) uncB]), ... - [prod(size(B, dimB)) prod(size(B, uncB))]); - C = C1 + C2; + if nnz(B.scalars) > 0 + assert(sum(dimB == 1 | dimB == 3, 'all') == 1, ... + 'Cannot deduce output space unless leg 1 xor leg 3 is connected.'); + assert(sum(dimB == 2 | dimB == 4, 'all') == 1, ... + 'Cannot deduce output space unless leg 2 xor leg 4 is connected.'); + + uncA = 1:nspaces(A); uncA(dimA) = []; + uncB = 1:nspaces(B); uncB(dimB) = []; + + A = tpermute(A, [uncA flip(dimA)], [length(uncA) length(dimA)]); + B = reshape(permute(B.scalars, [flip(dimB) uncB]), ... + [prod(size(B, dimB)) prod(size(B, uncB))]); + C = C + A * B; + end end - - + end + + function O = ctranspose(O) + O.tensors = tpermute(O.tensors', [4 1 2 3], [2 2]); + O.scalars = conj(permute(O.scalars, [1 4 3 2])); + end + + function O = transpose(O) + O.tensors = tpermute(O.tensors, [3 4 1 2], [2 2]); + O.scalars = permute(O.scalars, [3 4 1 2]); end % % function C = contract(tensors, indices, kwargs) diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index f1396fd..2099b50 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -1,6 +1,5 @@ classdef MpsTensor < Tensor - %MPSTENSOR Summary of this class goes here - % Detailed explanation goes here + % Generic mps tensor objects that have a notion of virtual, physical and auxiliary legs. properties plegs = 1 @@ -60,11 +59,16 @@ function [AL, L] = leftorth(A, alg) arguments A - alg = 'qr' + alg = 'polar' end if A.alegs == 0 [AL, L] = leftorth@Tensor(A, 1:nspaces(A)-1, nspaces(A), alg); + if isdual(space(L, 1)) == isdual(space(L, 2)) + L.codomain = conj(L.codomain); + L = twist(L, 1); + AL.domain = conj(AL.domain); + end else [AL, L] = leftorth@Tensor(A, [1:A.plegs+1 A.plegs+3], A.plegs+2, alg); AL = permute(AL, [1:A.plegs+1 A.plegs+3 A.plegs+2], rank(A)); @@ -84,7 +88,7 @@ kwargs.EigsInit = 3 kwargs.EigsFrequence = 2 end - +% % constants EIG_TOLFACTOR = 1/50; EIG_MAXTOL = 1e-4; @@ -98,10 +102,14 @@ A = arrayfun(@(a) MpsTensor(repartition(a, [nspaces(a)-1 1])), A); AL = A; + eta_best = Inf; + ctr_best = 0; + for ctr = 1:kwargs.MaxIter if ctr > kwargs.EigsInit && mod(ctr, kwargs.EigsFrequence) == 0 C_ = repartition(C(end), [2 0]); - [C_, ~] = eigsolve(@(x) applyleft(A, conj(AL), x), C_, ... + T = transfermatrix(A, AL); + [C_, ~] = eigsolve(T, C_, ... 1, 'largestabs', ... 'Tol', min(eta * EIG_TOLFACTOR, EIG_MAXTOL), ... 'KrylovDim', between(MINKRYLOVDIM, ... @@ -110,6 +118,10 @@ 'NoBuild', 4, ... 'Verbosity', kwargs.Verbosity - 1); [~, C(end)] = leftorth(C_, 1, 2, kwargs.Method); + if isdual(space(C(end), 2)) == isdual(space(C(end), 1)) + C(end).codomain = conj(C(end).codomain); + C(end) = twist(C(end), 1); + end end C_ = C(end); @@ -121,17 +133,26 @@ lambdas(w) = norm(C(w)); if kwargs.Normalize, C(w) = C(w) ./ lambdas(w); end end + try eta = norm(C_ - C(end), Inf); - + catch + bla + end if eta < kwargs.Tol if kwargs.Verbosity >= Verbosity.conv fprintf('Conv %2d:\terror = %0.4e\n', ctr, eta); end break; - else - if kwargs.Verbosity >= Verbosity.iter - fprintf('Iter %2d:\terror = %0.4e\n', ctr, eta); - end + elseif eta < eta_best + eta_best = eta; + ctr_best = ctr; + elseif ctr - ctr_best > 3 + warning('uniform_orthright:stagnate', 'Algorithm stagnated'); + break; + end + + if kwargs.Verbosity >= Verbosity.iter + fprintf('Iter %2d:\terror = %0.4e\n', ctr, eta); end end @@ -145,11 +166,17 @@ function [R, AR] = rightorth(A, alg) arguments A - alg = 'lq' + alg = 'rqpos' end [R, AR] = rightorth@Tensor(A, 1, 2:nspaces(A), alg); - AR = MpsTensor(repartition(AR, rank(A)), A.alegs); + if isdual(space(R, 1)) == isdual(space(R, 2)) + R.domain = conj(R.domain); + R = twist(R, 2); + AR.codomain = conj(AR.codomain); + end + +% AR = MpsTensor(repartition(AR, rank(A)), A.alegs); end function [AR, C, lambda, eta] = uniform_rightorth(A, C, kwargs) @@ -164,6 +191,13 @@ kwargs.EigsInit = 3 kwargs.EigsFrequence = 2 end + opts = namedargs2cell(kwargs); + [AR, C, lambda, eta] = uniform_leftorth(A', C', opts{:}); + AR = AR'; + C = C'; + lambda = conj(lambda); + return + % constants EIG_TOLFACTOR = 1/50; @@ -178,10 +212,14 @@ A = arrayfun(@(a) MpsTensor(repartition(a, [1 nspaces(a)-1])), A); AR = A; + eta_best = Inf; + ctr_best = 0; + for ctr = 1:kwargs.MaxIter if ctr > kwargs.EigsInit && mod(ctr, kwargs.EigsFrequence) == 0 C_ = repartition(C(end), [nspaces(C(end)) 0]); - [C_, ~] = eigsolve(@(x) applyright(A, conj(AR), x), C_, ... + T = transfermatrix(A, AR)'; + [C_, ~] = eigsolve(T, C_, ... 1, 'largestabs', ... 'Tol', min(eta * EIG_TOLFACTOR, EIG_MAXTOL), ... 'KrylovDim', between(MINKRYLOVDIM, ... @@ -189,7 +227,11 @@ MAXKRYLOVDIM), ... 'NoBuild', 4, ... 'Verbosity', kwargs.Verbosity - 1); - [C(end), ~] = rightorth(C_, 1, 2, kwargs.Method); + [C(end), ~] = rightorth(C_', 1, 2, kwargs.Method); + if isdual(space(C(end), 1)) + C.domain = conj(C.domain); + C = twist(C, 2); + end end C_ = C(end); @@ -208,10 +250,15 @@ fprintf('Conv %2d:\terror = %0.4e\n', ctr, eta); end break; - else - if kwargs.Verbosity >= Verbosity.iter - fprintf('Iter %2d:\terror = %0.4e\n', ctr, eta); - end + elseif eta < eta_best + eta_best = eta; + ctr_best = ctr; + elseif ctr - ctr_best > 3 + warning('uniform_orthright:stagnate', 'Algorithm stagnated'); + break; + end + if kwargs.Verbosity >= Verbosity.iter + fprintf('Iter %2d:\terror = %0.4e\n', ctr, eta); end end @@ -222,6 +269,37 @@ if nargout > 2, lambda = prod(lambdas); end end + function T = transfermatrix(A, B) + arguments + A MpsTensor + B MpsTensor = A + end + + + for i = length(A):-1:1 + B(i) = twist(B(i), [isdual(space(B(i), 1:2)) ~isdual(space(B(i), 3))]); + T(i, 1) = FiniteMpo(B(i)', {}, A(i)); + end + end + + function v = applytransfer(L, R, v) + arguments + L MpsTensor + R MpsTensor + v + end + + auxlegs_v = nspaces(v) - 2; + auxlegs_l = L.alegs; + auxlegs_r = R.alegs; + auxlegs = auxlegs_v + auxlegs_l + auxlegs_r; + + v = contract(v, [1 3 (-(1:auxlegs_v) - 2 - auxlegs_l)], ... + L, [-1 2 1 (-(1:auxlegs_l) - 2)], ... + R, [3 2 -2 (-(1:auxlegs_r) - 3 - auxlegs_l - auxlegs_v)], ... + 'Rank', rank(v) + [0 auxlegs]); + end + function rho = applyleft(T, B, rho) if nargin == 2 rho = []; @@ -322,23 +400,28 @@ end function A = multiplyleft(A, C) - A = MpsTensor(repartition(... - repartition(C, [1 1]) * repartition(A, [1 nspaces(A)-1]), ... - rank(A)), A.alegs); +% A = MpsTensor(repartition(... +% repartition(C, [1 1]) * repartition(A, [1 nspaces(A)-1]), ... +% rank(A)), A.alegs); % if ~isdual(space(C, 2)) % C = twist(C, 2); % end +% if isdual(space(A, 1)), C = twist(C, 2); end + A = MpsTensor(contract(C, [-1 1], A, [1 -2 -3], 'Rank', rank(A))); +% A = MpsTensor(tpermute(... +% multiplyright(MpsTensor(tpermute(A, [3 2 1])), tpermute(C, [2 1])), [3 2 1])); +% A = MpsTensor(multiplyright(A', C')'); % A = MpsTensor(contract(C, [-1 1], A, [1 -(2:nspaces(A))], 'Rank', rank(A))); end function A = multiplyright(A, C) - if A.alegs == 0 - A = MpsTensor(repartition(... - repartition(A, [nspaces(A)-1 1]) * repartition(C, [1 1]), ... - rank(A)), 0); - return - end - if isdual(space(C, 1)), C = twist(C, 1); end +% if A.alegs == 0 +% A = MpsTensor(repartition(... +% repartition(A, [nspaces(A)-1 1]) * repartition(C, [1 1]), ... +% rank(A)), 0); +% return +% end +% if isdual(space(C, 1)), C = twist(C, 1); end Alegs = nspaces(A); if A.alegs == 0 A = contract(A, [-(1:Alegs-1) 1], C, [1 -Alegs], 'Rank', rank(A)); diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index 15710d7..d65afb2 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -150,8 +150,8 @@ if kwargs.ComputeAC for i = 1:height(mps) for w = period(mps(i)):-1:1 - mps(i).AC(w) = multiplyleft(mps(i).AR(w), ... - mps(i).C(prev(w, period(mps(i))))); + mps(i).AC(w) = multiplyright(mps(i).AL(w), ... + mps(i).C(w)); end end end @@ -187,6 +187,30 @@ end end + function T = transfermatrix(mps1, mps2, sites, kwargs) + arguments + mps1 + mps2 = mps1 + sites = 1:period(mps1) + kwargs.Type {mustBeMember(kwargs.Type, {'LL' 'LR' 'RL' 'RR'})} = 'RR' + end + + assert(all(diff(sites) == 1), 'sites must be neighbouring and increasing.'); + + if kwargs.Type(1) == 'L' + A1 = mps1.AL(sites); + else + A1 = mps1.AR(sites); + end + if kwargs.Type(2) == 'L' + A2 = mps2.AL(sites); + else + A2 = mps2.AR(sites); + end + + T = transfermatrix(A1, A2); + end + function [V, D] = transfereigs(mps1, mps2, howmany, which, eigopts, kwargs) arguments mps1 @@ -199,41 +223,28 @@ eigopts.Tol = eps(underlyingType(mps1))^(3/4) kwargs.Verbosity = 0 kwargs.Type {mustBeMember(kwargs.Type, ... - {'l_LL' 'l_LR' 'l_RL' 'l_RR' 'r_LL' 'r_LR' 'r_RL' 'r_RR'})} = 'l_LL' + {'l_LL' 'l_LR' 'l_RL' 'l_RR' 'r_LL' 'r_LR' 'r_RL' 'r_RR'})} = 'r_RR' kwargs.Charge = [] end - % extract tensors - if strcmp(kwargs.Type(3), 'L'), T = mps1.AL; else, T = mps1.AR; end - if strcmp(kwargs.Type(4), 'L'), B = mps2.AL; else, B = mps2.AR; end + T = transfermatrix(mps1, mps2, 'Type', kwargs.Type(3:4)); + if kwargs.Type(1) == 'r', T = T'; end - % generate initial guess - if strcmp(kwargs.Type(1), 'l') - vspace1 = leftvspace(mps2, 1); - vspace2 = leftvspace(mps1, 1); - else - vspace1 = rightvspace(mps1, period(mps)); - vspace2 = rightvspace(mps2, period(mps)); - end - if isempty(kwargs.Charge) || isequal(kwargs.Charge, one(kwargs.Charge)) - v0 = Tensor.randnc([vspace1 vspace2'], []); + if ~isempty(kwargs.Charge) + Tdomain = domain(T); + auxspace = Tdomain.new(... + struct('charges', kwargs.Charge, 'degeneracies', 1), ... + false); + v0 = Tensor.randnc([Tdomain auxspace], []); else - dims = struct('charges', kwargs.Charge, ... - 'degeneracies', ones(size(kwargs.Charge))); - auxspace = vspace1.new(dims, false); - v0 = Tensor.randnc([vspace1 vspace2' auxspace], []); + v0 = []; end - % find eigenvectors eigkwargs = [fieldnames(eigopts).'; struct2cell(eigopts).']; - if strcmp(kwargs.Type(1), 'l') - [V, D] = eigsolve(@(x) applyleft(T, conj(B), x), v0, howmany, which, ... - eigkwargs{:}, 'Verbosity', kwargs.Verbosity); - else - [V, D] = eigsolve(@(x) applyright(T, conj(B), x), v0, howmany, which, ... - eigkwargs{:}, 'Verbosity', kwargs.Verbosity); - end + [V, D] = eigsolve(T, v0, howmany, which, ... + eigkwargs{:}, 'Verbosity', kwargs.Verbosity); + if kwargs.Type(1) == 'r', V = V'; end if nargout < 2, V = D; end end @@ -259,25 +270,33 @@ {'l_LL' 'l_LR' 'l_RL' 'l_RR' 'r_LL' 'r_LR' 'r_RL' 'r_RR'})} w = strcmp(type(1), 'l') * 1 + strcmp(type(1), 'r') * period(mps) end - ww = prev(w, period(mps)); switch type case 'l_RR' - rho = mps.C(ww)' * mps.C(ww); + rho = contract(mps.C(ww)', [-1 1], mps.C(ww), [1 -2], 'Rank', [1 1]); +% if isdual(space(rho, 1)), rho = twist(rho, 1); end case 'l_RL' rho = mps.C(ww); +% if isdual(space(rho, 1)), rho = twist(rho, 1); end case 'l_LR' rho = mps.C(ww)'; +% if isdual(space(rho, 1)), rho = twist(rho, 1); end case 'l_LL' rho = mps.C.eye(leftvspace(mps, w), leftvspace(mps, w)); + if isdual(space(rho, 1)), rho = twist(rho, 1); end + case 'r_RR' rho = mps.C.eye(rightvspace(mps, w)', rightvspace(mps, w)'); + if isdual(space(rho, 2)), rho = twist(rho, 2); end case 'r_RL' - rho = mps.C(w)'; + rho = twist(mps.C(w)', 2); +% if isdual(space(rho, 1)), rho = twist(rho, 2); end case 'r_LR' - rho = mps.C(w); + rho = twist(mps.C(w), 2); +% if ~isdual(space(rho, 2)), rho = twist(rho, 2); end case 'r_LL' - rho = mps.C(w) * mps.C(w)'; + rho = contract(mps.C(w), [-1 1], mps.C(w)', [1 -2], 'Rank', [1 1]); + rho = twist(rho, 2); end end diff --git a/src/tensors/AbstractTensor.m b/src/tensors/AbstractTensor.m index 9ffd288..bb755c5 100644 --- a/src/tensors/AbstractTensor.m +++ b/src/tensors/AbstractTensor.m @@ -252,6 +252,7 @@ 'tensors:ArgumentError', 'Invalid choice of eigenvalue selector.'); nargoutchk(0, 3); + x0_vec = vectorize(x0); sz = size(x0_vec); @@ -267,7 +268,7 @@ 'Tolerance', options.Tol, 'MaxIterations', options.MaxIter, ... 'SubspaceDimension', options.KrylovDim, 'IsFunctionSymmetric', ... options.IsSymmetric, 'StartVector', x0_vec, ... - 'Display', options.Verbosity == 3); + 'Display', options.Verbosity >= 3); if nargout <= 1 varargout = {D}; @@ -308,7 +309,6 @@ end assert(length(kwargs.Conj) == length(tensors)); - celldisp(indices); for i = 1:length(tensors) if length(indices{i}) > 1 assert(length(unique(indices{i})) == length(indices{i}), ... diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 26883e9..7175b45 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -125,14 +125,14 @@ end end - function t = fill_tensor(t, data, trees) + function t = fill_tensor(t, data) % Fill the tensor blocks of a tensor. % % Usage % ----- - % :code:`t = fill_tensor(t, tensors, trees)` + % :code:`t = fill_tensor(t, tensors)` % - % :code:`t = fill_tensor(t, fun, trees)` + % :code:`t = fill_tensor(t, fun)` % % Arguments % --------- @@ -144,9 +144,6 @@ % % fun : :class:`function_handle` % function of signature :code:`fun(dims, trees)` to fill with. - % - % trees : :class:`FusionTree` - % optional list of fusion trees to identify the tensor blocks. % % Returns % ------- @@ -156,7 +153,6 @@ arguments t data - trees = [] end if isnumeric(data), data = {data}; end @@ -475,36 +471,6 @@ function t = full(t) end -% function t = horzcat(varargin) -% sp = space(varargin{1}, [1, 3:nspaces(varargin{1})]); -% for i = 2:length(varargin) -% sp2 = space(varargin{2}, [1, 3:nspaces(varargin{1})]); -% assert(isequal(sp, sp2), 'tensors:SpaceMismatch', ... -% 'hozcat tensors may only differ in the second space.'); -% end -% t = builtin('horzcat', varargin{:}); -% end -% -% function t = vertcat(varargin) -% sp = space(varargin{1}, 2:nspaces(varargin{1})); -% for i = 2:length(varargin) -% sp2 = space(varargin{2}, 2:nspaces(varargin{1})); -% assert(isequal(sp, sp2), 'tensors:SpaceMismatch', ... -% 'vertcat tensors may only differ in the first space.'); -% end -% t = builtin('vertcat', varargin{:}); -% end -% -% function t = cat(dim, varargin) -% sp = space(varargin{1}, [1:dim-1 dim+1:nspaces(varargin{1})]); -% for i = 2:length(varargin) -% sp2 = space(varargin{2}, [1:dim-1 dim+1:nspaces(varargin{1})]); -% assert(isequal(sp, sp2), 'tensors:SpaceMismatch', ... -% 'cat tensors may only differ in the concatenated dimension.'); -% end -% t = builtin('cat', dim, varargin{:}); -% end - function tdst = insert_onespace(tsrc, i, dual) arguments tsrc @@ -564,8 +530,8 @@ 'tensors:SizeError', 'Incompatible sizes for vectorized function.'); % make everything a vector -% A = arrayfun(@repartition, A); -% B = arrayfun(@repartition, B); + A = arrayfun(@repartition, A); + B = arrayfun(@repartition, B); d = norm(A - B); end @@ -1110,7 +1076,8 @@ if ~isequal(A_.domain, B_.codomain) error('tensors:SpaceMismatch', ... 'Contracted spaces incompatible.\n%s\n%s', ... - string(A_.domain), string(B_.codomain)); + join(string(A_.domain), ' '), ... + join(string(B_.codomain), ' ')); end if ~isempty(A_.codomain) || ~isempty(B_.domain) med.C = Tensor.zeros(A_.codomain, B_.domain); @@ -1216,7 +1183,6 @@ function t = transpose(t, p, r) % Compute the transpose of a tensor. This is defined as rotating the domain to % the codomain and vice versa, while cyclicly permuting the tensor blocks. - % Currently not implemented. % % Usage % ----- @@ -1238,8 +1204,17 @@ % ------- % t : :class:`Tensor` % transposed output tensor. + if nargin < 2 + p = circshift(1:nspaces(t), length(t.domain)); + else + assert(iscircperm(p)); + end + + if nargin < 3 + r = flip(rank(t)); + end - error('tensors:TBA', 'This method has not been implemented.'); + t = tpermute(t, p, r); end function t = twist(t, i, inv) @@ -1441,7 +1416,7 @@ if strcmp(alg, 'polar') assert(isequal(V, prod(t.domain))); - W = t.domain; + W = V; elseif length(p1) == 1 && V == t.codomain W = t.codomain; elseif length(p2) == 1 && V == t.domain @@ -1521,7 +1496,7 @@ if strcmp(alg, 'polar') assert(isequal(V, prod(t.codomain)), ... 'linalg:polar', 'polar decomposition should lead to square R.'); - W = t.codomain; + W = V; elseif length(p1) == 1 && V == t.codomain W = t.codomain; elseif length(p2) == 1 && V == t.domain diff --git a/src/tensors/charges/Z2.m b/src/tensors/charges/Z2.m index 58a9376..84981d2 100644 --- a/src/tensors/charges/Z2.m +++ b/src/tensors/charges/Z2.m @@ -40,7 +40,7 @@ end function s = GetMD5_helper(data) - s = logical(data); + s = {'Z2' logical(data)}; end function c = mtimes(a, b) diff --git a/src/tensors/charges/fZ2.m b/src/tensors/charges/fZ2.m index 901acff..9c0da2b 100644 --- a/src/tensors/charges/fZ2.m +++ b/src/tensors/charges/fZ2.m @@ -42,6 +42,10 @@ function theta = twist(a) theta = -2 * double(a) + 1; end + + function s = GetMD5_helper(data) + s = {'fZ2' logical(data)}; + end end end diff --git a/src/tensors/kernels/AbstractBlock.m b/src/tensors/kernels/AbstractBlock.m index 5511b03..3e10122 100644 --- a/src/tensors/kernels/AbstractBlock.m +++ b/src/tensors/kernels/AbstractBlock.m @@ -57,6 +57,9 @@ med = MatrixBlock(codomain, domain); end end +% if ~strcmp(class(charges(codomain)), class(med(1).charge)) +% bla +% end X = med; end end diff --git a/src/tensors/spaces/GradedSpace.m b/src/tensors/spaces/GradedSpace.m index ec9672f..3635f02 100644 --- a/src/tensors/spaces/GradedSpace.m +++ b/src/tensors/spaces/GradedSpace.m @@ -96,7 +96,7 @@ end if isa(varargin{1}, 'AbstractCharge') - assert(mod(nargin, 3) == 0); + assert(mod(nargin, 3) == 0, 'Unknown caller syntax'); args = cell(2, nargin / 3); for i = 1:size(args, 2) args{1, i} = struct('charges', varargin{3 * i - 2}, ... @@ -419,10 +419,10 @@ function disp(spaces) spaces.dimensions.degeneracies.'), ', '); if spaces.dual - s = sprintf('%s Space (%d)*: %s', class(spaces.dimensions.charges), ... + s = sprintf("%s Space (%d)*: %s", class(spaces.dimensions.charges), ... dims(spaces), chargestring); else - s = sprintf('%s Space (%d): %s', class(spaces.dimensions.charges), ... + s = sprintf("%s Space (%d): %s", class(spaces.dimensions.charges), ... dims(spaces), chargestring); end end diff --git a/src/utility/linalg/tensorprod.m b/src/utility/linalg/tensorprod.m index 679f77d..49ea959 100644 --- a/src/utility/linalg/tensorprod.m +++ b/src/utility/linalg/tensorprod.m @@ -24,12 +24,12 @@ options.NumDimensionsA = ndims(A) end -persistent version -if isempty(version), version = ~isMATLABReleaseOlderThan("R2022a", "release", 1); end -if version - C = builtin('tensorprod', dimA, dimB, 'NumDimensionsA', options.NumDimensionsA); - return -end +% persistent version +% if isempty(version), version = ~isMATLABReleaseOlderThan("R2022a", "release", 1); end +% if version +% C = builtin('tensorprod', dimA, dimB, 'NumDimensionsA', options.NumDimensionsA); +% return +% end szA = size(A, 1:options.NumDimensionsA); diff --git a/src/utility/permutations/iscircperm.m b/src/utility/permutations/iscircperm.m new file mode 100644 index 0000000..0c1ce18 --- /dev/null +++ b/src/utility/permutations/iscircperm.m @@ -0,0 +1,12 @@ +function bool = iscircperm(p) + +bool = true; +for i = 1:length(p) + if isequal(circshift(p, i), 1:length(p)) + return + end +end +bool = false; + +end + diff --git a/src/utility/time2str.m b/src/utility/time2str.m new file mode 100644 index 0000000..1634e02 --- /dev/null +++ b/src/utility/time2str.m @@ -0,0 +1,33 @@ +function timeStr = time2str(time, unit, precision) +%TIMESTRING Returns string version of a duration in seconds. + +if nargin < 3, precision = 1; end + + +if nargin == 1 || isempty(unit) + if time >= 3600 + unit = 'h'; + elseif time >= 60 + unit = 'm'; + else + unit = 's'; + end +end + + +switch unit + case 'h' + time = time / 3600; + + case 'm' + time = time / 60; + +end + + +timeStr = sprintf('%0.*f%c', precision, time, unit); + + +end + + diff --git a/test/TestFiniteMpo.m b/test/TestFiniteMpo.m index 4027741..2b0177d 100644 --- a/test/TestFiniteMpo.m +++ b/test/TestFiniteMpo.m @@ -3,29 +3,39 @@ properties (TestParameter) mpo = struct('trivial0', ... - FiniteMpo(Tensor.randnc(ComplexSpace.new(2, false, 4, true, 2, false), []), ... - {}, Tensor.randnc(ComplexSpace.new(2, false, 4, false, 2, true), []))..., ... [2 4 2], [false true true]), {}, Tensor.randnc([2 4 2], [true false false])), ... - ...'trivial1', ... - ... FiniteMpo(Tensor.randnc([2 4 2]), {Tensor.randnc([4 2 4 2])}, ... - ... Tensor.randnc([2 4 2])) ... - ) + FiniteMpo.randnc(ComplexSpace.new(2, false, 3, false), ComplexSpace.new(4, false)), ... + 'trivial1', ... + FiniteMpo.randnc(ComplexSpace.new(2, false, 4, true, 2, true), ... + ComplexSpace.new(2, true, 3, false)) ... + ) end methods (Test) function testFixedpoints(tc, mpo) - + [V, D] = eigsolve(mpo); + tc.assertTrue(isapprox(mpo.apply(V), D * V)); end function testProperties(tc, mpo) mpo_tensor = Tensor(mpo); - tc.assertTrue(isequal(mpo.domain, mpo_tensor.domain)); - tc.assertTrue(isequal(mpo.codomain, mpo_tensor.codomain)); + tc.assertTrue(isequal(mpo.domain, mpo_tensor.domain), ... + 'domain should remain fixed after conversion.'); + tc.assertTrue(isequal(mpo.codomain, mpo_tensor.codomain), ... + 'codomain should remain fixed after conversion.'); v = initialize_fixedpoint(mpo); tc.assertTrue(isapprox(mpo.apply(v), mpo_tensor * v)); end function testTransposes(tc, mpo) + tc.assertTrue(isapprox(Tensor(mpo)', Tensor(mpo')), ... + 'ctranspose should not change mpo'); + tc.assertTrue(isequal(domain(mpo'), codomain(mpo))); + tc.assertTrue(isequal(codomain(mpo'), domain(mpo))); + tc.assertTrue(isapprox(Tensor(mpo).', Tensor(mpo.')), ... + 'transpose should not change mpo'); + tc.assertTrue(isequal(domain(mpo.'), codomain(mpo)')); + tc.assertTrue(isequal(codomain(mpo.'), domain(mpo)')); end end diff --git a/test/TestInfiniteMpo.m b/test/TestInfiniteMpo.m index 72e4737..d9a6708 100644 --- a/test/TestInfiniteMpo.m +++ b/test/TestInfiniteMpo.m @@ -15,24 +15,58 @@ methods (Test, ParameterCombination='sequential') function testEnvironments(tc, mpo, mps) - [GL, lambdaL] = leftenvironment(mpo, mps, mps); - tc.assertTrue(isapprox(transferleft(mpo, mps, mps, GL{1}), GL{1} * lambdaL)); - [GR, lambdaR] = rightenvironment(mpo, mps, mps); - tc.assertTrue(isapprox(transferright(mpo, mps, mps, GR{1}), GR{1} * lambdaR)); + + T = transfermatrix(mpo, mps); + [GL, lambdaL] = eigsolve(T, [], 1, 'largestabs'); + tc.assertTrue(isapprox(apply(T, GL), lambdaL * GL)); + + [GR, lambdaR] = eigsolve(T', [], 1, 'largestabs'); + tc.assertTrue(isapprox(apply(T', GR), lambdaR * GR)); N = period(mps); - for w = 1:N - tc.assertTrue(isapprox(... - applyleft(mpo.O{w}, mps.AL(w), conj(mps.AL(w)), GL{w}), ... - lambdaL^(1/N) * GL{next(w, N)})); - tc.assertTrue(isapprox(... - applyright(mpo.O{w}, mps.AR(w), conj(mps.AR(w)), GR{prev(w, N)}), ... - lambdaR^(1/N) * GR{w})); - end end function testDerivatives(tc, mpo, mps) + [GL, GR] = environments(mpo, mps, mps); + + H_AC = AC_hamiltonian(mpo, mps, GL, GR); + H_C = C_hamiltonian(mpo, mps, GL, GR); + [AC_, lambda] = eigsolve(H_AC, mps.AC, 1, 'largestabs'); + tc.assertTrue(isapprox(apply(H_AC, AC_), lambda * AC_)); + + [C_, lambda] = eigsolve(H_C, mps.C, 1, 'largestabs'); + tc.assertTrue(isapprox(apply(H_C, C_), lambda * C_)); + end + + function test2dIsing(tc) + beta = 0.95 * log(1 + sqrt(2)) / 2; + theta = 0:1e-6:pi/2; + x = 2 * sinh(2 * beta) / cosh(2 * beta)^2; + freeEnergyExact = -1 / beta * (log(2 * cosh(2 * beta)) + 1 / pi * ... + trapz(theta, log(1/2 * (1 + sqrt(1 - x^2 * sin(theta).^2))))); + + D = 64; + + mpo = InfiniteMpo.Ising(beta); + mps = UniformMps.randnc(CartesianSpace.new(2), CartesianSpace.new(D)); + [mps2, lambda] = fixedpoint(Vumps(), mpo, mps); + tc.assertEqual(-log(lambda) / beta, freeEnergyExact, 'RelTol', 1e-5); + + mps = UniformMps.randnc(GradedSpace.new(Z2(0, 1), [1 1], false), ... + GradedSpace.new(Z2(0, 1), [D D] ./ 2, false)); + mpo = InfiniteMpo.Ising(beta, 'Symmetry', 'Z2'); + [mps2, lambda] = fixedpoint(Vumps(), mpo, mps); + tc.assertEqual(-log(lambda) / beta, freeEnergyExact, 'RelTol', 1e-5); + end + + function test2dfDimer(tc) + D = 32; + mpo = block(InfiniteMpo.fDimer()); + mps = UniformMps.randnc(GradedSpace.new(fZ2(0, 1), [1 1], false), ... + GradedSpace.new(fZ2(0, 1), [D D], false)); + [mps2, lambda] = fixedpoint(Vumps('tol', 1e-4, 'maxiter', 25), mpo, mps); + tc.assertEqual(log(abs(lambda)) / 2, 0.29156, 'RelTol', 1e-4); end end end diff --git a/test/TestUniformMps.m b/test/TestUniformMps.m index 9025486..dff69ad 100644 --- a/test/TestUniformMps.m +++ b/test/TestUniformMps.m @@ -2,24 +2,39 @@ % Unit tests for uniform matrix product states. properties (TestParameter) - mps = struct(... - 'trivial', UniformMps.randnc(CartesianSpace.new(2), CartesianSpace.new(4)), ... - 'trivial2', UniformMps.randnc(CartesianSpace.new([2 2]), CartesianSpace.new([12 12])), ... - 'fermion1', UniformMps.randnc(GradedSpace.new(fZ2(0,1), [1 1], false), ... + A = struct(... + 'trivial', Tensor.randnc(CartesianSpace.new([4 2]), CartesianSpace.new(4)), ... +... 'trivial2', UniformMps.randnc(CartesianSpace.new([2 2]), CartesianSpace.new([12 12])), ... + 'fermion1', Tensor.randnc(... + GradedSpace.new(fZ2(0,1), [2 2], false, fZ2(0,1), [1 1], false), ... GradedSpace.new(fZ2(0,1), [2 2], false)), ... - 'fermion2', UniformMps.randnc(GradedSpace.new(fZ2(0,1), [1 1], false), ... + 'fermion2', Tensor.randnc(... + GradedSpace.new(fZ2(0,1), [2 2], true, fZ2(0,1), [1 1], false), ... GradedSpace.new(fZ2(0,1), [2 2], true)), ... - 'fermion3', UniformMps.randnc(GradedSpace.new(fZ2(0,1), [1 1], true), ... + 'fermion3', Tensor.randnc(... + GradedSpace.new(fZ2(0,1), [2 2], false, fZ2(0,1), [1 1], true), ... GradedSpace.new(fZ2(0,1), [2 2], false)), ... - 'fermion4', UniformMps.randnc(GradedSpace.new(fZ2(0,1), [1 1], true), ... - GradedSpace.new(fZ2(0,1), [2 2], true)), ... - 'haldane', UniformMps.randnc(GradedSpace.new(SU2(2), 1, false, SU2(2), 1, false), ... - GradedSpace.new(SU2(1:2:5), [5 3 2], false, SU2(2:2:6), [5 2 1], false)) ... + 'fermion4', Tensor.randnc(... + GradedSpace.new(fZ2(0,1), [2 2], true, fZ2(0,1), [1 1], true), ... + GradedSpace.new(fZ2(0,1), [2 2], true)) ... +... 'haldane', UniformMps.randnc(GradedSpace.new(SU2(2), 1, false, SU2(2), 1, false), ... +... GradedSpace.new(SU2(1:2:5), [5 3 2], false, SU2(2:2:6), [5 2 1], false)) ... ) end methods (Test) - function testCanonical(tc, mps) + function testCanonical(tc, A) + mps = UniformMps(A); + for i = 1:length(A) + tc.assertTrue(... + isequal(space(A(i)), space(mps.AL(i)), ... + space(mps.AR(i)), space(mps.AC(i)))); + end + T = transfermatrix(MpsTensor(A), mps.AL); + [v, d] = eigsolve(transfermatrix(MpsTensor(A), mps.AL), [], 1, 'largestabs'); + [v2, d2] = eigsolve(transfermatrix(MpsTensor(A), MpsTensor(A)), [], 1, 'largestabs'); + tc.verifyTrue(isapprox(d^2, d2), 'canonical changed state?'); + mps = canonicalize(mps); AL = mps.AL; AR = mps.AR; C = mps.C; AC = mps.AC; tc.assertTrue(all(isisometry(mps.AL, 'left')), ... @@ -28,39 +43,31 @@ function testCanonical(tc, mps) 'AR should be a right isometry.'); for w = 1:period(mps) - ALC = repartition(multiplyright(AL(w), C(w)), rank(AC(w))); - CAR = multiplyleft(AR(w), C(prev(w, period(mps)))); + ALC = multiplyright(AL(w), C(w)); + CAR = repartition(multiplyleft(AR(w), C(prev(w, period(mps)))), rank(AC(w))); tc.assertTrue(isapprox(ALC, AC(w)) && isapprox(AC(w), CAR), ... 'AL, AR, C and AC should be properly related.'); end end - function testDiagonalC(tc, mps) + function testDiagonalC(tc, A) + mps = UniformMps(A); mps2 = diagonalizeC(mps); f = fidelity(mps, mps2, 'Verbosity', Verbosity.diagnostics); tc.assertTrue(isapprox(f, 1), 'Diagonalizing C should not alter the state.'); end - function testFixedpoints(tc, mps) + function testFixedpoints(tc, A) + mps = UniformMps(A); for top = ["L" "R"] - if strcmp(top, "L") - T = mps.AL; - else - T = mps.AR; - end - for bot = ["L" "R"] - if strcmp(bot, "L") - B = mps.AL; - else - B = mps.AR; - end - + T = transfermatrix(mps, mps, 'Type', sprintf('%c%c', top, bot)); rhoL = fixedpoint(mps, sprintf('l_%c%c', top, bot)); - tc.assertTrue(isapprox(rhoL, applyleft(T, conj(B), rhoL)), ... - 'rho_left should be a fixed point.'); rhoR = fixedpoint(mps, sprintf('r_%c%c', top, bot)); - tc.assertTrue(isapprox(rhoR, applyright(T, conj(B), rhoR))); + tc.verifyTrue(isapprox(rhoL, T.apply(rhoL)), ... + sprintf('rho_left should be a %c%c fixed point.', top, bot)); + tc.verifyTrue(isapprox(rhoR, apply(T', rhoR)), ... + sprintf('rho_right should be a %c%c fixed point.', top, bot)); end end end From 7373fc5f48ef2843ea077ea31922ccc7789f132b Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 19 Oct 2022 02:34:23 +0200 Subject: [PATCH 046/245] add simple warning for degeneracies == 0 --- src/tensors/spaces/GradedSpace.m | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/tensors/spaces/GradedSpace.m b/src/tensors/spaces/GradedSpace.m index 3635f02..dac0e71 100644 --- a/src/tensors/spaces/GradedSpace.m +++ b/src/tensors/spaces/GradedSpace.m @@ -40,8 +40,14 @@ 'tensors:ArgumentError', ... 'Charges should be unique.'); - assert(all(dimensions{i}.degeneracies > 0), 'tensors:argerror', ... - 'degeneracies should be strictly positive.'); + assert(all(dimensions{i}.degeneracies >= 0), 'tensors:argerror', ... + 'degeneracies should be positive.'); + mask = dimensions{i}.degeneracies == 0; + if any(mask) + warning('degeneracies should be strictly positive, removing 0.'); + dimensions{i}.charges = dimensions{i}.charges(mask); + dimensions{i}.degeneracies = dimensions{i}.degeneracies(mask); + end [dimensions{i}.charges, I] = sort(dimensions{i}.charges); dimensions{i}.degeneracies = dimensions{i}.degeneracies(I); end From 055f019b64120e13562ee73dd532eb49d9a61792 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 19 Oct 2022 02:34:49 +0200 Subject: [PATCH 047/245] maybe bugfix productcharges --- src/tensors/charges/AbstractCharge.m | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/tensors/charges/AbstractCharge.m b/src/tensors/charges/AbstractCharge.m index bec1d32..a0612ca 100644 --- a/src/tensors/charges/AbstractCharge.m +++ b/src/tensors/charges/AbstractCharge.m @@ -707,7 +707,7 @@ if any(size(a) == numel(a)) && any(size(b) == numel(b)) lia = a == b; else - lia = a(:) == b(:); + lia = reshape(a == b, [], 1); end if ~any(lia) @@ -725,8 +725,9 @@ return end - [sortab, indsortab] = sort([a(:); b(:)]); - d = sortab(1:end-1) == sortab(2:end); + [sortab, indsortab] = sort([reshape(a, [], 1); reshape(b, [], 1)]); + d = subsref(sortab, substruct('()', {1:length(sortab)-1})) == ... + subsref(sortab, substruct('()', {2:length(sortab)})); ndx1 = indsortab(d); if nargout <= 1 From baaf477d486b414116e947215ec6f0fd194b5af7 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 19 Oct 2022 10:16:57 +0200 Subject: [PATCH 048/245] Orthogonalisation bugfix with empty tensors. --- src/tensors/Tensor.m | 10 ++++++++++ src/tensors/kernels/MatrixBlock.m | 10 ++++++++-- test/TestTensor.m | 6 +++--- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 7175b45..533aa74 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -1559,6 +1559,11 @@ dims.degeneracies(i) = size(Ns{i}, 2); end + mask = dims.degeneracies > 0; + dims.charges = dims.charges(mask); + dims.degeneracies = dims.degeneracies(mask); + Ns = Ns(mask); + N = t.eye(t.codomain, t.codomain.new(dims, false)); N.var = fill_matrix_data(N.var, Ns, dims.charges); end @@ -1610,6 +1615,11 @@ dims.degeneracies(i) = size(Ns{i}, 1); end + mask = dims.degeneracies > 0; + dims.charges = dims.charges(mask); + dims.degeneracies = dims.degeneracies(mask); + Ns = Ns(mask); + N = Tensor.eye(t.domain.new(dims, false), t.domain); N.var = fill_matrix_data(N.var, Ns, dims.charges); end diff --git a/src/tensors/kernels/MatrixBlock.m b/src/tensors/kernels/MatrixBlock.m index 88370b6..c682059 100644 --- a/src/tensors/kernels/MatrixBlock.m +++ b/src/tensors/kernels/MatrixBlock.m @@ -19,6 +19,12 @@ rank = [length(codomain) length(domain)]; trees = fusiontrees(codomain, domain); + if isempty(trees) + warning('tensors:empty', ... + 'No fusion channels available for the given spaces.'); + b = MatrixBlock.empty(0, 1); + return + end assert(~isempty(trees), 'tensors:empty', ... 'no fusion channels available for the given spaces.'); @@ -384,13 +390,13 @@ function b = fill_matrix_data(b, vars, charges) if nargin < 3 || isempty(charges) - assert(length(vars) == length(b)); + assert(length(vars) == length(b), ... + 'Invalid number of blocks'); for i = 1:length(b) b(i).var = vars{i}; end return end - [lia, locb] = ismember(charges, [b.charge]); assert(all(lia)); for i = 1:length(vars) diff --git a/test/TestTensor.m b/test/TestTensor.m index 4f43f3b..7f9dac3 100644 --- a/test/TestTensor.m +++ b/test/TestTensor.m @@ -26,8 +26,8 @@ function classSetup(tc) U1(0, 1, -1), [1 3 3], true), ... 'SU2', GradedSpace.new(SU2(1, 2), [3 1], false, SU2(1, 3), [2 1], false, ... SU2(2, 3), [1 1], true, SU2(1, 2), [2 2], false, SU2(1, 2, 4), [1 1 1], true), ... - 'A4', GradedSpace.new(A4(1:2), [2 1], false, A4([1 4]), [1 2], false, ... - A4(1:4), [2 1 3 1], true, A4(1:4), [2 2 1 2], false, A4(2:3), [1 2], true), ... + 'A4', GradedSpace.new(A4(1:2), [2 1], false, A4(1:4), [2 1 2 1], false, ... + A4([1 4]), [1 2], true, A4(1:4), [2 2 1 1], false, A4(2:4), [1 2 2], true), ... 'U1xSU2', GradedSpace.new(... ProductCharge(U1(-1:1), SU2(2,1,2)), [1 2 1], false, ... ProductCharge(U1(-2:2), SU2(1,2,1,2,1)), [2 1 2 2 2], true, ... @@ -36,8 +36,8 @@ function classSetup(tc) ProductCharge(U1(-1:1), SU2(2,1,2)), [1 2 2], false), ... 'Hubbard', GradedSpace.new(... ProductCharge(U1(0, 1, 2), SU2(1, 2, 1), fZ2(0, 1, 0)), [1 1 1], false, ... - ProductCharge(U1(0, 1), SU2(1, 2), fZ2(0, 1)), [1 1], true, ... ProductCharge(U1(-2:2), SU2(1, 2, 1, 2, 1), fZ2(0, 1, 0, 1, 0)), [1 1 3 1 1], false, ... + ProductCharge(U1(0, 1), SU2(1, 2), fZ2(0, 1)), [1 1], true, ... ProductCharge(U1(0, 1, 2), SU2(1, 2, 1), fZ2(0, 1, 0)), [1 1 1], false, ... ProductCharge(U1(0, 1), SU2(1, 2), fZ2(0, 1)), [1 1], true) ... ) From 17082327864c80cb7608408bea1e30c18ff93623 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 19 Oct 2022 10:17:16 +0200 Subject: [PATCH 049/245] utility functions --- src/utility/indices/next.m | 11 +++++++++++ src/utility/indices/prev.m | 10 ++++++++++ 2 files changed, 21 insertions(+) create mode 100644 src/utility/indices/next.m create mode 100644 src/utility/indices/prev.m diff --git a/src/utility/indices/next.m b/src/utility/indices/next.m new file mode 100644 index 0000000..b07a986 --- /dev/null +++ b/src/utility/indices/next.m @@ -0,0 +1,11 @@ +function j = next(i, total) +%NEXT Give the next index in a cyclic loop. +% j = next(i, total) +% gives the i + 1' th index, but loops back to 1 when j > total. +% +% See also prev + +j = mod(i, total) + 1; + +end + diff --git a/src/utility/indices/prev.m b/src/utility/indices/prev.m new file mode 100644 index 0000000..1318aaf --- /dev/null +++ b/src/utility/indices/prev.m @@ -0,0 +1,10 @@ +function j = prev(i, total) +%PREV Give the previous index in a cyclic loop. +% j = prev(i, total) +% gives the i - 1'th index, but loops back to total when j < 1. +% +% See also next + +j = mod(i - 2, total) + 1; + +end From 230789108945b2462a75d459145e812b1f3e68a5 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 19 Oct 2022 13:38:26 +0200 Subject: [PATCH 050/245] missing things --- src/tensors/spaces/CartesianSpace.m | 4 ++++ src/utility/between.m | 10 ++++++++++ 2 files changed, 14 insertions(+) create mode 100644 src/utility/between.m diff --git a/src/tensors/spaces/CartesianSpace.m b/src/tensors/spaces/CartesianSpace.m index 42cab5e..ca19135 100644 --- a/src/tensors/spaces/CartesianSpace.m +++ b/src/tensors/spaces/CartesianSpace.m @@ -180,6 +180,10 @@ style = FusionStyle.Unique; end + + function space = one(~) + space = CartesianSpace(1, []); + end end diff --git a/src/utility/between.m b/src/utility/between.m new file mode 100644 index 0000000..6559175 --- /dev/null +++ b/src/utility/between.m @@ -0,0 +1,10 @@ +function x = between(x1,x,x2) + +assert(x1 <= x2, 'range', 'x1 should be smaller than or equal to x2'); +if x < x1 + x = x1; +elseif x > x2 + x = x2; +end + +end \ No newline at end of file From 2ccc157574f7e08b16e14983e2758db8598b1fb4 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 19 Oct 2022 21:07:18 +0200 Subject: [PATCH 051/245] Prettier printing for ProductCharge --- src/tensors/charges/ProductCharge.m | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tensors/charges/ProductCharge.m b/src/tensors/charges/ProductCharge.m index f80ede4..62b1126 100644 --- a/src/tensors/charges/ProductCharge.m +++ b/src/tensors/charges/ProductCharge.m @@ -533,6 +533,7 @@ function disp(a) function s = string(a) charge_str = cellfun(@string, a.charges, 'UniformOutput', false); s = join(cat(ndims(a) + 1, charge_str{:}), ' x ', ndims(a) + 1); + s = compose("(%s)", s); end function a = one(a) From b3666ebc5470cf2fd9f0fdfcbc1c3b35d07fdc52 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 19 Oct 2022 23:28:59 +0200 Subject: [PATCH 052/245] update --- src/algorithms/Vumps.m | 17 +- src/mps/FiniteMpo.m | 31 +++- src/mps/InfJMpo.m | 152 ++++++++++++++++ src/mps/{InfiniteMpo.m => InfMpo.m} | 23 +-- src/mps/MpoTensor.m | 126 +++---------- src/mps/MpsTensor.m | 2 +- src/sparse/SparseTensor.m | 148 ++++++++++++++-- src/tensors/AbstractTensor.m | 23 +++ src/tensors/Tensor.m | 216 ++++++++++++++++------- src/tensors/spaces/CartesianSpace.m | 10 ++ src/tensors/spaces/GradedSpace.m | 2 +- src/utility/between.m | 2 +- src/utility/dim2str.m | 6 + src/utility/linalg/isapprox.m | 5 +- test/TestInfJMpo.m | 81 +++++++++ test/{TestInfiniteMpo.m => TestInfMpo.m} | 24 ++- 16 files changed, 662 insertions(+), 206 deletions(-) create mode 100644 src/mps/InfJMpo.m rename src/mps/{InfiniteMpo.m => InfMpo.m} (95%) create mode 100644 src/utility/dim2str.m create mode 100644 test/TestInfJMpo.m rename test/{TestInfiniteMpo.m => TestInfMpo.m} (80%) diff --git a/src/algorithms/Vumps.m b/src/algorithms/Vumps.m index e33cd5e..ab14d51 100644 --- a/src/algorithms/Vumps.m +++ b/src/algorithms/Vumps.m @@ -82,8 +82,6 @@ disp_iter(alg, iter, lambda, eta, toc(t_iter)); end - warning('vumps:maxiter', ... - 'Reached maximum amount of iterations without converging.'); disp_maxiter(alg, iter, lambda, eta, toc(t_total)); end end @@ -204,6 +202,21 @@ function disp_conv(alg, iter, lambda, eta, t) end fprintf('---------------\n'); end + + function disp_maxiter(alg, iter, lambda, eta, t) + if alg.verbosity < Verbosity.warn, return; end + s = settings; + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('MaxIter %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + otherwise + fprintf('MaxIter %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + + end + fprintf('---------------\n'); + end end end diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index 977779c..63e453a 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -48,7 +48,6 @@ end if isempty(v0), v0 = initialize_fixedpoint(mpo(1)); end - kwargs = namedargs2cell(options); [V, D, flag] = eigsolve(@(x) mpo.apply(x), v0, howmany, sigma, kwargs{:}); end @@ -102,6 +101,36 @@ fliplr(mpo(d).O), 'UniformOutput', false); end end + + function mpo = slice(mpo, i, j) + if strcmp(i, ':') + i = 1:size(mpo(end).O, 2); + end + if strcmp(j, ':') + j = 1:size(mpo(1).O{1}, 4); + end + assert(all(1 <= i) && all(i <= size(mpo(end).O{1}, 2))); + assert(all(1 <= j) && all(j <= size(mpo(1).O{1}, 4))); + + assert(depth(mpo) == 1, 'TBA'); + mpo.O{1} = mpo.O{1}(:, i, :, j); + end + + function bool = iszero(mpo) + if isempty(mpo.O) + bool = false; + return + end + bool = any(cellfun(@nnz, mpo.O) == 0); + end + + function bool = iseye(mpo) + if isempty(mpo.O) + bool = true; + return + end + bool = all(cellfun(@iseye, mpo.O)); + end % % function v = applyleft(mpo, v) % diff --git a/src/mps/InfJMpo.m b/src/mps/InfJMpo.m new file mode 100644 index 0000000..24443f9 --- /dev/null +++ b/src/mps/InfJMpo.m @@ -0,0 +1,152 @@ +classdef InfJMpo < InfMpo + % Infinite Mpo with a Jordan block structure + + methods + function mpo = InfJMpo(varargin) + mpo@InfMpo(varargin{:}); + if nargin > 0 + assert(istriu(mpo.O{1})); + assert(iseye(mpo.O{1}(1, 1, 1, 1)) && iseye(mpo.O{1}(end, 1, end, 1))); + assert(isconnected(mpo)); + end + end + + function bool = isconnected(mpo) + bool = true; + end + + function [GL, lambda] = leftenvironment(mpo, mps1, mps2, GL, eigopts) + arguments + mpo + mps1 + mps2 = mps1 + GL = [] + eigopts.KrylovDim = 30 + eigopts.MaxIter = 1000 + eigopts.ReOrth = 2 + eigopts.Tol = eps(underlyingType(mps1))^(3/4) + end + + T = transfermatrix(mpo, mps1, mps2, 'Type', 'LL'); + + if isempty(GL) + GL = SparseTensor.zeros(1, size(T.O{1}, 2), 1); + pSpace = pspace(mpo.O{1}); + GL(1) = insert_onespace(fixedpoint(mps1, 'l_LL'), ... + 2, ~isdual(pSpace(1))); + end + + for i = 2:size(mpo.O{1}, 1) + rhs = apply(slice(T, i, 1:i-1), GL(1, 1:i-1, 1)); + Tdiag = slice(T, i, i); + if iszero(Tdiag) + GL(i) = rhs; + elseif iseye(Tdiag) + fp_left = insert_onespace(fixedpoint(mps1, 'l_LL'), ... + 2, isdual(space(rhs, 2))); + fp_right = insert_onespace(fixedpoint(mps1, 'r_LL'), ... + 2, ~isdual(space(rhs, 2))); + lambda = contract(rhs, 1:3, fp_right, 3:-1:1); + + rhs = rhs - lambda * fp_left; + GL(i) = linsolve(@(x) x - apply(Tdiag, x), rhs, ... + GL(i)); + GL(i) = GL(i) - contract(GL(i), 1:3, fp_right, 3:-1:1) * fp_left; + else + GL(i) = linsolve(@(x) x - apply(Tdiag, x), rhs, ... + GL(i)); + end + end + end + + function [GR, lambda] = rightenvironment(mpo, mps1, mps2, GR, eigopts) + arguments + mpo + mps1 + mps2 = mps1 + GR = [] + eigopts.KrylovDim = 30 + eigopts.MaxIter = 1000 + eigopts.ReOrth = 2 + eigopts.Tol = eps(underlyingType(mps1))^(3/4) + end + + T = transfermatrix(mpo, mps1, mps2, 'Type', 'RR').'; + N = size(T.O{1}, 2); + if isempty(GR) + GR = SparseTensor.zeros(1, N, 1); + pSpace = pspace(mpo.O{1}); + GR(1, N, 1) = insert_onespace(fixedpoint(mps1, 'r_RR'), ... + 2, isdual(pSpace(end))); + end + + for i = N-1:-1:1 + rhs = apply(slice(T, i, i+1:N), GR(1, i+1:N, 1)); + Tdiag = slice(T, i, i); + if iszero(Tdiag) + GR(i) = rhs; + elseif iseye(Tdiag) + fp_left = insert_onespace(fixedpoint(mps1, 'l_RR'), ... + 2, ~isdual(space(rhs, 2))); + fp_right = insert_onespace(fixedpoint(mps1, 'r_RR'), ... + 2, isdual(space(rhs, 2))); + lambda = contract(rhs, 1:3, fp_left, 3:-1:1); + + rhs = rhs - lambda * fp_right; + GR(i) = linsolve(@(x) x - apply(Tdiag, x), rhs, ... + GR(i)); + GR(i) = GR(i) - contract(GR(i), 1:3, fp_left, 3:-1:1) * fp_right; + else + GR(i) = linsolve(@(x) x - apply(Tdiag, x), rhs, ... + GR(i)); + end + end + end + + end + + methods (Static) + function mpo = Ising(J, h, kwargs) + arguments + J = 1 + h = 1 + kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'Z2'})} = 'Z1' + end + + sigma_x = [0 1; 1 0]; + sigma_z = [1 0; 0 -1]; + + if strcmp(kwargs.Symmetry, 'Z1') + pSpace = CartesianSpace.new(2); + S = Tensor([one(pSpace) pSpace], [pSpace one(pSpace)]); + Sx = fill_matrix(S, sigma_x); + Sz = fill_matrix(S, sigma_z); + + O = MpoTensor.zeros(3, 1, 3, 1); + O(1, 1, 1, 1) = 1; + O(3, 1, 3, 1) = 1; + O(1, 1, 2, 1) = -J * Sx; + O(2, 1, 3, 1) = Sx; + O(1, 1, 3, 1) = (-J * h) * Sz; + + else + pSpace = GradedSpace.new(Z2(0, 1), [1 1], false); + vSpace = GradedSpace.new(Z2(1), 1, false); + trivSpace = one(pSpace); + + Sx_l = fill_matrix(Tensor([trivSpace pSpace], [pSpace vSpace]), {1 1}); + Sx_r = fill_matrix(Tensor([vSpace pSpace], [pSpace trivSpace]), {1 1}); + Sz = fill_matrix(Tensor([trivSpace pSpace], [pSpace trivSpace]), {1 -1}); + + O = MpoTensor.zeros(3, 1, 3, 1); + O(1, 1, 1, 1) = 1; + O(3, 1, 3, 1) = 1; + O(1, 1, 2, 1) = -J * Sx_l; + O(2, 1, 3, 1) = Sx_r; + O(1, 1, 3, 1) = (-J * h) * Sz; + end + + mpo = InfJMpo(O); + end + end +end diff --git a/src/mps/InfiniteMpo.m b/src/mps/InfMpo.m similarity index 95% rename from src/mps/InfiniteMpo.m rename to src/mps/InfMpo.m index 3689906..2dad8ea 100644 --- a/src/mps/InfiniteMpo.m +++ b/src/mps/InfMpo.m @@ -1,4 +1,4 @@ -classdef InfiniteMpo +classdef InfMpo % Infinite translation invariant matrix product operator. properties @@ -6,13 +6,13 @@ end methods - function mpo = InfiniteMpo(varargin) + function mpo = InfMpo(varargin) if nargin == 0, return; end if nargin == 1 O = varargin{1}; - if isa(O, 'InfiniteMpo') + if isa(O, 'InfMpo') for i = numel(O):1:1 mpo(i, 1).O = O(i, 1).O; end @@ -67,7 +67,7 @@ arguments mpo mps1 - mps2 + mps2 = [] GL = [] eigopts.KrylovDim = 30 eigopts.MaxIter = 1000 @@ -122,8 +122,12 @@ end overlap = sqrt(contract(GL, [1 3 2], mps1.C, [2 4], mps2.C', [5 1], GR, [4 3 5])); - GL = GL / overlap; - GR = GR / overlap; +% GL = GL / overlap; +% GR = GR / overlap; + end + + function s = pspace(mpo) + s = pspace(mpo.O{1}); end end %% Derived operators @@ -152,9 +156,6 @@ A2 = twist(A2, 1 + find(isdual(space(A1, 2:nspaces(A1)-1)))); T = FiniteMpo(A2', {rot90(mpo.O{1})}, A1); - -% T = transfermatrix(mps1, mps2, sites, 'Type', kwargs.Type); -% T.O = {rot90(mpo.O{1})}; end function H = AC_hamiltonian(mpo, mps, GL, GR) @@ -209,7 +210,7 @@ ~logical(f.uncoupled) .* cosh(beta)))); end - mpo = InfiniteMpo(O); + mpo = InfMpo(O); end function mpo = fDimer() @@ -220,7 +221,7 @@ O2 = fill_tensor(O, @(~, f) ~any(f.uncoupled) || ... (f.uncoupled(4) && sum(f.uncoupled) == 2)); - mpo = InfiniteMpo([O1; O2]); + mpo = InfMpo([O1; O2]); end end end diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index 328025d..099e953 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -94,8 +94,8 @@ v end auxlegs_v = nspaces(v) - 3; - auxlegs_l = L.alegs; - auxlegs_r = R.alegs; + auxlegs_l = nspaces(L) - 3; + auxlegs_r = nspaces(R) - 3; auxlegs = auxlegs_v + auxlegs_l + auxlegs_r; v = contract(v, [1 3 5 (-(1:auxlegs_v) - 3 - auxlegs_l)], ... @@ -228,12 +228,13 @@ A = reshape(permute(A.scalars, [uncA flip(dimA)]), ... [prod(size(A, uncA)) prod(size(A, dimA))]); - B = tpermute(B, [dimB uncB], [length(dimB) length(uncB)]); - C = C + A * B; + B = reshape(tpermute(B, [dimB uncB], [length(dimB) length(uncB)]), ... + [prod(size(B, dimB)) prod(size(B, uncB))]); + C = C + reshape(sparse(A) * B, size(C)); end else C = tensorprod(A, B.tensors, dimA, dimB, ca, cb); - + szC = size(C); if nnz(B.scalars) > 0 assert(sum(dimB == 1 | dimB == 3, 'all') == 1, ... 'Cannot deduce output space unless leg 1 xor leg 3 is connected.'); @@ -243,10 +244,11 @@ uncA = 1:nspaces(A); uncA(dimA) = []; uncB = 1:nspaces(B); uncB(dimB) = []; - A = tpermute(A, [uncA flip(dimA)], [length(uncA) length(dimA)]); + A = reshape(tpermute(A, [uncA flip(dimA)], [length(uncA) length(dimA)]), ... + [prod(size(A, uncA)) prod(size(A, dimA))]); B = reshape(permute(B.scalars, [flip(dimB) uncB]), ... [prod(size(B, dimB)) prod(size(B, uncB))]); - C = C + A * B; + C = C + reshape(A * sparse(B), size(C)); end end end @@ -260,99 +262,13 @@ O.tensors = tpermute(O.tensors, [3 4 1 2], [2 2]); O.scalars = permute(O.scalars, [3 4 1 2]); end -% -% function C = contract(tensors, indices, kwargs) -% arguments (Repeating) -% tensors -% indices (1, :) {mustBeInteger} -% end -% -% arguments -% kwargs.Conj (1, :) logical = false(size(tensors)) -% kwargs.Rank = [] -% kwargs.Debug = false -% end -% -% % replace mpo with tensor + scalar contribution -% i = find(cellfun(@(x) isa(x, 'MpoTensor'), tensors), 1); -% -% args1 = [tensors; indices]; -% args1{1, i} = args1{1, i}.tensors; -% conj1 = kwargs.Conj; -% -% args2 = [tensors([1:i-1 i+1:end]); indices([1:i-1 i+1:end])]; -% conj2 = kwargs.Conj([1:i-1 i+1:end]); -% -% C = contract(args1{:}, ... -% 'Conj', conj1, 'Rank', kwargs.Rank, 'Debug', kwargs.Debug) + ... -% contract(args2{:}, ... -% 'Conj', conj2, 'Rank', kwargs.Rank, 'Debug', kwargs.Debug); -% -% -% for ii = 1:length(tensors) -% if isa(tensors{ii}, 'MpoTensor') && strcmp(tensors{ii}.info, 'dense') -% tensors{ii} = tensors{ii}.var; -% end -% end -% -% % replace scalar mpo's and correct indices -% scalar = 1; -% mpoInds = cellfun(@(x) isa(x, 'MpoTensor'), tensors); -% -% % collect scalars -% for ii = 1:length(tensors) -% if mpoInds(ii) -% if kwargs.Conj(ii) -% scalar = scalar * conj(tensors{ii}.var); -% else -% scalar = scalar * tensors{ii}.var; -% end -% end -% end -% -% if scalar == 0 -% warning('Endresult is 0, probably could have been more efficient.'); -% end -% -% % remove mpo's from tensorlist -% tensors(mpoInds) = []; -% kwargs.Conj(mpoInds) = []; -% -% % correct indices -% toCorrect = indices(mpoInds); -% indices = indices(~mpoInds); -% -% for ii = 1:length(toCorrect) -% vertical = toCorrect{ii}([1 3]); -% horizont = toCorrect{ii}([2 4]); -% vertMax = max(vertical); -% vertMin = min(vertical); -% horMax = max(horizont); -% horMin = min(horizont); -% -% for jj = 1:length(indices) -% indices{jj}(indices{jj} == vertMax) = vertMin; -% indices{jj}(indices{jj} == horMax ) = horMin; -% end -% for jj = ii+1:length(toCorrect) -% toCorrect{jj}(toCorrect{jj} == vertMax) = vertMin; -% toCorrect{jj}(toCorrect{jj} == horMax ) = horMin; -% end -% end -% -% -% %% Perform contraction -% args = [tensors; indices]; -% C = contract(args{:}, ... -% 'Conj', kwargs.Conj, 'Rank', kwargs.Rank, 'Debug', kwargs.Debug); -% % [output, med] = Contract(tensors, indices, endcenter, med); -% if ~isequal(scalar, 1) -% C = C * scalar; -% end -% end end methods + function s = space(O, i) + s = space(O.tensors, i); + end + function s = pspace(O) s = space(O.tensors, 2); end @@ -372,6 +288,22 @@ s = space(O.tensors(:, :, lvls, :), 3); end end + + function bool = istriu(O) + sz = size(O, 1:4); + sz1 = sz(1) * sz(2); + sz2 = sz(3) * sz(4); + bool = istriu(reshape(O.scalars, [sz1, sz2])) && ... + istriu(reshape(O.tensors, [sz1, sz2])); + end + + function bool = iseye(O) + bool = nnz(O.tensors) == 0 && isequal(O.scalars, eye(size(O.scalars))); + end + + function n = nnz(O) + n = nnz(O.tensors) + nnz(O.scalars); + end end methods diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index 2099b50..c943856 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -25,7 +25,7 @@ if isempty(tensor) args = {}; else - args = {tensor}; + args = {full(tensor)}; end A@Tensor(args{:}); if ~isempty(tensor) diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index 1f280c7..62ea91f 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -72,7 +72,21 @@ t.sz = t.sz(p); end - function t = reshape(t, sz) + function t = reshape(t, varargin) + if nargin == 1 + sz = varargin{1}; + else + hasempty = find(cellfun(@isempty, varargin)); + if isempty(hasempty) + sz = [varargin{:}]; + elseif isscalar(hasempty) + varargin{hasempty} = 1; + sz = [varargin{:}]; + sz(hasempty) = round(numel(t) / prod(sz)); + else + error('Can only accept a single empty size index.'); + end + end assert(prod(sz) == prod(t.sz), ... 'sparse:argerror', 'To reshape the number of elements must not change.'); idx = sub2ind_(t.sz, t.ind); @@ -130,27 +144,36 @@ end end - function sz = size(a, i) + function varargout = size(a, i) if nargin == 1 sz = a.sz; - return + else + sz = ones(1, max(i)); + sz(1:length(a.sz)) = a.sz; + sz = sz(i); end - sz = ones(1, max(i)); - sz(1:length(a.sz)) = a.sz; - sz = sz(i); + if nargout <= 1 + varargout = {sz}; + else + varargout = num2cell(sz); + end + end + + function n = numel(t) + n = prod(t.sz); end function disp(t) nz = nnz(t); if nz == 0 fprintf('all-zero %s of size %s\n', class(t), ... - regexprep(mat2str(t.sz), {'\[', '\]', '\s+'}, {'', '', 'x'})); + dim2str(t.sz)); return end fprintf('%s of size %s with %d nonzeros:\n', class(t), ... - regexprep(mat2str(t.sz), {'\[', '\]', '\s+'}, {'', '', 'x'}), nz); + dim2str(t.sz), nz); spc = floor(log10(max(double(t.ind), [], 1))) + 1; if numel(spc) == 1 @@ -244,14 +267,19 @@ function disp(t) if isempty(ia), d = 0; return; end d = dot(a.var(ia), b.var(ib)); end - + function a = minus(a, b) a = a + (-b); end function c = mtimes(a, b) - szA = a.sz; - szB = b.sz; + if isscalar(a) || isscalar(b) + c = a .* b; + return + end + + szA = size(a); + szB = size(b); assert(length(szA) == 2 && length(szB) == 2, 'sparse:argerror', ... 'mtimes only defined for matrices.'); assert(szA(2) == szB(1), 'sparse:dimerror', ... @@ -316,7 +344,7 @@ function disp(t) if ~issparse(a) if nnz(b) > 0 idx = sub2ind_(b.sz, b.ind); - a(idx) = a(idx) + b.var; + a(idx) = reshape(a(idx), [], 1) + b.var; end return end @@ -335,6 +363,69 @@ function disp(t) a.ind = [a.ind; b.ind(~lia, :)]; end + function B = sum(A, dim) + arguments + A + dim = [] + end + + if isscalar(A) + B = A; + return + end + + if isempty(dim), dim = find(size(A) ~= 1, 1); end + + if strcmp(dim, 'all') + if nnz(A) == 0 + B = A; + B.sz = [1 1]; + return + end + + B = sum(A.var, 'all'); + return + end + + if isvector(A) + if nnz(A) == 0 + B = SparseTensor.zeros(1, 1); + else + B = sum(A.var, 'all'); + end + return + end + + if ismatrix(A) + if dim == 1 + B = SparseTensor.zeros(1, size(A, 2)); + n = nnz(A); + for i = 1:size(A, 2) + if n == 0, break; end + idx = A.ind(:, 2) == i; + if ~any(idx), continue; end + B(1, i) = sum(A.var(idx)); + n = n - sum(idx); + end + return + end + + if dim == 2 + B = SparseTensor.zeros(size(A, 1), 1); + n = nnz(A); + for i = 1:size(A, 2) + if n == 0, break; end + idx = A.ind(:, 1) == i; + if ~any(idx), continue; end + B(i, 1) = sum(A.var(idx)); + n = n - sum(idx); + end + return + end + end + error('TBA'); + end + function C = tensorprod(A, B, dimA, dimB, ca, cb, options) arguments A SparseTensor @@ -397,7 +488,7 @@ function disp(t) else Cvar = A.var.empty(0, 1); Cind = double.empty(0, length(uncA) + length(uncB)); - + if nnz(A) > 0 && nnz(B) > 0 for i = 1:size(A, 1) for j = 1:size(B, 2) @@ -420,7 +511,7 @@ function disp(t) end end end - + C = reshape(SparseTensor(Cind, Cvar, [size(A,1) size(B,2)]), szC); if size(Cind, 1) == prod(szC), C = full(C); end end @@ -486,6 +577,16 @@ function disp(t) t = permute(t, p); end + function t = repartition(t, r) + if nnz(t) > 0 + if nargin == 1 + t.var = arrayfun(@repartition, t.var); + else + t.var = arrayfun(@(x) repartition(x, r), t.var); + end + end + end + function t = twist(t, i) if nnz(t) > 0 t.var = twist(t.var, i); @@ -500,6 +601,17 @@ function disp(t) end end + methods + function bool = ismatrix(a) + bool = ndims(a) == 2; + end + + function bool = istriu(a) + assert(ismatrix(a), 'sparse:matrix', 'istriu is only defined for matrices'); + bool = all(a.ind(:, 1) <= a.ind(:, 2)); + end + end + %% Indexing methods @@ -577,6 +689,7 @@ function disp(t) end subsize(i) = length(s(1).subs{i}); end + if nnz(v) == 0, return; end if isscalar(v), v = repmat(v, subsize); end subs = combvec(s(1).subs{:}).'; @@ -643,5 +756,12 @@ function disp(t) t.var = t.var(p); end end + + methods (Static) + function t = zeros(varargin) + sz = [varargin{:}]; + t = SparseTensor([], [], sz); + end + end end diff --git a/src/tensors/AbstractTensor.m b/src/tensors/AbstractTensor.m index bb755c5..95b31cb 100644 --- a/src/tensors/AbstractTensor.m +++ b/src/tensors/AbstractTensor.m @@ -357,6 +357,29 @@ C = tpermute(C, order, kwargs.Rank); end end + + function d = distance(A, B) + % Compute the Euclidean distance between two tensors. + % + % Arguments + % --------- + % A, B : :class:`Tensor` + % + % Returns + % ------- + % d : numeric + % Euclidean distance, defined as the norm of the distance. + + n = max(ndims(A), ndims(B)); + assert(isequal(size(A, 1:n), size(B, 1:n)) || isscalar(A) || isscalar(B), ... + 'tensors:SizeError', 'Incompatible sizes for vectorized function.'); + + % make everything a vector + A = repartition(A); + B = repartition(B); + + d = norm(A - B); + end end end diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 533aa74..69b4090 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -423,7 +423,7 @@ end function n = nspaces(t) - n = length(t.domain) + length(t.codomain); + n = length(t(1).domain) + length(t(1).codomain); end function r = rank(t, i) @@ -514,27 +514,7 @@ %% Comparison methods - function d = distance(A, B) - % Compute the Euclidean distance between two tensors. - % - % Arguments - % --------- - % A, B : :class:`Tensor` - % - % Returns - % ------- - % d : numeric - % Euclidean distance, defined as the norm of the distance. - - assert(isequal(size(A), size(B)) || isscalar(A) || isscalar(B), ... - 'tensors:SizeError', 'Incompatible sizes for vectorized function.'); - - % make everything a vector - A = arrayfun(@repartition, A); - B = arrayfun(@repartition, B); - - d = norm(A - B); - end + end @@ -560,8 +540,6 @@ end end - - function t = ctranspose(t) % Compute the adjoint of a tensor. This is defined as swapping the codomain and % domain, while computing the adjoint of the matrix blocks. @@ -724,40 +702,65 @@ % C : :class:`Tensor` % output tensor. - if ~isscalar(A) || ~isscalar(B) - szA = size(A); - szB = size(B); - assert(szA(2) == szB(1)); + if isscalar(A) || isscalar(B) + C = A .* B; + return + end + + szA = size(A); + szB = size(B); + if szA(2) ~= szB(1) + error('mtimes:dimagree', ... + 'incompatible dimensions (%d) (%d)', szA(2), szB(1)); + end + + if isnumeric(A) + if issparse(A) && ~all(any(A, 2)) + C = SparseTensor.zeros(szA(1), szB(2)); + else + C = Tensor.empty(szA(1), szB(2), 0); + end - % Tensor multiplications - for i = szA(1):-1:1 - for j = szB(2):-1:1 - C(i,j) = A(i, 1) * B(1, j); - for k = 2:szA(2) - C(i,j) = C(i,j) + A(i, k) * B(k, j); - end + for i = 1:szA(1) + for j = 1:szB(2) + C(i, j) = sum(A(i, :).' .* B(:, j)); end end return end - if isnumeric(A) || isnumeric(B) - C = A .* B; + if isnumeric(B) + if issparse(B) && ~all(any(B, 1)) + C = SparseTensor.zeros(szA(1), szB(2)); + else + C = Tensor.empty(szA(1), szB(2), 0); + end + + for i = 1:szA(1) + for j = 1:szB(2) + try + C(i, j) = sum(A(i, :) .* B(:, j).'); + catch + bla + end + + end + end return - end + end - assert(isequal(A.domain, B.codomain), 'tensors:SpaceMismatch', ... - 'Multiplied spaces incompatible.'); - if ~isempty(A.codomain) || ~isempty(B.domain) - C = Tensor.zeros(A.codomain, B.domain); - C.var = mul(C.var, A.var, B.var); - else - Ablocks = matrixblocks(A.var); - Bblocks = matrixblocks(B.var); - C = horzcat(Ablocks{:}) * vertcat(Bblocks{:}); + % Tensor multiplications + for i = szA(1):-1:1 + for j = szB(2):-1:1 + C(i, j) = sum(reshape(A(i, :), [], 1) .* B(:, j)); + end end end + function n = nnz(A) + n = numel(A); + end + function n = norm(t, p) % Compute the matrix p-norm of a tensor. % @@ -815,7 +818,11 @@ function t1 = plus(t1, t2) if isnumeric(t1), t1 = t2 + t1; return; end - assert(isequal(size(t1), size(t2)), 'Incompatible sizes for vectorized plus.'); + if ~isequal(size(t1), size(t2)) + error('plus:dimagree', ... + 'Incompatible sizes for vectorized plus. (%s) (%s)', ... + dim2str(size(t1)), dim2str(size(t2))); + end for i = 1:numel(t1) if isnumeric(t2(i)) @@ -828,9 +835,10 @@ elseif iszero(t1(i)) t1(i) = t2(i); else - assert(isequal(t1(i).domain, t2(i).domain) && ... - isequal(t1(i).codomain, t2(i).codomain), 'tensors:SpaceMismatch', ... - 'Cannot add tensors of different structures.'); + if ~isequal(t1(i).domain, t2(i).domain) || ... + ~isequal(t1(i).codomain, t2(i).codomain) + error('plus:spacemismatch', 'Incompatible spaces.'); + end t1(i).var = t1(i).var + t2(i).var; end end @@ -922,8 +930,11 @@ r (1,2) = [nspaces(t) 0] end - assert(sum(r) == sum(rank(t)), 'tensors:ValueError', 'Invalid new rank.'); - t = tpermute(t, 1:nspaces(t), r); + for i = 1:numel(t) + t(i) = tpermute(t(i), 1:nspaces(t(i)), r); + assert(sum(r) == sum(rank(t(i))), ... + 'tensors:ValueError', 'Invalid new rank.'); + end end function t = rdivide(t, a) @@ -951,6 +962,45 @@ t.var = rdivide(t.var, a); end + function C = sum(A, dim) + arguments + A + dim = [] + end + + if isscalar(A), C = A; return; end + + if isempty(dim), dim = find(size(A) ~= 1, 1); end + + if strcmp(dim, 'all') + C = A(1); + for i = 2:numel(A) + C = C + A(i); + end + return + end + + if ismatrix(A) + if dim == 1 + C = A(1, :); + for i = 2:size(A, 1) + C = C + A(i, :); + end + return + end + + if dim == 2 + C = A(:, 1); + for i = 2:size(A, 2) + C = C + A(:, i); + end + return + end + end + + error('TBA'); + end + function C = tensorprod(A, B, dimA, dimB, ca, cb, options) % Compute the contraction of two tensors through the selected spaces. % @@ -1123,7 +1173,7 @@ C.var = mul(C.var, A.var, B.var); end - function t = times(t, a) + function C = times(A, B) % Scalar product of a tensor and a scalar. % % Usage @@ -1145,16 +1195,49 @@ % t : :class:`Tensor` % output tensor. - if isnumeric(t), [t, a] = swapvars(t, a); end - if isscalar(a) && ~isscalar(t) - a = repmat(a, size(t)); - else - assert(isequal(size(a), size(t)), 'tensors:dimerror', ... - 'input sizes incompatible.'); + if isscalar(A) && ~isscalar(B) + A = repmat(A, size(B)); + elseif isscalar(B) && ~isscalar(A) + B = repmat(B, size(A)); end - for i = 1:numel(t) - t(i).var = times(t(i).var, a(i)); + + assert(isequal(size(A), size(B)), ... + 'times:dimagree', 'incompatible dimensions.'); + + if isnumeric(A) + if issparse(A) + C = SparseTensor.zeros(size(A)); + I = find(A); + if isempty(I), return; end + C(I) = full(A(I)) .* B(I); + return + end + + C = B; + for i = 1:numel(C) + C(i).var = C(i).var .* A(i); + end + return + end + + if isnumeric(B) + C = B .* A; + return end + + for i = numel(A):-1:1 + assert(isequal(A(i).domain, B(i).codomain), 'tensors:SpaceMismatch', ... + 'Multiplied spaces incompatible.'); + if ~isempty(A.codomain) || ~isempty(B.domain) + C(i) = Tensor.zeros(A(i).codomain, B(i).domain); + C(i).var = mul(C(i).var, A(i).var, B(i).var); + else + Ablocks = matrixblocks(A(i).var); + Bblocks = matrixblocks(B(i).var); + C(i) = horzcat(Ablocks{:}) * vertcat(Bblocks{:}); + end + end + C = reshape(C, size(A)); end function tr = trace(t) @@ -1242,7 +1325,14 @@ inv = false end - if isempty(i) || ~any(i) || istwistless(braidingstyle(t)) + if isempty(i) || ~any(i) || istwistless(braidingstyle(t(1))) + return + end + + if numel(t) > 1 + for i = 1:numel(t) + t(i) = twist(t(i), i, inv); + end return end diff --git a/src/tensors/spaces/CartesianSpace.m b/src/tensors/spaces/CartesianSpace.m index ca19135..187a246 100644 --- a/src/tensors/spaces/CartesianSpace.m +++ b/src/tensors/spaces/CartesianSpace.m @@ -184,6 +184,16 @@ function space = one(~) space = CartesianSpace(1, []); end + + function spaces = insertone(spaces, i, ~) + arguments + spaces + i = length(spaces) + 1 + ~ + end + + spaces = [spaces(1:i-1) one(spaces) spaces(i:end)]; + end end diff --git a/src/tensors/spaces/GradedSpace.m b/src/tensors/spaces/GradedSpace.m index dac0e71..1542139 100644 --- a/src/tensors/spaces/GradedSpace.m +++ b/src/tensors/spaces/GradedSpace.m @@ -274,7 +274,7 @@ function spaces = insertone(spaces, i, dual) arguments spaces - i = length(spaces) + i = length(spaces) + 1 dual = false end diff --git a/src/utility/between.m b/src/utility/between.m index 6559175..944f40b 100644 --- a/src/utility/between.m +++ b/src/utility/between.m @@ -1,4 +1,4 @@ -function x = between(x1,x,x2) +function x = between(x1, x, x2) assert(x1 <= x2, 'range', 'x1 should be smaller than or equal to x2'); if x < x1 diff --git a/src/utility/dim2str.m b/src/utility/dim2str.m new file mode 100644 index 0000000..a403bc7 --- /dev/null +++ b/src/utility/dim2str.m @@ -0,0 +1,6 @@ +function s = dim2str(sz) + +s = regexprep(mat2str(sz), {'\[', '\]', '\s+'}, {'', '', 'x'}); + +end + diff --git a/src/utility/linalg/isapprox.m b/src/utility/linalg/isapprox.m index 91ea4c2..f131a6c 100644 --- a/src/utility/linalg/isapprox.m +++ b/src/utility/linalg/isapprox.m @@ -8,6 +8,7 @@ tol.AbsTol = 0 end -assert(all(size(A) == size(B)), 'Incompatible sizes'); -bool = distance(A, B) <= max(tol.AbsTol, tol.RelTol * max(norm(A(:)), norm(B(:)))); +% assert(all(size(A) == size(B)), 'Incompatible sizes'); +bool = distance(A, B) <= max(tol.AbsTol, tol.RelTol * ... + max(norm(reshape(A, 1, [])), norm(reshape(B, 1, [])))); end diff --git a/test/TestInfJMpo.m b/test/TestInfJMpo.m new file mode 100644 index 0000000..4edd888 --- /dev/null +++ b/test/TestInfJMpo.m @@ -0,0 +1,81 @@ +classdef TestInfJMpo < matlab.unittest.TestCase + % Unit tests for infinite matrix product operators. + + properties (TestParameter) + mpo = struct(... + 'trivial', InfJMpo.Ising() ... + ) + mps = struct(... + 'trivial', UniformMps.randnc(CartesianSpace.new(2), CartesianSpace.new(4)) ... + ) + end + + methods (Test, ParameterCombination='sequential') + function testEnvironments(tc, mpo, mps) + [GL, lambdaL] = leftenvironment(mpo, mps, mps); + tc.verifyTrue(isapprox(abs(lambdaL), abs(real(lambdaL)), 'RelTol', 1e-6), ... + sprintf('lambda should be real. (%-g i)', imag(lambdaL))); + T = transfermatrix(mpo, mps, mps, 'Type', 'LL'); + +% for i = 1:size(mpo.O{1}, 1) +% tc.assertTrue(isapprox(apply(slice(T, i, 1:i-1), GL(1:i-1)), ... +% apply(slice(T, i, i), GL(i)))); +% end + GL_ = apply(T, GL); + for i = 1:numel(GL_) + tc.verifyTrue(isapprox(GL_(i), GL(i)), ... + sprintf('GL(%d) disagrees', i)); + end +% tc.assertTrue(isapprox(apply(T, GL), GL)); + + [GR, lambdaR] = rightenvironment(mpo, mps, mps); + tc.verifyTrue(isapprox(abs(lambdaR), abs(real(lambdaR)), 'RelTol', 1e-6), ... + sprintf('lambda should be real. (%gi)', imag(lambdaR))); + T = transfermatrix(mpo, mps, mps, 'Type', 'RR'); + GR_ = apply(T.', GR); + for i = 1:numel(GR_) + tc.verifyTrue(isapprox(GR_(i), GR(i)), ... + sprintf('GR(%d) disagrees', i)); + end + + tc.verifyTrue(isapprox(lambdaL, lambdaR), 'lambdas should be equal.') + end + + function testDerivatives(tc, mpo, mps) + [GL, GR] = environments(mpo, mps, mps); + + H_AC = AC_hamiltonian(mpo, mps, GL, GR); + H_C = C_hamiltonian(mpo, mps, GL, GR); + + [AC_, lambda] = eigsolve(H_AC, mps.AC, 1, 'largestabs'); + tc.assertTrue(isapprox(apply(H_AC, AC_), lambda * AC_)); + + [C_, lambda] = eigsolve(H_C, mps.C, 1, 'largestabs'); + tc.assertTrue(isapprox(apply(H_C, C_), lambda * C_)); + end + + function test1dIsing(tc) + alg = Vumps('which', 'smallestreal', 'maxiter', 20, 'verbosity', Verbosity.iter); + D = 16; + mpo = InfJMpo.Ising(1, 1); + mps = UniformMps.randnc(CartesianSpace.new(2), CartesianSpace.new(D)); + [mps2, lambda] = fixedpoint(alg, mpo, mps); + profile on + mpo = InfJMpo.Ising(1, 1, 'Symmetry', 'Z2'); + mps = UniformMps.randnc(pspace(mpo), ... + GradedSpace.new(Z2(0, 1), [D D] ./ 2, false)); + [mps2, lambda2] = fixedpoint(alg, mpo, mps); + profile viewer + end + + function test2dfDimer(tc) + D = 32; + mpo = block(InfMpo.fDimer()); + mps = UniformMps.randnc(GradedSpace.new(fZ2(0, 1), [1 1], false), ... + GradedSpace.new(fZ2(0, 1), [D D], false)); + [mps2, lambda] = fixedpoint(Vumps('tol', 1e-4, 'maxiter', 25), mpo, mps); + tc.assertEqual(log(abs(lambda)) / 2, 0.29156, 'RelTol', 1e-4); + end + end +end + diff --git a/test/TestInfiniteMpo.m b/test/TestInfMpo.m similarity index 80% rename from test/TestInfiniteMpo.m rename to test/TestInfMpo.m index d9a6708..b1fe42c 100644 --- a/test/TestInfiniteMpo.m +++ b/test/TestInfMpo.m @@ -1,10 +1,10 @@ -classdef TestInfiniteMpo < matlab.unittest.TestCase +classdef TestInfMpo < matlab.unittest.TestCase % Unit tests for infinite matrix product operators. properties (TestParameter) mpo = struct(... - 'trivial', InfiniteMpo.Ising(), ... - 'Z2', InfiniteMpo.Ising('Symmetry', 'Z2') ... + 'trivial', InfMpo.Ising(), ... + 'Z2', InfMpo.Ising('Symmetry', 'Z2') ... ) mps = struct(... 'trivial', UniformMps.randnc(CartesianSpace.new(2), CartesianSpace.new(4)), ... @@ -15,15 +15,13 @@ methods (Test, ParameterCombination='sequential') function testEnvironments(tc, mpo, mps) - - T = transfermatrix(mpo, mps); - [GL, lambdaL] = eigsolve(T, [], 1, 'largestabs'); + [GL, lambdaL] = leftenvironment(mpo, mps, mps); + T = transfermatrix(mpo, mps, mps, 'Type', 'LL'); tc.assertTrue(isapprox(apply(T, GL), lambdaL * GL)); - [GR, lambdaR] = eigsolve(T', [], 1, 'largestabs'); - tc.assertTrue(isapprox(apply(T', GR), lambdaR * GR)); - - N = period(mps); + [GR, lambdaR] = rightenvironment(mpo, mps, mps); + T = transfermatrix(mpo, mps, mps, 'Type', 'RR'); + tc.assertTrue(isapprox(apply(T.', GR), lambdaR * GR)); end function testDerivatives(tc, mpo, mps) @@ -48,21 +46,21 @@ function test2dIsing(tc) D = 64; - mpo = InfiniteMpo.Ising(beta); + mpo = InfMpo.Ising(beta); mps = UniformMps.randnc(CartesianSpace.new(2), CartesianSpace.new(D)); [mps2, lambda] = fixedpoint(Vumps(), mpo, mps); tc.assertEqual(-log(lambda) / beta, freeEnergyExact, 'RelTol', 1e-5); mps = UniformMps.randnc(GradedSpace.new(Z2(0, 1), [1 1], false), ... GradedSpace.new(Z2(0, 1), [D D] ./ 2, false)); - mpo = InfiniteMpo.Ising(beta, 'Symmetry', 'Z2'); + mpo = InfMpo.Ising(beta, 'Symmetry', 'Z2'); [mps2, lambda] = fixedpoint(Vumps(), mpo, mps); tc.assertEqual(-log(lambda) / beta, freeEnergyExact, 'RelTol', 1e-5); end function test2dfDimer(tc) D = 32; - mpo = block(InfiniteMpo.fDimer()); + mpo = block(InfMpo.fDimer()); mps = UniformMps.randnc(GradedSpace.new(fZ2(0, 1), [1 1], false), ... GradedSpace.new(fZ2(0, 1), [D D], false)); [mps2, lambda] = fixedpoint(Vumps('tol', 1e-4, 'maxiter', 25), mpo, mps); From 02d3fe743d3beb678e0a1f416a5b344e877cdf10 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 20 Oct 2022 02:35:51 +0200 Subject: [PATCH 053/245] multisite vumps? --- src/algorithms/Vumps.m | 40 +++++++++++------- src/mps/FiniteMpo.m | 26 ++++++++---- src/mps/InfJMpo.m | 74 ++++++++++++++++++++------------- src/mps/InfMpo.m | 87 ++++++++++++++++++++++++++++++--------- src/mps/MpoTensor.m | 12 ++++++ src/mps/MpsTensor.m | 12 +++--- src/mps/UniformMps.m | 8 ++++ src/sparse/SparseTensor.m | 81 ++++++++++++++++-------------------- src/tensors/Tensor.m | 9 +--- src/tensors/charges/Z2.m | 2 +- src/tensors/charges/fZ2.m | 4 -- test/TestInfJMpo.m | 19 +++------ test/TestInfMpo.m | 16 ++++--- 13 files changed, 240 insertions(+), 150 deletions(-) diff --git a/src/algorithms/Vumps.m b/src/algorithms/Vumps.m index ab14d51..c92b333 100644 --- a/src/algorithms/Vumps.m +++ b/src/algorithms/Vumps.m @@ -92,21 +92,26 @@ function AC = updateAC(alg, mpo, mps, GL, GR) kwargs = namedargs2cell(alg.alg_eigs); H_AC = AC_hamiltonian(mpo, mps, GL, GR); - [AC, ~] = eigsolve(H_AC, mps.AC, 1, alg.which, kwargs{:}); + for i = period(mps):-1:1 + [AC(i), ~] = eigsolve(H_AC{i}, mps.AC(i), 1, alg.which, kwargs{:}); + end end function C = updateC(alg, mpo, mps, GL, GR) kwargs = namedargs2cell(alg.alg_eigs); H_C = C_hamiltonian(mpo, mps, GL, GR); - [C, ~] = eigsolve(H_C, mps.C, 1, alg.which, kwargs{:}); + for i = period(mps):-1:1 + [C(i), ~] = eigsolve(H_C{i}, mps.C(i), 1, alg.which, kwargs{:}); + end end function mps = updatemps(alg, AC, C) - [~, Q_AC] = rightorth(AC); - [~, Q_C] = rightorth(C, 1, 2); - - AR = contract(conj(Q_C), [1 -1], Q_AC, [1 -2 -3], 'Rank', [1 2]); - + for i = length(AC):-1:1 + [~, Q_AC] = rightorth(AC(i)); + [~, Q_C] = rightorth(C(i), 1, 2); + + AR(i) = contract(conj(Q_C), [1 -1], Q_AC, [1 -2 -3], 'Rank', [1 2]); + end mps = UniformMps([], AR, C, []); kwargs = namedargs2cell(alg.alg_canonical); mps = canonicalize(mps, kwargs{:}); @@ -127,15 +132,22 @@ end function eta = convergence(alg, mpo, mps, GL, GR) - AC_ = apply(AC_hamiltonian(mpo, mps, GL, GR), mps.AC); - lambda_AC = dot(AC_, mps.AC); - AC_ = normalize(AC_ ./ lambda_AC); + H_AC = AC_hamiltonian(mpo, mps, GL, GR); + H_C = C_hamiltonian(mpo, mps, GL, GR); + eta = zeros(1, period(mps)); + for w = 1:period(mps) + AC_ = apply(H_AC{w}, mps.AC(w)); + lambda_AC = dot(AC_, mps.AC(w)); + AC_ = normalize(AC_ ./ lambda_AC); - C_ = apply(C_hamiltonian(mpo, mps, GL, GR), mps.C); - lambda_C = dot(C_, mps.C); - C_ = normalize(C_ ./ lambda_C); + C_ = apply(H_C{w}, mps.C(w)); + lambda_C = dot(C_, mps.C(w)); + C_ = normalize(C_ ./ lambda_C); - eta = distance(AC_ , repartition(multiplyleft(mps.AR, C_), rank(AC_))); + eta(w) = distance(AC_ , ... + repartition(multiplyleft(mps.AR(w), C_), rank(AC_))); + end + eta = max(eta, [], 'all'); end end diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index 63e453a..43938ed 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -53,10 +53,14 @@ end function v = initialize_fixedpoint(mpo) - v = Tensor.randnc(domain(mpo), []); + N = prod(cellfun(@(x) size(x, 4), mpo.O)); + for i = N:-1:1 + v(i) = Tensor.randnc(domain(slice(mpo, i, 1:N)), []); + end end function s = domain(mpo) + N = prod(cellfun(@(x) size(x, 4), mpo.O)); s = conj(... [rightvspace(mpo(1).L) cellfun(@(x) pspace(x)', mpo(1).O) leftvspace(mpo(1).R)]); end @@ -112,24 +116,30 @@ assert(all(1 <= i) && all(i <= size(mpo(end).O{1}, 2))); assert(all(1 <= j) && all(j <= size(mpo(1).O{1}, 4))); - assert(depth(mpo) == 1, 'TBA'); - mpo.O{1} = mpo.O{1}(:, i, :, j); + mpo(end).O{1} = mpo(end).O{1}(:, i, :, :); + mpo(1).O{1} = mpo(1).O{1}(:, :, :, j); end function bool = iszero(mpo) - if isempty(mpo.O) + if isempty(mpo(1).O) bool = false; return end - bool = any(cellfun(@nnz, mpo.O) == 0); + for i = 1:depth(mpo) + bool = any(cellfun(@nnz, mpo(i).O) == 0); + if bool, return; end + end end - function bool = iseye(mpo) - if isempty(mpo.O) + function bool = iseye(mpo, i) + if isempty(mpo(1).O) bool = true; return end - bool = all(cellfun(@iseye, mpo.O)); + for d = 1:depth(mpo) + bool = all(cellfun(@(x) iseye(x(:,i,:,i)), mpo(d).O)); + if ~bool, return; end + end end % % function v = applyleft(mpo, v) diff --git a/src/mps/InfJMpo.m b/src/mps/InfJMpo.m index 24443f9..d323d7f 100644 --- a/src/mps/InfJMpo.m +++ b/src/mps/InfJMpo.m @@ -20,7 +20,7 @@ mpo mps1 mps2 = mps1 - GL = [] + GL = cell(1, period(mps1)) eigopts.KrylovDim = 30 eigopts.MaxIter = 1000 eigopts.ReOrth = 2 @@ -29,19 +29,20 @@ T = transfermatrix(mpo, mps1, mps2, 'Type', 'LL'); - if isempty(GL) - GL = SparseTensor.zeros(1, size(T.O{1}, 2), 1); - pSpace = pspace(mpo.O{1}); - GL(1) = insert_onespace(fixedpoint(mps1, 'l_LL'), ... + if isempty(GL) || isempty(GL{1}) + GL = cell(1, period(mps1)); + GL{1} = SparseTensor.zeros(1, size(T(1).O{1}, 2), 1); + pSpace = space(T(1).O{1}(:,:,:,1), 4); + GL{1}(1) = insert_onespace(fixedpoint(mps1, 'l_LL'), ... 2, ~isdual(pSpace(1))); end - for i = 2:size(mpo.O{1}, 1) - rhs = apply(slice(T, i, 1:i-1), GL(1, 1:i-1, 1)); + for i = 2:size(GL{1}, 2) + rhs = apply(slice(T, i, 1:i-1), GL{1}(1, 1:i-1, 1)); Tdiag = slice(T, i, i); if iszero(Tdiag) - GL(i) = rhs; - elseif iseye(Tdiag) + GL{1}(i) = rhs; + elseif iseye(T, i) fp_left = insert_onespace(fixedpoint(mps1, 'l_LL'), ... 2, isdual(space(rhs, 2))); fp_right = insert_onespace(fixedpoint(mps1, 'r_LL'), ... @@ -49,14 +50,20 @@ lambda = contract(rhs, 1:3, fp_right, 3:-1:1); rhs = rhs - lambda * fp_left; - GL(i) = linsolve(@(x) x - apply(Tdiag, x), rhs, ... - GL(i)); - GL(i) = GL(i) - contract(GL(i), 1:3, fp_right, 3:-1:1) * fp_left; + GL{1}(i) = linsolve(@(x) x - apply(Tdiag, x), rhs, ... + GL{1}(i)); + GL{1}(i) = GL{1}(i) - ... + contract(GL{1}(i), 1:3, fp_right, 3:-1:1) * fp_left; else - GL(i) = linsolve(@(x) x - apply(Tdiag, x), rhs, ... - GL(i)); + GL{1}(i) = linsolve(@(x) x - apply(Tdiag, x), rhs, ... + GL{1}(i)); end end + + for w = 1:period(mps1)-1 + T = transfermatrix(mpo, mps1, mps2, w, 'Type', 'LL'); + GL{next(w, period(mps1))} = apply(T, GL{w}); + end end function [GR, lambda] = rightenvironment(mpo, mps1, mps2, GR, eigopts) @@ -64,7 +71,7 @@ mpo mps1 mps2 = mps1 - GR = [] + GR = cell(1, period(mps1)) eigopts.KrylovDim = 30 eigopts.MaxIter = 1000 eigopts.ReOrth = 2 @@ -72,20 +79,21 @@ end T = transfermatrix(mpo, mps1, mps2, 'Type', 'RR').'; - N = size(T.O{1}, 2); - if isempty(GR) - GR = SparseTensor.zeros(1, N, 1); - pSpace = pspace(mpo.O{1}); - GR(1, N, 1) = insert_onespace(fixedpoint(mps1, 'r_RR'), ... + N = size(T(1).O{1}, 2); + if isempty(GR) || isempty(GR{1}) + GR = cell(1, period(mps1)); + GR{1} = SparseTensor.zeros(1, N, 1); + pSpace = space(T(1).O{1}(:, end, :, :), 2); + GR{1}(1, N, 1) = insert_onespace(fixedpoint(mps1, 'r_RR'), ... 2, isdual(pSpace(end))); end for i = N-1:-1:1 - rhs = apply(slice(T, i, i+1:N), GR(1, i+1:N, 1)); + rhs = apply(slice(T, i, i+1:N), GR{1}(1, i+1:N, 1)); Tdiag = slice(T, i, i); if iszero(Tdiag) - GR(i) = rhs; - elseif iseye(Tdiag) + GR{1}(i) = rhs; + elseif iseye(T, i) fp_left = insert_onespace(fixedpoint(mps1, 'l_RR'), ... 2, ~isdual(space(rhs, 2))); fp_right = insert_onespace(fixedpoint(mps1, 'r_RR'), ... @@ -93,16 +101,26 @@ lambda = contract(rhs, 1:3, fp_left, 3:-1:1); rhs = rhs - lambda * fp_right; - GR(i) = linsolve(@(x) x - apply(Tdiag, x), rhs, ... - GR(i)); - GR(i) = GR(i) - contract(GR(i), 1:3, fp_left, 3:-1:1) * fp_right; + GR{1}(i) = linsolve(@(x) x - apply(Tdiag, x), rhs, ... + GR{1}(i)); + GR{1}(i) = GR{1}(i) - ... + contract(GR{1}(i), 1:3, fp_left, 3:-1:1) * fp_right; else - GR(i) = linsolve(@(x) x - apply(Tdiag, x), rhs, ... - GR(i)); + GR{1}(i) = linsolve(@(x) x - apply(Tdiag, x), rhs, ... + GR{1}(i)); end end + + for w = period(mps1):-1:2 + T = transfermatrix(mpo, mps1, mps2, w, 'Type', 'RR').'; + GR{w} = apply(T, GR{next(w, period(mps1))}); + end end + function mpo = horzcat(varargin) + Os = cellfun(@(x) x.O, varargin, 'UniformOutput', false); + mpo = InfJMpo([Os{:}]); + end end methods (Static) diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index 2dad8ea..42794f7 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -67,26 +67,31 @@ arguments mpo mps1 - mps2 = [] - GL = [] + mps2 = mps1 + GL = cell(1, period(mps1)) eigopts.KrylovDim = 30 eigopts.MaxIter = 1000 eigopts.ReOrth = 2 eigopts.Tol = eps(underlyingType(mps1))^(3/4) end - + N = period(mps1); T = transfermatrix(mpo, mps1, mps2, 'Type', 'LL'); - [GL, lambda] = eigsolve(T, GL, 1, 'largestabs', ... + [GL{1}, lambda] = eigsolve(T, GL{1}, 1, 'largestabs', ... 'KrylovDim', eigopts.KrylovDim, 'Tol', eigopts.Tol, ... 'ReOrth', eigopts.ReOrth, 'MaxIter', eigopts.MaxIter); + + for i = 2:N + T = transfermatrix(mpo, mps1, mps2, i-1, 'Type', 'LL'); + GL{i} = apply(T, GL{i-1}) ./ lambda^(1/N); + end end function [GR, lambda] = rightenvironment(mpo, mps1, mps2, GR, eigopts) arguments mpo mps1 - mps2 - GR = [] + mps2 = mps1 + GR = cell(1, period(mps1)) eigopts.KrylovDim = 30 eigopts.MaxIter = 1000 eigopts.ReOrth = 2 @@ -94,10 +99,14 @@ end T = transfermatrix(mpo, mps1, mps2, 'Type', 'RR').'; - - [GR, lambda] = eigsolve(T, GR, 1, 'largestabs', ... + [GR{1}, lambda] = eigsolve(T, GR{1}, 1, 'largestabs', ... 'KrylovDim', eigopts.KrylovDim, 'Tol', eigopts.Tol, ... 'ReOrth', eigopts.ReOrth, 'MaxIter', eigopts.MaxIter); + N = period(mps1); + for i = N:-1:2 + T = transfermatrix(mpo, mps1, mps2, i, 'Type', 'RR').'; + GR{i} = apply(T, GR{next(i, N)}) ./ lambda^(1/N); + end end function [GL, GR, lambda] = environments(mpo, mps1, mps2, GL, GR, eigopts) @@ -105,8 +114,8 @@ mpo mps1 mps2 = mps1 - GL = [] - GR = [] + GL = cell(1, period(mps1)) + GR = cell(1, period(mps1)) eigopts.KrylovDim = 30 eigopts.MaxIter = 1000 eigopts.ReOrth = 2 @@ -121,7 +130,9 @@ warning('lambdas disagree'); end - overlap = sqrt(contract(GL, [1 3 2], mps1.C, [2 4], mps2.C', [5 1], GR, [4 3 5])); +% for w = 1:length(mps1) +% overlap = sqrt(contract(GL, [1 3 2], mps1.C, [2 4], mps2.C', [5 1], GR, [4 3 5])); +% end % GL = GL / overlap; % GR = GR / overlap; end @@ -129,6 +140,11 @@ function s = pspace(mpo) s = pspace(mpo.O{1}); end + + function mpo = horzcat(varargin) + Os = cellfun(@(x) x.O, varargin, 'UniformOutput', false); + mpo = InfMpo([Os{:}]); + end end %% Derived operators methods @@ -137,7 +153,7 @@ mpo mps1 mps2 = mps1 - sites = 1:period(mpo) + sites = 1:period(mps1) kwargs.Type {mustBeMember(kwargs.Type, {'LL' 'LR' 'RL' 'RR'})} = 'RR' end @@ -153,9 +169,15 @@ else A2 = mps2.AR(sites); end + O = cellfun(@rot90, mpo.O(sites), 'UniformOutput', false); - A2 = twist(A2, 1 + find(isdual(space(A1, 2:nspaces(A1)-1)))); - T = FiniteMpo(A2', {rot90(mpo.O{1})}, A1); + for i = 1:numel(A2) + T(i, 1) = FiniteMpo(... + twist(A2(i)', 1 + find(isdual(space(A1(i), 2:nspaces(A1(i))-1)))), ... + O(i), A1(i)); + end + +% T = FiniteMpo(A2, {rot90(mpo.O{1})}, A1); end function H = AC_hamiltonian(mpo, mps, GL, GR) @@ -165,9 +187,22 @@ GL = fixedpoint(transfermatrix(mpo, mps, 'Type', 'LL')) GR = fixedpoint(transfermatrix(mpo, mps, 'Type', 'RR').') end - GR = twist(GR, find(isdual(space(GR, nspaces(GR)))) + nspaces(GR)-1); - GL = twist(GL, find(isdual(space(GL, 1)))); - H = FiniteMpo(GL, mpo.O, GR); + H = cell(1, period(mps)); + for i = 1:period(mps) + gl = GL{i}; + for j = 1:numel(gl) + gl(j) = twist(gl(j), find(isdual(space(gl(j), 1)))); + end + gr = GR{next(i, length(mps))}; + for j = 1:numel(gr) + gr(j) = twist(gr(j), find(isdual(space(gr(j), nspaces(gr(j))))) + ... + nspaces(gr(j)) - 1); + end + H{i} = FiniteMpo(gl, mpo.O{i}, gr); + end +% GR = twist(GR, find(isdual(space(GR, nspaces(GR)))) + nspaces(GR)-1); +% GL = twist(GL, find(isdual(space(GL, 1)))); +% H = FiniteMpo(GL, mpo.O, GR); end function H = C_hamiltonian(mpo, mps, GL, GR) @@ -177,9 +212,21 @@ GL = fixedpoint(transfermatrix(mpo, mps, 'Type', 'LL')) GR = fixedpoint(transfermatrix(mpo, mps, 'Type', 'RR').') end - GR = twist(GR, find(isdual(space(GR, nspaces(GR)))) + nspaces(GR)-1); - GL = twist(GL, find(isdual(space(GL, 1)))); - H = FiniteMpo(GL, {}, GR); + for i = 1:period(mps) + gl = GL{i}; + for j = 1:numel(gl) + gl(j) = twist(gl(j), find(isdual(space(gl(j), 1)))); + end + gr = GR{i}; + for j = 1:numel(gr) + gr(j) = twist(gr(j), find(isdual(space(gr(j), nspaces(gr(j))))) + ... + nspaces(gr(j)) - 1); + end + H{i} = FiniteMpo(gl, {}, gr); + end +% GR = twist(GR, find(isdual(space(GR, nspaces(GR)))) + nspaces(GR)-1); +% GL = twist(GL, find(isdual(space(GL, 1)))); +% H = FiniteMpo(GL, {}, GR); end end diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index 099e953..8e59a6d 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -336,6 +336,18 @@ end end + function i = end(t, k, n) + if n == 1 + i = prod(size(t)); + return + end + if n > ndims(t) + i = 1; + else + i = size(t, k); + end + end + function bools = eq(a, b) arguments a MpoTensor diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index c943856..9592328 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -169,11 +169,13 @@ alg = 'rqpos' end - [R, AR] = rightorth@Tensor(A, 1, 2:nspaces(A), alg); - if isdual(space(R, 1)) == isdual(space(R, 2)) - R.domain = conj(R.domain); - R = twist(R, 2); - AR.codomain = conj(AR.codomain); + for i = numel(A):-1:1 + [R(i), AR(i)] = rightorth@Tensor(A(i), 1, 2:nspaces(A(i)), alg); + if isdual(space(R(i), 1)) == isdual(space(R(i), 2)) + R(i).domain = conj(R(i).domain); + R(i) = twist(R(i), 2); + AR(i).codomain = conj(AR(i).codomain); + end end % AR = MpsTensor(repartition(AR, rank(A)), A.alegs); diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index d65afb2..942179a 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -91,6 +91,14 @@ d = size(mps, 1); end + function mps = horzcat(varargin) + ALs = cellfun(@(x) x.AL, varargin, 'UniformOutput', false); + ARs = cellfun(@(x) x.AR, varargin, 'UniformOutput', false); + Cs = cellfun(@(x) x.C, varargin, 'UniformOutput', false); + ACs = cellfun(@(x) x.AC, varargin, 'UniformOutput', false); + mps = UniformMps([ALs{:}], [ARs{:}], [Cs{:}], [ACs{:}]); + end + function s = leftvspace(mps, w) if nargin == 1 || isempty(w), w = 1:period(mps); end s = arrayfun(@leftvspace, mps.AL(w)); diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index 62ea91f..bc868a1 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -115,7 +115,7 @@ assert(isscalar(i), 'sparse:argerror', ... 'Can only obtain spaces for single index.'); for j = size(t, i):-1:1 - el = t.var(find(t.ind(:, i), 1)); + el = t.var(find(t.ind(:, i) == j, 1)); if isempty(el) warning('cannot deduce space.'); continue; @@ -285,6 +285,32 @@ function disp(t) assert(szA(2) == szB(1), 'sparse:dimerror', ... 'incompatible sizes for mtimes.'); + if isnumeric(a) + c = SparseTensor.zeros(szA(1), szB(2)); + + for i = 1:szA(1) + for j = 1:szB(2) + c = subsasgn(c, substruct('()', {i, j}), ... + sum(a(i, :).' .* ... + subsref(b, substruct('()', {':', j})))); + end + end + return + end + + if isnumeric(b) + c = SparseTensor.zeros(szA(1), szB(2)); + + for i = 1:szA(1) + for j = 1:szB(2) + c = subsasgn(c, substruct('()', {i, j}), ... + sum(subsref(a, substruct('()', {i, ':'})) .* ... + b(:, j).')); + end + end + return + end + cvar = []; cind = double.empty(0, 2); @@ -518,56 +544,21 @@ function disp(t) end function t = times(t1, t2) - if isnumeric(t1) - if isempty(t2.var) - t = t2; - return - end - t = t2; - t.var = t1 .* t.var; - return - end - - if isnumeric(t2) - t = t2 .* t1; - return - end - if isscalar(t1) && ~isscalar(t2) t1 = repmat(t1, size(t2)); elseif isscalar(t2) && ~isscalar(t1) t2 = repmat(t2, size(t1)); end - assert(isequal(size(t1), size(t2)), 'sparse:dimerror', ... - 'incompatible input sizes.'); - - if ~issparse(t1) - if isempty(t2.var) - t = t2; - return - end - - idx = sub2ind_(t2.sz, t2.ind); - t = t2; - t.var = t1(idx) .* t.var; - return - end - - if ~issparse(t2) - if isempty(t1.var) - t = t1; - return - end - - idx = sub2ind_(t1.sz, t1.ind); - t = t1; - t.var = t.var .* t2(idx); - return - end + assert(isequal(size(t1), size(t2))); - [inds, ia, ib] = intersect(t1.ind, t2.ind, 'rows'); - t = SparseTensor(inds, t1.var(ia) .* t2.var(ib), t1.sz); + t = SparseTensor.zeros(size(t1)); + I = intersect(find(t1), find(t2)); + a = full(subsref(t1, substruct('()', {I}))); + b = full(subsref(t2, substruct('()', {I}))); + subs = ind2sub_(t.sz, I); + t.ind = subs; + t.var = reshape(a .* b, [], 1); end function t = tpermute(t, p, r) @@ -703,7 +694,7 @@ function disp(t) t.ind = [t.ind; subs]; t.var = [t.var; full(v(i))]; else - t.var(idx) = v(i); + t.var(idx) = full(v(i)); end end end diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 69b4090..1f177b7 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -427,7 +427,7 @@ end function r = rank(t, i) - r = [length(t.codomain) length(t.domain)]; + r = [length(t(1).codomain) length(t(1).domain)]; if nargin > 1 r = r(i); end @@ -733,17 +733,12 @@ if issparse(B) && ~all(any(B, 1)) C = SparseTensor.zeros(szA(1), szB(2)); else - C = Tensor.empty(szA(1), szB(2), 0); + C(szA(1), szB(2)) = Tensor(); end for i = 1:szA(1) for j = 1:szB(2) - try C(i, j) = sum(A(i, :) .* B(:, j).'); - catch - bla - end - end end return diff --git a/src/tensors/charges/Z2.m b/src/tensors/charges/Z2.m index 84981d2..58a9376 100644 --- a/src/tensors/charges/Z2.m +++ b/src/tensors/charges/Z2.m @@ -40,7 +40,7 @@ end function s = GetMD5_helper(data) - s = {'Z2' logical(data)}; + s = logical(data); end function c = mtimes(a, b) diff --git a/src/tensors/charges/fZ2.m b/src/tensors/charges/fZ2.m index 9c0da2b..901acff 100644 --- a/src/tensors/charges/fZ2.m +++ b/src/tensors/charges/fZ2.m @@ -42,10 +42,6 @@ function theta = twist(a) theta = -2 * double(a) + 1; end - - function s = GetMD5_helper(data) - s = {'fZ2' logical(data)}; - end end end diff --git a/test/TestInfJMpo.m b/test/TestInfJMpo.m index 4edd888..fcc759f 100644 --- a/test/TestInfJMpo.m +++ b/test/TestInfJMpo.m @@ -55,26 +55,19 @@ function testDerivatives(tc, mpo, mps) end function test1dIsing(tc) - alg = Vumps('which', 'smallestreal', 'maxiter', 20, 'verbosity', Verbosity.iter); + alg = Vumps('which', 'smallestreal', 'maxiter', 5, 'verbosity', Verbosity.iter); D = 16; mpo = InfJMpo.Ising(1, 1); mps = UniformMps.randnc(CartesianSpace.new(2), CartesianSpace.new(D)); - [mps2, lambda] = fixedpoint(alg, mpo, mps); - profile on +% [mps2, lambda] = fixedpoint(alg, mpo, mps); mpo = InfJMpo.Ising(1, 1, 'Symmetry', 'Z2'); mps = UniformMps.randnc(pspace(mpo), ... GradedSpace.new(Z2(0, 1), [D D] ./ 2, false)); [mps2, lambda2] = fixedpoint(alg, mpo, mps); - profile viewer - end - - function test2dfDimer(tc) - D = 32; - mpo = block(InfMpo.fDimer()); - mps = UniformMps.randnc(GradedSpace.new(fZ2(0, 1), [1 1], false), ... - GradedSpace.new(fZ2(0, 1), [D D], false)); - [mps2, lambda] = fixedpoint(Vumps('tol', 1e-4, 'maxiter', 25), mpo, mps); - tc.assertEqual(log(abs(lambda)) / 2, 0.29156, 'RelTol', 1e-4); + + mpo = [mpo mpo]; + mps = [mps mps]; + [mps2, lambda2] = fixedpoint(alg, mpo, mps); end end end diff --git a/test/TestInfMpo.m b/test/TestInfMpo.m index b1fe42c..0ce1fd3 100644 --- a/test/TestInfMpo.m +++ b/test/TestInfMpo.m @@ -38,24 +38,30 @@ function testDerivatives(tc, mpo, mps) end function test2dIsing(tc) - beta = 0.95 * log(1 + sqrt(2)) / 2; + beta = 0.9 * log(1 + sqrt(2)) / 2; theta = 0:1e-6:pi/2; x = 2 * sinh(2 * beta) / cosh(2 * beta)^2; freeEnergyExact = -1 / beta * (log(2 * cosh(2 * beta)) + 1 / pi * ... trapz(theta, log(1/2 * (1 + sqrt(1 - x^2 * sin(theta).^2))))); - D = 64; - + D = 16; + alg = Vumps('MaxIter', 10); mpo = InfMpo.Ising(beta); mps = UniformMps.randnc(CartesianSpace.new(2), CartesianSpace.new(D)); - [mps2, lambda] = fixedpoint(Vumps(), mpo, mps); + [mps2, lambda] = fixedpoint(alg, mpo, mps); tc.assertEqual(-log(lambda) / beta, freeEnergyExact, 'RelTol', 1e-5); mps = UniformMps.randnc(GradedSpace.new(Z2(0, 1), [1 1], false), ... GradedSpace.new(Z2(0, 1), [D D] ./ 2, false)); mpo = InfMpo.Ising(beta, 'Symmetry', 'Z2'); - [mps2, lambda] = fixedpoint(Vumps(), mpo, mps); + [mps2, lambda] = fixedpoint(alg, mpo, mps); tc.assertEqual(-log(lambda) / beta, freeEnergyExact, 'RelTol', 1e-5); + + mps = [mps mps]; + mpo = [mpo mpo]; + + [mps2, lambda] = fixedpoint(alg, mpo, mps); + tc.assertEqual(-log(sqrt(lambda)) / beta, freeEnergyExact, 'RelTol', 1e-5); end function test2dfDimer(tc) From cdbdf0a2b578a83792329e60bb43ced586062991 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 20 Oct 2022 20:40:44 +0200 Subject: [PATCH 054/245] Some bugfixes, some cleanup --- src/algorithms/Vumps.m | 17 +++++++---- src/mps/FiniteMpo.m | 5 ++++ src/mps/InfJMpo.m | 64 ++++++++++++++++++++++++++++++------------ src/mps/InfMpo.m | 13 +++++---- test/TestInfJMpo.m | 43 +++++++++++----------------- test/TestInfMpo.m | 21 ++++++++------ 6 files changed, 98 insertions(+), 65 deletions(-) diff --git a/src/algorithms/Vumps.m b/src/algorithms/Vumps.m index c92b333..63459cf 100644 --- a/src/algorithms/Vumps.m +++ b/src/algorithms/Vumps.m @@ -14,10 +14,10 @@ KrylovDim = 20 dynamical_tols = true - tol_min = 1e-13 - tol_max = 1e-10 + tol_min = 1e-12 + tol_max = 1e-6 eigs_tolfactor = 1e-4 - canonical_tolfactor = 1e-4 + canonical_tolfactor = 1e-8 environments_tolfactor = 1e-4 end @@ -71,7 +71,7 @@ C = updateC (alg, mpo, mps, GL, GR); mps = updatemps(alg, AC, C); - [GL, GR, lambda] = environments(mpo, mps, mps, GL, GR); + [GL, GR, lambda] = environments(alg, mpo, mps, GL, GR); eta = convergence(alg, mpo, mps, GL, GR); if iter > alg.miniter && eta < alg.tol @@ -122,8 +122,8 @@ alg mpo mps - GL = [] - GR = [] + GL = cell(1, period(mps)) + GR = cell(1, period(mps)) end kwargs = namedargs2cell(alg.alg_environments); @@ -163,6 +163,11 @@ alg.tol_max / iter); alg.alg_environments.Tol = between(alg.tol_min, eta * alg.environments_tolfactor, ... alg.tol_max / iter); + + if alg.verbosity > Verbosity.iter + fprintf('Updated subalgorithm tolerances: (%e,\t%e,\t%e)\n', ... + alg.alg_eigs.Tol, alg.alg_canonical.Tol, alg.alg_environments.Tol); + end end end diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index 43938ed..bf13ed2 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -29,6 +29,11 @@ end end + function v = apply_regularised(mpo, fp1, fp2, v) + v = apply(mpo, v); + v = v - contract(fp1, 1:nspaces(fp1), v, nspaces(fp1):-1:1) * fp2; + end + function [V, D, flag] = eigsolve(mpo, v0, howmany, sigma, options) arguments mpo diff --git a/src/mps/InfJMpo.m b/src/mps/InfJMpo.m index d323d7f..0f47fac 100644 --- a/src/mps/InfJMpo.m +++ b/src/mps/InfJMpo.m @@ -15,18 +15,20 @@ bool = true; end - function [GL, lambda] = leftenvironment(mpo, mps1, mps2, GL, eigopts) + function [GL, lambda] = leftenvironment(mpo, mps1, mps2, GL, linopts) arguments mpo mps1 mps2 = mps1 GL = cell(1, period(mps1)) - eigopts.KrylovDim = 30 - eigopts.MaxIter = 1000 - eigopts.ReOrth = 2 - eigopts.Tol = eps(underlyingType(mps1))^(3/4) + linopts.Algorithm = 'bicgstab' + linopts.MaxIter = 500 + linopts.Verbosity = Verbosity.warn + linopts.Tol = eps(underlyingType(mps1))^(3/4) end + linkwargs = namedargs2cell(linopts); + T = transfermatrix(mpo, mps1, mps2, 'Type', 'LL'); if isempty(GL) || isempty(GL{1}) @@ -50,13 +52,13 @@ lambda = contract(rhs, 1:3, fp_right, 3:-1:1); rhs = rhs - lambda * fp_left; - GL{1}(i) = linsolve(@(x) x - apply(Tdiag, x), rhs, ... - GL{1}(i)); + [GL{1}(i), ~] = linsolve(@(x) x - apply(Tdiag, x), rhs, GL{1}(i), ... + linkwargs{:}); GL{1}(i) = GL{1}(i) - ... contract(GL{1}(i), 1:3, fp_right, 3:-1:1) * fp_left; else - GL{1}(i) = linsolve(@(x) x - apply(Tdiag, x), rhs, ... - GL{1}(i)); + [GL{1}(i), ~] = linsolve(@(x) x - apply(Tdiag, x), rhs, GL{1}(i), ... + linkwargs{:}); end end @@ -66,20 +68,23 @@ end end - function [GR, lambda] = rightenvironment(mpo, mps1, mps2, GR, eigopts) + function [GR, lambda] = rightenvironment(mpo, mps1, mps2, GR, linopts) arguments mpo mps1 mps2 = mps1 GR = cell(1, period(mps1)) - eigopts.KrylovDim = 30 - eigopts.MaxIter = 1000 - eigopts.ReOrth = 2 - eigopts.Tol = eps(underlyingType(mps1))^(3/4) + linopts.Algorithm = 'bicgstab' + linopts.MaxIter = 500 + linopts.Verbosity = Verbosity.warn + linopts.Tol = eps(underlyingType(mps1))^(3/4) end + linkwargs = namedargs2cell(linopts); + T = transfermatrix(mpo, mps1, mps2, 'Type', 'RR').'; N = size(T(1).O{1}, 2); + if isempty(GR) || isempty(GR{1}) GR = cell(1, period(mps1)); GR{1} = SparseTensor.zeros(1, N, 1); @@ -101,13 +106,14 @@ lambda = contract(rhs, 1:3, fp_left, 3:-1:1); rhs = rhs - lambda * fp_right; - GR{1}(i) = linsolve(@(x) x - apply(Tdiag, x), rhs, ... - GR{1}(i)); + [GR{1}(i), ~] = ... + linsolve(@(x) x - apply(Tdiag, x), rhs, GR{1}(i), linkwargs{:}); + GR{1}(i) = GR{1}(i) - ... contract(GR{1}(i), 1:3, fp_left, 3:-1:1) * fp_right; else - GR{1}(i) = linsolve(@(x) x - apply(Tdiag, x), rhs, ... - GR{1}(i)); + [GR{1}(i), ~] = linsolve(@(x) x - apply(Tdiag, x), rhs, GR{1}(i), ... + linkwargs{:}); end end @@ -117,6 +123,28 @@ end end + function [GL, GR, lambda] = environments(mpo, mps1, mps2, GL, GR, linopts) + arguments + mpo + mps1 + mps2 = mps1 + GL = cell(1, period(mps1)) + GR = cell(1, period(mps1)) + linopts.Algorithm = 'bicgstab' + linopts.MaxIter = 500 + linopts.Verbosity = Verbosity.warn + linopts.Tol = eps(underlyingType(mps1))^(3/4) + end + + kwargs = namedargs2cell(linopts); + [GL, lambdaL] = leftenvironment(mpo, mps1, mps2, GL, kwargs{:}); + [GR, lambdaR] = rightenvironment(mpo, mps1, mps2, GR, kwargs{:}); + lambda = (lambdaL + lambdaR) / 2; + if abs(lambdaL - lambdaR)/abs(lambda) > eps(lambda)^(1/3) + warning('lambdas disagree'); + end + end + function mpo = horzcat(varargin) Os = cellfun(@(x) x.O, varargin, 'UniformOutput', false); mpo = InfJMpo([Os{:}]); diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index 42794f7..c73e6b2 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -122,7 +122,7 @@ eigopts.Tol = eps(underlyingType(mps1))^(3/4) end - kwargs = [fieldnames(eigopts).'; struct2cell(eigopts).']; + kwargs = namedargs2cell(eigopts); [GL, lambdaL] = leftenvironment(mpo, mps1, mps2, GL, kwargs{:}); [GR, lambdaR] = rightenvironment(mpo, mps1, mps2, GR, kwargs{:}); lambda = (lambdaL + lambdaR) / 2; @@ -130,11 +130,12 @@ warning('lambdas disagree'); end -% for w = 1:length(mps1) -% overlap = sqrt(contract(GL, [1 3 2], mps1.C, [2 4], mps2.C', [5 1], GR, [4 3 5])); -% end -% GL = GL / overlap; -% GR = GR / overlap; + for w = 1:length(mps1) + overlap = sqrt(contract(GL{w}, [1 3 2], mps1.C(w), [2 4], ... + mps2.C(w)', [5 1], GR{w}, [4 3 5])); + GL{w} = GL{w} ./ overlap; + GR{w} = GR{w} ./ overlap; + end end function s = pspace(mpo) diff --git a/test/TestInfJMpo.m b/test/TestInfJMpo.m index fcc759f..0b9b9cf 100644 --- a/test/TestInfJMpo.m +++ b/test/TestInfJMpo.m @@ -15,59 +15,48 @@ function testEnvironments(tc, mpo, mps) [GL, lambdaL] = leftenvironment(mpo, mps, mps); tc.verifyTrue(isapprox(abs(lambdaL), abs(real(lambdaL)), 'RelTol', 1e-6), ... sprintf('lambda should be real. (%-g i)', imag(lambdaL))); - T = transfermatrix(mpo, mps, mps, 'Type', 'LL'); - -% for i = 1:size(mpo.O{1}, 1) -% tc.assertTrue(isapprox(apply(slice(T, i, 1:i-1), GL(1:i-1)), ... -% apply(slice(T, i, i), GL(i)))); -% end - GL_ = apply(T, GL); - for i = 1:numel(GL_) - tc.verifyTrue(isapprox(GL_(i), GL(i)), ... - sprintf('GL(%d) disagrees', i)); - end -% tc.assertTrue(isapprox(apply(T, GL), GL)); [GR, lambdaR] = rightenvironment(mpo, mps, mps); tc.verifyTrue(isapprox(abs(lambdaR), abs(real(lambdaR)), 'RelTol', 1e-6), ... sprintf('lambda should be real. (%gi)', imag(lambdaR))); - T = transfermatrix(mpo, mps, mps, 'Type', 'RR'); - GR_ = apply(T.', GR); - for i = 1:numel(GR_) - tc.verifyTrue(isapprox(GR_(i), GR(i)), ... - sprintf('GR(%d) disagrees', i)); - end - tc.verifyTrue(isapprox(lambdaL, lambdaR), 'lambdas should be equal.') + tc.verifyTrue(isapprox(lambdaL, lambdaR), 'lambdas should be equal.'); end function testDerivatives(tc, mpo, mps) [GL, GR] = environments(mpo, mps, mps); H_AC = AC_hamiltonian(mpo, mps, GL, GR); - H_C = C_hamiltonian(mpo, mps, GL, GR); - - [AC_, lambda] = eigsolve(H_AC, mps.AC, 1, 'largestabs'); - tc.assertTrue(isapprox(apply(H_AC, AC_), lambda * AC_)); + for i = 1:numel(H_AC) + [AC_, lambda] = eigsolve(H_AC{i}, mps.AC(i), 1, 'largestabs'); + tc.assertTrue(isapprox(apply(H_AC{i}, AC_), lambda * AC_)); + end - [C_, lambda] = eigsolve(H_C, mps.C, 1, 'largestabs'); - tc.assertTrue(isapprox(apply(H_C, C_), lambda * C_)); + H_C = C_hamiltonian(mpo, mps, GL, GR); + for i = 1:numel(H_C) + [C_, lambda] = eigsolve(H_C{i}, mps.C(i), 1, 'largestabs'); + tc.assertTrue(isapprox(apply(H_C{i}, C_), lambda * C_)); + end end function test1dIsing(tc) - alg = Vumps('which', 'smallestreal', 'maxiter', 5, 'verbosity', Verbosity.iter); + alg = Vumps('which', 'smallestreal', 'maxiter', 5); D = 16; mpo = InfJMpo.Ising(1, 1); mps = UniformMps.randnc(CartesianSpace.new(2), CartesianSpace.new(D)); -% [mps2, lambda] = fixedpoint(alg, mpo, mps); + [mps2, lambda] = fixedpoint(alg, mpo, mps); + tc.verifyTrue(isapprox(lambda, -1.27, 'RelTol', 1e-2)) + mpo = InfJMpo.Ising(1, 1, 'Symmetry', 'Z2'); mps = UniformMps.randnc(pspace(mpo), ... GradedSpace.new(Z2(0, 1), [D D] ./ 2, false)); [mps2, lambda2] = fixedpoint(alg, mpo, mps); + tc.verifyTrue(isapprox(lambda, -1.27, 'RelTol', 1e-2)) mpo = [mpo mpo]; mps = [mps mps]; [mps2, lambda2] = fixedpoint(alg, mpo, mps); + tc.verifyTrue(isapprox(lambda2/2, -1.27, 'RelTol', 5e-2)) end end end diff --git a/test/TestInfMpo.m b/test/TestInfMpo.m index 0ce1fd3..1d6cb7e 100644 --- a/test/TestInfMpo.m +++ b/test/TestInfMpo.m @@ -17,24 +17,29 @@ function testEnvironments(tc, mpo, mps) [GL, lambdaL] = leftenvironment(mpo, mps, mps); T = transfermatrix(mpo, mps, mps, 'Type', 'LL'); - tc.assertTrue(isapprox(apply(T, GL), lambdaL * GL)); + tc.assertTrue(isapprox(apply(T, GL{1}), lambdaL * GL{1})); [GR, lambdaR] = rightenvironment(mpo, mps, mps); T = transfermatrix(mpo, mps, mps, 'Type', 'RR'); - tc.assertTrue(isapprox(apply(T.', GR), lambdaR * GR)); + tc.assertTrue(isapprox(apply(T.', GR{1}), lambdaR * GR{1})); + + tc.assertTrue(isapprox(lambdaL, lambdaR)); end function testDerivatives(tc, mpo, mps) [GL, GR] = environments(mpo, mps, mps); H_AC = AC_hamiltonian(mpo, mps, GL, GR); - H_C = C_hamiltonian(mpo, mps, GL, GR); + for i = 1:numel(H_AC) + [AC_, lambda] = eigsolve(H_AC{i}, mps.AC(i), 1, 'largestabs'); + tc.assertTrue(isapprox(apply(H_AC{i}, AC_), lambda * AC_)); + end - [AC_, lambda] = eigsolve(H_AC, mps.AC, 1, 'largestabs'); - tc.assertTrue(isapprox(apply(H_AC, AC_), lambda * AC_)); - - [C_, lambda] = eigsolve(H_C, mps.C, 1, 'largestabs'); - tc.assertTrue(isapprox(apply(H_C, C_), lambda * C_)); + H_C = C_hamiltonian(mpo, mps, GL, GR); + for i = 1:numel(H_C) + [C_, lambda] = eigsolve(H_C{i}, mps.C(i), 1, 'largestabs'); + tc.assertTrue(isapprox(apply(H_C{i}, C_), lambda * C_)); + end end function test2dIsing(tc) From daa0df7bfe7614e8c944e5d60afc855cdebc07af Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 20 Oct 2022 21:15:07 +0200 Subject: [PATCH 055/245] more bugfixes and more cleanup --- src/mps/FiniteMpo.m | 1 + src/mps/MpsTensor.m | 81 ++++----------------------------------- src/sparse/SparseTensor.m | 37 ++++++++++++++---- src/tensors/Tensor.m | 7 +++- test/TestUniformMps.m | 11 ++++-- 5 files changed, 52 insertions(+), 85 deletions(-) diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index bf13ed2..beef00c 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -112,6 +112,7 @@ end function mpo = slice(mpo, i, j) + if isempty(mpo.O), return; end if strcmp(i, ':') i = 1:size(mpo(end).O, 2); end diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index 9592328..b73e110 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -96,7 +96,7 @@ MAXKRYLOVDIM = 30; % initialization - N = width(A); + N = size(A, 2); if isempty(C), C = initializeC(A, circshift(A, 1)); end if kwargs.Normalize, C(1) = normalize(C(1)); end A = arrayfun(@(a) MpsTensor(repartition(a, [nspaces(a)-1 1])), A); @@ -193,82 +193,17 @@ kwargs.EigsInit = 3 kwargs.EigsFrequence = 2 end - opts = namedargs2cell(kwargs); - [AR, C, lambda, eta] = uniform_leftorth(A', C', opts{:}); - AR = AR'; - C = C'; - lambda = conj(lambda); - return - - - % constants - EIG_TOLFACTOR = 1/50; - EIG_MAXTOL = 1e-4; - MINKRYLOVDIM = 8; - MAXKRYLOVDIM = 30; - % initialization - N = width(A); - if isempty(C), C = initializeC(A, circshift(A, 1)); end - if kwargs.Normalize, C(1) = normalize(C(1)); end - A = arrayfun(@(a) MpsTensor(repartition(a, [1 nspaces(a)-1])), A); - AR = A; - - eta_best = Inf; - ctr_best = 0; + opts = namedargs2cell(kwargs); - for ctr = 1:kwargs.MaxIter - if ctr > kwargs.EigsInit && mod(ctr, kwargs.EigsFrequence) == 0 - C_ = repartition(C(end), [nspaces(C(end)) 0]); - T = transfermatrix(A, AR)'; - [C_, ~] = eigsolve(T, C_, ... - 1, 'largestabs', ... - 'Tol', min(eta * EIG_TOLFACTOR, EIG_MAXTOL), ... - 'KrylovDim', between(MINKRYLOVDIM, ... - MAXKRYLOVDIM - ctr / 2 + 4, ... - MAXKRYLOVDIM), ... - 'NoBuild', 4, ... - 'Verbosity', kwargs.Verbosity - 1); - [C(end), ~] = rightorth(C_', 1, 2, kwargs.Method); - if isdual(space(C(end), 1)) - C.domain = conj(C.domain); - C = twist(C, 2); - end - end - - C_ = C(end); - lambdas = ones(1, N); - for w = N:-1:1 - ww = prev(w, N); - AC = multiplyright(A(w), C(w)); - [C(ww), AR(w)] = rightorth(AC, kwargs.Method); - lambdas(w) = norm(C(ww)); - if kwargs.Normalize, C(ww) = C(ww) ./ lambdas(w); end - end - eta = norm(C_ - C(end), Inf); - - if eta < kwargs.Tol - if kwargs.Verbosity >= Verbosity.conv - fprintf('Conv %2d:\terror = %0.4e\n', ctr, eta); - end - break; - elseif eta < eta_best - eta_best = eta; - ctr_best = ctr; - elseif ctr - ctr_best > 3 - warning('uniform_orthright:stagnate', 'Algorithm stagnated'); - break; - end - if kwargs.Verbosity >= Verbosity.iter - fprintf('Iter %2d:\terror = %0.4e\n', ctr, eta); - end - end + Ad = arrayfun(@ctranspose, A); + Cd = arrayfun(@ctranspose, C); - if kwargs.Verbosity >= Verbosity.warn && eta > kwargs.Tol - fprintf('Not converged %2d:\terror = %0.4e\n', ctr, eta); - end + [AR, C, lambda, eta] = uniform_leftorth(Ad, Cd, opts{:}); - if nargout > 2, lambda = prod(lambdas); end + AR = arrayfun(@ctranspose, AR); + C = arrayfun(@ctranspose, C); + lambda = conj(lambda); end function T = transfermatrix(A, B) diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index bc868a1..19397d5 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -552,13 +552,36 @@ function disp(t) assert(isequal(size(t1), size(t2))); - t = SparseTensor.zeros(size(t1)); - I = intersect(find(t1), find(t2)); - a = full(subsref(t1, substruct('()', {I}))); - b = full(subsref(t2, substruct('()', {I}))); - subs = ind2sub_(t.sz, I); - t.ind = subs; - t.var = reshape(a .* b, [], 1); + if isnumeric(t1) + t = t2; + idx = sub2ind_(t.sz, t.ind); + t1 = t1(idx); + idx2 = find(t1); + t.var = t.var(idx2) .* t1(idx2); + t.ind = t.ind(idx2, :); + return + end + + if isnumeric(t2) + t = t2 .* t1; + return + end + + if ~issparse(t1) + idx = sub2ind_(t2.sz, t2.ind); + t = t2; + t.var = t.var .* t1(idx); + return + end + + if ~issparse(t2) + t = t2 .* t1; + return + end + + t = t1; + [t.ind, ia, ib] = intersect(t1.ind, t2.ind, 'rows'); + t.var = t1.var(ia) .* t2.var(ib); end function t = tpermute(t, p, r) diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 1f177b7..9e692cc 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -48,6 +48,11 @@ return end + if nargin == 1 && isa(varargin{1}, 'SparseTensor') + t = full(varargin{1}); + return + end + % t = Tensor(array) if nargin == 1 && isnumeric(varargin{1}) t.domain = []; @@ -1223,7 +1228,7 @@ for i = numel(A):-1:1 assert(isequal(A(i).domain, B(i).codomain), 'tensors:SpaceMismatch', ... 'Multiplied spaces incompatible.'); - if ~isempty(A.codomain) || ~isempty(B.domain) + if ~isempty(A(i).codomain) || ~isempty(B(i).domain) C(i) = Tensor.zeros(A(i).codomain, B(i).domain); C(i).var = mul(C(i).var, A(i).var, B(i).var); else diff --git a/test/TestUniformMps.m b/test/TestUniformMps.m index dff69ad..eecebc6 100644 --- a/test/TestUniformMps.m +++ b/test/TestUniformMps.m @@ -4,7 +4,8 @@ properties (TestParameter) A = struct(... 'trivial', Tensor.randnc(CartesianSpace.new([4 2]), CartesianSpace.new(4)), ... -... 'trivial2', UniformMps.randnc(CartesianSpace.new([2 2]), CartesianSpace.new([12 12])), ... + 'trivial2', [Tensor.randnc(CartesianSpace.new([4 2]), CartesianSpace.new(4)), ... + Tensor.randnc(CartesianSpace.new([4 2]), CartesianSpace.new(4))], ... 'fermion1', Tensor.randnc(... GradedSpace.new(fZ2(0,1), [2 2], false, fZ2(0,1), [1 1], false), ... GradedSpace.new(fZ2(0,1), [2 2], false)), ... @@ -16,9 +17,11 @@ GradedSpace.new(fZ2(0,1), [2 2], false)), ... 'fermion4', Tensor.randnc(... GradedSpace.new(fZ2(0,1), [2 2], true, fZ2(0,1), [1 1], true), ... - GradedSpace.new(fZ2(0,1), [2 2], true)) ... -... 'haldane', UniformMps.randnc(GradedSpace.new(SU2(2), 1, false, SU2(2), 1, false), ... -... GradedSpace.new(SU2(1:2:5), [5 3 2], false, SU2(2:2:6), [5 2 1], false)) ... + GradedSpace.new(fZ2(0,1), [2 2], true)), ... + 'haldane', [Tensor.randnc(GradedSpace.new(SU2(1:2:5), [5 3 2], false, SU2(2), 1, false), ... + GradedSpace.new(SU2(2:2:6), [5 2 1], false)), ... + Tensor.randnc(GradedSpace.new(SU2(2:2:6), [5 2 1], false, SU2(2), 1, false), ... + GradedSpace.new(SU2(1:2:5), [5 3 2], false))] ... ) end From 3215e668785685288cfecaf3fcc22c9b893c6402 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Fri, 21 Oct 2022 14:39:06 +0200 Subject: [PATCH 056/245] small bugfix --- src/mps/FiniteMpo.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index beef00c..71babf6 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -112,7 +112,7 @@ end function mpo = slice(mpo, i, j) - if isempty(mpo.O), return; end + if isempty(mpo(1).O), return; end if strcmp(i, ':') i = 1:size(mpo(end).O, 2); end From 9d454e29d54698349903988ef3212a45379197f1 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Fri, 21 Oct 2022 15:07:06 +0200 Subject: [PATCH 057/245] bugfixes --- src/algorithms/Vumps.m | 9 +++++---- src/mps/InfMpo.m | 4 ++-- src/mps/MpoTensor.m | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/algorithms/Vumps.m b/src/algorithms/Vumps.m index 63459cf..25f2461 100644 --- a/src/algorithms/Vumps.m +++ b/src/algorithms/Vumps.m @@ -108,7 +108,7 @@ function mps = updatemps(alg, AC, C) for i = length(AC):-1:1 [~, Q_AC] = rightorth(AC(i)); - [~, Q_C] = rightorth(C(i), 1, 2); + [~, Q_C] = rightorth(C(prev(i, length(C))), 1, 2); AR(i) = contract(conj(Q_C), [1 -1], Q_AC, [1 -2 -3], 'Rank', [1 2]); end @@ -139,9 +139,10 @@ AC_ = apply(H_AC{w}, mps.AC(w)); lambda_AC = dot(AC_, mps.AC(w)); AC_ = normalize(AC_ ./ lambda_AC); - - C_ = apply(H_C{w}, mps.C(w)); - lambda_C = dot(C_, mps.C(w)); + + ww = prev(w, period(mps)); + C_ = apply(H_C{ww}, mps.C(ww)); + lambda_C = dot(C_, mps.C(ww)); C_ = normalize(C_ ./ lambda_C); eta(w) = distance(AC_ , ... diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index c73e6b2..faf659e 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -194,7 +194,7 @@ for j = 1:numel(gl) gl(j) = twist(gl(j), find(isdual(space(gl(j), 1)))); end - gr = GR{next(i, length(mps))}; + gr = GR{next(i, period(mps))}; for j = 1:numel(gr) gr(j) = twist(gr(j), find(isdual(space(gr(j), nspaces(gr(j))))) + ... nspaces(gr(j)) - 1); @@ -223,7 +223,7 @@ gr(j) = twist(gr(j), find(isdual(space(gr(j), nspaces(gr(j))))) + ... nspaces(gr(j)) - 1); end - H{i} = FiniteMpo(gl, {}, gr); + H{prev(i, period(mps))} = FiniteMpo(gl, {}, gr); end % GR = twist(GR, find(isdual(space(GR, nspaces(GR)))) + nspaces(GR)-1); % GL = twist(GL, find(isdual(space(GL, 1)))); diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index 8e59a6d..444e91c 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -325,7 +325,7 @@ assert(length(s) == 1, 'mpotensor:index', ... 'only a single level of indexing allowed.'); assert(strcmp(s.type, '()'), 'mpotensor:index', 'only () indexing allowed.'); - + if isempty(t), t = MpoTensor(); end if isnumeric(v) t.scalars = subsasgn(t.scalars, s, v); elseif isa(v, 'MpoTensor') From f175a033ea1358795e0202a1006ad9cc5095776a Mon Sep 17 00:00:00 2001 From: lkdvos Date: Fri, 21 Oct 2022 16:03:45 +0200 Subject: [PATCH 058/245] entanglement spectra --- src/mps/UniformMps.m | 59 ++++++++++++++++++++++++++++++++++++++++++++ src/utility/colors.m | 15 +++++++++++ 2 files changed, 74 insertions(+) create mode 100644 src/utility/colors.m diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index 942179a..76d8ac5 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -308,6 +308,65 @@ end end + function [svals, charges] = schmidt_values(mps, w) + arguments + mps + w = 1 + end + + [svals, charges] = matrixblocks(tsvd(mps.C(w))); + svals = cellfun(@diag, svals, 'UniformOutput', false); + end + + function plot_entanglementspectrum(ax, mps, w) + if nargin == 1 + mps = ax; + w = 1:period(mps); + ax = gobjects(depth(mps), width(mps)); + for ww = 1:length(w) + ax(1, ww) = subplot(1, length(w), ww); + end + elseif nargin == 2 + w = mps; + mps = ax; + ax = gobjects(depth(mps), width(mps)); + for ww = 1:length(w) + ax(1, ww) = subplot(1, length(w), ww); + end + end + + lim_y = 1; + for ww = 1:length(w) + [svals, charges] = schmidt_values(mps, w(ww)); + ctr = 0; + hold off; + lim_x = 1; + + ticks = zeros(size(svals)); + labels = arrayfun(@string, charges, 'UniformOutput', false); + + for i = 1:length(svals) + ticks(i) = ctr + 1; + lim_x = max(lim_x, ctr + length(svals{i})); + semilogy(ax(1, ww), ctr+1:ctr+length(svals{i}), svals{i}, ... + '.', 'MarkerSize', 10, 'Color', colors(i)); + hold on + + ctr = ctr + length(svals{i}) + 3; + lim_y = min(lim_y, min(svals{i})); + end + + set(ax(1, ww), 'TickLabelInterpreter', 'latex'); + set(ax(1, ww), 'Xtick', ticks, 'XTickLabel', labels, 'fontsize', 10, ... + 'XtickLabelRotation', 60, 'Xgrid', 'on'); + xlim(ax(1, ww), [1 - 1e-9 lim_x + 1e-9]); + end + + for ww = 1:length(w) + ylim(ax(1, ww), [10^(floor(log10(lim_y))) 1]); + end + end + function mps = desymmetrize(mps) if numel(mps) > 1 mps = arrayfun(@desymmetrize, mps); diff --git a/src/utility/colors.m b/src/utility/colors.m new file mode 100644 index 0000000..37d5628 --- /dev/null +++ b/src/utility/colors.m @@ -0,0 +1,15 @@ +function y = colors(i) + +y=[0 0.4470 0.7410 + 0.8500 0.3250 0.0980 + 0.9290 0.6940 0.1250 + 0.4940 0.1840 0.5560 + 0.4660 0.6740 0.1880 + 0.3010 0.7450 0.9330 + 0.6350 0.0780 0.1840 + 0.2500 0.2500 0.2500]; + +i = 1 + mod(i - 1, size(y, 1)); +y = y(i, :); + +end \ No newline at end of file From b093e992154ec34839d8033bdeea0bef8e44f377 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sat, 22 Oct 2022 11:49:42 +0200 Subject: [PATCH 059/245] Better input validation for contract --- src/tensors/AbstractTensor.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tensors/AbstractTensor.m b/src/tensors/AbstractTensor.m index 95b31cb..1ea60b0 100644 --- a/src/tensors/AbstractTensor.m +++ b/src/tensors/AbstractTensor.m @@ -298,7 +298,7 @@ function C = contract(tensors, indices, kwargs) arguments (Repeating) - tensors + tensors {mustBeA(tensors, 'AbstractTensor')} indices (1, :) {mustBeInteger} end From 6665ada854a120c9dc3cf3610fe99a27606bbedd Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sat, 22 Oct 2022 12:59:41 +0200 Subject: [PATCH 060/245] Error messages, loosen stagnation criterium --- src/algorithms/Vumps.m | 7 +++++++ src/mps/MpsTensor.m | 19 ++++++++++++------- src/tensors/AbstractTensor.m | 8 ++------ 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/algorithms/Vumps.m b/src/algorithms/Vumps.m index 25f2461..3047d61 100644 --- a/src/algorithms/Vumps.m +++ b/src/algorithms/Vumps.m @@ -59,6 +59,13 @@ end function [mps, lambda, GL, GR] = fixedpoint(alg, mpo, mps) + + if period(mpo) ~= period(mps) + error('vumps:argerror', ... + 'periodicity of mpo (%d) should be equal to that of the mps (%d)', ... + period(mpo), period(mps)); + end + t_total = tic; disp_init(alg); diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index b73e110..7d967e3 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -104,6 +104,9 @@ eta_best = Inf; ctr_best = 0; + AL_best = AL; + C_best = C; + lambda_best = 0; for ctr = 1:kwargs.MaxIter if ctr > kwargs.EigsInit && mod(ctr, kwargs.EigsFrequence) == 0 @@ -133,11 +136,8 @@ lambdas(w) = norm(C(w)); if kwargs.Normalize, C(w) = C(w) ./ lambdas(w); end end - try + lambda = prod(lambdas); eta = norm(C_ - C(end), Inf); - catch - bla - end if eta < kwargs.Tol if kwargs.Verbosity >= Verbosity.conv fprintf('Conv %2d:\terror = %0.4e\n', ctr, eta); @@ -146,8 +146,15 @@ elseif eta < eta_best eta_best = eta; ctr_best = ctr; - elseif ctr - ctr_best > 3 + AL_best = AL; + C_best = C; + lambda_best = lambda; + elseif ctr > 40 && ctr - ctr_best > 5 warning('uniform_orthright:stagnate', 'Algorithm stagnated'); + eta = eta_best; + AL = AL_best; + C = C_best; + lambda = lambda_best; break; end @@ -159,8 +166,6 @@ if kwargs.Verbosity >= Verbosity.warn && eta > kwargs.Tol fprintf('Not converged %2d:\terror = %0.4e\n', ctr, eta); end - - if nargout > 2, lambda = prod(lambdas); end end function [R, AR] = rightorth(A, alg) diff --git a/src/tensors/AbstractTensor.m b/src/tensors/AbstractTensor.m index 1ea60b0..8b242d1 100644 --- a/src/tensors/AbstractTensor.m +++ b/src/tensors/AbstractTensor.m @@ -287,12 +287,8 @@ if flag && options.Verbosity > 0 warning('eigsolve did not converge.'); - end - - if options.Verbosity > 1 - if flag - fprintf('eigsolve converged.\n'); - end + elseif ~flag && options.Verbosity > 1 + fprintf('eigsolve converged.\n'); end end From 1543d64a3c95883e0e85158c7b940ea9cf46aaf8 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sat, 22 Oct 2022 13:16:11 +0200 Subject: [PATCH 061/245] Update disp for ProductCharge --- src/tensors/charges/ProductCharge.m | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/tensors/charges/ProductCharge.m b/src/tensors/charges/ProductCharge.m index 62b1126..9670061 100644 --- a/src/tensors/charges/ProductCharge.m +++ b/src/tensors/charges/ProductCharge.m @@ -433,8 +433,13 @@ end function disp(a) - for i = 1:length(a.charges) - disp(a.charges{i}); + s = string(a); + + + fprintf('\t%s (%s) Array:\n', ... + dim2str(size(a)), join(cellfun(@(x)string(class(x)), a.charges), 'x')); + for i = 1:size(a, 1) + fprintf('\t\t%s\n', join(s(i, :), ' ')); end end From 0c3a2f3196f95dc7a24144d4d053cc7a765dfc90 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sat, 22 Oct 2022 13:38:04 +0200 Subject: [PATCH 062/245] fU1 and fSU2 --- src/tensors/charges/SU2.m | 6 ++--- src/tensors/charges/U1.m | 1 - src/tensors/charges/fSU2.m | 33 ++++++++++++++++++++++++++ src/tensors/charges/fU1.m | 47 ++++++++++++++++++++++++++++++++++++++ test/TestCharge.m | 2 ++ 5 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 src/tensors/charges/fSU2.m create mode 100644 src/tensors/charges/fU1.m diff --git a/src/tensors/charges/SU2.m b/src/tensors/charges/SU2.m index 645721c..5f39445 100644 --- a/src/tensors/charges/SU2.m +++ b/src/tensors/charges/SU2.m @@ -97,9 +97,9 @@ function varargout = prod(varargin) [varargout{1:nargout}] = prod@AbstractCharge(varargin{:}); - if nargout > 0 - varargout{1} = SU2(varargout{1}); - end +% if nargout > 0 +% varargout{1} = SU2(varargout{1}); +% end end function d = qdim(a) diff --git a/src/tensors/charges/U1.m b/src/tensors/charges/U1.m index b8b77dc..58e2918 100644 --- a/src/tensors/charges/U1.m +++ b/src/tensors/charges/U1.m @@ -44,7 +44,6 @@ Nsymbol(e, c, d) .* Nsymbol(a, f, d)); end - function style = fusionstyle(~) style = FusionStyle.Unique; end diff --git a/src/tensors/charges/fSU2.m b/src/tensors/charges/fSU2.m new file mode 100644 index 0000000..3aaca94 --- /dev/null +++ b/src/tensors/charges/fSU2.m @@ -0,0 +1,33 @@ +classdef fSU2 < SU2 + % Fermionic spin charges. + % This is equivalent to representations of SU2 x fZ2, but restricted to only allow + % for the combinations of integer with trivial and halfinteger with fermion charges. + + methods + function style = braidingstyle(~) + style = BraidingStyle.Fermionic; + end + + function c = mtimes(a, b) + c = fSU2(mtimes@SU2(a, b)); + end + + function e = one(~) + e = fSU2(ones(1, 1, 'uint8')); + end + + function p = parity(a) + p = ~mod(a, 2); + end + + function R = Rsymbol(a, b, c, inv) + if nargin < 4, inv = false; end + R = (-2 .* double(parity(a) & parity(b)) + 1) .* Rsymbol@SU2(a, b, c, inv); + end + + function theta = twist(a) + theta = -2 * double(parity(a)) + 1; + end + end +end + diff --git a/src/tensors/charges/fU1.m b/src/tensors/charges/fU1.m new file mode 100644 index 0000000..e40fde3 --- /dev/null +++ b/src/tensors/charges/fU1.m @@ -0,0 +1,47 @@ +classdef fU1 < U1 + % Fermionic U1 charges. + % This is equivalent to representations of U1 x fZ2, but restricted to only allow + % for the combinations of even with trivial and odd with fermion charges. + + methods + function style = braidingstyle(~) + style = BraidingStyle.Fermionic; + end + + function a = conj(a) + a = fU1(conj@U1(a)); + end + + function varargout = cumprod(a) + [varargout{1:nargout}] = cumprod@U1(a); + varargout{1} = fU1(varargout{1}); + end + + function c = mtimes(a, b) + c = fU1(mtimes@U1(a, b)); + end + + function e = one(a) + e = fU1(one@U1(a)); + end + + function p = parity(a) + p = logical(mod(a, 2)); + end + + function varargout = prod(varargin) + [varargout{1:nargout}] = prod@U1(varargin{:}); + varargout{1} = fU1(varargout{1}); + end + + function R = Rsymbol(a, b, c, inv) + if nargin < 4, inv = false; end + R = (-2 .* double(parity(a) & parity(b)) + 1) .* Rsymbol@U1(a, b, c, inv); + end + + function theta = twist(a) + theta = -2 * double(parity(a)) + 1; + end + end +end + diff --git a/test/TestCharge.m b/test/TestCharge.m index 3e0d5b4..d9ca1ff 100644 --- a/test/TestCharge.m +++ b/test/TestCharge.m @@ -12,8 +12,10 @@ 'fZ2', fZ2([false true]), ... 'Z4', ZN(4, 0:3), ... 'U1', U1(-2:2), ... + 'fU1', fU1(-2:2), ... 'O2', O2([0 0 1 2], [0 1 2 2]), ... 'SU2', SU2(1:4), ... + 'fSU2', fSU2(1:4), ... 'A4', A4(1:4), ... 'SU3', SUN([1 0 0], [2 0 0], [3 0 0]), ... 'Z2xU1', ProductCharge(Z2(0, 0, 0, 1, 1, 1), U1(-1, 0, 1, -1, 0, 1)), ... From f6fe2822624dbe02cac37a0bf825110ef638672a Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sat, 22 Oct 2022 15:32:14 +0200 Subject: [PATCH 063/245] Add Heisenberg tests --- src/algorithms/Vumps.m | 93 +++++++++++++++++++++++++++--------------- src/mps/InfJMpo.m | 36 ++++++++++++++++ src/mps/InfMpo.m | 49 +++++++++++----------- src/mps/MpsTensor.m | 52 ++++++++++++----------- src/mps/UniformMps.m | 2 +- test/TestInfJMpo.m | 14 +++++++ 6 files changed, 166 insertions(+), 80 deletions(-) diff --git a/src/algorithms/Vumps.m b/src/algorithms/Vumps.m index 3047d61..4ba8f78 100644 --- a/src/algorithms/Vumps.m +++ b/src/algorithms/Vumps.m @@ -10,15 +10,16 @@ verbosity = Verbosity.iter which = 'largestabs' - - KrylovDim = 20 - dynamical_tols = true tol_min = 1e-12 tol_max = 1e-6 eigs_tolfactor = 1e-4 canonical_tolfactor = 1e-8 environments_tolfactor = 1e-4 + + multiAC = 'parallel' + dynamical_multiAC = false; + tol_multiAC = Inf end properties (Access = private) @@ -74,9 +75,10 @@ for iter = 1:alg.maxiter t_iter = tic; - AC = updateAC(alg, mpo, mps, GL, GR); - C = updateC (alg, mpo, mps, GL, GR); - mps = updatemps(alg, AC, C); + + AC = updateAC(alg, iter, mpo, mps, GL, GR); + C = updateC (alg, iter, mpo, mps, GL, GR); + mps = updatemps(alg, iter, mps, AC, C); [GL, GR, lambda] = environments(alg, mpo, mps, GL, GR); eta = convergence(alg, mpo, mps, GL, GR); @@ -96,30 +98,49 @@ %% Subroutines methods - function AC = updateAC(alg, mpo, mps, GL, GR) + function AC = updateAC(alg, iter, mpo, mps, GL, GR) kwargs = namedargs2cell(alg.alg_eigs); - H_AC = AC_hamiltonian(mpo, mps, GL, GR); - for i = period(mps):-1:1 - [AC(i), ~] = eigsolve(H_AC{i}, mps.AC(i), 1, alg.which, kwargs{:}); + if strcmp(alg.multiAC, 'sequential') + sites = mod(iter, period(mps)) + 1; + else + sites = 1:period(mps); + end + + H_AC = AC_hamiltonian(mpo, mps, GL, GR, sites); + for i = length(sites):-1:1 + [AC(i), ~] = eigsolve(H_AC{sites(i)}, mps.AC(sites(i)), 1, alg.which, ... + kwargs{:}); end end - function C = updateC(alg, mpo, mps, GL, GR) + function C = updateC(alg, iter, mpo, mps, GL, GR) kwargs = namedargs2cell(alg.alg_eigs); - H_C = C_hamiltonian(mpo, mps, GL, GR); - for i = period(mps):-1:1 - [C(i), ~] = eigsolve(H_C{i}, mps.C(i), 1, alg.which, kwargs{:}); + if strcmp(alg.multiAC, 'sequential') + sites = mod(iter, period(mps)) + 1; + else + sites = 1:period(mps); + end + + H_C = C_hamiltonian(mpo, mps, GL, GR, sites); + for i = length(sites):-1:1 + [C(i), ~] = eigsolve(H_C{sites(i)}, mps.C(sites(i)), 1, alg.which, ... + kwargs{:}); end end - function mps = updatemps(alg, AC, C) + function mps = updatemps(alg, iter, mps, AC, C) + if strcmp(alg.multiAC, 'sequential') + sites = mod(iter, period(mps)) + 1; + else + sites = 1:period(mps); + end + for i = length(AC):-1:1 - [~, Q_AC] = rightorth(AC(i)); - [~, Q_C] = rightorth(C(prev(i, length(C))), 1, 2); - - AR(i) = contract(conj(Q_C), [1 -1], Q_AC, [1 -2 -3], 'Rank', [1 2]); + [Q_AC, ~] = leftorth(AC(i)); + [Q_C, ~] = leftorth(C(i), 1, 2); + mps.AL(sites(i)) = multiplyright(Q_AC, Q_C'); end - mps = UniformMps([], AR, C, []); + kwargs = namedargs2cell(alg.alg_canonical); mps = canonicalize(mps, kwargs{:}); end @@ -163,18 +184,26 @@ %% Option handling methods function alg = updatetols(alg, iter, eta) - if ~alg.dynamical_tols, return; end - - alg.alg_eigs.Tol = between(alg.tol_min, eta * alg.eigs_tolfactor, ... - alg.tol_max / iter); - alg.alg_canonical.Tol = between(alg.tol_min, eta * alg.canonical_tolfactor, ... - alg.tol_max / iter); - alg.alg_environments.Tol = between(alg.tol_min, eta * alg.environments_tolfactor, ... - alg.tol_max / iter); - - if alg.verbosity > Verbosity.iter - fprintf('Updated subalgorithm tolerances: (%e,\t%e,\t%e)\n', ... - alg.alg_eigs.Tol, alg.alg_canonical.Tol, alg.alg_environments.Tol); + if alg.dynamical_tols + alg.alg_eigs.Tol = between(alg.tol_min, eta * alg.eigs_tolfactor, ... + alg.tol_max / iter); + alg.alg_canonical.Tol = between(alg.tol_min, ... + eta * alg.canonical_tolfactor, alg.tol_max / iter); + alg.alg_environments.Tol = between(alg.tol_min, ... + eta * alg.environments_tolfactor, alg.tol_max / iter); + + if alg.verbosity > Verbosity.iter + fprintf('Updated subalgorithm tolerances: (%e,\t%e,\t%e)\n', ... + alg.alg_eigs.Tol, alg.alg_canonical.Tol, alg.alg_environments.Tol); + end + end + + if alg.dynamical_multiAC + if eta < alg.tol_multiAC + alg.multiAC = 'sequential'; + else + alg.multiAC = 'parallel'; + end end end end diff --git a/src/mps/InfJMpo.m b/src/mps/InfJMpo.m index 0f47fac..09f7af9 100644 --- a/src/mps/InfJMpo.m +++ b/src/mps/InfJMpo.m @@ -194,5 +194,41 @@ mpo = InfJMpo(O); end + + function mpo = Heisenberg(J, h, kwargs) + arguments + J = 1 + h = 0 + kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'U1', 'SU2'})} = 'Z1' + kwargs.Spin = SU2(3) + end + + switch kwargs.Symmetry + case 'SU2' + assert(isscalar(J) || all(J == J(1)), ... + 'Different spin couplings not invariant under SU2'); + assert(h == 0, 'Magnetic field not invariant under SU2'); + + pSpace = GradedSpace.new(kwargs.Spin, 1, false); + aSpace = GradedSpace.new(SU2(3), 1, false); + tSpace = one(aSpace); + + s = spin(kwargs.Spin); + L = Tensor.ones([tSpace pSpace], [pSpace aSpace]); + L = L * (-J(1) * (s^2 + s)); + R = Tensor.ones([aSpace pSpace], [pSpace tSpace]); + + + O = MpoTensor.zeros(3, 1, 3, 1); + O(1, 1, 1, 1) = 1; + O(3, 1, 3, 1) = 1; + O(1, 1, 2, 1) = L; + O(2, 1, 3, 1) = R; + + otherwise + error('TBA'); + end + mpo = InfJMpo(O); + end end end diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index faf659e..213dde2 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -73,6 +73,7 @@ eigopts.MaxIter = 1000 eigopts.ReOrth = 2 eigopts.Tol = eps(underlyingType(mps1))^(3/4) + eigopts.Verbosity = Verbosity.warn end N = period(mps1); T = transfermatrix(mpo, mps1, mps2, 'Type', 'LL'); @@ -96,6 +97,7 @@ eigopts.MaxIter = 1000 eigopts.ReOrth = 2 eigopts.Tol = eps(underlyingType(mps1))^(3/4) + eigopts.Verbosity = Verbosity.warn end T = transfermatrix(mpo, mps1, mps2, 'Type', 'RR').'; @@ -120,6 +122,7 @@ eigopts.MaxIter = 1000 eigopts.ReOrth = 2 eigopts.Tol = eps(underlyingType(mps1))^(3/4) + eigopts.Verbosity = Verbosity.warn end kwargs = namedargs2cell(eigopts); @@ -130,9 +133,11 @@ warning('lambdas disagree'); end - for w = 1:length(mps1) - overlap = sqrt(contract(GL{w}, [1 3 2], mps1.C(w), [2 4], ... - mps2.C(w)', [5 1], GR{w}, [4 3 5])); + for w = 1:period(mps1) + overlap = sqrt(contract(GL{w}, [1 3 2], ... + mps1.C(prev(w, period(mps1))), [2 4], ... + mps2.C(prev(w, period(mps1)))', [5 1], ... + GR{w}, [4 3 5])); GL{w} = GL{w} ./ overlap; GR{w} = GR{w} ./ overlap; end @@ -147,6 +152,7 @@ mpo = InfMpo([Os{:}]); end end + %% Derived operators methods function T = transfermatrix(mpo, mps1, mps2, sites, kwargs) @@ -177,57 +183,54 @@ twist(A2(i)', 1 + find(isdual(space(A1(i), 2:nspaces(A1(i))-1)))), ... O(i), A1(i)); end - -% T = FiniteMpo(A2, {rot90(mpo.O{1})}, A1); end - function H = AC_hamiltonian(mpo, mps, GL, GR) + function H = AC_hamiltonian(mpo, mps, GL, GR, sites) arguments mpo mps GL = fixedpoint(transfermatrix(mpo, mps, 'Type', 'LL')) GR = fixedpoint(transfermatrix(mpo, mps, 'Type', 'RR').') + sites = 1:period(mps) end - H = cell(1, period(mps)); - for i = 1:period(mps) - gl = GL{i}; + + H = cell(1, length(sites)); + for i = 1:length(sites) + gl = GL{sites(i)}; for j = 1:numel(gl) gl(j) = twist(gl(j), find(isdual(space(gl(j), 1)))); end - gr = GR{next(i, period(mps))}; + gr = GR{next(sites(i), period(mps))}; for j = 1:numel(gr) gr(j) = twist(gr(j), find(isdual(space(gr(j), nspaces(gr(j))))) + ... nspaces(gr(j)) - 1); end - H{i} = FiniteMpo(gl, mpo.O{i}, gr); + H{i} = FiniteMpo(gl, mpo.O{sites(i)}, gr); end -% GR = twist(GR, find(isdual(space(GR, nspaces(GR)))) + nspaces(GR)-1); -% GL = twist(GL, find(isdual(space(GL, 1)))); -% H = FiniteMpo(GL, mpo.O, GR); end - function H = C_hamiltonian(mpo, mps, GL, GR) + function H = C_hamiltonian(~, mps, GL, GR, sites) arguments - mpo + ~ mps GL = fixedpoint(transfermatrix(mpo, mps, 'Type', 'LL')) GR = fixedpoint(transfermatrix(mpo, mps, 'Type', 'RR').') + sites = 1:period(mps) end - for i = 1:period(mps) - gl = GL{i}; + + H = cell(1, length(sites)); + for i = 1:length(sites) + gl = GL{next(sites(i), period(mps))}; for j = 1:numel(gl) gl(j) = twist(gl(j), find(isdual(space(gl(j), 1)))); end - gr = GR{i}; + gr = GR{next(sites(i), period(mps))}; for j = 1:numel(gr) gr(j) = twist(gr(j), find(isdual(space(gr(j), nspaces(gr(j))))) + ... nspaces(gr(j)) - 1); end - H{prev(i, period(mps))} = FiniteMpo(gl, {}, gr); + H{i} = FiniteMpo(gl, {}, gr); end -% GR = twist(GR, find(isdual(space(GR, nspaces(GR)))) + nspaces(GR)-1); -% GL = twist(GL, find(isdual(space(GL, 1)))); -% H = FiniteMpo(GL, {}, GR); end end diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index 7d967e3..a6990b8 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -59,7 +59,7 @@ function [AL, L] = leftorth(A, alg) arguments A - alg = 'polar' + alg = 'qrpos' end if A.alegs == 0 @@ -76,10 +76,10 @@ AL = MpsTensor(AL, A.alegs); end - function [AL, C, lambda, eta] = uniform_leftorth(A, C, kwargs) + function [AL, CL, lambda, eta] = uniform_leftorth(A, CL, kwargs) arguments A - C = initializeC(A, circshift(A, 1)) + CL = [] kwargs.Tol = eps(underlyingType(A))^(3/4) kwargs.MaxIter = 500 kwargs.Method = 'polar' @@ -97,20 +97,20 @@ % initialization N = size(A, 2); - if isempty(C), C = initializeC(A, circshift(A, 1)); end - if kwargs.Normalize, C(1) = normalize(C(1)); end + if isempty(CL), CL = initializeC(A, circshift(A, 1)); end + if kwargs.Normalize, CL(1) = normalize(CL(1)); end A = arrayfun(@(a) MpsTensor(repartition(a, [nspaces(a)-1 1])), A); AL = A; eta_best = Inf; ctr_best = 0; AL_best = AL; - C_best = C; + C_best = CL; lambda_best = 0; for ctr = 1:kwargs.MaxIter if ctr > kwargs.EigsInit && mod(ctr, kwargs.EigsFrequence) == 0 - C_ = repartition(C(end), [2 0]); + C_ = repartition(CL(end), [2 0]); T = transfermatrix(A, AL); [C_, ~] = eigsolve(T, C_, ... 1, 'largestabs', ... @@ -120,24 +120,24 @@ MAXKRYLOVDIM), ... 'NoBuild', 4, ... 'Verbosity', kwargs.Verbosity - 1); - [~, C(end)] = leftorth(C_, 1, 2, kwargs.Method); - if isdual(space(C(end), 2)) == isdual(space(C(end), 1)) - C(end).codomain = conj(C(end).codomain); - C(end) = twist(C(end), 1); + [~, CL(end)] = leftorth(C_, 1, 2, kwargs.Method); + if isdual(space(CL(end), 2)) == isdual(space(CL(end), 1)) + CL(end).codomain = conj(CL(end).codomain); + CL(end) = twist(CL(end), 1); end end - C_ = C(end); + C_ = CL(end); lambdas = ones(1, N); for w = 1:N ww = prev(w, N); - CA = multiplyleft(A(w), C(ww)); - [AL(w), C(w)] = leftorth(CA, kwargs.Method); - lambdas(w) = norm(C(w)); - if kwargs.Normalize, C(w) = C(w) ./ lambdas(w); end + CA = multiplyleft(A(w), CL(ww)); + [AL(w), CL(w)] = leftorth(CA, kwargs.Method); + lambdas(w) = norm(CL(w)); + if kwargs.Normalize, CL(w) = CL(w) ./ lambdas(w); end end lambda = prod(lambdas); - eta = norm(C_ - C(end), Inf); + eta = norm(C_ - CL(end), Inf); if eta < kwargs.Tol if kwargs.Verbosity >= Verbosity.conv fprintf('Conv %2d:\terror = %0.4e\n', ctr, eta); @@ -147,13 +147,13 @@ eta_best = eta; ctr_best = ctr; AL_best = AL; - C_best = C; + C_best = CL; lambda_best = lambda; elseif ctr > 40 && ctr - ctr_best > 5 warning('uniform_orthright:stagnate', 'Algorithm stagnated'); eta = eta_best; AL = AL_best; - C = C_best; + CL = C_best; lambda = lambda_best; break; end @@ -186,10 +186,10 @@ % AR = MpsTensor(repartition(AR, rank(A)), A.alegs); end - function [AR, C, lambda, eta] = uniform_rightorth(A, C, kwargs) + function [AR, CR, lambda, eta] = uniform_rightorth(A, CR, kwargs) arguments A - C = initializeC(A, circshift(A, 1)) + CR = [] kwargs.Tol = eps(underlyingType(A))^(3/4) kwargs.MaxIter = 500 kwargs.Method = 'polar' @@ -202,12 +202,16 @@ opts = namedargs2cell(kwargs); Ad = arrayfun(@ctranspose, A); - Cd = arrayfun(@ctranspose, C); + if isempty(CR) + Cd = []; + else + Cd = circshift(arrayfun(@ctranspose, CR), -1); + end - [AR, C, lambda, eta] = uniform_leftorth(Ad, Cd, opts{:}); + [AR, CR, lambda, eta] = uniform_leftorth(Ad, Cd, opts{:}); AR = arrayfun(@ctranspose, AR); - C = arrayfun(@ctranspose, C); + CR = circshift(arrayfun(@ctranspose, CR), 1); lambda = conj(lambda); end diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index 76d8ac5..84b530e 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -131,7 +131,7 @@ end for i = 1:length(mps) - if strcmp(kwargs.Order, 'lr') + if strcmp(kwargs.Order, 'rl') [mps(i).AR, ~, ~, eta1] = uniform_rightorth(... mps(i).AR, [], ... 'Tol', kwargs.Tol, 'MaxIter', kwargs.MaxIter, ... diff --git a/test/TestInfJMpo.m b/test/TestInfJMpo.m index 0b9b9cf..6cbe59a 100644 --- a/test/TestInfJMpo.m +++ b/test/TestInfJMpo.m @@ -58,6 +58,20 @@ function test1dIsing(tc) [mps2, lambda2] = fixedpoint(alg, mpo, mps); tc.verifyTrue(isapprox(lambda2/2, -1.27, 'RelTol', 5e-2)) end + + function test1dHeisenberg(tc) + alg = Vumps('which', 'smallestreal', 'maxiter', 5); + + mpo = InfJMpo.Heisenberg('Spin', SU2(3), 'Symmetry', 'SU2'); + mpo = [mpo mpo]; + + mps = UniformMps.randnc(pspace(mpo), ... + GradedSpace.new(SU2(1:2:5), [5 5 1], false, SU2(1:2:5), [5 5 1], false)); + + [gs_mps, lambda] = fixedpoint(alg, mpo, mps); + tc.verifyEqual(lambda / period(mps), -1.40, 'RelTol', 1e-2); + + end end end From 6f0c6ab6ada2a3d19f4d3d0c9cf6e1beb72f03a4 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 24 Oct 2022 12:11:48 +0200 Subject: [PATCH 064/245] Many algorithms --- src/algorithms/IDmrg.m | 188 ++++++++++++++++++++ src/algorithms/IDmrg2.m | 255 ++++++++++++++++++++++++++++ src/algorithms/Vomps.m | 248 +++++++++++++++++++++++++++ src/algorithms/Vumps.m | 37 ++-- src/mps/InfMpo.m | 58 +++++-- src/mps/MpoTensor.m | 75 +------- src/mps/UniformMps.m | 40 ++++- src/tensors/spaces/CartesianSpace.m | 7 +- src/utility/mod1.m | 9 + test/TestAlgorithms.m | 85 ++++++++++ 10 files changed, 898 insertions(+), 104 deletions(-) create mode 100644 src/algorithms/IDmrg.m create mode 100644 src/algorithms/IDmrg2.m create mode 100644 src/algorithms/Vomps.m create mode 100644 src/utility/mod1.m create mode 100644 test/TestAlgorithms.m diff --git a/src/algorithms/IDmrg.m b/src/algorithms/IDmrg.m new file mode 100644 index 0000000..404a90b --- /dev/null +++ b/src/algorithms/IDmrg.m @@ -0,0 +1,188 @@ +classdef IDmrg + % Infinite Density Matrix Renormalization Group algorithm + + properties + tol = 1e-10 + miniter = 5 + maxiter = 100 + verbosity = Verbosity.iter + which = 'largestabs' + + dynamical_tols = true + tol_min = 1e-12 + tol_max = 1e-6 + eigs_tolfactor = 1e-4 + end + + properties (Access = private) + alg_eigs = struct('MaxIter', 100, 'KrylovDim', 20) + end + + methods + function alg = IDmrg(kwargs) + arguments + kwargs.?IDmrg + end + + fields = fieldnames(kwargs); + if ~isempty(fields) + for field = fields.' + alg.(field{1}) = kwargs.(field{1}); + end + end + + if ~isfield('alg_eigs', kwargs) + alg.alg_eigs.Tol = sqrt(alg.tol_min * alg.tol_max); + alg.alg_eigs.Verbosity = alg.verbosity - 2; + end + end + + function [mps, lambda, GL, GR] = fixedpoint(alg, mpo, mps) + if period(mpo) ~= period(mps) + error('idmrg:argerror', ... + 'periodicity of mpo (%d) should be equal to that of the mps (%d)', ... + period(mpo), period(mps)); + end + + t_total = tic; + disp_init(alg); + + [GL, GR] = environments(mpo, mps, mps); + + for iter = 1:alg.maxiter + t_iter = tic; + + C_ = mps.C(end); + kwargs = {}; + lambdas = zeros(1, period(mps)); + for pos = 1:period(mps) + H = AC_hamiltonian(mpo, mps, GL, GR, pos); + [mps.AC(pos), lambdas(pos)] = ... + eigsolve(H{1}, mps.AC(pos), 1, alg.which, kwargs{:}); + [mps.AL(pos), mps.C(pos)] = leftorth(mps.AC(pos)); + + T = transfermatrix(mpo, mps, mps, pos, 'Type', 'LL'); + GL{next(pos, period(mps))} = apply(T, GL{pos}) / lambdas(pos); + end + + for pos = period(mps):-1:1 + H = AC_hamiltonian(mpo, mps, GL, GR, pos); + [mps.AC(pos), lambdas(pos)] = ... + eigsolve(H{1}, mps.AC(pos), 1, alg.which, kwargs{:}); + [mps.C(prev(pos, period(mps))), mps.AR(pos)] = rightorth(mps.AC(pos)); + + T = transfermatrix(mpo, mps, mps, pos, 'Type', 'RR').'; + GR{pos} = apply(T, GR{next(pos, period(mps))}) / lambdas(pos); + end + + eta = distance(C_, mps.C(end)); + lambda = prod(lambdas); + + if iter > alg.miniter && eta < alg.tol + mps = canonicalize(mps); + disp_conv(alg, iter, lambda, eta, toc(t_total)); + return + end + + alg = updatetols(alg, iter, eta); + disp_iter(alg, iter, lambda, eta, toc(t_iter)); + end + + mps = canonicalize(mps); + disp_maxiter(alg, iter, lambda, eta, toc(t_total)); + end + end + + + methods + function [GL, GR, lambda] = environments(alg, mpo, mps, GL, GR) + arguments + alg + mpo + mps + GL = cell(1, period(mps)) + GR = cell(1, period(mps)) + end + + [GL, GR, lambda] = environments(mpo, mps, mps, GL, GR); + end + + function alg = updatetols(alg, iter, eta) + if alg.dynamical_tols + alg.alg_eigs.Tol = between(alg.tol_min, eta * alg.eigs_tolfactor, ... + alg.tol_max / iter); + + if alg.verbosity > Verbosity.iter + fprintf('Updated subalgorithm tolerances: (%e,\t%e,\t%e)\n', ... + alg.alg_eigs.Tol, alg.alg_canonical.Tol, alg.alg_environments.Tol); + end + end + end + end + + %% Display + methods (Access = private) + function disp_init(alg) + if alg.verbosity < Verbosity.conv, return; end + fprintf('---- IDmrg ----\n'); + end + + function disp_iter(alg, iter, lambda, eta, t) + if alg.verbosity < Verbosity.iter, return; end + + s = settings; + if abs(imag(lambda)) < eps(lambda)^(3/4) * abs(lambda) + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('IDmrg %2d:\tE = %-0.4f\terror = %0.1e\t(%s)\n', ... + iter, real(lambda), eta, time2str(t)); + otherwise + fprintf('IDmrg %4d:\tE = %-0.15f\terror = %0.4e\t(%s)\n', ... + iter, real(lambda), eta, time2str(t, 's')); + + end + else + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('IDmrg %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + otherwise + fprintf('IDmrg %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + + end + end + end + + function disp_conv(alg, iter, lambda, eta, t) + if alg.verbosity < Verbosity.conv, return; end + s = settings; + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('IDmrg converged %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + otherwise + fprintf('IDmrg converged %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + + end + fprintf('---------------\n'); + end + + function disp_maxiter(alg, iter, lambda, eta, t) + if alg.verbosity < Verbosity.warn, return; end + s = settings; + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('IDmrg max iterations %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + otherwise + fprintf('IDmrg max iterations %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + + end + fprintf('---------------\n'); + end + end +end + diff --git a/src/algorithms/IDmrg2.m b/src/algorithms/IDmrg2.m new file mode 100644 index 0000000..3c63ab5 --- /dev/null +++ b/src/algorithms/IDmrg2.m @@ -0,0 +1,255 @@ +classdef IDmrg2 + % Infinite Density Matrix Renormalization Group algorithm + + properties + tol = 1e-10 + miniter = 5 + maxiter = 100 + verbosity = Verbosity.iter + which = 'largestabs' + + dynamical_tols = true + tol_min = 1e-12 + tol_max = 1e-6 + eigs_tolfactor = 1e-4 + trunc = {'TruncDim', 10} + end + + properties (Access = private) + alg_eigs = struct('MaxIter', 100, 'KrylovDim', 20) + end + + methods + function alg = IDmrg2(kwargs) + arguments + kwargs.?IDmrg2 + end + + fields = fieldnames(kwargs); + if ~isempty(fields) + for field = fields.' + alg.(field{1}) = kwargs.(field{1}); + end + end + + if ~isfield('alg_eigs', kwargs) + alg.alg_eigs.Tol = sqrt(alg.tol_min * alg.tol_max); + alg.alg_eigs.Verbosity = alg.verbosity - 2; + end + end + + function [mps, lambda, GL, GR] = fixedpoint(alg, mpo, mps) + if period(mpo) ~= period(mps) + error('idmrg2:argerror', ... + 'periodicity of mpo (%d) should be equal to that of the mps (%d)', ... + period(mpo), period(mps)); + end + if period(mps) < 2 + error('idmrg2:argerror', ... + 'IDmrg2 is only defined for periodicity > 1'); + end + + t_total = tic; + disp_init(alg); + + [GL, GR] = environments(mpo, mps, mps); + + for iter = 1:alg.maxiter + t_iter = tic; + + C_ = mps.C(end); + kwargs = {}; + lambdas = zeros(1, period(mps)); + + % sweep from left to right + for pos = 1:period(mps)-1 + AC2 = contract(mps.AC(pos), [-1 -2 1], ... + mps.AR(pos + 1), [1 -3 -4], ... + 'Rank', [2 2]); + H = AC2_hamiltonian(mpo, mps, GL, GR, pos); + [AC2, lambdas(pos)] = ... + eigsolve(H{1}, AC2, 1, alg.which, kwargs{:}); + [mps.AL(pos), C, mps.AR(pos + 1), delta] = ... + tsvd(AC2, [1 2], [3 4], alg.trunc{:}); + + mps.C(pos) = normalize(C); + mps.AC(pos + 1) = multiplyleft(mps.AR(pos + 1), mps.C(pos)); + + TL = transfermatrix(mpo, mps, mps, pos, 'Type', 'LL'); + GL{pos + 1} = apply(TL, GL{pos}) / sqrt(lambdas(pos)); + TR = transfermatrix(mpo, mps, mps, pos + 1, 'Type', 'RR'); + GR{pos + 1} = apply(TR.', GR{mod1(pos + 2, length(GR))}) / ... + sqrt(lambdas(pos)); + end + + % update edge + AC2 = contract(mps.AC(end), [-1 -2 1], inv(mps.C(end)), [1 2], ... + mps.AL(1), [2 -3 3], mps.C(1), [3 -4], 'Rank', [2 2]); + H = AC2_hamiltonian(mpo, mps, GL, GR, period(mps)); + [AC2, lambdas(end)] = eigsolve(H{1}, AC2, 1, alg.which, kwargs{:}); + + [mps.AL(end), C, mps.AR(1)] = ... + tsvd(AC2, [1 2], [3 4], alg.trunc{:}); + mps.C(end) = normalize(C); + mps.AC(end) = multiplyright(mps.AL(end), mps.C(end)); + mps.AC(1) = multiplyleft(mps.AR(1), mps.C(end)); + mps.AL(1) = multiplyright(mps.AC(1), inv(mps.C(1))); + + TL = transfermatrix(mpo, mps, mps, period(mps), 'Type', 'LL'); + GL{1} = apply(TL, GL{end}) / sqrt(lambdas(end)); + TR = transfermatrix(mpo, mps, mps, 1, 'Type', 'RR'); + GR{1} = apply(TR.', GR{2}) / sqrt(lambdas(end)); + + % sweep from right to left + for pos = period(mps)-1:-1:1 + AC2 = contract(mps.AL(pos), [-1 -2 1], ... + mps.AC(pos + 1), [1 -3 -4], 'Rank', [2 2]); + H = AC2_hamiltonian(mpo, mps, GL, GR, pos); + [AC2, lambdas(pos)] = eigsolve(H{1}, AC2, 1, alg.which, kwargs{:}); + + [mps.AL(pos), C, mps.AR(pos + 1)] = ... + tsvd(AC2, [1 2], [3 4], alg.trunc{:}); + mps.C(pos) = normalize(C); + mps.AC(pos) = multiplyright(mps.AL(pos), mps.C(pos)); + mps.AC(pos + 1) = multiplyleft(mps.AR(pos + 1), mps.C(pos)); + + TL = transfermatrix(mpo, mps, mps, pos, 'Type', 'LL'); + GL{pos + 1} = apply(TL, GL{pos}) / sqrt(lambdas(pos)); + TR = transfermatrix(mpo, mps, mps, pos + 1, 'Type', 'RR'); + GR{pos} = apply(TR.', GR{pos + 1}) / sqrt(lambdas(pos)); + end + + % update edge + AC2 = contract(mps.C(end-1), [-1 1], mps.AR(end), [1 -2 2], ... + inv(mps.C(end)), [2 3], mps.AC(1), [3 -3 -4], 'Rank', [2 2]); + H = AC2_hamiltonian(mpo, mps, GL, GR, period(mps)); + [AC2, lambdas(1)] = eigsolve(H{1}, AC2, 1, alg.which, kwargs{:}); + + [mps.AL(end), C, mps.AR(1)] = ... + tsvd(AC2, [1 2], [3 4], alg.trunc{:}); + mps.C(end) = normalize(C); + mps.AC(1) = multiplyleft(mps.AR(1), mps.C(end)); + mps.AR(end) = multiplyleft(multiplyright(mps.AL(end), mps.C(end)), ... + inv(mps.C(end - 1))); + + TL = transfermatrix(mpo, mps, mps, period(mps), 'Type', 'LL'); + GL{1} = apply(TL, GL{end}) / sqrt(lambdas(end)); + TR = transfermatrix(mpo, mps, mps, 1, 'Type', 'RR'); + GR{end} = apply(TR.', GR{1}) / sqrt(lambdas(end)); + + % error measure + infspace = infimum(space(C_, 1), space(mps.C(end), 1)); + e1 = C_.eye(space(mps.C(end), 1), infspace); + e2 = C_.eye(space(C_, 1), infspace); + + eta = distance(e2' * C_ * e2, e1' * mps.C(end) * e1); + lambda = prod(sqrt(lambdas)); + + if iter > alg.miniter && eta < alg.tol + mps = canonicalize(mps); + disp_conv(alg, iter, lambda, eta, toc(t_total)); + return + end + + alg = updatetols(alg, iter, eta); + disp_iter(alg, iter, lambda, eta, toc(t_iter)); + end + + mps = canonicalize(mps); + disp_maxiter(alg, iter, lambda, eta, toc(t_total)); + end + end + + + methods + function [GL, GR, lambda] = environments(alg, mpo, mps, GL, GR) + arguments + alg + mpo + mps + GL = cell(1, period(mps)) + GR = cell(1, period(mps)) + end + + [GL, GR, lambda] = environments(mpo, mps, mps, GL, GR); + end + + function alg = updatetols(alg, iter, eta) + if alg.dynamical_tols + alg.alg_eigs.Tol = between(alg.tol_min, eta * alg.eigs_tolfactor, ... + alg.tol_max / iter); + + if alg.verbosity > Verbosity.iter + fprintf('Updated subalgorithm tolerances: (%e,\t%e,\t%e)\n', ... + alg.alg_eigs.Tol, alg.alg_canonical.Tol, alg.alg_environments.Tol); + end + end + end + end + + %% Display + methods (Access = private) + function disp_init(alg) + if alg.verbosity < Verbosity.conv, return; end + fprintf('---- IDmrg2 ----\n'); + end + + function disp_iter(alg, iter, lambda, eta, t) + if alg.verbosity < Verbosity.iter, return; end + + s = settings; + if abs(imag(lambda)) < eps(lambda)^(3/4) * abs(lambda) + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('IDmrg2 %2d:\tE = %-0.4f\terror = %0.1e\t(%s)\n', ... + iter, real(lambda), eta, time2str(t)); + otherwise + fprintf('IDmrg2 %4d:\tE = %-0.15f\terror = %0.4e\t(%s)\n', ... + iter, real(lambda), eta, time2str(t, 's')); + + end + else + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('IDmrg2 %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + otherwise + fprintf('IDmrg2 %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + + end + end + end + + function disp_conv(alg, iter, lambda, eta, t) + if alg.verbosity < Verbosity.conv, return; end + s = settings; + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('IDmrg converged %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + otherwise + fprintf('IDmrg converged %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + + end + fprintf('---------------\n'); + end + + function disp_maxiter(alg, iter, lambda, eta, t) + if alg.verbosity < Verbosity.warn, return; end + s = settings; + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('IDmrg max iterations %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + otherwise + fprintf('IDmrg max iterations %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + + end + fprintf('---------------\n'); + end + end +end + diff --git a/src/algorithms/Vomps.m b/src/algorithms/Vomps.m new file mode 100644 index 0000000..52ab432 --- /dev/null +++ b/src/algorithms/Vomps.m @@ -0,0 +1,248 @@ +classdef Vomps + % Fixed point algorithm for maximizing overlap. + + %% Options + properties + tol = 1e-10 + miniter = 2 + maxiter = 100 + verbosity = Verbosity.iter + which = 'largestabs' + + dynamical_tols = true + tol_min = 1e-12 + tol_max = 1e-6 + eigs_tolfactor = 1e-4 + canonical_tolfactor = 1e-8 + environments_tolfactor = 1e-4 + + multiAC = 'parallel' + dynamical_multiAC = false; + tol_multiAC = Inf + end + + properties (Access = private) + alg_canonical = struct('Method', 'polar') + alg_environments = struct + end + + methods + function v = Vomps(kwargs) + arguments + kwargs.?Vumps + end + + fields = fieldnames(kwargs); + if ~isempty(fields) + for field = fields.' + v.(field{1}) = kwargs.(field{1}); + end + end + + if ~isfield('alg_canonical', kwargs) + v.alg_canonical.Tol = sqrt(v.tol_min * v.tol_max); + v.alg_canonical.Verbosity = v.verbosity - 2; + end + + if ~isfield('alg_environments', kwargs) + v.alg_environments.Tol = sqrt(v.tol_min * v.tol_max); + v.alg_environments.Verbosity = v.verbosity - 2; + end + end + + function [mps2, GL, GR] = approximate(alg, mpo, mps1, mps2) + if period(mpo) ~= period(mps1) || period(mpo) ~= period(mps2) + error('vumps:argerror', ... + 'periodicitys should match: mpo (%d), mps1 (%d), mps2(%d)', ... + period(mpo), period(mps1), period(mps2)); + end + + t_total = tic; + disp_init(alg); + + mps2 = canonicalize(mps2); + [GL, GR] = environments(alg, mpo, mps1, mps2); + + for iter = 1:alg.maxiter + t_iter = tic; + + AC = updateAC(alg, iter, mpo, mps1, GL, GR); + C = updateC (alg, iter, mpo, mps1, GL, GR); + mps2 = updatemps(alg, iter, mps2, AC, C); + + [GL, GR] = environments(alg, mpo, mps1, mps2, GL, GR); + eta = convergence(alg, mpo, mps1, mps2, GL, GR); + + if iter > alg.miniter && eta < alg.tol + disp_conv(alg, iter, eta, toc(t_total)); + return + end + alg = updatetols(alg, iter, eta); + disp_iter(alg, iter, eta, toc(t_iter)); + end + + disp_maxiter(alg, iter, eta, toc(t_total)); + end + end + + %% Subroutines + methods + function AC = updateAC(alg, iter, mpo, mps, GL, GR) + if strcmp(alg.multiAC, 'sequential') + sites = mod(iter, period(mps)) + 1; + else + sites = 1:period(mps); + end + + H_AC = AC_hamiltonian(mpo, mps, GL, GR, sites); + for i = length(sites):-1:1 + AC(i) = MpsTensor(apply(H_AC{sites(i)}, mps.AC(sites(i))), ... + mps.AC(sites(i)).alegs); + end + end + + function C = updateC(alg, iter, mpo, mps, GL, GR) + if strcmp(alg.multiAC, 'sequential') + sites = mod(iter, period(mps)) + 1; + else + sites = 1:period(mps); + end + + H_C = C_hamiltonian(mpo, mps, GL, GR, sites); + for i = length(sites):-1:1 + C(i) = apply(H_C{sites(i)}, mps.C(sites(i))); + end + end + + function mps = updatemps(alg, iter, mps, AC, C) + if strcmp(alg.multiAC, 'sequential') + sites = mod(iter, period(mps)) + 1; + else + sites = 1:period(mps); + end + + for i = length(AC):-1:1 + [Q_AC, ~] = leftorth(AC(i)); + [Q_C, ~] = leftorth(C(i), 1, 2); + mps.AL(sites(i)) = multiplyright(Q_AC, Q_C'); + end + + kwargs = namedargs2cell(alg.alg_canonical); + mps = canonicalize(mps, kwargs{:}); + end + + function [GL, GR, lambda] = environments(alg, mpo, mps1, mps2, GL, GR) + arguments + alg + mpo + mps1 + mps2 + GL = cell(1, period(mps1)) + GR = cell(1, period(mps1)) + end + + kwargs = namedargs2cell(alg.alg_environments); + [GL, GR, lambda] = environments(mpo, mps1, mps2, GL, GR, ... + kwargs{:}); + end + + function eta = convergence(alg, mpo, mps1, mps2, GL, GR) + H_AC = AC_hamiltonian(mpo, mps1, GL, GR); + H_C = C_hamiltonian(mpo, mps1, GL, GR); + eta = zeros(1, period(mps1)); + for w = 1:period(mps1) + AC_ = apply(H_AC{w}, mps1.AC(w)); + lambda_AC = dot(AC_, mps2.AC(w)); + AC_ = normalize(AC_ ./ lambda_AC); + + ww = prev(w, period(mps1)); + C_ = apply(H_C{ww}, mps1.C(ww)); + lambda_C = dot(C_, mps2.C(ww)); + C_ = normalize(C_ ./ lambda_C); + + eta(w) = distance(AC_ , ... + repartition(multiplyleft(mps2.AR(w), C_), rank(AC_))); + end + eta = max(eta, [], 'all'); + end + end + + %% Option handling + methods + function alg = updatetols(alg, iter, eta) + if alg.dynamical_tols + alg.alg_canonical.Tol = between(alg.tol_min, ... + eta * alg.canonical_tolfactor, alg.tol_max / iter); + alg.alg_environments.Tol = between(alg.tol_min, ... + eta * alg.environments_tolfactor, alg.tol_max / iter); + + if alg.verbosity > Verbosity.iter + fprintf('Updated subalgorithm tolerances: (%e,\t%e,\t%e)\n', ... + alg.alg_eigs.Tol, alg.alg_canonical.Tol, alg.alg_environments.Tol); + end + end + + if alg.dynamical_multiAC + if eta < alg.tol_multiAC + alg.multiAC = 'sequential'; + else + alg.multiAC = 'parallel'; + end + end + end + end + + %% Display + methods (Access = private) + function disp_init(alg) + if alg.verbosity < Verbosity.conv, return; end + fprintf('---- VOMPS ----\n'); + end + + function disp_iter(alg, iter, eta, t) + if alg.verbosity < Verbosity.iter, return; end + + s = settings; + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('Vomps %2d:\terror = %0.1e\t(%s)\n', ... + iter, eta, time2str(t)); + otherwise + fprintf('Vomps %4d:\terror = %0.4e\t(%s)\n', ... + iter, eta, time2str(t)); + + end + end + + function disp_conv(alg, iter, eta, t) + if alg.verbosity < Verbosity.conv, return; end + s = settings; + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('Vomps converged %2d:\terror = %0.1e\t(%s)\n', ... + iter, eta, time2str(t)); + otherwise + fprintf('Vomps converged %4d:\terror = %0.4e\t(%s)\n', ... + iter, eta, time2str(t)); + + end + fprintf('---------------\n'); + end + + function disp_maxiter(alg, iter, eta, t) + if alg.verbosity < Verbosity.warn, return; end + s = settings; + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('Vomps max iterations %2d:\terror = %0.1e\t(%s)\n', ... + iter, eta, time2str(t)); + otherwise + fprintf('Vomps max iterations %4d:\terror = %0.4e\t(%s)\n', ... + iter, eta, time2str(t)); + + end + fprintf('---------------\n'); + end + end +end + diff --git a/src/algorithms/Vumps.m b/src/algorithms/Vumps.m index 4ba8f78..437b9b0 100644 --- a/src/algorithms/Vumps.m +++ b/src/algorithms/Vumps.m @@ -1,6 +1,5 @@ classdef Vumps - %UNTITLED Summary of this class goes here - % Detailed explanation goes here + % Variational fixed point algorithm for uniform matrix product states. %% Options properties @@ -31,7 +30,7 @@ %% methods - function v = Vumps(kwargs) + function alg = Vumps(kwargs) arguments kwargs.?Vumps end @@ -39,23 +38,23 @@ fields = fieldnames(kwargs); if ~isempty(fields) for field = fields.' - v.(field{1}) = kwargs.(field{1}); + alg.(field{1}) = kwargs.(field{1}); end end if ~isfield('alg_eigs', kwargs) - v.alg_eigs.Tol = sqrt(v.tol_min * v.tol_max); - v.alg_eigs.Verbosity = v.verbosity - 2; + alg.alg_eigs.Tol = sqrt(alg.tol_min * alg.tol_max); + alg.alg_eigs.Verbosity = alg.verbosity - 2; end if ~isfield('alg_canonical', kwargs) - v.alg_canonical.Tol = sqrt(v.tol_min * v.tol_max); - v.alg_canonical.Verbosity = v.verbosity - 2; + alg.alg_canonical.Tol = sqrt(alg.tol_min * alg.tol_max); + alg.alg_canonical.Verbosity = alg.verbosity - 2; end if ~isfield('alg_environments', kwargs) - v.alg_environments.Tol = sqrt(v.tol_min * v.tol_max); - v.alg_environments.Verbosity = v.verbosity - 2; + alg.alg_environments.Tol = sqrt(alg.tol_min * alg.tol_max); + alg.alg_environments.Verbosity = alg.verbosity - 2; end end @@ -71,7 +70,7 @@ disp_init(alg); mps = canonicalize(mps); - [GL, GR] = environments(mpo, mps); + [GL, GR] = environments(alg, mpo, mps); for iter = 1:alg.maxiter t_iter = tic; @@ -222,20 +221,20 @@ function disp_iter(alg, iter, lambda, eta, t) if abs(imag(lambda)) < eps(lambda)^(3/4) * abs(lambda) switch s.matlab.commandwindow.NumericFormat.ActiveValue case 'short' - fprintf('Iter %2d:\tE = %-0.4f\terror = %0.1e\t(%s)\n', ... + fprintf('Vumps %2d:\tE = %-0.4f\terror = %0.1e\t(%s)\n', ... iter, real(lambda), eta, time2str(t)); otherwise - fprintf('Iter %4d:\tE = %-0.15f\terror = %0.4e\t(%s)\n', ... + fprintf('Vumps %4d:\tE = %-0.15f\terror = %0.4e\t(%s)\n', ... iter, real(lambda), eta, time2str(t, 's')); end else switch s.matlab.commandwindow.NumericFormat.ActiveValue case 'short' - fprintf('Iter %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... + fprintf('Vumps %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... iter, real(lambda), imag(lambda), eta, time2str(t)); otherwise - fprintf('Iter %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... + fprintf('Vumps %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... iter, real(lambda), imag(lambda), eta, time2str(t)); end @@ -247,10 +246,10 @@ function disp_conv(alg, iter, lambda, eta, t) s = settings; switch s.matlab.commandwindow.NumericFormat.ActiveValue case 'short' - fprintf('Conv %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... + fprintf('Vumps converged %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... iter, real(lambda), imag(lambda), eta, time2str(t)); otherwise - fprintf('Conv %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... + fprintf('Vumps converged %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... iter, real(lambda), imag(lambda), eta, time2str(t)); end @@ -262,10 +261,10 @@ function disp_maxiter(alg, iter, lambda, eta, t) s = settings; switch s.matlab.commandwindow.NumericFormat.ActiveValue case 'short' - fprintf('MaxIter %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... + fprintf('Vumps max iterations %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... iter, real(lambda), imag(lambda), eta, time2str(t)); otherwise - fprintf('MaxIter %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... + fprintf('Vumps max iterations %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... iter, real(lambda), imag(lambda), eta, time2str(t)); end diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index 213dde2..142a1d8 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -63,6 +63,28 @@ mpo.O = O_; end + function s = pspace(mpo) + s = pspace(mpo.O{1}); + end + + function mpo = horzcat(varargin) + Os = cellfun(@(x) x.O, varargin, 'UniformOutput', false); + mpo = InfMpo([Os{:}]); + end + + function mps = initialize_mps(mpo, vspaces) + arguments + mpo + vspaces + end + + mps = UniformMps.randnc(pspace(mpo), vspaces); + end + end + + + %% Environments + methods function [GL, lambda] = leftenvironment(mpo, mps1, mps2, GL, eigopts) arguments mpo @@ -142,17 +164,9 @@ GR{w} = GR{w} ./ overlap; end end - - function s = pspace(mpo) - s = pspace(mpo.O{1}); - end - - function mpo = horzcat(varargin) - Os = cellfun(@(x) x.O, varargin, 'UniformOutput', false); - mpo = InfMpo([Os{:}]); - end end + %% Derived operators methods function T = transfermatrix(mpo, mps1, mps2, sites, kwargs) @@ -205,7 +219,31 @@ gr(j) = twist(gr(j), find(isdual(space(gr(j), nspaces(gr(j))))) + ... nspaces(gr(j)) - 1); end - H{i} = FiniteMpo(gl, mpo.O{sites(i)}, gr); + H{i} = FiniteMpo(gl, mpo.O(sites(i)), gr); + end + end + + function H = AC2_hamiltonian(mpo, mps, GL, GR, sites) + arguments + mpo + mps + GL = fixedpoint(transfermatrix(mpo, mps, 'Type', 'LL')) + GR = fixedpoint(transfermatrix(mpo, mps, 'Type', 'RR').') + sites = 1:period(mps) + end + + H = cell(1, length(sites)); + for i = 1:length(sites) + gl = GL{sites(i)}; + for j = 1:numel(gl) + gl(j) = twist(gl(j), find(isdual(space(gl(j), 1)))); + end + gr = GR{mod1(sites(i) + 2, period(mps))}; + for j = 1:numel(gr) + gr(j) = twist(gr(j), find(isdual(space(gr(j), nspaces(gr(j))))) + ... + nspaces(gr(j)) - 1); + end + H{i} = FiniteMpo(gl, mpo.O(mod1(sites(i) + [0 1], period(mps))), gr); end end diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index 444e91c..4e66b8f 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -117,86 +117,15 @@ auxlegs_r = R.alegs; auxlegs = auxlegs_v + auxlegs_l + auxlegs_r; - Oinds = cellfun(@(x) [2*x-2 -x 2*x 2*x-1], 2:N-1, 'UniformOutput', false); + Oinds = arrayfun(@(x) [2*x-2 -x 2*x 2*x-1], 2:N-1, 'UniformOutput', false); O = [varargin(1:end-3); Oinds]; - v = contract(v, [1:2:2*N-1 ((-1:auxlegs_v) - N - auxlegs_l)], ... + v = contract(v, [1:2:2*N-1 (-(1:auxlegs_v) - N - auxlegs_l)], ... L, [-1 2 1 (-(1:auxlegs_l) - N)], ... O{:}, ... R, [2*N-1 2*N-2 -N (-(1:auxlegs_r) - N - auxlegs_l - auxlegs_v)], ... 'Rank', rank(v) + [0 auxlegs]); end - function y = applyleft(O, T, B, x) - arguments - O MpoTensor - T MpsTensor - B MpsTensor - x - end - - switch num2str([nspaces(x) nspaces(T) nspaces(B)]) - case '3 3 3' - if isdual(space(B, 2)), twist(B, 2); end - - y = contract(x, [4 2 1], O.tensors, [2 5 -2 3], ... - T, [1 3 -3], B, [4 5 -1], ... - 'Rank', [2 1]); - scals = reshape(O.scalars, size(O, 1), size(O, 3)); - for j = 1:size(O, 1) - cols = find(scals(j, :)); - if isempty(cols), continue; end - y_ = contract(x(j), [3 -2 1], T, [1 2 -3], B, [3 2 -1], ... - 'Rank', [2 1]); - for i = cols - y(i) = y(i) + scals(j, i) * y_; - end - end - y2 = contract(x, [4 2 1], O, [2 5 -2 3], ... - T, [1 3 -3], B, [4 5 -1], ... - 'Rank', [2 1]); - try - assert(isapprox(y, y2)) - catch - bla - end - - otherwise - error('not implemented.'); - end - end - - function y = applyright(O, T, B, x) - arguments - O MpoTensor - T MpsTensor - B MpsTensor - x - end - - switch num2str([nspaces(x) nspaces(T) nspaces(B)]) - case '3 3 3' - if isdual(space(B, 2)), twist(B, 2); end - - y = contract(x, [1 2 4], O.tensors, [-2 5 2 3], ... - T, [-1 3 1], B, [-3 5 4], ... - 'Rank', [2 1]); - - scals = reshape(O.scalars, size(O, 1), size(O, 3)); - for i = 1:size(O, 3) - rows = find(scals(:, i)); - if isempty(rows), continue; end - y_ = contract(x(i), [1 -2 4], T, [-1 2 1], B, [-3 2 4], ... - 'Rank', [2 1]); - for j = rows - y(j) = y(j) + scals(j, i) * y_; - end - end - - otherwise - error('not implemented.'); - end - end - function O = rot90(O) O.tensors = tpermute(O.tensors, [2 3 4 1], [2 2]); O.scalars = permute(O.scalars, [2 3 4 1]); diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index 84b530e..261684d 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -195,6 +195,11 @@ end end + function mps = normalize(mps) + mps.C = arrayfun(@normalize, mps.C); + mps.AC = arrayfun(@normalize, mps.AC); + end + function T = transfermatrix(mps1, mps2, sites, kwargs) arguments mps1 @@ -271,6 +276,37 @@ f = transfereigs(mps1, mps2, 1, 'largestabs', eigkwargs{:}); end + function E = expectation_value(mps1, O, mps2) + arguments + mps1 + O + mps2 = mps1 + end + + if isa(O, 'InfMpo') + [GL, GR] = environments(O, mps1, mps2); + H = AC_hamiltonian(O, mps1, GL, GR); + E = zeros(size(H)); + for i = 1:length(H) + AC_ = apply(H{i}, mps1.AC(i)); + E(i) = dot(AC_, mps2.AC(i)); + end + elseif isa(O, 'InfJMpo') + [GL, GR] = environments(O, mps1, mps2); + H = AC_hamiltonian(O, mps1, GL, GR); + E = zeros(size(H)); + for i = 1:length(H) + H{i}.R = H{i}.R(1, end, 1); + H{i}.O{1} = H{i}.O{1}(:,:,end,:); + + AC_ = apply(H{i}, mps1.AC(i)); + E(i) = dot(AC_, mps2.AC(i)); + end + else + error('Unknown operator type (%s)', class(O)); + end + end + function rho = fixedpoint(mps, type, w) arguments mps @@ -322,6 +358,7 @@ function plot_entanglementspectrum(ax, mps, w) if nargin == 1 mps = ax; w = 1:period(mps); + figure; ax = gobjects(depth(mps), width(mps)); for ww = 1:length(w) ax(1, ww) = subplot(1, length(w), ww); @@ -329,6 +366,7 @@ function plot_entanglementspectrum(ax, mps, w) elseif nargin == 2 w = mps; mps = ax; + figure; ax = gobjects(depth(mps), width(mps)); for ww = 1:length(w) ax(1, ww) = subplot(1, length(w), ww); @@ -359,7 +397,7 @@ function plot_entanglementspectrum(ax, mps, w) set(ax(1, ww), 'TickLabelInterpreter', 'latex'); set(ax(1, ww), 'Xtick', ticks, 'XTickLabel', labels, 'fontsize', 10, ... 'XtickLabelRotation', 60, 'Xgrid', 'on'); - xlim(ax(1, ww), [1 - 1e-9 lim_x + 1e-9]); + xlim(ax(1, ww), [1 - 1e-8 lim_x + 1e-8]); end for ww = 1:length(w) diff --git a/src/tensors/spaces/CartesianSpace.m b/src/tensors/spaces/CartesianSpace.m index 187a246..41018c4 100644 --- a/src/tensors/spaces/CartesianSpace.m +++ b/src/tensors/spaces/CartesianSpace.m @@ -274,6 +274,11 @@ bool = degeneracies(space1) <= degeneracies(space2); end + function space = infimum(space1, space2) + assert(isscalar(space1) && isscalar(space2)); + space = CartesianSpace.new(min(dims(space1), dims(space2))); + end + function hashable = GetMD5_helper(spaces) % Helper function for hash algorithm. This converts the space object to a data % structure which can be processed by :func:`GetMD5`. @@ -310,7 +315,7 @@ function disp(spaces) if numel(spaces) > 1 s = strings(size(spaces)); for i = 1:numel(spaces) - s(i) = string(spaces); + s(i) = string(spaces(i)); end return end diff --git a/src/utility/mod1.m b/src/utility/mod1.m new file mode 100644 index 0000000..1e9d522 --- /dev/null +++ b/src/utility/mod1.m @@ -0,0 +1,9 @@ +function i = mod1(i, N) + + +i = mod(i, N); +i(i == 0) = N; + + +end + diff --git a/test/TestAlgorithms.m b/test/TestAlgorithms.m new file mode 100644 index 0000000..eddebb9 --- /dev/null +++ b/test/TestAlgorithms.m @@ -0,0 +1,85 @@ +classdef TestAlgorithms < matlab.unittest.TestCase + % Unit tests for algorithms + + + methods (Test) + function test2dIsing(tc) + mpo = InfMpo.Ising(); + mps = initialize_mps(mpo, CartesianSpace.new(12)); + + alg = Vumps('which', 'largestabs', 'maxiter', 5); + [gs, lambda] = fixedpoint(alg, mpo, mps); + tc.verifyEqual(lambda, 2.533, 'RelTol', 1e-2); + tc.verifyEqual(lambda, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); + + alg = IDmrg('which', 'largestabs', 'maxiter',10); + [gs, lambda] = fixedpoint(alg, mpo, mps); + tc.verifyEqual(lambda, 2.5337, 'RelTol', 1e-2); + tc.verifyEqual(lambda, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); + + mps2 = approximate(Vomps(), ... + mpo, gs, initialize_mps(mpo, CartesianSpace.new(24))); + tc.verifyEqual(fidelity(gs, mps2), 1, 'RelTol', 1e-2); + + mps = [mps mps]; + mpo = [mpo mpo]; + alg = IDmrg2('which', 'largestabs', 'maxiter', 10); + [gs, lambda] = fixedpoint(alg, mpo, mps); + tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); + tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... + 'RelTol', 1e-2); + + alg = IDmrg('which', 'largestabs', 'maxiter', 10); + [gs, lambda] = fixedpoint(alg, mpo, mps); + tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); + tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... + 'RelTol', 1e-2); + + alg = Vumps('which', 'largestabs', 'maxiter', 5); + [gs, lambda] = fixedpoint(alg, mpo, mps); + tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); + tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... + 'RelTol', 1e-2); + end + + function test1dIsing(tc) + mpo = InfJMpo.Ising(); + mps = initialize_mps(mpo, CartesianSpace.new(12)); + + alg = Vumps('which', 'smallestreal', 'maxiter', 5); + [gs, lambda] = fixedpoint(alg, mpo, mps); + tc.verifyEqual(lambda, -1.27, 'RelTol', 1e-2); + tc.verifyEqual(lambda, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); + + alg = IDmrg('which', 'smallestreal', 'maxiter',10); + [gs, lambda] = fixedpoint(alg, mpo, mps); + tc.verifyEqual(lambda, 2.5337, 'RelTol', 1e-2); + tc.verifyEqual(lambda, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); + + mps2 = approximate(Vomps(), ... + mpo, gs, initialize_mps(mpo, CartesianSpace.new(24))); + tc.verifyEqual(fidelity(gs, mps2), 1, 'RelTol', 1e-2); + + mps = [mps mps]; + mpo = [mpo mpo]; + alg = IDmrg2('which', 'smallestreal', 'maxiter', 10); + [gs, lambda] = fixedpoint(alg, mpo, mps); + tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); + tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... + 'RelTol', 1e-2); + + alg = IDmrg('which', 'smallestreal', 'maxiter', 10); + [gs, lambda] = fixedpoint(alg, mpo, mps); + tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); + tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... + 'RelTol', 1e-2); + + alg = Vumps('which', 'smallestreal', 'maxiter', 5); + [gs, lambda] = fixedpoint(alg, mpo, mps); + tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); + tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... + 'RelTol', 1e-2); + end + end +end + From a25c2df9d391248a85aa7414457534289ef88433 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 24 Oct 2022 12:39:38 +0200 Subject: [PATCH 065/245] Fix IDmrg2 for symmetries --- src/algorithms/IDmrg2.m | 7 ++-- src/tensors/spaces/ComplexSpace.m | 6 +++ src/tensors/spaces/GradedSpace.m | 12 ++++++ test/TestAlgorithms.m | 63 +++++++++++++++++++++++++------ 4 files changed, 73 insertions(+), 15 deletions(-) diff --git a/src/algorithms/IDmrg2.m b/src/algorithms/IDmrg2.m index 3c63ab5..19e4221 100644 --- a/src/algorithms/IDmrg2.m +++ b/src/algorithms/IDmrg2.m @@ -12,7 +12,7 @@ tol_min = 1e-12 tol_max = 1e-6 eigs_tolfactor = 1e-4 - trunc = {'TruncDim', 10} + trunc = {'TruncDim', 10} end properties (Access = private) @@ -116,7 +116,8 @@ TL = transfermatrix(mpo, mps, mps, pos, 'Type', 'LL'); GL{pos + 1} = apply(TL, GL{pos}) / sqrt(lambdas(pos)); TR = transfermatrix(mpo, mps, mps, pos + 1, 'Type', 'RR'); - GR{pos} = apply(TR.', GR{pos + 1}) / sqrt(lambdas(pos)); + GR{pos + 1} = apply(TR.', GR{mod1(pos + 2, period(mps))}) / ... + sqrt(lambdas(pos)); end % update edge @@ -135,7 +136,7 @@ TL = transfermatrix(mpo, mps, mps, period(mps), 'Type', 'LL'); GL{1} = apply(TL, GL{end}) / sqrt(lambdas(end)); TR = transfermatrix(mpo, mps, mps, 1, 'Type', 'RR'); - GR{end} = apply(TR.', GR{1}) / sqrt(lambdas(end)); + GR{1} = apply(TR.', GR{2}) / sqrt(lambdas(end)); % error measure infspace = infimum(space(C_, 1), space(mps.C(end), 1)); diff --git a/src/tensors/spaces/ComplexSpace.m b/src/tensors/spaces/ComplexSpace.m index 2aefc33..1aaa77d 100644 --- a/src/tensors/spaces/ComplexSpace.m +++ b/src/tensors/spaces/ComplexSpace.m @@ -208,6 +208,12 @@ space = ComplexSpace(prod(dims(spaces)), false); end + + function space1 = infimum(space1, space2) + assert(isscalar(space1) && isscalar(space2)); + assert(isdual(space1) == isdual(space2)); + space1.dimensions = min(dims(space1), dims(space2)); + end end diff --git a/src/tensors/spaces/GradedSpace.m b/src/tensors/spaces/GradedSpace.m index 1542139..139bf6f 100644 --- a/src/tensors/spaces/GradedSpace.m +++ b/src/tensors/spaces/GradedSpace.m @@ -63,6 +63,18 @@ struct('charges', one(spaces(1).dimensions.charges), 'degeneracies', 1), ... false); end + + function space = infimum(space1, space2) + assert(isscalar(space1) && isscalar(space2)); + assert(isdual(space1) == isdual(space2)); + + [dims.charges, ia, ib] = intersect(charges(space1), charges(space2)); + d1 = degeneracies(space1); + d2 = degeneracies(space2); + dims.degeneracies = min(d1(ia), d2(ib)); + + space = GradedSpace.new(dims, isdual(space1)); + end end methods (Static) diff --git a/test/TestAlgorithms.m b/test/TestAlgorithms.m index eddebb9..787b9f9 100644 --- a/test/TestAlgorithms.m +++ b/test/TestAlgorithms.m @@ -40,46 +40,85 @@ function test2dIsing(tc) tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... 'RelTol', 1e-2); - end - - function test1dIsing(tc) - mpo = InfJMpo.Ising(); - mps = initialize_mps(mpo, CartesianSpace.new(12)); - alg = Vumps('which', 'smallestreal', 'maxiter', 5); + mpo = InfMpo.Ising('Symmetry', 'Z2'); + mps = initialize_mps(mpo, GradedSpace.new(Z2(0, 1), [6 6], false)); + + alg = Vumps('which', 'largestabs', 'maxiter', 5); [gs, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyEqual(lambda, -1.27, 'RelTol', 1e-2); + tc.verifyEqual(lambda, 2.533, 'RelTol', 1e-2); tc.verifyEqual(lambda, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); - alg = IDmrg('which', 'smallestreal', 'maxiter',10); + alg = IDmrg('which', 'largestabs', 'maxiter',10); [gs, lambda] = fixedpoint(alg, mpo, mps); tc.verifyEqual(lambda, 2.5337, 'RelTol', 1e-2); tc.verifyEqual(lambda, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); mps2 = approximate(Vomps(), ... - mpo, gs, initialize_mps(mpo, CartesianSpace.new(24))); + mpo, gs, initialize_mps(mpo, GradedSpace.new(Z2(0, 1), [12 12], false))); tc.verifyEqual(fidelity(gs, mps2), 1, 'RelTol', 1e-2); mps = [mps mps]; mpo = [mpo mpo]; - alg = IDmrg2('which', 'smallestreal', 'maxiter', 10); + alg = IDmrg2('which', 'largestabs', 'maxiter', 10); [gs, lambda] = fixedpoint(alg, mpo, mps); tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... 'RelTol', 1e-2); - alg = IDmrg('which', 'smallestreal', 'maxiter', 10); + alg = IDmrg('which', 'largestabs', 'maxiter', 10); [gs, lambda] = fixedpoint(alg, mpo, mps); tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... 'RelTol', 1e-2); - alg = Vumps('which', 'smallestreal', 'maxiter', 5); + alg = Vumps('which', 'largestabs', 'maxiter', 5); [gs, lambda] = fixedpoint(alg, mpo, mps); tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... 'RelTol', 1e-2); end + + function test1dIsing(tc) + mpo = InfJMpo.Ising(); + mps = initialize_mps(mpo, CartesianSpace.new(12)); + + alg = Vumps('which', 'smallestreal', 'maxiter', 5); + [gs, lambda] = fixedpoint(alg, mpo, mps); + tc.verifyEqual(lambda, -1.273, 'RelTol', 1e-2); +% tc.verifyEqual(lambda, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); + + alg = IDmrg('which', 'smallestreal', 'maxiter',10); + [gs, lambda] = fixedpoint(alg, mpo, mps); + tc.verifyEqual(lambda, -1.273, 'RelTol', 1e-2); +% tc.verifyEqual(lambda, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); + +% mps2 = approximate(Vomps(), ... +% mpo, gs, initialize_mps(mpo, CartesianSpace.new(24))); +% tc.verifyEqual(fidelity(gs, mps2), 1, 'RelTol', 1e-2); + + mps = [mps mps]; + mpo = [mpo mpo]; + alg = IDmrg2('which', 'smallestreal', 'maxiter', 10); + [gs, lambda] = fixedpoint(alg, mpo, mps); +% tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); +% tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... +% 'RelTol', 1e-2); + + alg = IDmrg('which', 'smallestreal', 'maxiter', 10); + [gs, lambda] = fixedpoint(alg, mpo, mps); +% tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); +% tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... +% 'RelTol', 1e-2); + + alg = Vumps('which', 'smallestreal', 'maxiter', 5); + [gs, lambda] = fixedpoint(alg, mpo, mps); +% tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); +% tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... +% 'RelTol', 1e-2); + + + end end end From bf0014b603f6721b0b8f468aff2cbd3cf8eee8fb Mon Sep 17 00:00:00 2001 From: sanderdemeyer Date: Mon, 24 Oct 2022 12:41:24 +0200 Subject: [PATCH 066/245] Fixes to IDmrg2 --- src/algorithms/IDmrg2.m | 4 ++-- src/sparse/SparseTensor.m | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/algorithms/IDmrg2.m b/src/algorithms/IDmrg2.m index 3c63ab5..0e93b7a 100644 --- a/src/algorithms/IDmrg2.m +++ b/src/algorithms/IDmrg2.m @@ -116,7 +116,7 @@ TL = transfermatrix(mpo, mps, mps, pos, 'Type', 'LL'); GL{pos + 1} = apply(TL, GL{pos}) / sqrt(lambdas(pos)); TR = transfermatrix(mpo, mps, mps, pos + 1, 'Type', 'RR'); - GR{pos} = apply(TR.', GR{pos + 1}) / sqrt(lambdas(pos)); + GR{pos + 1} = apply(TR.', GR{mod1(pos + 2, period(mps))}) / sqrt(lambdas(pos)); end % update edge @@ -135,7 +135,7 @@ TL = transfermatrix(mpo, mps, mps, period(mps), 'Type', 'LL'); GL{1} = apply(TL, GL{end}) / sqrt(lambdas(end)); TR = transfermatrix(mpo, mps, mps, 1, 'Type', 'RR'); - GR{end} = apply(TR.', GR{1}) / sqrt(lambdas(end)); + GR{1} = apply(TR.', GR{2}) / sqrt(lambdas(end)); % error measure infspace = infimum(space(C_, 1), space(mps.C(end), 1)); diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index 19397d5..0f08453 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -557,8 +557,13 @@ function disp(t) idx = sub2ind_(t.sz, t.ind); t1 = t1(idx); idx2 = find(t1); - t.var = t.var(idx2) .* t1(idx2); - t.ind = t.ind(idx2, :); + if ~isempty(idx2) + t.var = t.var(idx2) .* t1(idx2); + t.ind = t.ind(idx2, :); + else + t.var = t.var(idx2); + t.ind = t.ind(idx2, :); + end return end From 946a7ea79cf131c210ce022a084b18ea88f5c7f1 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 24 Oct 2022 13:51:20 +0200 Subject: [PATCH 067/245] fix recanonicalisation in IDmrg2 --- src/algorithms/IDmrg2.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/algorithms/IDmrg2.m b/src/algorithms/IDmrg2.m index 19e4221..30b873a 100644 --- a/src/algorithms/IDmrg2.m +++ b/src/algorithms/IDmrg2.m @@ -147,7 +147,7 @@ lambda = prod(sqrt(lambdas)); if iter > alg.miniter && eta < alg.tol - mps = canonicalize(mps); + mps = canonicalize(mps, 'Order', 'rl'); disp_conv(alg, iter, lambda, eta, toc(t_total)); return end @@ -156,7 +156,7 @@ disp_iter(alg, iter, lambda, eta, toc(t_iter)); end - mps = canonicalize(mps); + mps = canonicalize(mps, 'Order', 'rl'); disp_maxiter(alg, iter, lambda, eta, toc(t_total)); end end From d97f57d8777e470501f9c70a22bb8cd3ed524faa Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 25 Oct 2022 10:52:18 +0200 Subject: [PATCH 068/245] Plot progress with VUMPS --- src/algorithms/Vumps.m | 51 +++++++++++++++++++++++++++++++++++++----- src/mps/UniformMps.m | 31 ++++++++++--------------- 2 files changed, 57 insertions(+), 25 deletions(-) diff --git a/src/algorithms/Vumps.m b/src/algorithms/Vumps.m index 437b9b0..e4273a2 100644 --- a/src/algorithms/Vumps.m +++ b/src/algorithms/Vumps.m @@ -1,4 +1,4 @@ -classdef Vumps +classdef Vumps < handle % Variational fixed point algorithm for uniform matrix product states. %% Options @@ -7,6 +7,7 @@ miniter = 5 maxiter = 100 verbosity = Verbosity.iter + doplot = false which = 'largestabs' dynamical_tols = true @@ -25,6 +26,8 @@ alg_eigs = struct('MaxIter', 100, 'KrylovDim', 20) alg_canonical = struct('Method', 'polar') alg_environments = struct + + progressfig end @@ -87,6 +90,7 @@ return end alg = updatetols(alg, iter, eta); + plot(alg, iter, mps, eta); disp_iter(alg, iter, lambda, eta, toc(t_iter)); end @@ -100,14 +104,14 @@ function AC = updateAC(alg, iter, mpo, mps, GL, GR) kwargs = namedargs2cell(alg.alg_eigs); if strcmp(alg.multiAC, 'sequential') - sites = mod(iter, period(mps)) + 1; + sites = mod1(iter, period(mps)); else sites = 1:period(mps); end H_AC = AC_hamiltonian(mpo, mps, GL, GR, sites); for i = length(sites):-1:1 - [AC(i), ~] = eigsolve(H_AC{sites(i)}, mps.AC(sites(i)), 1, alg.which, ... + [AC(i), ~] = eigsolve(H_AC{i}, mps.AC(sites(i)), 1, alg.which, ... kwargs{:}); end end @@ -115,21 +119,21 @@ function C = updateC(alg, iter, mpo, mps, GL, GR) kwargs = namedargs2cell(alg.alg_eigs); if strcmp(alg.multiAC, 'sequential') - sites = mod(iter, period(mps)) + 1; + sites = mod1(iter, period(mps)); else sites = 1:period(mps); end H_C = C_hamiltonian(mpo, mps, GL, GR, sites); for i = length(sites):-1:1 - [C(i), ~] = eigsolve(H_C{sites(i)}, mps.C(sites(i)), 1, alg.which, ... + [C(i), ~] = eigsolve(H_C{i}, mps.C(sites(i)), 1, alg.which, ... kwargs{:}); end end function mps = updatemps(alg, iter, mps, AC, C) if strcmp(alg.multiAC, 'sequential') - sites = mod(iter, period(mps)) + 1; + sites = mod1(iter, period(mps)); else sites = 1:period(mps); end @@ -209,6 +213,41 @@ %% Display methods (Access = private) + function plot(alg, iter, mps, eta) + if ~alg.doplot, return; end + persistent axhistory axspectrum + + D = depth(mps); + W = period(mps); + + if isempty(alg.progressfig) || ~isvalid(alg.progressfig) || iter == 1 + alg.progressfig = figure('Name', 'Vumps'); + axhistory = subplot(D + 1, W, 1:W); + axspectrum = gobjects(D, W); + for d = 1:D, for w = 1:W + axspectrum(d, w) = subplot(D + 1, W, w + d * W); + end, end + linkaxes(axspectrum, 'y'); + end + + + if isempty(axhistory.Children) + semilogy(axhistory, iter, eta, '.', 'Color', colors(1), ... + 'DisplayName', 'Errors', 'MarkerSize', 10); + hold on + ylim(axhistory, [alg.tol / 5, max([1 eta])]); + yline(axhistory, alg.tol, '-', 'Convergence', ... + 'LineWidth', 2); + hold off + else + axhistory.Children(end).XData(end+1) = iter; + axhistory.Children(end).YData(end+1) = eta; + end + + plot_entanglementspectrum(mps, 1:period(mps), axspectrum); + drawnow + end + function disp_init(alg) if alg.verbosity < Verbosity.conv, return; end fprintf('---- VUMPS ----\n'); diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index 261684d..f5bbf51 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -354,9 +354,8 @@ svals = cellfun(@diag, svals, 'UniformOutput', false); end - function plot_entanglementspectrum(ax, mps, w) + function plot_entanglementspectrum(mps, w, ax) if nargin == 1 - mps = ax; w = 1:period(mps); figure; ax = gobjects(depth(mps), width(mps)); @@ -364,8 +363,6 @@ function plot_entanglementspectrum(ax, mps, w) ax(1, ww) = subplot(1, length(w), ww); end elseif nargin == 2 - w = mps; - mps = ax; figure; ax = gobjects(depth(mps), width(mps)); for ww = 1:length(w) @@ -382,27 +379,23 @@ function plot_entanglementspectrum(ax, mps, w) ticks = zeros(size(svals)); labels = arrayfun(@string, charges, 'UniformOutput', false); - - for i = 1:length(svals) - ticks(i) = ctr + 1; - lim_x = max(lim_x, ctr + length(svals{i})); - semilogy(ax(1, ww), ctr+1:ctr+length(svals{i}), svals{i}, ... - '.', 'MarkerSize', 10, 'Color', colors(i)); - hold on - - ctr = ctr + length(svals{i}) + 3; - lim_y = min(lim_y, min(svals{i})); + lengths = cellfun(@length, svals); + ticks = cumsum(lengths); + try + semilogy(ax(1, ww), 1:sum(lengths), vertcat(svals{:}).', '.', 'MarkerSize', 10); + catch + bla end - set(ax(1, ww), 'TickLabelInterpreter', 'latex'); set(ax(1, ww), 'Xtick', ticks, 'XTickLabel', labels, 'fontsize', 10, ... 'XtickLabelRotation', 60, 'Xgrid', 'on'); - xlim(ax(1, ww), [1 - 1e-8 lim_x + 1e-8]); - end + xlim(ax(1, ww), [1 - 1e-8 ticks(end) + 1e-8]); - for ww = 1:length(w) - ylim(ax(1, ww), [10^(floor(log10(lim_y))) 1]); end + +% for ww = 1:length(w) +% ylim(ax(1, ww), [10^(floor(log10(lim_y))) 1]); +% end end function mps = desymmetrize(mps) From adf126ea6d219aa756a09c3bb1edf22aa78be9d7 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 26 Oct 2022 13:53:38 +0200 Subject: [PATCH 069/245] temp fix for idmrg environments normalization --- src/algorithms/IDmrg2.m | 65 +++++++++++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 12 deletions(-) diff --git a/src/algorithms/IDmrg2.m b/src/algorithms/IDmrg2.m index 30b873a..9b227ab 100644 --- a/src/algorithms/IDmrg2.m +++ b/src/algorithms/IDmrg2.m @@ -5,18 +5,25 @@ tol = 1e-10 miniter = 5 maxiter = 100 - verbosity = Verbosity.iter + verbosity = Verbosity.iter + doplot = false + which = 'largestabs' - dynamical_tols = true + dynamical_tols = true tol_min = 1e-12 tol_max = 1e-6 eigs_tolfactor = 1e-4 + trunc = {'TruncDim', 10} + + end properties (Access = private) alg_eigs = struct('MaxIter', 100, 'KrylovDim', 20) + + progressfig end methods @@ -76,10 +83,9 @@ mps.AC(pos + 1) = multiplyleft(mps.AR(pos + 1), mps.C(pos)); TL = transfermatrix(mpo, mps, mps, pos, 'Type', 'LL'); - GL{pos + 1} = apply(TL, GL{pos}) / sqrt(lambdas(pos)); + GL{pos + 1} = apply(TL, GL{pos}); TR = transfermatrix(mpo, mps, mps, pos + 1, 'Type', 'RR'); - GR{pos + 1} = apply(TR.', GR{mod1(pos + 2, length(GR))}) / ... - sqrt(lambdas(pos)); + GR{pos + 1} = apply(TR.', GR{mod1(pos + 2, length(GR))}); end % update edge @@ -96,9 +102,9 @@ mps.AL(1) = multiplyright(mps.AC(1), inv(mps.C(1))); TL = transfermatrix(mpo, mps, mps, period(mps), 'Type', 'LL'); - GL{1} = apply(TL, GL{end}) / sqrt(lambdas(end)); + GL{1} = apply(TL, GL{end}); TR = transfermatrix(mpo, mps, mps, 1, 'Type', 'RR'); - GR{1} = apply(TR.', GR{2}) / sqrt(lambdas(end)); + GR{1} = apply(TR.', GR{2}); % sweep from right to left for pos = period(mps)-1:-1:1 @@ -114,10 +120,9 @@ mps.AC(pos + 1) = multiplyleft(mps.AR(pos + 1), mps.C(pos)); TL = transfermatrix(mpo, mps, mps, pos, 'Type', 'LL'); - GL{pos + 1} = apply(TL, GL{pos}) / sqrt(lambdas(pos)); + GL{pos + 1} = apply(TL, GL{pos}); TR = transfermatrix(mpo, mps, mps, pos + 1, 'Type', 'RR'); - GR{pos + 1} = apply(TR.', GR{mod1(pos + 2, period(mps))}) / ... - sqrt(lambdas(pos)); + GR{pos + 1} = apply(TR.', GR{mod1(pos + 2, period(mps))}); end % update edge @@ -134,9 +139,9 @@ inv(mps.C(end - 1))); TL = transfermatrix(mpo, mps, mps, period(mps), 'Type', 'LL'); - GL{1} = apply(TL, GL{end}) / sqrt(lambdas(end)); + GL{1} = apply(TL, GL{end}); TR = transfermatrix(mpo, mps, mps, 1, 'Type', 'RR'); - GR{1} = apply(TR.', GR{2}) / sqrt(lambdas(end)); + GR{1} = apply(TR.', GR{2}); % error measure infspace = infimum(space(C_, 1), space(mps.C(end), 1)); @@ -153,6 +158,7 @@ end alg = updatetols(alg, iter, eta); + plot(alg, iter, mps, eta); disp_iter(alg, iter, lambda, eta, toc(t_iter)); end @@ -190,6 +196,41 @@ %% Display methods (Access = private) + function plot(alg, iter, mps, eta) + if ~alg.doplot, return; end + persistent axhistory axspectrum + + D = depth(mps); + W = period(mps); + + if isempty(alg.progressfig) || ~isvalid(alg.progressfig) || iter == 1 + alg.progressfig = figure('Name', 'Vumps'); + axhistory = subplot(D + 1, W, 1:W); + axspectrum = gobjects(D, W); + for d = 1:D, for w = 1:W + axspectrum(d, w) = subplot(D + 1, W, w + d * W); + end, end + linkaxes(axspectrum, 'y'); + end + + + if isempty(axhistory.Children) + semilogy(axhistory, iter, eta, '.', 'Color', colors(1), ... + 'DisplayName', 'Errors', 'MarkerSize', 10); + hold on + ylim(axhistory, [alg.tol / 5, max([1 eta])]); + yline(axhistory, alg.tol, '-', 'Convergence', ... + 'LineWidth', 2); + hold off + else + axhistory.Children(end).XData(end+1) = iter; + axhistory.Children(end).YData(end+1) = eta; + end + + plot_entanglementspectrum(mps, 1:period(mps), axspectrum); + drawnow + end + function disp_init(alg) if alg.verbosity < Verbosity.conv, return; end fprintf('---- IDmrg2 ----\n'); From 8dec52681f59d0b6b1c1bb368f629e4d4b257ba3 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 26 Oct 2022 17:15:29 +0200 Subject: [PATCH 070/245] memlimit fix --- src/caches/LRU.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/caches/LRU.m b/src/caches/LRU.m index 4daaf1a..cea7df4 100644 --- a/src/caches/LRU.m +++ b/src/caches/LRU.m @@ -105,9 +105,9 @@ cache.mem = cache.mem + memsize(val, 'B'); while cache.mem > cache.memlimit || length(cache.map) > cache.itemlimit dll_oldest = -cache.sentinel; - pop(dll_oldest); cache.mem = cache.mem - memsize(dll_oldest.val{2}, 'B'); - cache.map.remove(key); + cache.map.remove(dll_oldets.val{1}); + pop(dll_oldest); end end From deb968a64928dc8fdd9075da273bb9a24e2d2be2 Mon Sep 17 00:00:00 2001 From: lkdvos <37111893+lkdvos@users.noreply.github.com> Date: Fri, 28 Oct 2022 16:41:50 +0200 Subject: [PATCH 071/245] small fix --- src/algorithms/Vumps.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/algorithms/Vumps.m b/src/algorithms/Vumps.m index e4273a2..adf7297 100644 --- a/src/algorithms/Vumps.m +++ b/src/algorithms/Vumps.m @@ -188,12 +188,12 @@ methods function alg = updatetols(alg, iter, eta) if alg.dynamical_tols - alg.alg_eigs.Tol = between(alg.tol_min, eta * alg.eigs_tolfactor, ... - alg.tol_max / iter); + alg.alg_eigs.Tol = between(alg.tol_min, eta * alg.eigs_tolfactor / iter, ... + alg.tol_max); alg.alg_canonical.Tol = between(alg.tol_min, ... - eta * alg.canonical_tolfactor, alg.tol_max / iter); + eta * alg.canonical_tolfactor / iter, alg.tol_max); alg.alg_environments.Tol = between(alg.tol_min, ... - eta * alg.environments_tolfactor, alg.tol_max / iter); + eta * alg.environments_tolfactor / iter, alg.tol_max); if alg.verbosity > Verbosity.iter fprintf('Updated subalgorithm tolerances: (%e,\t%e,\t%e)\n', ... From b097156c7f5204271ad70cb55993dc7c6dd6933c Mon Sep 17 00:00:00 2001 From: sanderdemeyer Date: Mon, 31 Oct 2022 11:15:42 +0100 Subject: [PATCH 072/245] Save function in IDmrg2 and changes to ProductCharge --- src/algorithms/IDmrg2.m | 34 +++++++++++++++++++++++++++++ src/tensors/charges/ProductCharge.m | 12 ++++++++-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/algorithms/IDmrg2.m b/src/algorithms/IDmrg2.m index 9b227ab..027d6ae 100644 --- a/src/algorithms/IDmrg2.m +++ b/src/algorithms/IDmrg2.m @@ -17,6 +17,10 @@ trunc = {'TruncDim', 10} + doSave = false + saveIterations = false + saveMethod = 'full' + name = 'IDmrg2' end @@ -160,6 +164,10 @@ alg = updatetols(alg, iter, eta); plot(alg, iter, mps, eta); disp_iter(alg, iter, lambda, eta, toc(t_iter)); + + if alg.doSave && mod(iter, alg.saveIterations) == 0 + save(alg, mps, lambdas); + end end mps = canonicalize(mps, 'Order', 'rl'); @@ -292,6 +300,32 @@ function disp_maxiter(alg, iter, lambda, eta, t) end fprintf('---------------\n'); end + + function save(alg, mps, lambdas) + fileName = alg.name; + + fileData = struct; + fileData.mps = mps; + fileData.lambdas = lambdas; + + % save + if exist(fileName,'file') + old_file=load(fileName); + fileName_temp=[fileName(1:end-4),'_temp.mat']; + save(fileName_temp, '-struct', 'old_file', '-v7.3'); + saved_temp=1; + else + saved_temp=0; + end + + save(fileName, '-struct', 'fileData', '-v7.3'); + + if saved_temp + delete(fileName_temp); + end + + + end end end diff --git a/src/tensors/charges/ProductCharge.m b/src/tensors/charges/ProductCharge.m index 9670061..4241c2e 100644 --- a/src/tensors/charges/ProductCharge.m +++ b/src/tensors/charges/ProductCharge.m @@ -352,10 +352,18 @@ end end - function c = intersect(a, b) + %function c = intersect(a, b) + % c = subsref(a, substruct('()', ... + % {mod(find(reshape(a, [], 1) == reshape(b, 1, [])) - 1, length(a)) + 1})); + %end + + function [c, ia, ib] = intersect(a, b) c = subsref(a, substruct('()', ... - {mod(find(reshape(a, [], 1) == reshape(b, 1, [])) - 1, length(a)) + 1})); + {mod(find(reshape(a, [], 1) == reshape(b, 1, [])) - 1, length(a)) + 1})); + [~,ia] = ismember(c, a); + [~,ib] = ismember(c, b); end + function F = Fsymbol(a, b, c, d, e, f) if hasmultiplicity(fusionstyle(a)) From 2ca4012f541d90a8dec05eb51901f772bc865557 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 7 Nov 2022 21:55:41 +0100 Subject: [PATCH 073/245] Bugfix in braiding MpoTensors. --- src/mps/InfJMpo.m | 8 ++++++++ src/mps/MpoTensor.m | 39 ++++++++++++++++++++++++++++++++------- src/sparse/SparseTensor.m | 18 +++++++++++------- src/tensors/Tensor.m | 1 + 4 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/mps/InfJMpo.m b/src/mps/InfJMpo.m index 09f7af9..c3cfc87 100644 --- a/src/mps/InfJMpo.m +++ b/src/mps/InfJMpo.m @@ -62,6 +62,10 @@ end end + if nnz(GL{1}) == numel(GL{1}) + GL{1} = full(GL{1}); + end + for w = 1:period(mps1)-1 T = transfermatrix(mpo, mps1, mps2, w, 'Type', 'LL'); GL{next(w, period(mps1))} = apply(T, GL{w}); @@ -117,6 +121,10 @@ end end + if nnz(GR{1}) == numel(GR{1}) + GR{1} = full(GR{1}); + end + for w = period(mps1):-1:2 T = transfermatrix(mpo, mps1, mps2, w, 'Type', 'RR').'; GR{w} = apply(T, GR{next(w, period(mps1))}); diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index 4e66b8f..a8cadc9 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -142,7 +142,9 @@ options.NumDimensionsA = ndims(A) end - assert(~isa(A, 'MpoTensor') || ~isa(B, 'MpoTensor')); + assert(~isa(A, 'MpoTensor') || ~isa(B, 'MpoTensor'), 'mpotensor:tensorprod', ... + 'cannot contract two mpotensors.'); + if isa(A, 'MpoTensor') C = tensorprod(A.tensors, B, dimA, dimB, ca, cb); @@ -155,11 +157,22 @@ uncA = 1:nspaces(A); uncA(dimA) = []; uncB = 1:nspaces(B); uncB(dimB) = []; - A = reshape(permute(A.scalars, [uncA flip(dimA)]), ... + rB = [length(dimB) length(uncB)]; + + iA = [uncA dimA]; + iB = [flip(dimB) uncB]; + + if mod1(dimA(1) + 1, 4) ~= dimA(2) + iA(end-1:end) = flip(iA(end-1:end)); + iB(1:2) = flip(iB(1:2)); + end + + A_ = reshape(permute(A.scalars, iA), ... [prod(size(A, uncA)) prod(size(A, dimA))]); - B = reshape(tpermute(B, [dimB uncB], [length(dimB) length(uncB)]), ... + B = reshape(tpermute(B, iB, rB), ... [prod(size(B, dimB)) prod(size(B, uncB))]); - C = C + reshape(sparse(A) * B, size(C)); + + C = C + reshape(sparse(A_) * B, size(C)); end else C = tensorprod(A, B.tensors, dimA, dimB, ca, cb); @@ -173,11 +186,23 @@ uncA = 1:nspaces(A); uncA(dimA) = []; uncB = 1:nspaces(B); uncB(dimB) = []; - A = reshape(tpermute(A, [uncA flip(dimA)], [length(uncA) length(dimA)]), ... + rA = [length(uncA) length(dimA)]; + + iA = [uncA dimA]; + iB = [flip(dimB) uncB]; + + if mod1(dimB(1) + 1, 4) ~= dimB(2) + iA(end-1:end) = flip(iA(end-1:end)); + iB(1:2) = flip(iB(1:2)); + end + + A = reshape(tpermute(A, iA, rA), ... [prod(size(A, uncA)) prod(size(A, dimA))]); - B = reshape(permute(B.scalars, [flip(dimB) uncB]), ... + + B_ = reshape(permute(B.scalars, iB), ... [prod(size(B, dimB)) prod(size(B, uncB))]); - C = C + reshape(A * sparse(B), size(C)); + + C = C + reshape(A * sparse(B_), szC); end end end diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index 0f08453..43de70c 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -96,10 +96,8 @@ function B = full(A) inds = ind2sub_(A.sz, 1:prod(A.sz)); - [lia, locb] = ismember(inds, A.ind, 'rows'); B(lia) = A.var(locb(lia)); - if ~all(lia) s = arrayfun(@(i) space(A, i), 1:ndims(A), 'UniformOutput', false); r = rank(A.var(1)); @@ -108,6 +106,7 @@ B(i) = Tensor.zeros(allspace(1:r(1)), allspace(r(1)+1:end)'); end end + B = reshape(B, A.sz); end @@ -116,10 +115,7 @@ 'Can only obtain spaces for single index.'); for j = size(t, i):-1:1 el = t.var(find(t.ind(:, i) == j, 1)); - if isempty(el) - warning('cannot deduce space.'); - continue; - end + assert(~isempty(el), 'sparse:argerror', 'cannot deduce space'); s(j) = space(t.var(find(t.ind(:, i) == j, 1)), i); end end @@ -262,6 +258,14 @@ function disp(t) end end + function a = ctranspose(a) + for i = 1:nnz(a) + a.var(i) = a.var(i)'; + end + a.ind = fliplr(a.ind); + a.sz = fliplr(a.sz); + end + function d = dot(a, b) [~, ia, ib] = intersect(a.ind, b.ind, 'rows'); if isempty(ia), d = 0; return; end @@ -491,7 +495,7 @@ function disp(t) end A = reshape(permute(A, [uncA dimA]), [prod(szA(uncA)), prod(szA(dimA))]); - B = reshape(permute(B, [dimB uncB]), [prod(szB(dimB)), prod(szB(uncB))]); + B = reshape(permute(B, [flip(dimB) uncB]), [prod(szB(dimB)), prod(szB(uncB))]); if isempty(uncA) && isempty(uncB) C = 0; diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 9e692cc..9092479 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -1080,6 +1080,7 @@ return end + uncA = 1:nspaces(A); uncA(dimA) = []; iA = [uncA dimA]; rA = [length(uncA) length(dimA)]; From c9b908a43569d0ae30961248be4741aaa6298732 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 7 Nov 2022 21:56:12 +0100 Subject: [PATCH 074/245] Update tensors docs. --- docs/src/man/tensor.rst | 56 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/docs/src/man/tensor.rst b/docs/src/man/tensor.rst index 56299d1..def1ebd 100644 --- a/docs/src/man/tensor.rst +++ b/docs/src/man/tensor.rst @@ -253,9 +253,65 @@ In order to only change the partition without permuting indices, :code:`repartit While the partition of tensor indices might seem of little importance for tensors without internal structure, it can still have non-trivial consequences. This is demonstrated by comparing the ``matrixblocks`` and the ``tensorblocks`` before and after repartitioning. + Contractions ------------ +It is also possible to combine multiple tensors by contracting one or more indices. +This is only possible if the contracted spaces are compatible, i.e. one is the dual space of the other. +In general, all contractions will be performed pairwise, such that contracting ``A`` and ``B`` consists of permuting all uncontracted indices of ``A`` to its codomain, all contracted indices of ``A`` to its domain, and the reverse for ``B``. +Then, contraction is just a composition of linear maps, hence the need for the contracted spaces to be compatible. + +The syntax for specifying tensor contractions is based on the ``NCon`` (network contraction) algorithm described here (https://arxiv.org/abs/1402.0939). +The core principle is that contracted indices are indicated by incrementing positive integers, which are then pairwise contracted in ascending order. +Uncontracted indices are specified with negative integers, which are sorted in descending order (ascending absolute value). +It is also possible to specify the rank of the resulting tensor with a name-value argument ``'Rank'``, and use in-place conjugation with the name-value argument ``'Conj'``. + +.. autofunction:: src.utility.linalg.contract + :noindex: + Factorizations -------------- + +The reverse process, of splitting a single tensor into different, usually smaller, tensors with specific properties is done by means of factorizations. +In short, these are all analogies of their matrix counterpart, by performing the factorization on the tensor interpreted as a linear map. + +Many of these factorizations use the notion of orthogonality (unitarity when working over complex numbers). +An orthogonal tensor map ``t`` is characterised by the fact that ``t' * t = eye = t * t'``, which can be further relaxed to left- or right- orthogonal by respectively dropping the right- and left- hand side of the previous equation. + +.. automethod:: src.tensors.Tensor.leftorth + :noindex: + +.. automethod:: src.tensors.Tensor.rightorth + :noindex: + +.. automethod:: src.tensors.Tensor.tsvd + :noindex: + +.. automethod:: src.tensors.Tensor.leftnull + :noindex: + +.. automethod:: src.tensors.Tensor.rightnull + :noindex: + +.. automethod:: src.tensors.Tensor.eig + :noindex: + + +Matrix functions +---------------- + +Finally, several functions that are defined for matrices have a generalisation to tensors, again by interpreting them as linear maps. + +.. automethod:: src.tensors.Tensor.expm + :noindex: + +.. automethod:: src.tensors.Tensor.sqrtm + :noindex: + +.. automethod:: src.tensors.Tensor.inv + :noindex: + +.. automethod:: src.tensors.Tensor.mpower + :noindex: From 108a23bb45e3c40add75ab5f9d28bca18302ced0 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 7 Nov 2022 21:57:55 +0100 Subject: [PATCH 075/245] small changes --- src/algorithms/IDmrg2.m | 2 ++ test/TestAlgorithms.m | 40 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/algorithms/IDmrg2.m b/src/algorithms/IDmrg2.m index 9b227ab..f450a39 100644 --- a/src/algorithms/IDmrg2.m +++ b/src/algorithms/IDmrg2.m @@ -135,9 +135,11 @@ tsvd(AC2, [1 2], [3 4], alg.trunc{:}); mps.C(end) = normalize(C); mps.AC(1) = multiplyleft(mps.AR(1), mps.C(end)); + mps.AR(end) = multiplyleft(multiplyright(mps.AL(end), mps.C(end)), ... inv(mps.C(end - 1))); + TL = transfermatrix(mpo, mps, mps, period(mps), 'Type', 'LL'); GL{1} = apply(TL, GL{end}); TR = transfermatrix(mpo, mps, mps, 1, 'Type', 'RR'); diff --git a/test/TestAlgorithms.m b/test/TestAlgorithms.m index 787b9f9..79cd69b 100644 --- a/test/TestAlgorithms.m +++ b/test/TestAlgorithms.m @@ -7,12 +7,12 @@ function test2dIsing(tc) mpo = InfMpo.Ising(); mps = initialize_mps(mpo, CartesianSpace.new(12)); - alg = Vumps('which', 'largestabs', 'maxiter', 5); + alg = Vumps('which', 'largestabs', 'maxiter', 10); [gs, lambda] = fixedpoint(alg, mpo, mps); tc.verifyEqual(lambda, 2.533, 'RelTol', 1e-2); tc.verifyEqual(lambda, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); - alg = IDmrg('which', 'largestabs', 'maxiter',10); + alg = IDmrg('which', 'largestabs', 'maxiter', 10); [gs, lambda] = fixedpoint(alg, mpo, mps); tc.verifyEqual(lambda, 2.5337, 'RelTol', 1e-2); tc.verifyEqual(lambda, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); @@ -117,7 +117,43 @@ function test1dIsing(tc) % tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... % 'RelTol', 1e-2); + mpo = InfJMpo.Ising('Symmetry', 'Z2'); + mps = initialize_mps(mpo, GradedSpace.new(Z2(0, 1), [6 6], false)); + + alg = Vumps('which', 'smallestreal', 'maxiter', 5); + [gs, lambda] = fixedpoint(alg, mpo, mps); + tc.verifyEqual(lambda, -1.273, 'RelTol', 1e-2); +% tc.verifyEqual(lambda, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); + + alg = IDmrg('which', 'smallestreal', 'maxiter',10); + [gs, lambda] = fixedpoint(alg, mpo, mps); + tc.verifyEqual(lambda, -1.273, 'RelTol', 1e-2); +% tc.verifyEqual(lambda, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); + +% mps2 = approximate(Vomps(), ... +% mpo, gs, initialize_mps(mpo, CartesianSpace.new(24))); +% tc.verifyEqual(fidelity(gs, mps2), 1, 'RelTol', 1e-2); + mps = [mps mps]; + mpo = [mpo mpo]; + alg = IDmrg2('which', 'smallestreal', 'maxiter', 10, ... + 'trunc', {'TruncBelow', 1e-3}); + [gs, lambda] = fixedpoint(alg, mpo, mps); +% tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); +% tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... +% 'RelTol', 1e-2); + + alg = IDmrg('which', 'smallestreal', 'maxiter', 10); + [gs, lambda] = fixedpoint(alg, mpo, mps); +% tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); +% tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... +% 'RelTol', 1e-2); + + alg = Vumps('which', 'smallestreal', 'maxiter', 5); + [gs, lambda] = fixedpoint(alg, mpo, mps); +% tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); +% tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... +% 'RelTol', 1e-2); end end end From 956e2a2190bea9936aa7465910808a0249f5b0a0 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 7 Nov 2022 23:01:54 +0100 Subject: [PATCH 076/245] expectation values --- src/mps/InfJMpo.m | 24 ++++++++++++++++++++++++ src/mps/InfMpo.m | 6 +++++- src/mps/UniformMps.m | 14 +++++++++----- test/TestAlgorithms.m | 40 ++++++++++++++++++++-------------------- 4 files changed, 58 insertions(+), 26 deletions(-) diff --git a/src/mps/InfJMpo.m b/src/mps/InfJMpo.m index c3cfc87..a35d932 100644 --- a/src/mps/InfJMpo.m +++ b/src/mps/InfJMpo.m @@ -157,6 +157,30 @@ Os = cellfun(@(x) x.O, varargin, 'UniformOutput', false); mpo = InfJMpo([Os{:}]); end + + function mpo = plus(a, b) + if isa(a, 'InfJMpo') && isnumeric(b) + if period(a) > 1 && isscalar(b) + b = repmat(b, 1, period(a)); + end + + for i = 1:period(a) + a.O{i}(1, 1, end, 1) = a.O{i}(1, 1, end, 1) + b(i); + end + + elseif isnumeric(a) && isa(b, 'InfJMpo') + mpo = b + a; + end + end + + function mpo = mtimes(mpo, b) + if isnumeric(mpo) || isnumeric(b) + mpo = mpo .* b; + return + end + + mpo = [mpo; b]; + end end methods (Static) diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index 142a1d8..b2f3b5d 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -47,7 +47,6 @@ if depth(mpo) == 1, return; end O_ = mpo(1, 1).O; for d = 2:depth(mpo) - for w = period(mpo):-1:1 vspaces = [rightvspace(O_{w})' rightvspace(mpo(d, 1).O{w})']; fuser(w) = Tensor.eye(vspaces, prod(vspaces)); @@ -72,6 +71,11 @@ mpo = InfMpo([Os{:}]); end + function mpo = vertcat(varargin) + Os = cellfun(@(x) x.O, varargin, 'UniformOutput', false); + mpo = InfMpo(vertcat(Os{:})); + end + function mps = initialize_mps(mpo, vspaces) arguments mpo diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index f5bbf51..0d677ab 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -283,25 +283,29 @@ mps2 = mps1 end - if isa(O, 'InfMpo') + if isa(O, 'InfJMpo') [GL, GR] = environments(O, mps1, mps2); H = AC_hamiltonian(O, mps1, GL, GR); E = zeros(size(H)); for i = 1:length(H) + N = size(H{i}.R, 2); + H{i}.R = H{i}.R(1, N, 1); + H{i}.O{1} = H{i}.O{1}(:, :, N, :); AC_ = apply(H{i}, mps1.AC(i)); E(i) = dot(AC_, mps2.AC(i)); end - elseif isa(O, 'InfJMpo') + + elseif isa(O, 'InfMpo') [GL, GR] = environments(O, mps1, mps2); H = AC_hamiltonian(O, mps1, GL, GR); E = zeros(size(H)); for i = 1:length(H) - H{i}.R = H{i}.R(1, end, 1); - H{i}.O{1} = H{i}.O{1}(:,:,end,:); - AC_ = apply(H{i}, mps1.AC(i)); E(i) = dot(AC_, mps2.AC(i)); end + + elseif isa(O, 'AbstractTensor') + error('TBA'); else error('Unknown operator type (%s)', class(O)); end diff --git a/test/TestAlgorithms.m b/test/TestAlgorithms.m index 79cd69b..b81509c 100644 --- a/test/TestAlgorithms.m +++ b/test/TestAlgorithms.m @@ -86,12 +86,12 @@ function test1dIsing(tc) alg = Vumps('which', 'smallestreal', 'maxiter', 5); [gs, lambda] = fixedpoint(alg, mpo, mps); tc.verifyEqual(lambda, -1.273, 'RelTol', 1e-2); -% tc.verifyEqual(lambda, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); + tc.verifyEqual(lambda, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); alg = IDmrg('which', 'smallestreal', 'maxiter',10); [gs, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyEqual(lambda, -1.273, 'RelTol', 1e-2); -% tc.verifyEqual(lambda, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); +% tc.verifyEqual(lambda, -1.273, 'RelTol', 1e-2); + tc.verifyEqual(-1.273, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); % mps2 = approximate(Vomps(), ... % mpo, gs, initialize_mps(mpo, CartesianSpace.new(24))); @@ -102,20 +102,20 @@ function test1dIsing(tc) alg = IDmrg2('which', 'smallestreal', 'maxiter', 10); [gs, lambda] = fixedpoint(alg, mpo, mps); % tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); -% tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... -% 'RelTol', 1e-2); + tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... + 'RelTol', 1e-2); alg = IDmrg('which', 'smallestreal', 'maxiter', 10); [gs, lambda] = fixedpoint(alg, mpo, mps); % tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); -% tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... -% 'RelTol', 1e-2); + tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... + 'RelTol', 1e-2); alg = Vumps('which', 'smallestreal', 'maxiter', 5); [gs, lambda] = fixedpoint(alg, mpo, mps); -% tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); -% tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... -% 'RelTol', 1e-2); + tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); + tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... + 'RelTol', 1e-2); mpo = InfJMpo.Ising('Symmetry', 'Z2'); mps = initialize_mps(mpo, GradedSpace.new(Z2(0, 1), [6 6], false)); @@ -123,12 +123,12 @@ function test1dIsing(tc) alg = Vumps('which', 'smallestreal', 'maxiter', 5); [gs, lambda] = fixedpoint(alg, mpo, mps); tc.verifyEqual(lambda, -1.273, 'RelTol', 1e-2); -% tc.verifyEqual(lambda, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); + tc.verifyEqual(lambda, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); alg = IDmrg('which', 'smallestreal', 'maxiter',10); [gs, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyEqual(lambda, -1.273, 'RelTol', 1e-2); -% tc.verifyEqual(lambda, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); +% tc.verifyEqual(lambda, -1.273, 'RelTol', 1e-2); + tc.verifyEqual(-1.273, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); % mps2 = approximate(Vomps(), ... % mpo, gs, initialize_mps(mpo, CartesianSpace.new(24))); @@ -140,20 +140,20 @@ function test1dIsing(tc) 'trunc', {'TruncBelow', 1e-3}); [gs, lambda] = fixedpoint(alg, mpo, mps); % tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); -% tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... -% 'RelTol', 1e-2); + tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... + 'RelTol', 1e-2); alg = IDmrg('which', 'smallestreal', 'maxiter', 10); [gs, lambda] = fixedpoint(alg, mpo, mps); % tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); -% tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... -% 'RelTol', 1e-2); + tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... + 'RelTol', 1e-2); alg = Vumps('which', 'smallestreal', 'maxiter', 5); [gs, lambda] = fixedpoint(alg, mpo, mps); -% tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); -% tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... -% 'RelTol', 1e-2); + tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); + tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... + 'RelTol', 1e-2); end end end From cc1d2e77d5ff4c8148fac65a821de6616e808dc9 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 8 Nov 2022 17:48:06 +0100 Subject: [PATCH 077/245] global cache --- src/caches/LRU.m | 4 ++-- src/tensors/Tensor.m | 6 +++--- src/tensors/kernels/AbstractBlock.m | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/caches/LRU.m b/src/caches/LRU.m index cea7df4..db142bb 100644 --- a/src/caches/LRU.m +++ b/src/caches/LRU.m @@ -7,7 +7,7 @@ sentinel % sentinel of DLL, +sentinel is MRU, -sentinel is LRU map % map of key --> dll itemlimit = Inf % maximum size of cache in number of items - memlimit = 6 * 2^30 % maximum size of cache in bytes + memlimit = 0.005 * 2^30 % maximum size of cache in bytes mem = 0; % current memory usage in bytes. end @@ -106,7 +106,7 @@ while cache.mem > cache.memlimit || length(cache.map) > cache.itemlimit dll_oldest = -cache.sentinel; cache.mem = cache.mem - memsize(dll_oldest.val{2}, 'B'); - cache.map.remove(dll_oldets.val{1}); + cache.map.remove(dll_oldest.val{1}); pop(dll_oldest); end end diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 9092479..e107687 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -885,7 +885,7 @@ if (all(p == 1:nspaces(t)) && all(rank(t) == r)), return; end - persistent cache + global cache if isempty(cache), cache = LRU; end if Options.CacheEnabled() @@ -1099,7 +1099,7 @@ iB = idx(iB); end - persistent cache + global cache if isempty(cache), cache = LRU; end if Options.CacheEnabled() @@ -1337,7 +1337,7 @@ return end - persistent cache + global cache if isempty(cache), cache = LRU; end if Options.CacheEnabled() diff --git a/src/tensors/kernels/AbstractBlock.m b/src/tensors/kernels/AbstractBlock.m index 3e10122..fdc707c 100644 --- a/src/tensors/kernels/AbstractBlock.m +++ b/src/tensors/kernels/AbstractBlock.m @@ -34,7 +34,7 @@ return end - persistent cache + global cache if isempty(cache), cache = LRU; end if Options.CacheEnabled() From 47f49bac337925991959afdf7904f05c69d32c9b Mon Sep 17 00:00:00 2001 From: sanderdemeyer Date: Fri, 18 Nov 2022 23:51:27 +0100 Subject: [PATCH 078/245] Save options added to Vumps and IDmrg2 --- src/algorithms/IDmrg2.m | 24 +++++++++++------------ src/algorithms/Vumps.m | 43 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/src/algorithms/IDmrg2.m b/src/algorithms/IDmrg2.m index f1dccf1..c46d1ca 100644 --- a/src/algorithms/IDmrg2.m +++ b/src/algorithms/IDmrg2.m @@ -13,7 +13,7 @@ dynamical_tols = true tol_min = 1e-12 tol_max = 1e-6 - eigs_tolfactor = 1e-4 + eigs_tolfactor = 1e-7 trunc = {'TruncDim', 10} @@ -311,20 +311,20 @@ function save(alg, mps, lambdas) fileData.lambdas = lambdas; % save - if exist(fileName,'file') - old_file=load(fileName); - fileName_temp=[fileName(1:end-4),'_temp.mat']; - save(fileName_temp, '-struct', 'old_file', '-v7.3'); - saved_temp=1; - else - saved_temp=0; - end + %if exist(fileName,'file') + % old_file=load(fileName); + % fileName_temp=[fileName(1:end-4),'_temp.mat']; + % save(fileName_temp, '-struct', 'old_file', '-v7.3'); + % saved_temp=1; + %else + % saved_temp=0; + %end save(fileName, '-struct', 'fileData', '-v7.3'); - if saved_temp - delete(fileName_temp); - end + %if saved_temp + % delete(fileName_temp); + %end end diff --git a/src/algorithms/Vumps.m b/src/algorithms/Vumps.m index adf7297..82a5169 100644 --- a/src/algorithms/Vumps.m +++ b/src/algorithms/Vumps.m @@ -20,6 +20,12 @@ multiAC = 'parallel' dynamical_multiAC = false; tol_multiAC = Inf + + doSave = false + saveIterations = 1 + saveMethod = 'full' + name = 'VUMPS' + end properties (Access = private) @@ -92,6 +98,10 @@ alg = updatetols(alg, iter, eta); plot(alg, iter, mps, eta); disp_iter(alg, iter, lambda, eta, toc(t_iter)); + + if alg.doSave && mod(iter, alg.saveIterations) == 0 + save_iteration(alg, mps, lambda, iter); + end end disp_maxiter(alg, iter, lambda, eta, toc(t_total)); @@ -188,12 +198,12 @@ methods function alg = updatetols(alg, iter, eta) if alg.dynamical_tols - alg.alg_eigs.Tol = between(alg.tol_min, eta * alg.eigs_tolfactor / iter, ... - alg.tol_max); + alg.alg_eigs.Tol = between(alg.tol_min, eta * alg.eigs_tolfactor, ... + alg.tol_max / iter); alg.alg_canonical.Tol = between(alg.tol_min, ... - eta * alg.canonical_tolfactor / iter, alg.tol_max); + eta * alg.canonical_tolfactor, alg.tol_max / iter); alg.alg_environments.Tol = between(alg.tol_min, ... - eta * alg.environments_tolfactor / iter, alg.tol_max); + eta * alg.environments_tolfactor, alg.tol_max / iter); if alg.verbosity > Verbosity.iter fprintf('Updated subalgorithm tolerances: (%e,\t%e,\t%e)\n', ... @@ -309,6 +319,31 @@ function disp_maxiter(alg, iter, lambda, eta, t) end fprintf('---------------\n'); end + + function save_iteration(alg, mps, lambda, iter) + fileName = alg.name; + + fileData = struct; + fileData.mps = mps; + fileData.lambda = lambda; + fileData.iteration = iter; + % save + + %if exist(fileName,'file') + % old_file=load(fileName); + % fileName_temp=[fileName(1:end-4),'_temp.mat']; + % save(fileName_temp, '-struct', 'old_file', '-v7.3'); + % saved_temp=1; + %else + % saved_temp=0; + %end + + save(fileName, '-struct', 'fileData', '-v7.3'); + + %if saved_temp + % delete(fileName_temp); + %end + end end end From de6dec0de7e77733a4596000a0eec9eaf4cbef95 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 24 Nov 2022 17:30:43 +0100 Subject: [PATCH 079/245] sparse twists --- src/mps/InfMpo.m | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index b2f3b5d..52eac3c 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -240,12 +240,16 @@ for i = 1:length(sites) gl = GL{sites(i)}; for j = 1:numel(gl) - gl(j) = twist(gl(j), find(isdual(space(gl(j), 1)))); + if nnz(gl(j)) ~= 0 + gl(j) = twist(gl(j), find(isdual(space(gl(j), 1)))); + end end gr = GR{mod1(sites(i) + 2, period(mps))}; for j = 1:numel(gr) - gr(j) = twist(gr(j), find(isdual(space(gr(j), nspaces(gr(j))))) + ... + if nnz(gr(j)) ~= 0 + gr(j) = twist(gr(j), find(isdual(space(gr(j), nspaces(gr(j))))) + ... nspaces(gr(j)) - 1); + end end H{i} = FiniteMpo(gl, mpo.O(mod1(sites(i) + [0 1], period(mps))), gr); end @@ -264,12 +268,16 @@ for i = 1:length(sites) gl = GL{next(sites(i), period(mps))}; for j = 1:numel(gl) - gl(j) = twist(gl(j), find(isdual(space(gl(j), 1)))); + if nnz(gl(j)) ~= 0 + gl(j) = twist(gl(j), find(isdual(space(gl(j), 1)))); + end end gr = GR{next(sites(i), period(mps))}; for j = 1:numel(gr) - gr(j) = twist(gr(j), find(isdual(space(gr(j), nspaces(gr(j))))) + ... - nspaces(gr(j)) - 1); + if nnz(gr(j)) ~= 0 + gr(j) = twist(gr(j), find(isdual(space(gr(j), nspaces(gr(j))))) + ... + nspaces(gr(j)) - 1); + end end H{i} = FiniteMpo(gl, {}, gr); end From 8418f1ebe108535382b0852668cf34579ed9b143 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 30 Nov 2022 16:49:16 +0100 Subject: [PATCH 080/245] bugfix canonicalize --- src/mps/MpsTensor.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index a6990b8..1b4a9f7 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -97,7 +97,7 @@ % initialization N = size(A, 2); - if isempty(CL), CL = initializeC(A, circshift(A, 1)); end + if isempty(CL), CL = initializeC(A, circshift(A, -1)); end if kwargs.Normalize, CL(1) = normalize(CL(1)); end A = arrayfun(@(a) MpsTensor(repartition(a, [nspaces(a)-1 1])), A); AL = A; @@ -201,17 +201,17 @@ opts = namedargs2cell(kwargs); - Ad = arrayfun(@ctranspose, A); + Ad = flip(arrayfun(@ctranspose, A)); if isempty(CR) Cd = []; else - Cd = circshift(arrayfun(@ctranspose, CR), -1); + Cd = circshift(flip(arrayfun(@ctranspose, CR)), -1); end [AR, CR, lambda, eta] = uniform_leftorth(Ad, Cd, opts{:}); - AR = arrayfun(@ctranspose, AR); - CR = circshift(arrayfun(@ctranspose, CR), 1); + AR = flip(arrayfun(@ctranspose, AR)); + CR = flip(circshift(arrayfun(@ctranspose, CR), 1)); lambda = conj(lambda); end From 72a67ca628e893492b6388c08cd2d7cf52440bbe Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 30 Nov 2022 16:50:11 +0100 Subject: [PATCH 081/245] save fusiontree braids in cache --- src/tensors/FusionTree.m | 51 +++++++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/src/tensors/FusionTree.m b/src/tensors/FusionTree.m index 4fe9c0f..bf3c476 100644 --- a/src/tensors/FusionTree.m +++ b/src/tensors/FusionTree.m @@ -77,8 +77,8 @@ f.vertices = vertices; f.isdual = isdual; f.rank = rank; - -% assert(all(isallowed(f))); % comment out for debugging + + % assert(all(isallowed(f))); % comment out for debugging end end @@ -301,7 +301,7 @@ for j = 1:length(ia2) blocks{j} = Rsymbol(abc(j, 1), abc(j, 2), abc(j, 3), inv); end - + f.charges = f.charges(order, [2 1 3:end]); f.vertices = f.vertices(order, :); f.isdual(1:2) = f.isdual([2 1]); @@ -588,20 +588,39 @@ assert(N == f.legs, 'tensors:trees:ArgError', ... 'p has the wrong number of elements.'); + global cache + if isempty(cache), cache = LRU; end + + if Options.CacheEnabled() + key = GetMD5({f, p, lvl, rank}, 'Array', 'hex'); + med = get(cache, key); + + if ~isempty(med) + c = med.c; + f = med.f; + return + end + end + if all(p == 1:f.legs) [c, f] = repartition(f, rank); - return + else + swaps = perm2swap(p); + [c, f] = repartition(f, [f.legs 0]); + for s = swaps + [c_, f] = artinbraid(f, s, lvl(s) > lvl(s + 1)); + c = c * c_; + lvl(s:s + 1) = lvl([s + 1 s]); + end + [c_, f] = repartition(f, rank); + c = c * c_; end - swaps = perm2swap(p); - [c, f] = repartition(f, [f.legs 0]); - for s = swaps - [c_, f] = artinbraid(f, s, lvl(s) > lvl(s + 1)); - c = c * c_; - lvl(s:s + 1) = lvl([s + 1 s]); + if Options.CacheEnabled() + med.c = c; + med.f = f; + cache = set(cache, key, med); end - [c_, f] = repartition(f, rank); - c = c * c_; end function [c, f] = elementary_trace(f, i) @@ -911,8 +930,8 @@ 'Col', cols); else cols = [treeindsequence(f.rank(1)) + 1 ... % center charge - (1:treeindsequence(f.rank(2))) + treeindsequence(f.rank(1)) + 1 ... % fuse charges - fliplr(1:treeindsequence(f.rank(1)))]; % split charges + (1:treeindsequence(f.rank(2))) + treeindsequence(f.rank(1)) + 1 ... % fuse charges + fliplr(1:treeindsequence(f.rank(1)))]; % split charges if nargout > 1 [f.charges, p] = sortrows(f.charges, cols); else @@ -1262,5 +1281,9 @@ function displayNonScalarObject(f) end C = {C}; end + + function s = GetMD5_helper(f) + s = {f.charges, f.vertices, f.isdual, f.rank}; + end end end From 08ad415c350b03fbd37736f80a170a29bf280132 Mon Sep 17 00:00:00 2001 From: sanderdemeyer Date: Wed, 30 Nov 2022 16:56:23 +0100 Subject: [PATCH 082/245] Verandering --- src/caches/GetMD5/GetMD5.mexw64 | Bin 0 -> 25600 bytes src/caches/LRU.m | 5 +- src/mps/FiniteMpo.m | 4 +- src/mps/MpoTensor.m | 12 +- src/mps/MpoTensor_new.m | 317 +++++++++++++++++++++++++++++++ src/tensors/AbstractTensor.m | 1 - src/utility/uninit/uninit.mexw64 | Bin 0 -> 18944 bytes 7 files changed, 331 insertions(+), 8 deletions(-) create mode 100644 src/caches/GetMD5/GetMD5.mexw64 create mode 100644 src/mps/MpoTensor_new.m create mode 100644 src/utility/uninit/uninit.mexw64 diff --git a/src/caches/GetMD5/GetMD5.mexw64 b/src/caches/GetMD5/GetMD5.mexw64 new file mode 100644 index 0000000000000000000000000000000000000000..46968b201cbd5321488111b39b0ead338ff90003 GIT binary patch literal 25600 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 Y-e!sy3f)@Tv^gh60r8v9Kbi&pFXiau_W%F@ literal 0 HcmV?d00001 diff --git a/src/caches/LRU.m b/src/caches/LRU.m index db142bb..fd989f7 100644 --- a/src/caches/LRU.m +++ b/src/caches/LRU.m @@ -7,10 +7,9 @@ sentinel % sentinel of DLL, +sentinel is MRU, -sentinel is LRU map % map of key --> dll itemlimit = Inf % maximum size of cache in number of items - memlimit = 0.005 * 2^30 % maximum size of cache in bytes + memlimit = 20 * 2^30 % maximum size of cache in bytes mem = 0; % current memory usage in bytes. end - methods function cache = LRU(itemlimit, memlimit) % Construct a new LRU cache. @@ -93,7 +92,7 @@ dll_old = cache.map(key); pop(dll_old); cache.mem = cache.mem - memsize(dll_old.val{2}, 'B'); - cache.map.remove(key); + cache.map.(key); end % add new data diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index 71babf6..5a0ad64 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -2,9 +2,9 @@ % Finite Matrix product operators properties - L MpsTensor + L %MpsTensor O - R MpsTensor + R %MpsTensor end methods diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index a8cadc9..1573066 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -113,8 +113,16 @@ N = nargin - 1; auxlegs_v = nspaces(v) - N; - auxlegs_l = L.alegs; - auxlegs_r = R.alegs; + if isa(L, 'MpsTensor') + auxlegs_l = L.alegs; + else + auxlegs_l = 0; + end + if isa(R, 'MpsTensor') + auxlegs_r = R.alegs; + else + auxlegs_r = 0; + end auxlegs = auxlegs_v + auxlegs_l + auxlegs_r; Oinds = arrayfun(@(x) [2*x-2 -x 2*x 2*x-1], 2:N-1, 'UniformOutput', false); diff --git a/src/mps/MpoTensor_new.m b/src/mps/MpoTensor_new.m new file mode 100644 index 0000000..502412f --- /dev/null +++ b/src/mps/MpoTensor_new.m @@ -0,0 +1,317 @@ +classdef (InferiorClasses = {?Tensor, ?MpsTensor, ?SparseTensor}) MpoTensor < AbstractTensor + % Matrix product operator building block + % This object represents the MPO tensor at a single site as the sum of rank (2,2) + % (sparse) tensors and some scalars, which will be implicitly used as unit tensors. + % + % 4 + % ^ + % | + % 1 ->-- O -->- 3 + % | + % ^ + % 2 + + properties + tensors = [] + scalars = [] + end + + methods + function n = nspaces(~) + n = 4; + end + end + + methods + function t = MpoTensor(varargin) + if nargin == 0, return; end + + if nargin == 1 + if isnumeric(varargin{1}) + t.scalars = varargin{1}; + t.tensors = SparseTensor([], [], size(t.scalars)); + elseif isa(varargin{1}, 'MpoTensor') + t.scalars = varargin{1}.scalars; + t.tensors = varargin{1}.tensors; + elseif isa(varargin{1}, 'Tensor') || isa(varargin{1}, 'SparseTensor') + t.tensors = varargin{1}; + t.scalars = zeros(size(t.tensors)); + end + return + end + + if nargin == 2 + t.tensors = varargin{1}; + t.scalars = varargin{2}; + N = max(ndims(t.tensors), ndims(t.scalars)); + assert(isequal(size(t.tensors, 1:N), size(t.scalars, 1:N)), 'mpotensors:dimerror', ... + 'scalars and tensors should have the same size.'); + if any(ismember(find(t.tensors), find(t.scalars))) + warning('mpotensor contains both scalar and tensor at the same site.'); + end + end + end + + function t = minus(t, t2) + t.tensors = t.tensors - t2.tensors; + t.scalars = t.scalars - t2.scalars; + end + + function t = plus(t, t2) + t.tensors = t.tensors + t2.tensors; + t.scalars = t.scalars + t2.scalars; + end + + function t = times(t, a) + if isnumeric(t) + t = a .* t; + return + end + + assert(isnumeric(a), 'mpotensor:argerror', 'invalid input types.'); + + t.tensors = t.tensors .* a; + t.scalars = t.scalars .* a; + end + + function t = rdivide(t, a) + assert(isnumeric(a), 'mpotensor:argerror', 'invalid input types.'); + t.tensors = t.tensors ./ a; + t.scalars = t.scalars ./ a; + end + + function t = mrdivide(t, a) + assert(isnumeric(a), 'mpotensor:argerror', 'invalid input types.'); + t.tensors = t.tensors / a; + t.scalars = t.scalars / a; + end + + function v = applychannel(O, L, R, v) + arguments + O MpoTensor + L MpsTensor + R MpsTensor + v + end + auxlegs_v = nspaces(v) - 3; + auxlegs_l = nspaces(L) - 3; + auxlegs_r = nspaces(R) - 3; + auxlegs = auxlegs_v + auxlegs_l + auxlegs_r; + + v = contract(v, [1 3 5 (-(1:auxlegs_v) - 3 - auxlegs_l)], ... + L, [-1 2 1 (-(1:auxlegs_l) - 3)], ... + O, [2 -2 4 3], ... + R, [5 4 -3 (-(1:auxlegs_r) - 3 - auxlegs_l - auxlegs_v)], ... + 'Rank', rank(v) + [0 auxlegs]); + end + + function v = applympo(varargin) + assert(nargin >= 3) + v = varargin{end}; + R = varargin{end-1}; + L = varargin{end-2}; + N = nargin - 1; + + auxlegs_v = nspaces(v) - N; + auxlegs_l = L.alegs; + auxlegs_r = R.alegs; + auxlegs = auxlegs_v + auxlegs_l + auxlegs_r; + + Oinds = arrayfun(@(x) [2*x-2 -x 2*x 2*x-1], 2:N-1, 'UniformOutput', false); + O = [varargin(1:end-3); Oinds]; + v = contract(v, [1:2:2*N-1 (-(1:auxlegs_v) - N - auxlegs_l)], ... + L, [-1 2 1 (-(1:auxlegs_l) - N)], ... + O{:}, ... + R, [2*N-1 2*N-2 -N (-(1:auxlegs_r) - N - auxlegs_l - auxlegs_v)], ... + 'Rank', rank(v) + [0 auxlegs]); + end + + function O = rot90(O) + O.tensors = tpermute(O.tensors, [2 3 4 1], [2 2]); + O.scalars = permute(O.scalars, [2 3 4 1]); + end + + function C = tensorprod(A, B, dimA, dimB, ca, cb, options) + arguments + A + B + dimA + dimB + ca = false + cb = false + options.NumDimensionsA = ndims(A) + end + + assert(~isa(A, 'MpoTensor') || ~isa(B, 'MpoTensor'), 'mpotensor:tensorprod', ... + 'cannot contract two mpotensors.'); + + if isa(A, 'MpoTensor') + C = tensorprod(A.tensors, B, dimA, dimB, ca, cb); + + if nnz(A.scalars) > 0 + assert(sum(dimA == 1 | dimA == 3, 'all') == 1, ... + 'Cannot deduce output space unless leg 1 xor leg 3 is connected.'); + assert(sum(dimA == 2 | dimA == 4, 'all') == 1, ... + 'Cannot deduce output space unless leg 2 xor leg 4 is connected.'); + + uncA = 1:nspaces(A); uncA(dimA) = []; + uncB = 1:nspaces(B); uncB(dimB) = []; + + A = reshape(permute(A.scalars, [uncA flip(dimA)]), ... + [prod(size(A, uncA)) prod(size(A, dimA))]); + B = reshape(tpermute(B, [dimB uncB], [length(dimB) length(uncB)]), ... + [prod(size(B, dimB)) prod(size(B, uncB))]); + C = C + reshape(sparse(A) * B, size(C)); + end + else + C = tensorprod(A, B.tensors, dimA, dimB, ca, cb); + szC = size(C); + if nnz(B.scalars) > 0 + assert(sum(dimB == 1 | dimB == 3, 'all') == 1, ... + 'Cannot deduce output space unless leg 1 xor leg 3 is connected.'); + assert(sum(dimB == 2 | dimB == 4, 'all') == 1, ... + 'Cannot deduce output space unless leg 2 xor leg 4 is connected.'); + + uncA = 1:nspaces(A); uncA(dimA) = []; + uncB = 1:nspaces(B); uncB(dimB) = []; + + A = reshape(tpermute(A, [uncA dimA], [length(uncA) length(dimA)]), ... + [prod(size(A, uncA)) prod(size(A, dimA))]); + B = reshape(permute(B.scalars, [flip(dimB) uncB]), ... + [prod(size(B, dimB)) prod(size(B, uncB))]); + C = C + reshape(A * sparse(B), szC); + end + end + end + + function O = ctranspose(O) + O.tensors = tpermute(O.tensors', [4 1 2 3], [2 2]); + O.scalars = conj(permute(O.scalars, [1 4 3 2])); + end + + function O = transpose(O) + O.tensors = tpermute(O.tensors, [3 4 1 2], [2 2]); + O.scalars = permute(O.scalars, [3 4 1 2]); + end + end + + methods + function s = space(O, i) + s = space(O.tensors, i); + end + + function s = pspace(O) + s = space(O.tensors, 2); + end + + function s = leftvspace(O, lvls) + if nargin == 1 + s = space(O.tensors, 1); + else + s = space(O.tensors(lvls, :, :, :), 1); + end + end + + function s = rightvspace(O, lvls) + if nargin == 1 + s = space(O.tensors, 3); + else + s = space(O.tensors(:, :, lvls, :), 3); + end + end + + function bool = istriu(O) + sz = size(O, 1:4); + sz1 = sz(1) * sz(2); + sz2 = sz(3) * sz(4); + bool = istriu(reshape(O.scalars, [sz1, sz2])) && ... + istriu(reshape(O.tensors, [sz1, sz2])); + end + + function bool = iseye(O) + bool = nnz(O.tensors) == 0 && isequal(O.scalars, eye(size(O.scalars))); + end + + function n = nnz(O) + n = nnz(O.tensors) + nnz(O.scalars); + end + end + + methods + function t = subsref(t, s) + assert(length(s) == 1, 'mpotensor:index', ... + 'only a single level of indexing allowed.'); + switch s.type + case '.' + t = builtin('subsref', t, s); + case '()' + t.scalars = subsref(t.scalars, s); + t.tensors = subsref(t.tensors, s); + otherwise + error('mpotensor:index', 'invalid indexing expression'); + end + end + + function t = subsasgn(t, s, v) + assert(length(s) == 1, 'mpotensor:index', ... + 'only a single level of indexing allowed.'); + assert(strcmp(s.type, '()'), 'mpotensor:index', 'only () indexing allowed.'); + if isempty(t), t = MpoTensor(); end + if isnumeric(v) + t.scalars = subsasgn(t.scalars, s, v); + elseif isa(v, 'MpoTensor') + t.scalars = subsasgn(t.scalars, s, v.scalars); + t.tensors = subsasgn(t.tensors, s, v.tensors); + elseif isa(v, 'Tensor') + t.tensors = subsasgn(t.tensors, s, v); + end + end + + function i = end(t, k, n) + if n == 1 + i = prod(size(t)); + return + end + if n > ndims(t) + i = 1; + else + i = size(t, k); + end + end + + function bools = eq(a, b) + arguments + a MpoTensor + b MpoTensor + end + + bools = a.scalars == b.scalars & a.tensors == b.tensors; + end + + function I = find(O) + I = union(find(O.tensors), find(O.scalars)); + end + + function varargout = size(t, varargin) + if nargin > 1 + [varargout{1:nargout}] = size(t.scalars, varargin{:}); + else + [varargout{1:nargout}] = size(t.scalars); + end + end + end + + methods (Static) + function O = zeros(m, n, o, p) + if nargin == 1 + sz = m; + elseif nargin == 4 + sz = [m n o p]; + else + error('mpotensor:argerror', 'invalid amount of inputs.'); + end + O = MpoTensor(SparseTensor([], [], sz), zeros(sz)); + end + end +end + diff --git a/src/tensors/AbstractTensor.m b/src/tensors/AbstractTensor.m index 8b242d1..1fef306 100644 --- a/src/tensors/AbstractTensor.m +++ b/src/tensors/AbstractTensor.m @@ -303,7 +303,6 @@ kwargs.Rank = [] kwargs.Debug = false end - assert(length(kwargs.Conj) == length(tensors)); for i = 1:length(tensors) if length(indices{i}) > 1 diff --git a/src/utility/uninit/uninit.mexw64 b/src/utility/uninit/uninit.mexw64 new file mode 100644 index 0000000000000000000000000000000000000000..7b27d4550f0a47f552babfad3658079274a15aec GIT binary patch literal 18944 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 literal 0 HcmV?d00001 From 0a4628a7c417c802ee640f71538b442d9d8a78b9 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 5 Dec 2022 14:21:58 +0100 Subject: [PATCH 083/245] SparseMpsTensor attempts --- src/mps/FiniteMpo.m | 4 +- src/mps/MpsTensor.m | 182 +++++++++++++++++++++++++++++++++--------- src/tensors/Tensor.m | 5 +- test/TestUniformMps.m | 4 +- 4 files changed, 153 insertions(+), 42 deletions(-) diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index 5a0ad64..71babf6 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -2,9 +2,9 @@ % Finite Matrix product operators properties - L %MpsTensor + L MpsTensor O - R %MpsTensor + R MpsTensor end methods diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index 1b4a9f7..6aec2f6 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -1,7 +1,8 @@ -classdef MpsTensor < Tensor +classdef MpsTensor < AbstractTensor % Generic mps tensor objects that have a notion of virtual, physical and auxiliary legs. properties + var plegs = 1 alegs = 0 end @@ -22,14 +23,9 @@ 'Input sizes incompatible.'); end - if isempty(tensor) - args = {}; - else - args = {full(tensor)}; - end - A@Tensor(args{:}); if ~isempty(tensor) - for i = numel(A):-1:1 + for i = numel(tensor):-1:1 + A(i).var = tensor(i); A(i).plegs = nspaces(tensor(i)) - alegs(i) - 2; A(i).alegs = alegs(i); end @@ -40,6 +36,14 @@ %% Properties methods + function s = space(A, varargin) + s = space(A.var, varargin{:}); + end + + function n = nspaces(A) + n = nspaces(A.var); + end + function s = pspace(A) s = space(A, 1 + (1:A.plegs)); end @@ -51,29 +55,135 @@ function s = rightvspace(A) s = space(A, nspaces(A) - A.alegs); end + + function cod = codomain(A) + cod = A.var.codomain; + end + + function dom = domain(A) + dom = A.var.domain; + end + + function r = rank(A) + r = rank([A.var]); + end end %% Linear Algebra methods - function [AL, L] = leftorth(A, alg) + function [A, L] = leftorth(A, alg) arguments A alg = 'qrpos' end + if numel(A) > 1 + for i = numel(A):-1:1 + [A(i), L(i)] = leftorth(A, alg); + end + return + end + if A.alegs == 0 - [AL, L] = leftorth@Tensor(A, 1:nspaces(A)-1, nspaces(A), alg); + [A.var, L] = leftorth(A.var, 1:nspaces(A)-1, nspaces(A), alg); if isdual(space(L, 1)) == isdual(space(L, 2)) L.codomain = conj(L.codomain); L = twist(L, 1); - AL.domain = conj(AL.domain); + A.var.domain = conj(A.var.domain); end else - [AL, L] = leftorth@Tensor(A, [1:A.plegs+1 A.plegs+3], A.plegs+2, alg); - AL = permute(AL, [1:A.plegs+1 A.plegs+3 A.plegs+2], rank(A)); + [A.var, L] = leftorth(A.var, [1:A.plegs+1 A.plegs+3], A.plegs+2, alg); + A.var = permute(A.var, [1:A.plegs+1 A.plegs+3 A.plegs+2], rank(A)); end - AL = MpsTensor(AL, A.alegs); + end + + function A = repartition(A, r) + arguments + A + r = [nspaces(A)-1 1] + end + for i = 1:numel(A) + A(i).var = repartition(A(i).var, r); + end + end + + function A = plus(varargin) + for i = 1:2 + if isa(varargin{i}, 'MpsTensor') + varargin{i} = varargin{i}.var; + end + end + A = plus(varargin{:}); + end + + function A = minus(varargin) + for i = 1:2 + if isa(varargin{i}, 'MpsTensor') + varargin{i} = varargin{i}.var; + end + end + A = minus(varargin{:}); + end + + function n = norm(A) + n = norm([A.var]); + end + + function [R, A] = rightorth(A, alg) + arguments + A + alg = 'rqpos' + end + + if numel(A) > 1 + for i = numel(A):-1:1 + [R(i), A(i)] = rightorth(A, alg); + end + return + end + + [R, A.var] = rightorth(A.var, 1, 2:nspaces(A), alg); + if isdual(space(R, 1)) == isdual(space(R, 2)) + R.domain = conj(R.domain); + R = twist(R, 2); + A.var.codomain = conj(A.var.codomain); + end + end + + function t = ctranspose(t) + % Compute the adjoint of a tensor. This is defined as swapping the codomain and + % domain, while computing the adjoint of the matrix blocks. + % + % Usage + % ----- + % :code:`t = ctranspose(t)` + % :code:`t = t'` + % + % Arguments + % --------- + % t : :class:`Tensor` + % input tensor. + % + % Returns + % ------- + % t : :class:`Tensor` + % adjoint tensor. + + for i = 1:numel(t) + t(i).var = t(i).var'; + end + + t = permute(t, ndims(t):-1:1); + end + + function C = tensorprod(varargin) + for i = 1:2 + if isa(varargin{i}, 'MpsTensor') + varargin{i} = varargin{i}.var; + end + end + C = tensorprod(varargin{:}); end function [AL, CL, lambda, eta] = uniform_leftorth(A, CL, kwargs) @@ -99,7 +209,9 @@ N = size(A, 2); if isempty(CL), CL = initializeC(A, circshift(A, -1)); end if kwargs.Normalize, CL(1) = normalize(CL(1)); end - A = arrayfun(@(a) MpsTensor(repartition(a, [nspaces(a)-1 1])), A); + for i = 1:numel(A) + A(i).var = repartition(A(i).var, [nspaces(A(i).var) - 1, 1]); + end AL = A; eta_best = Inf; @@ -168,23 +280,7 @@ end end - function [R, AR] = rightorth(A, alg) - arguments - A - alg = 'rqpos' - end - - for i = numel(A):-1:1 - [R(i), AR(i)] = rightorth@Tensor(A(i), 1, 2:nspaces(A(i)), alg); - if isdual(space(R(i), 1)) == isdual(space(R(i), 2)) - R(i).domain = conj(R(i).domain); - R(i) = twist(R(i), 2); - AR(i).codomain = conj(AR(i).codomain); - end - end - -% AR = MpsTensor(repartition(AR, rank(A)), A.alegs); - end + function [AR, CR, lambda, eta] = uniform_rightorth(A, CR, kwargs) arguments @@ -223,8 +319,8 @@ for i = length(A):-1:1 - B(i) = twist(B(i), [isdual(space(B(i), 1:2)) ~isdual(space(B(i), 3))]); - T(i, 1) = FiniteMpo(B(i)', {}, A(i)); + B(i).var = twist(B(i).var, [isdual(space(B(i), 1:2)) ~isdual(space(B(i), 3))]); + T(i, 1) = FiniteMpo(B(i).var', {}, A(i)); end end @@ -353,7 +449,7 @@ % C = twist(C, 2); % end % if isdual(space(A, 1)), C = twist(C, 2); end - A = MpsTensor(contract(C, [-1 1], A, [1 -2 -3], 'Rank', rank(A))); + [A.var] = contract(C, [-1 1], [A.var], [1 -2 -3], 'Rank', rank(A)); % A = MpsTensor(tpermute(... % multiplyright(MpsTensor(tpermute(A, [3 2 1])), tpermute(C, [2 1])), [3 2 1])); % A = MpsTensor(multiplyright(A', C')'); @@ -380,7 +476,7 @@ function C = initializeC(AL, AR) for i = length(AL):-1:1 - C(i) = AL.eye(rightvspace(AL(i))', leftvspace(AR(i))); + C(i) = AL(i).var.eye(rightvspace(AL(i))', leftvspace(AR(i))); end end @@ -396,6 +492,20 @@ normalize(A.randnc(spaces(1:r(1)), spaces(r(1)+1:end)'))); end end + + function type = underlyingType(A) + type = underlyingType(A.var); + end + end + + + %% Converter + methods + function t = Tensor(A) + for i = numel(A):-1:1 + t(i) = full(A(i).var); + end + end end end diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index e107687..c717691 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -414,6 +414,7 @@ function tdst = zerosLike(t, varargin) tdst = repmat(0 * t, varargin{:}); end + end @@ -1025,8 +1026,8 @@ % spaces remain. arguments - A - B + A Tensor + B Tensor dimA dimB ca = false diff --git a/test/TestUniformMps.m b/test/TestUniformMps.m index eecebc6..a4be588 100644 --- a/test/TestUniformMps.m +++ b/test/TestUniformMps.m @@ -40,9 +40,9 @@ function testCanonical(tc, A) mps = canonicalize(mps); AL = mps.AL; AR = mps.AR; C = mps.C; AC = mps.AC; - tc.assertTrue(all(isisometry(mps.AL, 'left')), ... + tc.assertTrue(all(isisometry([mps.AL.var], 'left')), ... 'AL should be a left isometry.'); - tc.assertTrue(all(isisometry(mps.AR, 'right')), ... + tc.assertTrue(all(isisometry([mps.AR.var], 'right')), ... 'AR should be a right isometry.'); for w = 1:period(mps) From f1d099b24f2ad8e06541728bdf1c1f11ffaece1f Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 5 Dec 2022 19:01:56 +0100 Subject: [PATCH 084/245] Sparse MpsTensors part 2 --- src/algorithms/Vumps.m | 3 +- src/mps/InfMpo.m | 23 +++---------- src/mps/MpsTensor.m | 54 +++++++++++++++++-------------- src/mps/UniformMps.m | 26 ++++++++++----- src/sparse/SparseTensor.m | 24 +++++++++++--- src/tensors/Tensor.m | 38 ++++++++++++++++++---- src/tensors/kernels/MatrixBlock.m | 2 +- 7 files changed, 107 insertions(+), 63 deletions(-) diff --git a/src/algorithms/Vumps.m b/src/algorithms/Vumps.m index 82a5169..3b9ebc6 100644 --- a/src/algorithms/Vumps.m +++ b/src/algorithms/Vumps.m @@ -120,8 +120,9 @@ end H_AC = AC_hamiltonian(mpo, mps, GL, GR, sites); + AC = mps.AC(sites); for i = length(sites):-1:1 - [AC(i), ~] = eigsolve(H_AC{i}, mps.AC(sites(i)), 1, alg.which, ... + [AC(i).var, ~] = eigsolve(H_AC{i}, mps.AC(sites(i)).var, 1, alg.which, ... kwargs{:}); end end diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index 52eac3c..651dda7 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -214,15 +214,9 @@ H = cell(1, length(sites)); for i = 1:length(sites) - gl = GL{sites(i)}; - for j = 1:numel(gl) - gl(j) = twist(gl(j), find(isdual(space(gl(j), 1)))); - end + gl = twistdual(GL{sites(i)}, 1); gr = GR{next(sites(i), period(mps))}; - for j = 1:numel(gr) - gr(j) = twist(gr(j), find(isdual(space(gr(j), nspaces(gr(j))))) + ... - nspaces(gr(j)) - 1); - end + gr = twistdual(gr, nspaces(gr)); H{i} = FiniteMpo(gl, mpo.O(sites(i)), gr); end end @@ -239,18 +233,9 @@ H = cell(1, length(sites)); for i = 1:length(sites) gl = GL{sites(i)}; - for j = 1:numel(gl) - if nnz(gl(j)) ~= 0 - gl(j) = twist(gl(j), find(isdual(space(gl(j), 1)))); - end - end + gl = twistdual(gl, 1); gr = GR{mod1(sites(i) + 2, period(mps))}; - for j = 1:numel(gr) - if nnz(gr(j)) ~= 0 - gr(j) = twist(gr(j), find(isdual(space(gr(j), nspaces(gr(j))))) + ... - nspaces(gr(j)) - 1); - end - end + gr = twistdual(gr, nspaces(gr)); H{i} = FiniteMpo(gl, mpo.O(mod1(sites(i) + [0 1], period(mps))), gr); end end diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index 6aec2f6..42eb9df 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -1,9 +1,9 @@ -classdef MpsTensor < AbstractTensor +classdef (InferiorClasses = {?Tensor, ?SparseTensor}) MpsTensor < AbstractTensor % Generic mps tensor objects that have a notion of virtual, physical and auxiliary legs. properties var - plegs = 1 + plegs alegs = 0 end @@ -13,22 +13,13 @@ function A = MpsTensor(tensor, alegs) arguments tensor = [] - alegs = zeros(size(tensor)) - end - - if isscalar(alegs) && ~isscalar(tensor) - alegs = repmat(alegs, size(tensor)); - else - assert(isequal(size(alegs, 1:ndims(tensor)), size(tensor)), 'mps:ArgError', ... - 'Input sizes incompatible.'); + alegs = 0 end if ~isempty(tensor) - for i = numel(tensor):-1:1 - A(i).var = tensor(i); - A(i).plegs = nspaces(tensor(i)) - alegs(i) - 2; - A(i).alegs = alegs(i); - end + A.var = tensor; + A.plegs = nspaces(tensor) - alegs - 2; + A.alegs = alegs; end end end @@ -49,11 +40,11 @@ end function s = leftvspace(A) - s = space(A, 1); + s = space(A.var(1), 1); end function s = rightvspace(A) - s = space(A, nspaces(A) - A.alegs); + s = space(A.var(1), nspaces(A.var(1)) - A.alegs); end function cod = codomain(A) @@ -98,13 +89,19 @@ end end - function A = repartition(A, r) - arguments - A - r = [nspaces(A)-1 1] + function d = dot(A, B) + if isa(A, 'MpsTensor') + A = A.var; end + if isa(B, 'MpsTensor') + B = B.var; + end + d = dot(A, B); + end + + function A = repartition(A, varargin) for i = 1:numel(A) - A(i).var = repartition(A(i).var, r); + A(i).var = repartition(A(i).var, varargin{:}); end end @@ -130,6 +127,10 @@ n = norm([A.var]); end + function A = twist(A, varargin) + [A.var] = twist([A.var], varargin{:}); + end + function [R, A] = rightorth(A, alg) arguments A @@ -186,6 +187,7 @@ C = tensorprod(varargin{:}); end + function [AL, CL, lambda, eta] = uniform_leftorth(A, CL, kwargs) arguments A @@ -281,7 +283,6 @@ end - function [AR, CR, lambda, eta] = uniform_rightorth(A, CR, kwargs) arguments A @@ -499,13 +500,18 @@ end - %% Converter + %% Converters methods function t = Tensor(A) for i = numel(A):-1:1 t(i) = full(A(i).var); end end + + function t = SparseTensor(A) + t = reshape([A.var], size(A)); + t = sparse(t); + end end end diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index 0d677ab..c975899 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -4,10 +4,10 @@ % AL(w) * C(w) = AC(w) = C(w-1) * AR(w) properties - AL (1,:) MpsTensor = MpsTensor.empty(1, 0) - AR (1,:) MpsTensor = MpsTensor.empty(1, 0) - C (1,:) Tensor = Tensor.empty(1, 0) - AC (1,:) MpsTensor = MpsTensor.empty(1, 0) + AL (1,:) MpsTensor + AR (1,:) MpsTensor + C (1,:) Tensor + AC (1,:) MpsTensor end @@ -31,11 +31,19 @@ % by default we take the input as AR, as canonicalize right % orthogonalizes before left orthogonalization for i = height(varargin{1}):-1:1 - mps(i).AR = varargin{1}(i, :); - mps(i).AL = varargin{1}(i, :); + mps.AR = varargin{1}; + mps.AL = varargin{1}; end mps = canonicalize(mps); + elseif iscell(varargin{1}) + for i = flip(1:width(varargin{1})) + for j = 1:height(varargin{1}) + mps(j).AR(i) = varargin{1}{j, i}; + mps(j).AL(i) = varargin{1}{j, i}; + end + end + mps = canonicalize(mps); else error('Invalid constructor for UniformMps.') end @@ -43,7 +51,7 @@ elseif nargin == 4 mps.AL = varargin{1}; mps.AR = varargin{2}; - mps.C = varargin{3}; + mps.C = varargin{3}; mps.AC = varargin{4}; else @@ -66,7 +74,7 @@ end for w = length(pspaces):-1:1 - A(w) = Tensor.new(fun, [vspaces(w) pspaces(w)], ... + A{w} = Tensor.new(fun, [vspaces(w) pspaces(w)], ... vspaces(next(w, length(pspaces)))); end @@ -165,6 +173,8 @@ end end + + function mps = diagonalizeC(mps) for i = 1:height(mps) for w = 1:period(mps(i)) diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index 43de70c..64a7f14 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -1,4 +1,4 @@ -classdef (InferiorClasses = {?Tensor, ?MpsTensor}) SparseTensor < AbstractTensor +classdef (InferiorClasses = {?Tensor}) SparseTensor < AbstractTensor % Class for multi-dimensional sparse objects. properties (Access = private) @@ -562,7 +562,7 @@ function disp(t) t1 = t1(idx); idx2 = find(t1); if ~isempty(idx2) - t.var = t.var(idx2) .* t1(idx2); + t.var = t.var(idx2) .* full(t1(idx2)); t.ind = t.ind(idx2, :); else t.var = t.var(idx2); @@ -610,9 +610,25 @@ function disp(t) end end - function t = twist(t, i) + function t = twist(t, i, inv) + arguments + t + i + inv = false + end + if nnz(t) > 0 + t.var = twist(t.var, i, inv); + end + end + + function t = twistdual(t, i, inv) + arguments + t + i + inv = false + end if nnz(t) > 0 - t.var = twist(t.var, i); + t.var = twist(t.var, i, inv); end end diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index c717691..9b3de72 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -601,11 +601,7 @@ mblocks2 = matrixblocks(t2(i).var); qdims = qdim(mcharges); for j = 1:length(mblocks1) - try d = d + qdims(j) * sum(conj(mblocks1{j}) .* mblocks2{j}, 'all'); - catch - bla - end end end end @@ -1026,8 +1022,8 @@ % spaces remain. arguments - A Tensor - B Tensor + A + B dimA dimB ca = false @@ -1356,6 +1352,36 @@ t.var = axpby(1, t.var, 0, t.var, 1:nspaces(t), med); end + function t = twistdual(t, i, inv) + % Twist the spaces of a tensor if they are dual. + % + % Arguments + % --------- + % t : :class:`Tensor` + % input tensor. + % + % i : (1, :) int or logical + % indices to twist. + % + % inv : logical + % flag to indicate inverse twisting. + % + % Returns + % ------- + % t : :class:`Tensor` + % twisted tensor with desired rank. + arguments + t + i + inv = false + end + + for i = 1:numel(t) + i_dual = i(isdual(space(t(i), i))); + t(i) = twist(t(i), i_dual, inv); + end + end + function t = uplus(t) end diff --git a/src/tensors/kernels/MatrixBlock.m b/src/tensors/kernels/MatrixBlock.m index c682059..28bbf29 100644 --- a/src/tensors/kernels/MatrixBlock.m +++ b/src/tensors/kernels/MatrixBlock.m @@ -22,7 +22,7 @@ if isempty(trees) warning('tensors:empty', ... 'No fusion channels available for the given spaces.'); - b = MatrixBlock.empty(0, 1); +% b = MatrixBlock.empty(0, 1); return end assert(~isempty(trees), 'tensors:empty', ... From 4bb16536597026630fd8d10870a7a815ebac5d16 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 6 Dec 2022 14:41:54 +0100 Subject: [PATCH 085/245] Vumps2 --- src/algorithms/IDmrg.m | 8 +- src/algorithms/Vumps2.m | 369 ++++++++++++++++++++++++++++++++++++++++ src/mps/MpsTensor.m | 17 +- src/mps/UniformMps.m | 14 +- test/TestAlgorithms.m | 115 ++++++------- test/TestUniformMps.m | 31 ++-- 6 files changed, 464 insertions(+), 90 deletions(-) create mode 100644 src/algorithms/Vumps2.m diff --git a/src/algorithms/IDmrg.m b/src/algorithms/IDmrg.m index 404a90b..d99a3a7 100644 --- a/src/algorithms/IDmrg.m +++ b/src/algorithms/IDmrg.m @@ -57,8 +57,8 @@ lambdas = zeros(1, period(mps)); for pos = 1:period(mps) H = AC_hamiltonian(mpo, mps, GL, GR, pos); - [mps.AC(pos), lambdas(pos)] = ... - eigsolve(H{1}, mps.AC(pos), 1, alg.which, kwargs{:}); + [mps.AC(pos).var, lambdas(pos)] = ... + eigsolve(H{1}, mps.AC(pos).var, 1, alg.which, kwargs{:}); [mps.AL(pos), mps.C(pos)] = leftorth(mps.AC(pos)); T = transfermatrix(mpo, mps, mps, pos, 'Type', 'LL'); @@ -67,8 +67,8 @@ for pos = period(mps):-1:1 H = AC_hamiltonian(mpo, mps, GL, GR, pos); - [mps.AC(pos), lambdas(pos)] = ... - eigsolve(H{1}, mps.AC(pos), 1, alg.which, kwargs{:}); + [mps.AC(pos).var, lambdas(pos)] = ... + eigsolve(H{1}, mps.AC(pos).var, 1, alg.which, kwargs{:}); [mps.C(prev(pos, period(mps))), mps.AR(pos)] = rightorth(mps.AC(pos)); T = transfermatrix(mpo, mps, mps, pos, 'Type', 'RR').'; diff --git a/src/algorithms/Vumps2.m b/src/algorithms/Vumps2.m new file mode 100644 index 0000000..461a3e7 --- /dev/null +++ b/src/algorithms/Vumps2.m @@ -0,0 +1,369 @@ +classdef Vumps2 < handle + % Variational fixed point algorithm for uniform matrix product states. + + %% Options + properties + tol = 1e-10 + miniter = 5 + maxiter = 100 + verbosity = Verbosity.iter + doplot = false + which = 'largestabs' + + dynamical_tols = true + tol_min = 1e-12 + tol_max = 1e-6 + eigs_tolfactor = 1e-4 + canonical_tolfactor = 1e-8 + environments_tolfactor = 1e-4 + + trunc = {'TruncDim', 100} + + multiAC = 'parallel' + dynamical_multiAC = false; + tol_multiAC = Inf + + doSave = false + saveIterations = 1 + saveMethod = 'full' + name = 'VUMPS' + + end + + properties (Access = private) + alg_eigs = struct('MaxIter', 100, 'KrylovDim', 20) + alg_canonical = struct('Method', 'polar') + alg_environments = struct + + progressfig + end + + + %% + methods + function alg = Vumps2(kwargs) + arguments + kwargs.?Vumps2 + end + + fields = fieldnames(kwargs); + if ~isempty(fields) + for field = fields.' + alg.(field{1}) = kwargs.(field{1}); + end + end + + if ~isfield('alg_eigs', kwargs) + alg.alg_eigs.Tol = sqrt(alg.tol_min * alg.tol_max); + alg.alg_eigs.Verbosity = alg.verbosity - 2; + end + + if ~isfield('alg_canonical', kwargs) + alg.alg_canonical.Tol = sqrt(alg.tol_min * alg.tol_max); + alg.alg_canonical.Verbosity = alg.verbosity - 2; + end + + if ~isfield('alg_environments', kwargs) + alg.alg_environments.Tol = sqrt(alg.tol_min * alg.tol_max); + alg.alg_environments.Verbosity = alg.verbosity - 2; + end + end + + function [mps, lambda, GL, GR] = fixedpoint(alg, mpo, mps) + if period(mpo) ~= period(mps) + error('vumps:argerror', ... + 'periodicity of mpo (%d) should be equal to that of the mps (%d)', ... + period(mpo), period(mps)); + end + + if period(mps) < 2 + error('vumps2:argerror', ... + 'vumps2 needs a 2 site unitcell'); + end + + t_total = tic; + disp_init(alg); + + mps = canonicalize(mps); + [GL, GR] = environments(alg, mpo, mps); + + for iter = 1:alg.maxiter + t_iter = tic; + + AC2 = updateAC2(alg, iter, mpo, mps, GL, GR); + C = updateC (alg, iter, mpo, mps, GL, GR); + mps = updatemps(alg, iter, mps, AC2, C); + + [GL, GR, lambda] = environments(alg, mpo, mps); + eta = convergence(alg, mpo, mps, GL, GR); + + if iter > alg.miniter && eta < alg.tol + disp_conv(alg, iter, lambda, eta, toc(t_total)); + return + end + alg = updatetols(alg, iter, eta); + plot(alg, iter, mps, eta); + disp_iter(alg, iter, lambda, eta, toc(t_iter)); + + if alg.doSave && mod(iter, alg.saveIterations) == 0 + save_iteration(alg, mps, lambda, iter); + end + end + + disp_maxiter(alg, iter, lambda, eta, toc(t_total)); + end + end + + + %% Subroutines + methods + function AC2 = updateAC2(alg, iter, mpo, mps, GL, GR) + kwargs = namedargs2cell(alg.alg_eigs); + if strcmp(alg.multiAC, 'sequential') + sites = mod1(iter, period(mps)); + else + sites = 1:period(mps); + sites = sites(mod(sites, 2) == mod(iter, 2)); + end + + H_AC2 = AC2_hamiltonian(mpo, mps, GL, GR, sites); + for i = length(sites):-1:1 + AC2 = MpsTensor(contract(mps.AC(sites(i)), [-1 -2 1], ... + mps.AR(next(sites(i), period(mps))), [1 -3 -4], ... + 'Rank', [2 2])); + [AC2.var, ~] = eigsolve(H_AC2{i}, AC2.var, 1, alg.which, ... + kwargs{:}); + end + end + + function C = updateC(alg, iter, mpo, mps, GL, GR) + kwargs = namedargs2cell(alg.alg_eigs); + if strcmp(alg.multiAC, 'sequential') + sites = mod1(iter, period(mps)); + else + sites = 1:period(mps); + sites = next(sites(mod(sites, 2) == mod(iter, 2)), period(mps)); + end + + H_C = C_hamiltonian(mpo, mps, GL, GR, sites); + for i = length(sites):-1:1 + [C(i), ~] = eigsolve(H_C{i}, mps.C(sites(i)), 1, alg.which, ... + kwargs{:}); + end + end + + function mps = updatemps(alg, iter, mps, AC2, C) + if strcmp(alg.multiAC, 'sequential') + sites = mod1(iter, period(mps)); + else + sites = 1:period(mps); + sites = sites(mod(sites, 2) == mod(iter, 2)); + end + + for i = length(AC2):-1:1 + [Q_AC, ~] = leftorth(AC2(i)); + [Q_C, ~] = leftorth(C(i), 1, 2); + AL = multiplyright(Q_AC, Q_C'); + [AL1, C, AL2] = tsvd(AL.var, [1 2], [3 4], alg.trunc{:}); + mps.AL(sites(i)) = multiplyright(MpsTensor(AL1), C); + mps.AL(next(sites(i), period(mps))) = AL2; + end + + kwargs = namedargs2cell(alg.alg_canonical); +% mps.C = []; + newAL = cell(size(mps.AL)); + for i = 1:numel(newAL) + newAL{i} = mps.AL(i); + end + mps = UniformMps(newAL); + end + + function [GL, GR, lambda] = environments(alg, mpo, mps, GL, GR) + arguments + alg + mpo + mps + GL = cell(1, period(mps)) + GR = cell(1, period(mps)) + end + + kwargs = namedargs2cell(alg.alg_environments); + [GL, GR, lambda] = environments(mpo, mps, mps, GL, GR, ... + kwargs{:}); + end + + function eta = convergence(alg, mpo, mps, GL, GR) + H_AC = AC_hamiltonian(mpo, mps, GL, GR); + H_C = C_hamiltonian(mpo, mps, GL, GR); + eta = zeros(1, period(mps)); + for w = 1:period(mps) + AC_ = apply(H_AC{w}, mps.AC(w)); + lambda_AC = dot(AC_, mps.AC(w)); + AC_ = normalize(AC_ ./ lambda_AC); + + ww = prev(w, period(mps)); + C_ = apply(H_C{ww}, mps.C(ww)); + lambda_C = dot(C_, mps.C(ww)); + C_ = normalize(C_ ./ lambda_C); + + eta(w) = distance(AC_ , ... + repartition(multiplyleft(mps.AR(w), C_), rank(AC_))); + end + eta = max(eta, [], 'all'); + end + end + + + %% Option handling + methods + function alg = updatetols(alg, iter, eta) + if alg.dynamical_tols + alg.alg_eigs.Tol = between(alg.tol_min, eta * alg.eigs_tolfactor, ... + alg.tol_max / iter); + alg.alg_canonical.Tol = between(alg.tol_min, ... + eta * alg.canonical_tolfactor, alg.tol_max / iter); + alg.alg_environments.Tol = between(alg.tol_min, ... + eta * alg.environments_tolfactor, alg.tol_max / iter); + + if alg.verbosity > Verbosity.iter + fprintf('Updated subalgorithm tolerances: (%e,\t%e,\t%e)\n', ... + alg.alg_eigs.Tol, alg.alg_canonical.Tol, alg.alg_environments.Tol); + end + end + + if alg.dynamical_multiAC + if eta < alg.tol_multiAC + alg.multiAC = 'sequential'; + else + alg.multiAC = 'parallel'; + end + end + end + end + + %% Display + methods (Access = private) + function plot(alg, iter, mps, eta) + if ~alg.doplot, return; end + persistent axhistory axspectrum + + D = depth(mps); + W = period(mps); + + if isempty(alg.progressfig) || ~isvalid(alg.progressfig) || iter == 1 + alg.progressfig = figure('Name', 'Vumps'); + axhistory = subplot(D + 1, W, 1:W); + axspectrum = gobjects(D, W); + for d = 1:D, for w = 1:W + axspectrum(d, w) = subplot(D + 1, W, w + d * W); + end, end + linkaxes(axspectrum, 'y'); + end + + + if isempty(axhistory.Children) + semilogy(axhistory, iter, eta, '.', 'Color', colors(1), ... + 'DisplayName', 'Errors', 'MarkerSize', 10); + hold on + ylim(axhistory, [alg.tol / 5, max([1 eta])]); + yline(axhistory, alg.tol, '-', 'Convergence', ... + 'LineWidth', 2); + hold off + else + axhistory.Children(end).XData(end+1) = iter; + axhistory.Children(end).YData(end+1) = eta; + end + + plot_entanglementspectrum(mps, 1:period(mps), axspectrum); + drawnow + end + + function disp_init(alg) + if alg.verbosity < Verbosity.conv, return; end + fprintf('---- VUMPS2 ---\n'); + end + + function disp_iter(alg, iter, lambda, eta, t) + if alg.verbosity < Verbosity.iter, return; end + + s = settings; + if abs(imag(lambda)) < eps(lambda)^(3/4) * abs(lambda) + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('Vumps2 %2d:\tE = %-0.4f\terror = %0.1e\t(%s)\n', ... + iter, real(lambda), eta, time2str(t)); + otherwise + fprintf('Vumps2 %4d:\tE = %-0.15f\terror = %0.4e\t(%s)\n', ... + iter, real(lambda), eta, time2str(t, 's')); + + end + else + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('Vumps2 %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + otherwise + fprintf('Vumps2 %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + + end + end + end + + function disp_conv(alg, iter, lambda, eta, t) + if alg.verbosity < Verbosity.conv, return; end + s = settings; + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('Vumps converged %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + otherwise + fprintf('Vumps converged %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + + end + fprintf('---------------\n'); + end + + function disp_maxiter(alg, iter, lambda, eta, t) + if alg.verbosity < Verbosity.warn, return; end + s = settings; + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('Vumps max iterations %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + otherwise + fprintf('Vumps max iterations %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + + end + fprintf('---------------\n'); + end + + function save_iteration(alg, mps, lambda, iter) + fileName = alg.name; + + fileData = struct; + fileData.mps = mps; + fileData.lambda = lambda; + fileData.iteration = iter; + % save + + %if exist(fileName,'file') + % old_file=load(fileName); + % fileName_temp=[fileName(1:end-4),'_temp.mat']; + % save(fileName_temp, '-struct', 'old_file', '-v7.3'); + % saved_temp=1; + %else + % saved_temp=0; + %end + + save(fileName, '-struct', 'fileData', '-v7.3'); + + %if saved_temp + % delete(fileName_temp); + %end + end + end +end + diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index 42eb9df..d15bd9b 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -16,6 +16,13 @@ alegs = 0 end + if iscell(tensor) + for i = length(tensor):-1:1 + A(i) = MpsTensor(tensor{i}); + end + return + end + if ~isempty(tensor) A.var = tensor; A.plegs = nspaces(tensor) - alegs - 2; @@ -209,7 +216,7 @@ % initialization N = size(A, 2); - if isempty(CL), CL = initializeC(A, circshift(A, -1)); end + if isempty(CL), CL = initializeC(A, A); end if kwargs.Normalize, CL(1) = normalize(CL(1)); end for i = 1:numel(A) A(i).var = repartition(A(i).var, [nspaces(A(i).var) - 1, 1]); @@ -305,10 +312,10 @@ Cd = circshift(flip(arrayfun(@ctranspose, CR)), -1); end - [AR, CR, lambda, eta] = uniform_leftorth(Ad, Cd, opts{:}); + [ARd, CRd, lambda, eta] = uniform_leftorth(Ad, Cd, opts{:}); - AR = flip(arrayfun(@ctranspose, AR)); - CR = flip(circshift(arrayfun(@ctranspose, CR), 1)); + AR = flip(arrayfun(@ctranspose, ARd)); + CR = circshift(flip(arrayfun(@ctranspose, CRd)), -1); lambda = conj(lambda); end @@ -477,7 +484,7 @@ function C = initializeC(AL, AR) for i = length(AL):-1:1 - C(i) = AL(i).var.eye(rightvspace(AL(i))', leftvspace(AR(i))); + C(i) = AL(i).var.eye(rightvspace(AL(i))', leftvspace(AR(next(i, length(AR))))); end end diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index c975899..fdaff5f 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -296,14 +296,12 @@ if isa(O, 'InfJMpo') [GL, GR] = environments(O, mps1, mps2); H = AC_hamiltonian(O, mps1, GL, GR); - E = zeros(size(H)); - for i = 1:length(H) - N = size(H{i}.R, 2); - H{i}.R = H{i}.R(1, N, 1); - H{i}.O{1} = H{i}.O{1}(:, :, N, :); - AC_ = apply(H{i}, mps1.AC(i)); - E(i) = dot(AC_, mps2.AC(i)); - end + i = length(H); + N = size(H{i}.R.var, 2); + H{i}.R.var = H{i}.R.var(1, N, 1); + H{i}.O{1} = H{i}.O{1}(:, :, N, :); + AC_ = apply(H{i}, mps1.AC(i)); + E = dot(AC_, mps2.AC(i)); elseif isa(O, 'InfMpo') [GL, GR] = environments(O, mps1, mps2); diff --git a/test/TestAlgorithms.m b/test/TestAlgorithms.m index b81509c..58d55df 100644 --- a/test/TestAlgorithms.m +++ b/test/TestAlgorithms.m @@ -80,80 +80,77 @@ function test2dIsing(tc) end function test1dIsing(tc) - mpo = InfJMpo.Ising(); - mps = initialize_mps(mpo, CartesianSpace.new(12)); + E0 = -1.273; + + %% no symmetry + mpo1 = InfJMpo.Ising(); + mps1 = initialize_mps(mpo1, CartesianSpace.new(12)); alg = Vumps('which', 'smallestreal', 'maxiter', 5); - [gs, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyEqual(lambda, -1.273, 'RelTol', 1e-2); - tc.verifyEqual(lambda, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); + [gs1, lambda] = fixedpoint(alg, mpo1, mps1); + tc.verifyEqual(lambda, E0, 'RelTol', 1e-2); + tc.verifyEqual(expectation_value(gs1, mpo1, gs1), E0, 'RelTol', 1e-2); - alg = IDmrg('which', 'smallestreal', 'maxiter',10); - [gs, lambda] = fixedpoint(alg, mpo, mps); -% tc.verifyEqual(lambda, -1.273, 'RelTol', 1e-2); - tc.verifyEqual(-1.273, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); + alg = IDmrg('which', 'smallestreal', 'maxiter', 5); + [gs1, lambda] = fixedpoint(alg, mpo1, mps1); + tc.verifyEqual(expectation_value(gs1, mpo1, gs1), E0, 'RelTol', 1e-2); -% mps2 = approximate(Vomps(), ... -% mpo, gs, initialize_mps(mpo, CartesianSpace.new(24))); -% tc.verifyEqual(fidelity(gs, mps2), 1, 'RelTol', 1e-2); - - mps = [mps mps]; - mpo = [mpo mpo]; - alg = IDmrg2('which', 'smallestreal', 'maxiter', 10); - [gs, lambda] = fixedpoint(alg, mpo, mps); -% tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); - tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... - 'RelTol', 1e-2); - - alg = IDmrg('which', 'smallestreal', 'maxiter', 10); - [gs, lambda] = fixedpoint(alg, mpo, mps); -% tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); - tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... - 'RelTol', 1e-2); + mpo2 = [mpo1 mpo1]; + mps2 = [mps1 mps1]; alg = Vumps('which', 'smallestreal', 'maxiter', 5); - [gs, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); - tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... + [gs2, lambda] = fixedpoint(alg, mpo2, mps2); + tc.verifyEqual(lambda, 2 * E0, 'RelTol', 1e-2); + tc.verifyEqual(expectation_value(gs2, mpo2, gs2), 2 * E0, 'RelTol', 1e-2); + + alg = Vumps2('which', 'smallestreal', 'maxiter', 5); + [gs2, lambda] = fixedpoint(alg, mpo2, mps2); + tc.verifyEqual(lambda, E0 * 2, 'RelTol', 1e-2); + tc.verifyEqual(expectation_value(gs2, mpo2, gs2), 2 * E0, 'RelTol', 1e-2); + + alg = IDmrg('which', 'smallestreal', 'maxiter', 5); + [gs2, lambda] = fixedpoint(alg, mpo2, mps2); + tc.verifyEqual(expectation_value(gs2, mpo2, gs2), 2 * E0, ... 'RelTol', 1e-2); - - mpo = InfJMpo.Ising('Symmetry', 'Z2'); - mps = initialize_mps(mpo, GradedSpace.new(Z2(0, 1), [6 6], false)); - alg = Vumps('which', 'smallestreal', 'maxiter', 5); - [gs, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyEqual(lambda, -1.273, 'RelTol', 1e-2); - tc.verifyEqual(lambda, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); + alg = IDmrg2('which', 'smallestreal', 'maxiter', 5); + [gs2, lambda] = fixedpoint(alg, mpo2, mps2); + tc.verifyEqual(expectation_value(gs2, mpo2, gs2), 2 * E0, 'RelTol', 1e-2); - alg = IDmrg('which', 'smallestreal', 'maxiter',10); - [gs, lambda] = fixedpoint(alg, mpo, mps); -% tc.verifyEqual(lambda, -1.273, 'RelTol', 1e-2); - tc.verifyEqual(-1.273, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); + %% Z2 symmetry + mpo1 = InfJMpo.Ising('Symmetry', 'Z2'); + mps1 = initialize_mps(mpo1, GradedSpace.new(Z2(0, 1), [6 6], false)); -% mps2 = approximate(Vomps(), ... -% mpo, gs, initialize_mps(mpo, CartesianSpace.new(24))); -% tc.verifyEqual(fidelity(gs, mps2), 1, 'RelTol', 1e-2); + alg = Vumps('which', 'smallestreal', 'maxiter', 5); + [gs1, lambda] = fixedpoint(alg, mpo1, mps1); + tc.verifyEqual(lambda, E0, 'RelTol', 1e-2); + tc.verifyEqual(expectation_value(gs1, mpo1, gs1), E0, 'RelTol', 1e-2); - mps = [mps mps]; - mpo = [mpo mpo]; - alg = IDmrg2('which', 'smallestreal', 'maxiter', 10, ... - 'trunc', {'TruncBelow', 1e-3}); - [gs, lambda] = fixedpoint(alg, mpo, mps); -% tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); - tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... - 'RelTol', 1e-2); + alg = IDmrg('which', 'smallestreal', 'maxiter', 5); + [gs1, lambda] = fixedpoint(alg, mpo1, mps1); + tc.verifyEqual(expectation_value(gs1, mpo1, gs1), E0, 'RelTol', 1e-2); - alg = IDmrg('which', 'smallestreal', 'maxiter', 10); - [gs, lambda] = fixedpoint(alg, mpo, mps); -% tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); - tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... - 'RelTol', 1e-2); + mpo2 = [mpo1 mpo1]; + mps2 = [mps1 mps1]; alg = Vumps('which', 'smallestreal', 'maxiter', 5); - [gs, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); - tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... + [gs2, lambda] = fixedpoint(alg, mpo2, mps2); + tc.verifyEqual(lambda, 2 * E0, 'RelTol', 1e-2); + tc.verifyEqual(expectation_value(gs2, mpo2, gs2), 2 * E0, 'RelTol', 1e-2); + + alg = Vumps2('which', 'smallestreal', 'maxiter', 5); + [gs2, lambda] = fixedpoint(alg, mpo2, mps2); + tc.verifyEqual(lambda, E0 * 2, 'RelTol', 1e-2); + tc.verifyEqual(expectation_value(gs2, mpo2, gs2), 2 * E0, 'RelTol', 1e-2); + + alg = IDmrg('which', 'smallestreal', 'maxiter', 5); + [gs2, lambda] = fixedpoint(alg, mpo2, mps2); + tc.verifyEqual(expectation_value(gs2, mpo2, gs2), 2 * E0, ... 'RelTol', 1e-2); + + alg = IDmrg2('which', 'smallestreal', 'maxiter', 5); + [gs2, lambda] = fixedpoint(alg, mpo2, mps2); + tc.verifyEqual(expectation_value(gs2, mpo2, gs2), 2 * E0, 'RelTol', 1e-2); end end end diff --git a/test/TestUniformMps.m b/test/TestUniformMps.m index a4be588..c913a0f 100644 --- a/test/TestUniformMps.m +++ b/test/TestUniformMps.m @@ -3,25 +3,28 @@ properties (TestParameter) A = struct(... - 'trivial', Tensor.randnc(CartesianSpace.new([4 2]), CartesianSpace.new(4)), ... - 'trivial2', [Tensor.randnc(CartesianSpace.new([4 2]), CartesianSpace.new(4)), ... - Tensor.randnc(CartesianSpace.new([4 2]), CartesianSpace.new(4))], ... - 'fermion1', Tensor.randnc(... + 'trivial', {{Tensor.randnc(CartesianSpace.new([4 2]), CartesianSpace.new(4))}}, ... + 'trivial2', {{Tensor.randnc(CartesianSpace.new([4 2]), CartesianSpace.new(5)), ... + Tensor.randnc(CartesianSpace.new([5 2]), CartesianSpace.new(4))}}, ... + 'trivial3', {{Tensor.randnc(CartesianSpace.new([4 2]), CartesianSpace.new(5)), ... + Tensor.randnc(CartesianSpace.new([5 2]), CartesianSpace.new(6)), ... + Tensor.randnc(CartesianSpace.new([6 2]), CartesianSpace.new(4))}}, ... + 'fermion1', {{Tensor.randnc(... GradedSpace.new(fZ2(0,1), [2 2], false, fZ2(0,1), [1 1], false), ... - GradedSpace.new(fZ2(0,1), [2 2], false)), ... - 'fermion2', Tensor.randnc(... + GradedSpace.new(fZ2(0,1), [2 2], false))}}, ... + 'fermion2', {{Tensor.randnc(... GradedSpace.new(fZ2(0,1), [2 2], true, fZ2(0,1), [1 1], false), ... - GradedSpace.new(fZ2(0,1), [2 2], true)), ... - 'fermion3', Tensor.randnc(... + GradedSpace.new(fZ2(0,1), [2 2], true))}}, ... + 'fermion3', {{Tensor.randnc(... GradedSpace.new(fZ2(0,1), [2 2], false, fZ2(0,1), [1 1], true), ... - GradedSpace.new(fZ2(0,1), [2 2], false)), ... - 'fermion4', Tensor.randnc(... + GradedSpace.new(fZ2(0,1), [2 2], false))}}, ... + 'fermion4', {{Tensor.randnc(... GradedSpace.new(fZ2(0,1), [2 2], true, fZ2(0,1), [1 1], true), ... - GradedSpace.new(fZ2(0,1), [2 2], true)), ... - 'haldane', [Tensor.randnc(GradedSpace.new(SU2(1:2:5), [5 3 2], false, SU2(2), 1, false), ... + GradedSpace.new(fZ2(0,1), [2 2], true))}}, ... + 'haldane', {{Tensor.randnc(GradedSpace.new(SU2(1:2:5), [5 3 2], false, SU2(2), 1, false), ... GradedSpace.new(SU2(2:2:6), [5 2 1], false)), ... Tensor.randnc(GradedSpace.new(SU2(2:2:6), [5 2 1], false, SU2(2), 1, false), ... - GradedSpace.new(SU2(1:2:5), [5 3 2], false))] ... + GradedSpace.new(SU2(1:2:5), [5 3 2], false))}} ... ) end @@ -30,7 +33,7 @@ function testCanonical(tc, A) mps = UniformMps(A); for i = 1:length(A) tc.assertTrue(... - isequal(space(A(i)), space(mps.AL(i)), ... + isequal(space(A{i}), space(mps.AL(i)), ... space(mps.AR(i)), space(mps.AC(i)))); end T = transfermatrix(MpsTensor(A), mps.AL); From ff66bc92b6ee8d8dd5efc47b18834097837f073f Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 7 Dec 2022 12:49:48 +0100 Subject: [PATCH 086/245] Total dimension svd truncation --- src/tensors/Tensor.m | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 9b3de72..a6adf6d 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -1767,7 +1767,11 @@ % Keyword Arguments % ----------------- % TruncDim : int - % truncate such that the size of S is not larger than this value. + % truncate such that the dim of S is not larger than this value for any given + % charge. + % + % TruncTotalDim : int + % truncate such that the total dim of S is not larger than this value. % % TruncBelow : numeric % truncate such that there are no singular values below this value. @@ -1789,6 +1793,7 @@ p1 = 1:t.rank(1) p2 = t.rank(1) + (1:t.rank(2)) trunc.TruncDim + trunc.TruncTotalDim trunc.TruncBelow trunc.TruncSpace end @@ -1824,6 +1829,23 @@ Vs{i} = Vs{i}(1:dims.degeneracies(i), :); end end + if isfield(trunc, 'TruncTotalDim') + qdims = qdim(dims.charges); + totaldim = sum(dims.degeneracies .* qdims); + minvals = cellfun(@(x) min(diag(x)), Ss); + while totaldim > trunc.TruncTotalDim + [~, i] = min(minvals); + eta = eta + Ss{i}(end, end); + dims.degeneracies(i) = dims.degeneracies(i) - 1; + Ss{i} = Ss{i}(1:end-1, 1:end-1); + if dims.degeneracies(i) > 0 + minvals(i) = Ss{i}(end,end); + else + minvals(i) = Inf; + end + totaldim = totaldim - qdims(i); + end + end if isfield(trunc, 'TruncDim') for i = 1:length(mblocks) dims.degeneracies(i) = min(dims.degeneracies(i), trunc.TruncDim); From 19461a1bc1cbcb4ed3f9fb8c9f5ca0bc6ee9adeb Mon Sep 17 00:00:00 2001 From: sanderdemeyer Date: Wed, 7 Dec 2022 15:50:22 +0100 Subject: [PATCH 087/245] Added conj in MpsTensor --- src/mps/MpsTensor.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index d15bd9b..aabf0a6 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -134,6 +134,10 @@ n = norm([A.var]); end + function A = conj(A) + [A.var] = conj([A.var]); + end + function A = twist(A, varargin) [A.var] = twist([A.var], varargin{:}); end From 5d8cad644e5e19adabeb0ff7d5749b9403e16d60 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 12 Dec 2022 15:24:30 +0100 Subject: [PATCH 088/245] Assert full rank mps --- src/algorithms/IDmrg2.m | 4 +- src/caches/LRU.m | 2 +- src/mps/MpoTensor_new.m | 317 --------------------------- src/mps/MpsTensor.m | 4 +- src/mps/UniformMps.m | 10 +- src/tensors/Tensor.m | 3 +- src/tensors/charges/AbstractCharge.m | 6 - src/tensors/charges/O2.m | 4 + src/tensors/charges/Z1.m | 4 + src/tensors/spaces/AbstractSpace.m | 8 + 10 files changed, 30 insertions(+), 332 deletions(-) delete mode 100644 src/mps/MpoTensor_new.m diff --git a/src/algorithms/IDmrg2.m b/src/algorithms/IDmrg2.m index c46d1ca..73c5d88 100644 --- a/src/algorithms/IDmrg2.m +++ b/src/algorithms/IDmrg2.m @@ -1,5 +1,5 @@ classdef IDmrg2 - % Infinite Density Matrix Renormalization Group algorithm + % Infinite Density Matrix Renormalization Group algorithm with 2-site updates. properties tol = 1e-10 @@ -21,12 +21,10 @@ saveIterations = false saveMethod = 'full' name = 'IDmrg2' - end properties (Access = private) alg_eigs = struct('MaxIter', 100, 'KrylovDim', 20) - progressfig end diff --git a/src/caches/LRU.m b/src/caches/LRU.m index fd989f7..bf985c8 100644 --- a/src/caches/LRU.m +++ b/src/caches/LRU.m @@ -92,7 +92,7 @@ dll_old = cache.map(key); pop(dll_old); cache.mem = cache.mem - memsize(dll_old.val{2}, 'B'); - cache.map.(key); + cache.map.remove(key); end % add new data diff --git a/src/mps/MpoTensor_new.m b/src/mps/MpoTensor_new.m deleted file mode 100644 index 502412f..0000000 --- a/src/mps/MpoTensor_new.m +++ /dev/null @@ -1,317 +0,0 @@ -classdef (InferiorClasses = {?Tensor, ?MpsTensor, ?SparseTensor}) MpoTensor < AbstractTensor - % Matrix product operator building block - % This object represents the MPO tensor at a single site as the sum of rank (2,2) - % (sparse) tensors and some scalars, which will be implicitly used as unit tensors. - % - % 4 - % ^ - % | - % 1 ->-- O -->- 3 - % | - % ^ - % 2 - - properties - tensors = [] - scalars = [] - end - - methods - function n = nspaces(~) - n = 4; - end - end - - methods - function t = MpoTensor(varargin) - if nargin == 0, return; end - - if nargin == 1 - if isnumeric(varargin{1}) - t.scalars = varargin{1}; - t.tensors = SparseTensor([], [], size(t.scalars)); - elseif isa(varargin{1}, 'MpoTensor') - t.scalars = varargin{1}.scalars; - t.tensors = varargin{1}.tensors; - elseif isa(varargin{1}, 'Tensor') || isa(varargin{1}, 'SparseTensor') - t.tensors = varargin{1}; - t.scalars = zeros(size(t.tensors)); - end - return - end - - if nargin == 2 - t.tensors = varargin{1}; - t.scalars = varargin{2}; - N = max(ndims(t.tensors), ndims(t.scalars)); - assert(isequal(size(t.tensors, 1:N), size(t.scalars, 1:N)), 'mpotensors:dimerror', ... - 'scalars and tensors should have the same size.'); - if any(ismember(find(t.tensors), find(t.scalars))) - warning('mpotensor contains both scalar and tensor at the same site.'); - end - end - end - - function t = minus(t, t2) - t.tensors = t.tensors - t2.tensors; - t.scalars = t.scalars - t2.scalars; - end - - function t = plus(t, t2) - t.tensors = t.tensors + t2.tensors; - t.scalars = t.scalars + t2.scalars; - end - - function t = times(t, a) - if isnumeric(t) - t = a .* t; - return - end - - assert(isnumeric(a), 'mpotensor:argerror', 'invalid input types.'); - - t.tensors = t.tensors .* a; - t.scalars = t.scalars .* a; - end - - function t = rdivide(t, a) - assert(isnumeric(a), 'mpotensor:argerror', 'invalid input types.'); - t.tensors = t.tensors ./ a; - t.scalars = t.scalars ./ a; - end - - function t = mrdivide(t, a) - assert(isnumeric(a), 'mpotensor:argerror', 'invalid input types.'); - t.tensors = t.tensors / a; - t.scalars = t.scalars / a; - end - - function v = applychannel(O, L, R, v) - arguments - O MpoTensor - L MpsTensor - R MpsTensor - v - end - auxlegs_v = nspaces(v) - 3; - auxlegs_l = nspaces(L) - 3; - auxlegs_r = nspaces(R) - 3; - auxlegs = auxlegs_v + auxlegs_l + auxlegs_r; - - v = contract(v, [1 3 5 (-(1:auxlegs_v) - 3 - auxlegs_l)], ... - L, [-1 2 1 (-(1:auxlegs_l) - 3)], ... - O, [2 -2 4 3], ... - R, [5 4 -3 (-(1:auxlegs_r) - 3 - auxlegs_l - auxlegs_v)], ... - 'Rank', rank(v) + [0 auxlegs]); - end - - function v = applympo(varargin) - assert(nargin >= 3) - v = varargin{end}; - R = varargin{end-1}; - L = varargin{end-2}; - N = nargin - 1; - - auxlegs_v = nspaces(v) - N; - auxlegs_l = L.alegs; - auxlegs_r = R.alegs; - auxlegs = auxlegs_v + auxlegs_l + auxlegs_r; - - Oinds = arrayfun(@(x) [2*x-2 -x 2*x 2*x-1], 2:N-1, 'UniformOutput', false); - O = [varargin(1:end-3); Oinds]; - v = contract(v, [1:2:2*N-1 (-(1:auxlegs_v) - N - auxlegs_l)], ... - L, [-1 2 1 (-(1:auxlegs_l) - N)], ... - O{:}, ... - R, [2*N-1 2*N-2 -N (-(1:auxlegs_r) - N - auxlegs_l - auxlegs_v)], ... - 'Rank', rank(v) + [0 auxlegs]); - end - - function O = rot90(O) - O.tensors = tpermute(O.tensors, [2 3 4 1], [2 2]); - O.scalars = permute(O.scalars, [2 3 4 1]); - end - - function C = tensorprod(A, B, dimA, dimB, ca, cb, options) - arguments - A - B - dimA - dimB - ca = false - cb = false - options.NumDimensionsA = ndims(A) - end - - assert(~isa(A, 'MpoTensor') || ~isa(B, 'MpoTensor'), 'mpotensor:tensorprod', ... - 'cannot contract two mpotensors.'); - - if isa(A, 'MpoTensor') - C = tensorprod(A.tensors, B, dimA, dimB, ca, cb); - - if nnz(A.scalars) > 0 - assert(sum(dimA == 1 | dimA == 3, 'all') == 1, ... - 'Cannot deduce output space unless leg 1 xor leg 3 is connected.'); - assert(sum(dimA == 2 | dimA == 4, 'all') == 1, ... - 'Cannot deduce output space unless leg 2 xor leg 4 is connected.'); - - uncA = 1:nspaces(A); uncA(dimA) = []; - uncB = 1:nspaces(B); uncB(dimB) = []; - - A = reshape(permute(A.scalars, [uncA flip(dimA)]), ... - [prod(size(A, uncA)) prod(size(A, dimA))]); - B = reshape(tpermute(B, [dimB uncB], [length(dimB) length(uncB)]), ... - [prod(size(B, dimB)) prod(size(B, uncB))]); - C = C + reshape(sparse(A) * B, size(C)); - end - else - C = tensorprod(A, B.tensors, dimA, dimB, ca, cb); - szC = size(C); - if nnz(B.scalars) > 0 - assert(sum(dimB == 1 | dimB == 3, 'all') == 1, ... - 'Cannot deduce output space unless leg 1 xor leg 3 is connected.'); - assert(sum(dimB == 2 | dimB == 4, 'all') == 1, ... - 'Cannot deduce output space unless leg 2 xor leg 4 is connected.'); - - uncA = 1:nspaces(A); uncA(dimA) = []; - uncB = 1:nspaces(B); uncB(dimB) = []; - - A = reshape(tpermute(A, [uncA dimA], [length(uncA) length(dimA)]), ... - [prod(size(A, uncA)) prod(size(A, dimA))]); - B = reshape(permute(B.scalars, [flip(dimB) uncB]), ... - [prod(size(B, dimB)) prod(size(B, uncB))]); - C = C + reshape(A * sparse(B), szC); - end - end - end - - function O = ctranspose(O) - O.tensors = tpermute(O.tensors', [4 1 2 3], [2 2]); - O.scalars = conj(permute(O.scalars, [1 4 3 2])); - end - - function O = transpose(O) - O.tensors = tpermute(O.tensors, [3 4 1 2], [2 2]); - O.scalars = permute(O.scalars, [3 4 1 2]); - end - end - - methods - function s = space(O, i) - s = space(O.tensors, i); - end - - function s = pspace(O) - s = space(O.tensors, 2); - end - - function s = leftvspace(O, lvls) - if nargin == 1 - s = space(O.tensors, 1); - else - s = space(O.tensors(lvls, :, :, :), 1); - end - end - - function s = rightvspace(O, lvls) - if nargin == 1 - s = space(O.tensors, 3); - else - s = space(O.tensors(:, :, lvls, :), 3); - end - end - - function bool = istriu(O) - sz = size(O, 1:4); - sz1 = sz(1) * sz(2); - sz2 = sz(3) * sz(4); - bool = istriu(reshape(O.scalars, [sz1, sz2])) && ... - istriu(reshape(O.tensors, [sz1, sz2])); - end - - function bool = iseye(O) - bool = nnz(O.tensors) == 0 && isequal(O.scalars, eye(size(O.scalars))); - end - - function n = nnz(O) - n = nnz(O.tensors) + nnz(O.scalars); - end - end - - methods - function t = subsref(t, s) - assert(length(s) == 1, 'mpotensor:index', ... - 'only a single level of indexing allowed.'); - switch s.type - case '.' - t = builtin('subsref', t, s); - case '()' - t.scalars = subsref(t.scalars, s); - t.tensors = subsref(t.tensors, s); - otherwise - error('mpotensor:index', 'invalid indexing expression'); - end - end - - function t = subsasgn(t, s, v) - assert(length(s) == 1, 'mpotensor:index', ... - 'only a single level of indexing allowed.'); - assert(strcmp(s.type, '()'), 'mpotensor:index', 'only () indexing allowed.'); - if isempty(t), t = MpoTensor(); end - if isnumeric(v) - t.scalars = subsasgn(t.scalars, s, v); - elseif isa(v, 'MpoTensor') - t.scalars = subsasgn(t.scalars, s, v.scalars); - t.tensors = subsasgn(t.tensors, s, v.tensors); - elseif isa(v, 'Tensor') - t.tensors = subsasgn(t.tensors, s, v); - end - end - - function i = end(t, k, n) - if n == 1 - i = prod(size(t)); - return - end - if n > ndims(t) - i = 1; - else - i = size(t, k); - end - end - - function bools = eq(a, b) - arguments - a MpoTensor - b MpoTensor - end - - bools = a.scalars == b.scalars & a.tensors == b.tensors; - end - - function I = find(O) - I = union(find(O.tensors), find(O.scalars)); - end - - function varargout = size(t, varargin) - if nargin > 1 - [varargout{1:nargout}] = size(t.scalars, varargin{:}); - else - [varargout{1:nargout}] = size(t.scalars); - end - end - end - - methods (Static) - function O = zeros(m, n, o, p) - if nargin == 1 - sz = m; - elseif nargin == 4 - sz = [m n o p]; - else - error('mpotensor:argerror', 'invalid amount of inputs.'); - end - O = MpoTensor(SparseTensor([], [], sz), zeros(sz)); - end - end -end - diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index d15bd9b..b1c16cd 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -18,7 +18,7 @@ if iscell(tensor) for i = length(tensor):-1:1 - A(i) = MpsTensor(tensor{i}); + A(i) = MpsTensor(tensor{i}, alegs); end return end @@ -73,7 +73,7 @@ function [A, L] = leftorth(A, alg) arguments A - alg = 'qrpos' + alg = 'polar' end if numel(A) > 1 diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index fdaff5f..9e8079e 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -1,5 +1,5 @@ classdef UniformMps - %UNIFORMMPS Implementation of infinite translation invariant MPS + % UniformMps - Implementation of infinite translation invariant MPS % MPS is stored in center gauge, where % AL(w) * C(w) = AC(w) = C(w-1) * AR(w) @@ -74,6 +74,14 @@ end for w = length(pspaces):-1:1 + if vspaces(w) * pspaces(w) < vspaces(next(w, length(pspaces))) + error('mps:rank', ... + 'Cannot create a full rank mps with given spaces.'); + end + if vspaces(w) > pspaces(w) * vspaces(next(w, length(pspaces))) + error('mps:rank', ... + 'Cannot create a full rank mps with given spaces.'); + end A{w} = Tensor.new(fun, [vspaces(w) pspaces(w)], ... vspaces(next(w, length(pspaces)))); end diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index a6adf6d..9fff92a 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -1,6 +1,5 @@ classdef Tensor < AbstractTensor - %TENSOR Summary of this class goes here - % Detailed explanation goes here + % Tensor - Base implementation of a dense tensor array with optional symmetries. properties codomain diff --git a/src/tensors/charges/AbstractCharge.m b/src/tensors/charges/AbstractCharge.m index a0612ca..e9eab5b 100644 --- a/src/tensors/charges/AbstractCharge.m +++ b/src/tensors/charges/AbstractCharge.m @@ -746,10 +746,4 @@ end end end - - methods -% bool = issortedrows(A) -% bools = ne(A, B) -% [B, I] = sort(A, varargin) - end end diff --git a/src/tensors/charges/O2.m b/src/tensors/charges/O2.m index 1525737..cd15446 100644 --- a/src/tensors/charges/O2.m +++ b/src/tensors/charges/O2.m @@ -221,6 +221,10 @@ end end + function s = GetMD5_helper(data) + s = [data.j] + [data.s]; + end + function bool = issortedrows(A) bool = issortedrows(reshape([A.j] + [A.s], size(A))); end diff --git a/src/tensors/charges/Z1.m b/src/tensors/charges/Z1.m index bf4e95b..0629776 100644 --- a/src/tensors/charges/Z1.m +++ b/src/tensors/charges/Z1.m @@ -33,6 +33,10 @@ C = 1; end + function s = GetMD5_helper(data) + s = uint8(0); + end + function a = intersect(a, ~) end diff --git a/src/tensors/spaces/AbstractSpace.m b/src/tensors/spaces/AbstractSpace.m index 3b6a163..d1e5b3b 100644 --- a/src/tensors/spaces/AbstractSpace.m +++ b/src/tensors/spaces/AbstractSpace.m @@ -275,6 +275,14 @@ error('tensors:AbstractMethod', 'This method should be overloaded.'); end + function bool = lt(space1, space2) + bool = ~ge(space1, space2); + end + + function bool = gt(space1, space2) + bool = ~le(space1, space2); + end + function bool = isequal(spaces) % Check whether all input spaces are equal. Spaces are considered equal if they % are of same size, and are equal element-wise. For convenience, empty spaces From c3f61aa0de799c1636a63bc8c00db04d81c17b4c Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 12 Dec 2022 15:27:33 +0100 Subject: [PATCH 089/245] bugfix loop variable --- src/tensors/Tensor.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 9fff92a..6906d5c 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -1375,9 +1375,9 @@ inv = false end - for i = 1:numel(t) - i_dual = i(isdual(space(t(i), i))); - t(i) = twist(t(i), i_dual, inv); + for j = 1:numel(t) + i_dual = i(isdual(space(t(j), i))); + t(j) = twist(t(j), i_dual, inv); end end From b3e29c16991617621d588cf44cc091b7476e4d80 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 12 Dec 2022 16:20:40 +0100 Subject: [PATCH 090/245] fix tsvd trunc + add tests --- src/tensors/Tensor.m | 13 ++++++++----- test/TestTensor.m | 28 +++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 6906d5c..250e4e7 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -1807,7 +1807,7 @@ dims.degeneracies = zeros(size(mblocks)); doTrunc = ~isempty(fieldnames(trunc)); - if doTrunc, eta = 0; end + eta = 0; for i = 1:length(mblocks) if doTrunc [Us{i}, Ss{i}, Vs{i}] = svd(mblocks{i}, 'econ'); @@ -1822,7 +1822,7 @@ for i = 1:length(mblocks) s = diag(Ss{i}); dims.degeneracies(i) = sum(s > trunc.TruncBelow); - eta = eta + sum(s(dims.degeneracies(i) + 1:end)); + eta = eta + sum(s(dims.degeneracies(i) + 1:end).^2 * qdim(dims.charges(i))); Us{i} = Us{i}(:, 1:dims.degeneracies(i)); Ss{i} = diag(s(1:dims.degeneracies(i))); Vs{i} = Vs{i}(1:dims.degeneracies(i), :); @@ -1834,8 +1834,10 @@ minvals = cellfun(@(x) min(diag(x)), Ss); while totaldim > trunc.TruncTotalDim [~, i] = min(minvals); - eta = eta + Ss{i}(end, end); + eta = eta + Ss{i}(end, end)^2 * qdims(i); dims.degeneracies(i) = dims.degeneracies(i) - 1; + Us{i} = Us{i}(:, 1:end-1); + Vs{i} = Vs{i}(1:end-1, :); Ss{i} = Ss{i}(1:end-1, 1:end-1); if dims.degeneracies(i) > 0 minvals(i) = Ss{i}(end,end); @@ -1849,10 +1851,10 @@ for i = 1:length(mblocks) dims.degeneracies(i) = min(dims.degeneracies(i), trunc.TruncDim); s = diag(Ss{i}); - eta = eta + sum(s(dims.degeneracies(i) + 1:end)); + eta = eta + sum(s(dims.degeneracies(i) + 1:end).^2 * qdim(dims.charges(i))); Us{i} = Us{i}(:, 1:dims.degeneracies(i)); Ss{i} = diag(s(1:dims.degeneracies(i))); - Vs{i} = Vs{i}(1:1:dims.degeneracies(i), :); + Vs{i} = Vs{i}(1:dims.degeneracies(i), :); end end if isfield(trunc, 'TruncSpace') @@ -1888,6 +1890,7 @@ if nargout <= 1 U = S; end + eta = sqrt(eta); end end diff --git a/test/TestTensor.m b/test/TestTensor.m index 7f9dac3..8c938c0 100644 --- a/test/TestTensor.m +++ b/test/TestTensor.m @@ -305,7 +305,7 @@ function nullspace(tc, spaces) end function singularvalues(tc, spaces) - t = Tensor.randnc(spaces, []); + t = normalize(Tensor.randc(spaces, [])); [U, S, V] = tsvd(t, [3 4 2], [1 5]); assertTrue(tc, isapprox(tpermute(t, [3 4 2 1 5], [3 2]), U * S * V), ... 'USV should be a factorization.'); @@ -315,6 +315,32 @@ function singularvalues(tc, spaces) 'V should be an isometry.'); %% truncation + d = max(cellfun(@(x) min(size(x, 1), size(x, 2)), matrixblocks(S))); + [Utrunc, Strunc, Vtrunc, eta] = tsvd(t, [3 4 2], [1 5], 'TruncDim', d-1); + assertTrue(tc, isapprox(norm(tpermute(t, [3 4 2 1 5], [3 2]) - ... + Utrunc * Strunc * Vtrunc), eta, 'AbsTol', 1e-10, 'RelTol', 1e-6)); + assertTrue(tc, isisometry(U, 'left')); + assertTrue(tc, isisometry(V, 'right')); + d2 = max(cellfun(@(x) max(size(x, 1), size(x, 2)), matrixblocks(Strunc))); + assertTrue(tc, d2 <= ceil(0.95*d)); + + d = min(dims(S, 1:2)); + [Utrunc, Strunc, Vtrunc, eta] = tsvd(t, [3 4 2], [1 5], 'TruncTotalDim', ceil(0.9*d)); + assertTrue(tc, isapprox(norm(tpermute(t, [3 4 2 1 5], [3 2]) - ... + Utrunc * Strunc * Vtrunc), eta, 'AbsTol', 1e-10, 'RelTol', 1e-6)); + assertTrue(tc, isisometry(U, 'left')); + assertTrue(tc, isisometry(V, 'right')); + d2 = max(dims(Strunc, 1:2)); + assertTrue(tc, d2 <= ceil(0.9*d)); + + s = min(cellfun(@(x) min(diag(x), [], 'all'), matrixblocks(S))); + [Utrunc, Strunc, Vtrunc, eta] = tsvd(t, [3 4 2], [1 5], 'TruncBelow', s * 1.2); + assertTrue(tc, isapprox(norm(tpermute(t, [3 4 2 1 5], [3 2]) - ... + Utrunc * Strunc * Vtrunc), eta, 'AbsTol', 1e-10, 'RelTol', 1e-6)); + assertTrue(tc, isisometry(U, 'left')); + assertTrue(tc, isisometry(V, 'right')); + s2 = min(cellfun(@(x) min(diag(x)), matrixblocks(Strunc))); + assertTrue(tc, s * 1.2 <= s2); end From 2d883e3635c054fb35b7f5db19d08ea6239a0422 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 12 Dec 2022 17:22:20 +0100 Subject: [PATCH 091/245] algorithm updates. --- src/algorithms/Vumps.m | 20 ++++---- src/algorithms/Vumps2.m | 20 ++++---- test/TestAlgorithms.m | 101 ++++++++++++---------------------------- test/TestInfJMpo.m | 5 +- test/TestInfMpo.m | 6 ++- 5 files changed, 56 insertions(+), 96 deletions(-) diff --git a/src/algorithms/Vumps.m b/src/algorithms/Vumps.m index 3b9ebc6..36b7c6c 100644 --- a/src/algorithms/Vumps.m +++ b/src/algorithms/Vumps.m @@ -12,10 +12,10 @@ dynamical_tols = true tol_min = 1e-12 - tol_max = 1e-6 - eigs_tolfactor = 1e-4 + tol_max = 1e-10 + eigs_tolfactor = 1e-6 canonical_tolfactor = 1e-8 - environments_tolfactor = 1e-4 + environments_tolfactor = 1e-6 multiAC = 'parallel' dynamical_multiAC = false; @@ -122,7 +122,7 @@ H_AC = AC_hamiltonian(mpo, mps, GL, GR, sites); AC = mps.AC(sites); for i = length(sites):-1:1 - [AC(i).var, ~] = eigsolve(H_AC{i}, mps.AC(sites(i)).var, 1, alg.which, ... + [AC(i).var, ~] = eigsolve(H_AC{i}, AC(sites(i)).var, 1, alg.which, ... kwargs{:}); end end @@ -150,8 +150,8 @@ end for i = length(AC):-1:1 - [Q_AC, ~] = leftorth(AC(i)); - [Q_C, ~] = leftorth(C(i), 1, 2); + [Q_AC, ~] = leftorth(AC(i), 'polar'); + [Q_C, ~] = leftorth(C(i), 1, 2, 'polar'); mps.AL(sites(i)) = multiplyright(Q_AC, Q_C'); end @@ -199,12 +199,12 @@ methods function alg = updatetols(alg, iter, eta) if alg.dynamical_tols - alg.alg_eigs.Tol = between(alg.tol_min, eta * alg.eigs_tolfactor, ... - alg.tol_max / iter); + alg.alg_eigs.Tol = between(alg.tol_min, eta * alg.eigs_tolfactor / iter, ... + alg.tol_max); alg.alg_canonical.Tol = between(alg.tol_min, ... - eta * alg.canonical_tolfactor, alg.tol_max / iter); + eta * alg.canonical_tolfactor / iter, alg.tol_max); alg.alg_environments.Tol = between(alg.tol_min, ... - eta * alg.environments_tolfactor, alg.tol_max / iter); + eta * alg.environments_tolfactor / iter, alg.tol_max); if alg.verbosity > Verbosity.iter fprintf('Updated subalgorithm tolerances: (%e,\t%e,\t%e)\n', ... diff --git a/src/algorithms/Vumps2.m b/src/algorithms/Vumps2.m index 461a3e7..afff73b 100644 --- a/src/algorithms/Vumps2.m +++ b/src/algorithms/Vumps2.m @@ -12,14 +12,14 @@ dynamical_tols = true tol_min = 1e-12 - tol_max = 1e-6 - eigs_tolfactor = 1e-4 + tol_max = 1e-7 + eigs_tolfactor = 1e-6 canonical_tolfactor = 1e-8 - environments_tolfactor = 1e-4 + environments_tolfactor = 1e-6 - trunc = {'TruncDim', 100} + trunc = {'TruncTotalDim', 100} - multiAC = 'parallel' + multiAC {mustBeMember(multiAC, {'sequential'})} = 'sequential' dynamical_multiAC = false; tol_multiAC = Inf @@ -125,7 +125,6 @@ sites = 1:period(mps); sites = sites(mod(sites, 2) == mod(iter, 2)); end - H_AC2 = AC2_hamiltonian(mpo, mps, GL, GR, sites); for i = length(sites):-1:1 AC2 = MpsTensor(contract(mps.AC(sites(i)), [-1 -2 1], ... @@ -142,9 +141,9 @@ sites = mod1(iter, period(mps)); else sites = 1:period(mps); - sites = next(sites(mod(sites, 2) == mod(iter, 2)), period(mps)); + sites = sites(mod(sites, 2) == mod(iter, 2)); end - + sites = next(sites, period(mps)); H_C = C_hamiltonian(mpo, mps, GL, GR, sites); for i = length(sites):-1:1 [C(i), ~] = eigsolve(H_C{i}, mps.C(sites(i)), 1, alg.which, ... @@ -159,10 +158,9 @@ sites = 1:period(mps); sites = sites(mod(sites, 2) == mod(iter, 2)); end - for i = length(AC2):-1:1 - [Q_AC, ~] = leftorth(AC2(i)); - [Q_C, ~] = leftorth(C(i), 1, 2); + [Q_AC, ~] = leftorth(AC2(i), 'polar'); + [Q_C, ~] = leftorth(C(i), 1, 2, 'polar'); AL = multiplyright(Q_AC, Q_C'); [AL1, C, AL2] = tsvd(AL.var, [1 2], [3 4], alg.trunc{:}); mps.AL(sites(i)) = multiplyright(MpsTensor(AL1), C); diff --git a/test/TestAlgorithms.m b/test/TestAlgorithms.m index 58d55df..e479a62 100644 --- a/test/TestAlgorithms.m +++ b/test/TestAlgorithms.m @@ -1,7 +1,15 @@ classdef TestAlgorithms < matlab.unittest.TestCase % Unit tests for algorithms - + properties (TestParameter) + unitcell = {1, 2, 3, 4} + alg = {Vumps('which', 'smallestreal', 'maxiter', 5), ... + ...IDmrg('which', 'smallestreal', 'maxiter', 5), ... + Vumps2('which', 'smallestreal', 'maxiter', 6), ... + IDmrg2('which', 'smallestreal', 'maxiter', 5) ... + } + symm = {'Z1', 'Z2'} + end methods (Test) function test2dIsing(tc) mpo = InfMpo.Ising(); @@ -79,78 +87,29 @@ function test2dIsing(tc) 'RelTol', 1e-2); end - function test1dIsing(tc) - E0 = -1.273; - - %% no symmetry - mpo1 = InfJMpo.Ising(); - mps1 = initialize_mps(mpo1, CartesianSpace.new(12)); - - alg = Vumps('which', 'smallestreal', 'maxiter', 5); - [gs1, lambda] = fixedpoint(alg, mpo1, mps1); - tc.verifyEqual(lambda, E0, 'RelTol', 1e-2); - tc.verifyEqual(expectation_value(gs1, mpo1, gs1), E0, 'RelTol', 1e-2); - - alg = IDmrg('which', 'smallestreal', 'maxiter', 5); - [gs1, lambda] = fixedpoint(alg, mpo1, mps1); - tc.verifyEqual(expectation_value(gs1, mpo1, gs1), E0, 'RelTol', 1e-2); - - mpo2 = [mpo1 mpo1]; - mps2 = [mps1 mps1]; - - alg = Vumps('which', 'smallestreal', 'maxiter', 5); - [gs2, lambda] = fixedpoint(alg, mpo2, mps2); - tc.verifyEqual(lambda, 2 * E0, 'RelTol', 1e-2); - tc.verifyEqual(expectation_value(gs2, mpo2, gs2), 2 * E0, 'RelTol', 1e-2); - - alg = Vumps2('which', 'smallestreal', 'maxiter', 5); - [gs2, lambda] = fixedpoint(alg, mpo2, mps2); - tc.verifyEqual(lambda, E0 * 2, 'RelTol', 1e-2); - tc.verifyEqual(expectation_value(gs2, mpo2, gs2), 2 * E0, 'RelTol', 1e-2); - - alg = IDmrg('which', 'smallestreal', 'maxiter', 5); - [gs2, lambda] = fixedpoint(alg, mpo2, mps2); - tc.verifyEqual(expectation_value(gs2, mpo2, gs2), 2 * E0, ... - 'RelTol', 1e-2); + function test1dIsing(tc, unitcell, alg, symm) + + tc.assumeTrue(unitcell > 1 || isa(alg, 'IDmrg') || isa(alg, 'Vumps')) + E0 = -1.273 * unitcell; + + if strcmp(symm, 'Z1') + mpo1 = InfJMpo.Ising(); + mps1 = initialize_mps(mpo1, CartesianSpace.new(12)); + else + mpo1 = InfJMpo.Ising('Symmetry', 'Z2'); + mps1 = initialize_mps(mpo1, GradedSpace.new(Z2(0, 1), [6 6], false)); + end + + mpo = mpo1; + mps = mps1; + for i = 2:unitcell + mpo = [mpo mpo1]; + mps = [mps mps1]; + end - alg = IDmrg2('which', 'smallestreal', 'maxiter', 5); - [gs2, lambda] = fixedpoint(alg, mpo2, mps2); - tc.verifyEqual(expectation_value(gs2, mpo2, gs2), 2 * E0, 'RelTol', 1e-2); - - %% Z2 symmetry - mpo1 = InfJMpo.Ising('Symmetry', 'Z2'); - mps1 = initialize_mps(mpo1, GradedSpace.new(Z2(0, 1), [6 6], false)); - - alg = Vumps('which', 'smallestreal', 'maxiter', 5); - [gs1, lambda] = fixedpoint(alg, mpo1, mps1); + [gs, lambda] = fixedpoint(alg, mpo, mps); tc.verifyEqual(lambda, E0, 'RelTol', 1e-2); - tc.verifyEqual(expectation_value(gs1, mpo1, gs1), E0, 'RelTol', 1e-2); - - alg = IDmrg('which', 'smallestreal', 'maxiter', 5); - [gs1, lambda] = fixedpoint(alg, mpo1, mps1); - tc.verifyEqual(expectation_value(gs1, mpo1, gs1), E0, 'RelTol', 1e-2); - - mpo2 = [mpo1 mpo1]; - mps2 = [mps1 mps1]; - - alg = Vumps('which', 'smallestreal', 'maxiter', 5); - [gs2, lambda] = fixedpoint(alg, mpo2, mps2); - tc.verifyEqual(lambda, 2 * E0, 'RelTol', 1e-2); - tc.verifyEqual(expectation_value(gs2, mpo2, gs2), 2 * E0, 'RelTol', 1e-2); - - alg = Vumps2('which', 'smallestreal', 'maxiter', 5); - [gs2, lambda] = fixedpoint(alg, mpo2, mps2); - tc.verifyEqual(lambda, E0 * 2, 'RelTol', 1e-2); - tc.verifyEqual(expectation_value(gs2, mpo2, gs2), 2 * E0, 'RelTol', 1e-2); - - alg = IDmrg('which', 'smallestreal', 'maxiter', 5); - [gs2, lambda] = fixedpoint(alg, mpo2, mps2); - tc.verifyEqual(expectation_value(gs2, mpo2, gs2), 2 * E0, ... - 'RelTol', 1e-2); - - alg = IDmrg2('which', 'smallestreal', 'maxiter', 5); - [gs2, lambda] = fixedpoint(alg, mpo2, mps2); - tc.verifyEqual(expectation_value(gs2, mpo2, gs2), 2 * E0, 'RelTol', 1e-2); + tc.verifyEqual(expectation_value(gs, mpo), E0, 'RelTol', 1e-2); end end end diff --git a/test/TestInfJMpo.m b/test/TestInfJMpo.m index 6cbe59a..2988c22 100644 --- a/test/TestInfJMpo.m +++ b/test/TestInfJMpo.m @@ -28,8 +28,9 @@ function testDerivatives(tc, mpo, mps) H_AC = AC_hamiltonian(mpo, mps, GL, GR); for i = 1:numel(H_AC) - [AC_, lambda] = eigsolve(H_AC{i}, mps.AC(i), 1, 'largestabs'); - tc.assertTrue(isapprox(apply(H_AC{i}, AC_), lambda * AC_)); + AC_ = mps.AC(i); + [AC_.var, lambda] = eigsolve(H_AC{i}, mps.AC(i).var, 1, 'largestabs'); + tc.assertTrue(isapprox(apply(H_AC{i}, AC_), lambda * AC_.var)); end H_C = C_hamiltonian(mpo, mps, GL, GR); diff --git a/test/TestInfMpo.m b/test/TestInfMpo.m index 1d6cb7e..2782335 100644 --- a/test/TestInfMpo.m +++ b/test/TestInfMpo.m @@ -31,8 +31,10 @@ function testDerivatives(tc, mpo, mps) H_AC = AC_hamiltonian(mpo, mps, GL, GR); for i = 1:numel(H_AC) - [AC_, lambda] = eigsolve(H_AC{i}, mps.AC(i), 1, 'largestabs'); - tc.assertTrue(isapprox(apply(H_AC{i}, AC_), lambda * AC_)); + AC_ = mps.AC(i); + [AC_.var, lambda] = eigsolve(H_AC{i}, mps.AC(i).var, 1, 'largestabs'); + AC_2 = apply(H_AC{i}, AC_); + tc.assertTrue(isapprox(AC_2, lambda * AC_.var)); end H_C = C_hamiltonian(mpo, mps, GL, GR); From 8d4d70ec47234ad8003d754a39488e2db1799c43 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Fri, 16 Dec 2022 15:52:52 +0100 Subject: [PATCH 092/245] updated docs for UniformMps --- src/mps/MpsTensor.m | 1 - src/mps/UniformMps.m | 319 ++++++++++++++++++++++++++++++------------ test/TestUniformMps.m | 2 +- 3 files changed, 229 insertions(+), 93 deletions(-) diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index 9ac0fcd..e35b127 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -329,7 +329,6 @@ B MpsTensor = A end - for i = length(A):-1:1 B(i).var = twist(B(i).var, [isdual(space(B(i), 1:2)) ~isdual(space(B(i), 3))]); T(i, 1) = FiniteMpo(B(i).var', {}, A(i)); diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index 9e8079e..eb5cc0b 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -1,7 +1,22 @@ classdef UniformMps % UniformMps - Implementation of infinite translation invariant MPS - % MPS is stored in center gauge, where - % AL(w) * C(w) = AC(w) = C(w-1) * AR(w) + % + % The center gauge is defined to have: + % :math:`AL_w * C_w = AC_w = C_{w-1} * AR_w` + % + % Properties + % ---------- + % AL : :class:`MpsTensor` + % left-gauged mps tensors. + % + % AR : :class:`MpsTensor` + % right-gauged mps tensors. + % + % C : :class:`Tensor` + % center gauge transform. + % + % AC : :class:`MpsTensor` + % center-gauged mps tensors. properties AL (1,:) MpsTensor @@ -14,6 +29,27 @@ %% Constructors methods function mps = UniformMps(varargin) + % Usage + % ----- + % :code:`mps = UniformMps(A)` + % + % :code:`mps = UniformMps(AL, AR, C [, AC])` + % + % Arguments + % --------- + % A : :class:`MpsTensor` or :class:`PeriodicCell` + % set of tensors per site that define an MPS to be gauged. + % + % AL, AR, AC : :class:`MpsTensor` or :class:`PeriodicCell` + % set of gauged MpsTensors. + % + % C : :class:`Tensor` + % gauge tensor. + % + % Returns + % ------- + % mps : :class:`UniformMps` + % gauged uniform MPS. if nargin == 0, return; end % default empty constructor @@ -48,6 +84,11 @@ error('Invalid constructor for UniformMps.') end + elseif nargin == 3 + mps.AL = varargin{1}; + mps.AR = varargin{2}; + mps.C = varargin{3}; + elseif nargin == 4 mps.AL = varargin{1}; mps.AR = varargin{2}; @@ -62,35 +103,65 @@ methods (Static) function mps = new(fun, pspaces, vspaces) - if isempty(fun), fun = @randnc; end + % Create a uniform matrix product state with data using a function handle. + % + % Usage + % ----- + % :code:`UniformMps.new(fun, pspaces, vspaces)` + % + % Arguments + % --------- + % fun : :class:`function_handle` + % function to initialize the tensor. + % + % Repeating Aruguments + % -------------------- + % pspaces : :class:`AbstractSpace` + % physical spaces for each site. + % + % vspaces : :class:`AbstractSpace` + % virtual spaces between each site. (entry `i` corresponds to left of site + % `i`.) - if isscalar(pspaces) && ~isscalar(vspaces) - pspaces = repmat(pspaces, size(vspaces)); - elseif isscalar(vspaces) && ~isscalar(pspaces) - vspaces = repmat(vspaces, size(pspaces)); - else - assert(isequal(size(vspaces), size(pspaces)), 'mps:dimagree', ... - 'Invalid sizes of input spaces.'); + arguments + fun = [] + end + arguments (Repeating) + pspaces + vspaces end + if isempty(fun), fun = @randnc; end + L = length(pspaces); + for w = length(pspaces):-1:1 - if vspaces(w) * pspaces(w) < vspaces(next(w, length(pspaces))) + rankdefficient = vspaces{w} * pspaces{w} < vspaces{next(w, L)} || ... + vspaces{w} > pspaces{w} * vspaces{next(w, L)}; + if rankdefficient error('mps:rank', ... 'Cannot create a full rank mps with given spaces.'); end - if vspaces(w) > pspaces(w) * vspaces(next(w, length(pspaces))) - error('mps:rank', ... - 'Cannot create a full rank mps with given spaces.'); - end - A{w} = Tensor.new(fun, [vspaces(w) pspaces(w)], ... - vspaces(next(w, length(pspaces)))); + + A{w} = Tensor.new(fun, [vspaces{w} pspaces{w}], ... + vspaces{next(w, L)}); end mps = UniformMps(A); end function mps = randnc(pspaces, vspaces) - mps = UniformMps.new(@randnc, pspaces, vspaces); + % Create a uniform matrix product state with random entries. + % + % See Also + % -------- + % :method:`UniformMps.new` + + arguments (Repeating) + pspaces + vspaces + end + args = [pspaces; vspaces]; + mps = UniformMps.new(@randnc, args{:}); end end @@ -98,12 +169,14 @@ %% Properties methods function p = period(mps) + % period over which the mps is translation invariant. for i = numel(mps):-1:1 p(i) = length(mps(i).AL); end end function d = depth(mps) + % amount of lines in a multi-line mps. d = size(mps, 1); end @@ -116,11 +189,18 @@ end function s = leftvspace(mps, w) + % return the virtual space to the left of site w. if nargin == 1 || isempty(w), w = 1:period(mps); end s = arrayfun(@leftvspace, mps.AL(w)); end + function s = pspace(mps, w) + % return the physical space at site w. + s = pspace(mps.AL(w)); + end + function s = rightvspace(mps, w) + % return the virtual space to the right of site w. if nargin == 1 || isempty(w), w = 1:period(mps); end s = arrayfun(@rightvspace, mps.AL(w)); end @@ -134,6 +214,43 @@ %% Methods methods function [mps, lambda] = canonicalize(mps, kwargs) + % Compute the center-gauged form of an mps. + % + % Usage + % ----- + % :code:`[mps, lambda] = canonicalize(mps, kwargs)` + % + % Arguments + % --------- + % mps : :class:`UniformMps` + % input mps, from which AL or AR is used as the state, and optionally C as an + % initial guess for the gauge. + % + % Keyword Arguments + % ----------------- + % Tol : numeric + % tolerance for the algorithm. + % + % MaxIter : integer + % maximum amount of iterations. + % + % Method : char + % algorithm used for decomposition. Must be 'polar', 'qr' or 'qrpos'. + % + % Verbosity : :class:`Verbosity` + % level of output. + % + % DiagC : logical + % flag to indicate if `C` needs to be diagonalized. + % + % ComputeAC : logical + % flag to indicate if `AC` needs to be computed. + % + % Order : 'lr' or 'rl' + % order of gauge fixing: + % 'lr' uses AL as input tensors, first leftorth, then rightorth. + % 'rl' uses AR as input tensors, first rightorth, then leftorth. + arguments mps kwargs.Tol = eps(underlyingType(mps))^(3/4) @@ -171,6 +288,7 @@ if kwargs.DiagC mps = diagonalizeC(mps); end + if kwargs.ComputeAC for i = 1:height(mps) for w = period(mps(i)):-1:1 @@ -181,9 +299,9 @@ end end - - function mps = diagonalizeC(mps) + % gauge transform an mps such that C is diagonal. + for i = 1:height(mps) for w = 1:period(mps(i)) C_iw = mps(i).C(w); @@ -214,11 +332,43 @@ end function mps = normalize(mps) + % normalize an mps state. + mps.C = arrayfun(@normalize, mps.C); mps.AC = arrayfun(@normalize, mps.AC); end function T = transfermatrix(mps1, mps2, sites, kwargs) + % A finite matrix product operator that represents the transfer matrix of an + % mps. + % + % Usage + % ----- + % :code:`T = transfermatrix(mps1, mps2, sites, kwargs)` + % + % Arguments + % --------- + % mps1 : :class:`UniformMps` + % input mps for top layer. + % + % mps2 : :class:`UniformMps` + % input mps for bottom layer, by default equal to the top. + % + % sites : integer + % optionally slice the unit cell of the mps and only define the transfer + % matrix for this slice. + % + % Keyword Arguments + % ----------------- + % Type : char + % 'LL', 'LR', 'RL', 'RR' to determine if the top or bottom respectively are AL + % or AR. + % + % Returns + % ------- + % T : FiniteMpo + % transfer matrix of an mps, acting to the left. + arguments mps1 mps2 = mps1 @@ -242,6 +392,64 @@ T = transfermatrix(A1, A2); end + function rho = fixedpoint(mps, type, w) + % compute the fixed point of the transfer matrix of an mps. + % + % Usage + % ----- + % :code:`rho = fixedpoint(mps, type, w)` + % + % Arguments + % --------- + % mps : :class:`UniformMps` + % input state. + % + % type : char + % specification of the type of transfer matrix: + % general format: sprintf(%c_%c%c, side, top, bot) where side is 'l' or 'r' to + % determine which fixedpoint, and top and bot are 'L' or 'R' to specify + % whether to use AL or AR in the transfer matrix. + % + % w : integer + % position within the mps unitcell of the fixed point. + + arguments + mps + type {mustBeMember(type, ... + {'l_LL' 'l_LR' 'l_RL' 'l_RR' 'r_LL' 'r_LR' 'r_RL' 'r_RR'})} + w = strcmp(type(1), 'l') * 1 + strcmp(type(1), 'r') * period(mps) + end + + ww = prev(w, period(mps)); + switch type + case 'l_RR' + rho = contract(mps.C(ww)', [-1 1], mps.C(ww), [1 -2], 'Rank', [1 1]); +% if isdual(space(rho, 1)), rho = twist(rho, 1); end + case 'l_RL' + rho = mps.C(ww); +% if isdual(space(rho, 1)), rho = twist(rho, 1); end + case 'l_LR' + rho = mps.C(ww)'; +% if isdual(space(rho, 1)), rho = twist(rho, 1); end + case 'l_LL' + rho = mps.C.eye(leftvspace(mps, w), leftvspace(mps, w)); + if isdual(space(rho, 1)), rho = twist(rho, 1); end + + case 'r_RR' + rho = mps.C.eye(rightvspace(mps, w)', rightvspace(mps, w)'); + if isdual(space(rho, 2)), rho = twist(rho, 2); end + case 'r_RL' + rho = twist(mps.C(w)', 2); +% if isdual(space(rho, 1)), rho = twist(rho, 2); end + case 'r_LR' + rho = twist(mps.C(w), 2); +% if ~isdual(space(rho, 2)), rho = twist(rho, 2); end + case 'r_LL' + rho = contract(mps.C(w), [-1 1], mps.C(w)', [1 -2], 'Rank', [1 1]); + rho = twist(rho, 2); + end + end + function [V, D] = transfereigs(mps1, mps2, howmany, which, eigopts, kwargs) arguments mps1 @@ -327,43 +535,6 @@ end end - function rho = fixedpoint(mps, type, w) - arguments - mps - type {mustBeMember(type, ... - {'l_LL' 'l_LR' 'l_RL' 'l_RR' 'r_LL' 'r_LR' 'r_RL' 'r_RR'})} - w = strcmp(type(1), 'l') * 1 + strcmp(type(1), 'r') * period(mps) - end - ww = prev(w, period(mps)); - switch type - case 'l_RR' - rho = contract(mps.C(ww)', [-1 1], mps.C(ww), [1 -2], 'Rank', [1 1]); -% if isdual(space(rho, 1)), rho = twist(rho, 1); end - case 'l_RL' - rho = mps.C(ww); -% if isdual(space(rho, 1)), rho = twist(rho, 1); end - case 'l_LR' - rho = mps.C(ww)'; -% if isdual(space(rho, 1)), rho = twist(rho, 1); end - case 'l_LL' - rho = mps.C.eye(leftvspace(mps, w), leftvspace(mps, w)); - if isdual(space(rho, 1)), rho = twist(rho, 1); end - - case 'r_RR' - rho = mps.C.eye(rightvspace(mps, w)', rightvspace(mps, w)'); - if isdual(space(rho, 2)), rho = twist(rho, 2); end - case 'r_RL' - rho = twist(mps.C(w)', 2); -% if isdual(space(rho, 1)), rho = twist(rho, 2); end - case 'r_LR' - rho = twist(mps.C(w), 2); -% if ~isdual(space(rho, 2)), rho = twist(rho, 2); end - case 'r_LL' - rho = contract(mps.C(w), [-1 1], mps.C(w)', [1 -2], 'Rank', [1 1]); - rho = twist(rho, 2); - end - end - function [svals, charges] = schmidt_values(mps, w) arguments mps @@ -429,14 +600,6 @@ function plot_entanglementspectrum(mps, w, ax) mps.AC = desymmetrize(mps.AC); end - % essential - [mps, lambda] = Canonical(mps, options); - [f, rho] = TransferEigs(mps1, mps2, x0, num, charge, choice, options); - - % utility - PlotTransferEigs(mps1, mps2, x0, num, charges); - [schmidt, charges] = SchmidtValues(mps, loc) - PlotEntSpectrum(mps, svalue, opts) xi = CorrelationLength(mps, charge); [epsilon, delta, spectrum] = MarekGap(mps, charge, angle, num) S = EntanglementEntropy(mps, loc); @@ -466,33 +629,7 @@ function plot_entanglementspectrum(mps, w, ax) [mps, xi] = Retract(mps, eta, alpha) n = Inner(x, eta, xi) - mps = TensorNone(mps) - - % to be done later - - Add; % is this useful? - MultiplyLeft; % no clue - MultiplyRight; % no clue - - end - - methods (Static) - - [AR, C, lambda, error] = OrthRight(A, C, options) - [AL, C, lambda, error] = OrthLeft (A, C, options) - out = Random(virtualLeg, varargin); - - % conversion from legacy datastructure - mps = FromCell(A); end - - - methods (Access = protected) - - % anything? - - end - end diff --git a/test/TestUniformMps.m b/test/TestUniformMps.m index c913a0f..c4b3f5a 100644 --- a/test/TestUniformMps.m +++ b/test/TestUniformMps.m @@ -59,7 +59,7 @@ function testCanonical(tc, A) function testDiagonalC(tc, A) mps = UniformMps(A); mps2 = diagonalizeC(mps); - f = fidelity(mps, mps2, 'Verbosity', Verbosity.diagnostics); + f = fidelity(mps, mps2); tc.assertTrue(isapprox(f, 1), 'Diagonalizing C should not alter the state.'); end From 986d1b51aa438a2653e00169c10f4b25523e2164 Mon Sep 17 00:00:00 2001 From: sanderdemeyer Date: Sat, 17 Dec 2022 13:45:15 +0100 Subject: [PATCH 093/245] Added insertone for ComplexSpace --- src/tensors/spaces/ComplexSpace.m | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/tensors/spaces/ComplexSpace.m b/src/tensors/spaces/ComplexSpace.m index 1aaa77d..7c78d13 100644 --- a/src/tensors/spaces/ComplexSpace.m +++ b/src/tensors/spaces/ComplexSpace.m @@ -209,6 +209,19 @@ space = ComplexSpace(prod(dims(spaces)), false); end + function spaces = insertone(spaces, i, dual) + arguments + spaces + i = length(spaces) + 1 + dual = false + end + + trivialspace = ComplexSpace(1, false); + if dual, trivialspace = conj(trivialspace); end + spaces = [spaces(1:i-1) trivialspace spaces(i:end)]; + end + + function space1 = infimum(space1, space2) assert(isscalar(space1) && isscalar(space2)); assert(isdual(space1) == isdual(space2)); From f344a161a9a4730fb6d1953e0f706bb0f31f0bb3 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sun, 18 Dec 2022 10:43:02 +0100 Subject: [PATCH 094/245] Update hashfunction to distinguish between objects with same data representation. --- src/caches/GetMD5/GetMD5.c | 6 ++++-- src/caches/GetMD5/GetMD5.mexa64 | Bin 27264 -> 31360 bytes src/caches/GetMD5/GetMD5.mexw64 | Bin 25600 -> 0 bytes src/caches/GetMD5/uTest_GetMD5.m | 4 ++-- 4 files changed, 6 insertions(+), 4 deletions(-) delete mode 100644 src/caches/GetMD5/GetMD5.mexw64 diff --git a/src/caches/GetMD5/GetMD5.c b/src/caches/GetMD5/GetMD5.c index 329c191..798ca05 100644 --- a/src/caches/GetMD5/GetMD5.c +++ b/src/caches/GetMD5/GetMD5.c @@ -590,7 +590,7 @@ void ArrayCore(MD5_CTX *context, const mxArray *V) // mxGetDimensions replies [1x1] for opaque arrays, e.g. the string class. // They are forwarded to the helper function, where SIZE() replies the true // dimensions. - if (!dataOpaq) { + if (true) { // Consider class as name, not as ClassID, because the later might change // with the Matlab version: Len = strlen(ClassName); @@ -606,7 +606,7 @@ void ArrayCore(MD5_CTX *context, const mxArray *V) } MD5_Update(context, (uchar_T *) header, lenHeader * sizeof(int64_T)); mxFree(header); - } + } // Include the contents of the array: switch (ClassID) { @@ -639,6 +639,8 @@ void ArrayCore(MD5_CTX *context, const mxArray *V) break; default: // FUNCTION, VOID, OPAQUE, UNKNOWN CLASS: ----------------------- + + // Treat deep recursion as an error: if (++RecursionCount > MAX_RECURSION) { ERROR("DeepRecursion", "Cannot serialize recursive data type.\n" diff --git a/src/caches/GetMD5/GetMD5.mexa64 b/src/caches/GetMD5/GetMD5.mexa64 index b80a80a1501acfd846ae669133018da7a8d18902..ea69356f8466f522f3c707e2f84a38b0a6c163b1 100755 GIT binary patch delta 5956 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 K@-=PgfPVvz1EggD diff --git a/src/caches/GetMD5/GetMD5.mexw64 b/src/caches/GetMD5/GetMD5.mexw64 deleted file mode 100644 index 46968b201cbd5321488111b39b0ead338ff90003..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25600 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 Y-e!sy3f)@Tv^gh60r8v9Kbi&pFXiau_W%F@ diff --git a/src/caches/GetMD5/uTest_GetMD5.m b/src/caches/GetMD5/uTest_GetMD5.m index 8c3f411..eb94a20 100644 --- a/src/caches/GetMD5/uTest_GetMD5.m +++ b/src/caches/GetMD5/uTest_GetMD5.m @@ -209,13 +209,13 @@ function uTest_GetMD5(doSpeed) if MatlabV >= 901 % R2016b Data = string('hello'); S1 = GetMD5(Data, 'Array'); - if ~isequal(S1, '2614526bcbd4af5a8e7bf79d1d0d92ab') + if ~isequal(S1, '54d669bbc5d2f755929a1ade4869f80b') error([ErrID, ':String'], 'Bad result for string.'); end Data = string({'hello', 'world'}); S1 = GetMD5(Data, 'Array'); - if ~isequal(S1, 'a1bdbbe9a15c249764847ead9bf47326') + if ~isequal(S1, '0b633f8d7bc5bc54171f8ce6b60a096b') error([ErrID, ':String'], 'Bad result for string.'); end fprintf(' ok: String class\n'); From 3db0cb39f533625e7bac1535b38455995060ec60 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sun, 18 Dec 2022 10:43:31 +0100 Subject: [PATCH 095/245] insert onespace on the level of abstractspace. --- src/tensors/spaces/AbstractSpace.m | 12 ++++++++++++ src/tensors/spaces/GradedSpace.m | 12 ------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/tensors/spaces/AbstractSpace.m b/src/tensors/spaces/AbstractSpace.m index d1e5b3b..9ce9927 100644 --- a/src/tensors/spaces/AbstractSpace.m +++ b/src/tensors/spaces/AbstractSpace.m @@ -257,6 +257,18 @@ end end + function spaces = insertone(spaces, i, dual) + arguments + spaces + i = length(spaces) + 1 + dual = false + end + + trivialspace = one(spaces); + if dual, trivialspace = conj(trivialspace); end + spaces = [spaces(1:i-1) trivialspace spaces(i:end)]; + end + %% Utility function bools = eq(spaces1, spaces2) diff --git a/src/tensors/spaces/GradedSpace.m b/src/tensors/spaces/GradedSpace.m index 139bf6f..1640170 100644 --- a/src/tensors/spaces/GradedSpace.m +++ b/src/tensors/spaces/GradedSpace.m @@ -283,18 +283,6 @@ space = GradedSpace(newdimensions, false); end - function spaces = insertone(spaces, i, dual) - arguments - spaces - i = length(spaces) + 1 - dual = false - end - - trivialspace = one(spaces); - if dual, trivialspace = conj(trivialspace); end - spaces = [spaces(1:i-1) trivialspace spaces(i:end)]; - end - %% Utility function bools = eq(spaces1, spaces2) From ac5908a3bc1cc73663e5adbdf3cb0462f73de857 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sun, 18 Dec 2022 10:44:42 +0100 Subject: [PATCH 096/245] fix some tests. --- src/mps/UniformMps.m | 25 ++++++------ test/TestAlgorithms.m | 89 +++++++++---------------------------------- test/TestInfMpo.m | 7 +++- 3 files changed, 36 insertions(+), 85 deletions(-) diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index eb5cc0b..186a49d 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -511,22 +511,23 @@ if isa(O, 'InfJMpo') [GL, GR] = environments(O, mps1, mps2); - H = AC_hamiltonian(O, mps1, GL, GR); - i = length(H); - N = size(H{i}.R.var, 2); - H{i}.R.var = H{i}.R.var(1, N, 1); - H{i}.O{1} = H{i}.O{1}(:, :, N, :); - AC_ = apply(H{i}, mps1.AC(i)); - E = dot(AC_, mps2.AC(i)); + Hs = AC_hamiltonian(O, mps1, GL, GR, length(H)); + H = Hs{1}; + N = size(H.R.var, 2); + H.R.var = H.R.var(1, N, 1); + H.O{1} = H.O{1}(:, :, N, :); + AC_ = apply(H, mps1.AC(end)); + E = dot(AC_, mps2.AC(end)); elseif isa(O, 'InfMpo') - [GL, GR] = environments(O, mps1, mps2); - H = AC_hamiltonian(O, mps1, GL, GR); - E = zeros(size(H)); - for i = 1:length(H) - AC_ = apply(H{i}, mps1.AC(i)); + [GL, GR] = environments(O, mps1, mps2); % should be normalized + Hs = AC_hamiltonian(O, mps1, GL, GR); + E = zeros(size(Hs)); + for i = 1:length(Hs) + AC_ = apply(Hs{i}, mps1.AC(i)); E(i) = dot(AC_, mps2.AC(i)); end + E = prod(E); elseif isa(O, 'AbstractTensor') error('TBA'); diff --git a/test/TestAlgorithms.m b/test/TestAlgorithms.m index e479a62..1ca291c 100644 --- a/test/TestAlgorithms.m +++ b/test/TestAlgorithms.m @@ -11,84 +11,31 @@ symm = {'Z1', 'Z2'} end methods (Test) - function test2dIsing(tc) - mpo = InfMpo.Ising(); - mps = initialize_mps(mpo, CartesianSpace.new(12)); - - alg = Vumps('which', 'largestabs', 'maxiter', 10); - [gs, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyEqual(lambda, 2.533, 'RelTol', 1e-2); - tc.verifyEqual(lambda, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); - - alg = IDmrg('which', 'largestabs', 'maxiter', 10); - [gs, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyEqual(lambda, 2.5337, 'RelTol', 1e-2); - tc.verifyEqual(lambda, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); - - mps2 = approximate(Vomps(), ... - mpo, gs, initialize_mps(mpo, CartesianSpace.new(24))); - tc.verifyEqual(fidelity(gs, mps2), 1, 'RelTol', 1e-2); - - mps = [mps mps]; - mpo = [mpo mpo]; - alg = IDmrg2('which', 'largestabs', 'maxiter', 10); - [gs, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); - tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... - 'RelTol', 1e-2); - - alg = IDmrg('which', 'largestabs', 'maxiter', 10); - [gs, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); - tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... - 'RelTol', 1e-2); - - alg = Vumps('which', 'largestabs', 'maxiter', 5); - [gs, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); - tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... - 'RelTol', 1e-2); - - mpo = InfMpo.Ising('Symmetry', 'Z2'); - mps = initialize_mps(mpo, GradedSpace.new(Z2(0, 1), [6 6], false)); - - alg = Vumps('which', 'largestabs', 'maxiter', 5); - [gs, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyEqual(lambda, 2.533, 'RelTol', 1e-2); - tc.verifyEqual(lambda, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); - - alg = IDmrg('which', 'largestabs', 'maxiter',10); - [gs, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyEqual(lambda, 2.5337, 'RelTol', 1e-2); - tc.verifyEqual(lambda, expectation_value(gs, mpo, gs), 'RelTol', 1e-2); - - mps2 = approximate(Vomps(), ... - mpo, gs, initialize_mps(mpo, GradedSpace.new(Z2(0, 1), [12 12], false))); - tc.verifyEqual(fidelity(gs, mps2), 1, 'RelTol', 1e-2); + function test2dIsing(tc, alg, unitcell, symm) + alg.which = 'largestabs'; + tc.assumeTrue(unitcell > 1 || isa(alg, 'IDmrg') || isa(alg, 'Vumps')) - mps = [mps mps]; - mpo = [mpo mpo]; - alg = IDmrg2('which', 'largestabs', 'maxiter', 10); - [gs, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); - tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... - 'RelTol', 1e-2); + E0 = 2.5337 ^ unitcell; + if strcmp(symm, 'Z1') + mpo1 = InfMpo.Ising(); + mps1 = initialize_mps(mpo1, CartesianSpace.new(12)); + else + mpo1 = InfMpo.Ising('Symmetry', 'Z2'); + mps1 = initialize_mps(mpo1, GradedSpace.new(Z2(0, 1), [6 6], false)); + end - alg = IDmrg('which', 'largestabs', 'maxiter', 10); - [gs, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); - tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... - 'RelTol', 1e-2); + mpo = mpo1; + mps = mps1; + for i = 2:unitcell + mpo = [mpo mpo1]; + mps = [mps mps1]; + end - alg = Vumps('which', 'largestabs', 'maxiter', 5); [gs, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyEqual(lambda, 2.533^2, 'RelTol', 1e-2); - tc.verifyEqual(expectation_value(gs, mpo, gs), [2.533 2.533], ... - 'RelTol', 1e-2); + tc.verifyEqual(expectation_value(gs, mpo, gs), E0, 'RelTol', 1e-2); end function test1dIsing(tc, unitcell, alg, symm) - tc.assumeTrue(unitcell > 1 || isa(alg, 'IDmrg') || isa(alg, 'Vumps')) E0 = -1.273 * unitcell; diff --git a/test/TestInfMpo.m b/test/TestInfMpo.m index 2782335..a03bfaf 100644 --- a/test/TestInfMpo.m +++ b/test/TestInfMpo.m @@ -4,12 +4,15 @@ properties (TestParameter) mpo = struct(... 'trivial', InfMpo.Ising(), ... - 'Z2', InfMpo.Ising('Symmetry', 'Z2') ... + 'Z2', InfMpo.Ising('Symmetry', 'Z2'), ... + 'fermion', block(InfMpo.fDimer()) ... ) mps = struct(... 'trivial', UniformMps.randnc(CartesianSpace.new(2), CartesianSpace.new(4)), ... 'Z2', UniformMps.randnc(GradedSpace.new(Z2(0,1), [1 1], false), ... - GradedSpace.new(Z2(0,1), [4 4], false)) ... + GradedSpace.new(Z2(0,1), [4 4], false)), ... + 'fermion', UniformMps.randnc(GradedSpace.new(fZ2(0, 1), [1 1], false), ... + GradedSpace.new(fZ2(0, 1), [4 4], false)) ... ) end From 25c05260436f4600320e4329da024ee7ac330c95 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sun, 18 Dec 2022 10:46:21 +0100 Subject: [PATCH 097/245] remove extra method def --- src/tensors/spaces/ComplexSpace.m | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/tensors/spaces/ComplexSpace.m b/src/tensors/spaces/ComplexSpace.m index 7c78d13..1aaa77d 100644 --- a/src/tensors/spaces/ComplexSpace.m +++ b/src/tensors/spaces/ComplexSpace.m @@ -209,19 +209,6 @@ space = ComplexSpace(prod(dims(spaces)), false); end - function spaces = insertone(spaces, i, dual) - arguments - spaces - i = length(spaces) + 1 - dual = false - end - - trivialspace = ComplexSpace(1, false); - if dual, trivialspace = conj(trivialspace); end - spaces = [spaces(1:i-1) trivialspace spaces(i:end)]; - end - - function space1 = infimum(space1, space2) assert(isscalar(space1) && isscalar(space2)); assert(isdual(space1) == isdual(space2)); From fa8c099e2851b42fd4e44b5e8c0673161d86228a Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sun, 18 Dec 2022 15:09:01 +0100 Subject: [PATCH 098/245] faster hash --- src/tensors/charges/SU2.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tensors/charges/SU2.m b/src/tensors/charges/SU2.m index 5f39445..dd54954 100644 --- a/src/tensors/charges/SU2.m +++ b/src/tensors/charges/SU2.m @@ -129,5 +129,9 @@ end charge@uint8(labels); end + + function s = GetMD5_helper(a) + s = uint8(a); + end end end From cffb6a3ecbd7343765a3c23da71351416be7678e Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sun, 18 Dec 2022 15:09:26 +0100 Subject: [PATCH 099/245] fix some tests --- src/mps/InfMpo.m | 10 +++++++--- src/mps/UniformMps.m | 2 +- test/TestAlgorithms.m | 2 +- test/TestInfJMpo.m | 8 +++++--- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index 651dda7..d9a4f21 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -62,8 +62,8 @@ mpo.O = O_; end - function s = pspace(mpo) - s = pspace(mpo.O{1}); + function s = pspace(mpo, x) + s = pspace(mpo.O{x}); end function mpo = horzcat(varargin) @@ -79,10 +79,14 @@ function mps = initialize_mps(mpo, vspaces) arguments mpo + end + arguments (Repeating) vspaces end - mps = UniformMps.randnc(pspace(mpo), vspaces); + pspaces = arrayfun(@(x) pspace(mpo, x), 1:period(mpo), 'UniformOutput', false); + args = [pspaces; vspaces]; + mps = UniformMps.randnc(args{:}); end end diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index 186a49d..44a28ad 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -511,7 +511,7 @@ if isa(O, 'InfJMpo') [GL, GR] = environments(O, mps1, mps2); - Hs = AC_hamiltonian(O, mps1, GL, GR, length(H)); + Hs = AC_hamiltonian(O, mps1, GL, GR, period(mps1)); H = Hs{1}; N = size(H.R.var, 2); H.R.var = H.R.var(1, N, 1); diff --git a/test/TestAlgorithms.m b/test/TestAlgorithms.m index 1ca291c..31864a0 100644 --- a/test/TestAlgorithms.m +++ b/test/TestAlgorithms.m @@ -36,6 +36,7 @@ function test2dIsing(tc, alg, unitcell, symm) end function test1dIsing(tc, unitcell, alg, symm) + alg.which = 'smallestreal'; tc.assumeTrue(unitcell > 1 || isa(alg, 'IDmrg') || isa(alg, 'Vumps')) E0 = -1.273 * unitcell; @@ -55,7 +56,6 @@ function test1dIsing(tc, unitcell, alg, symm) end [gs, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyEqual(lambda, E0, 'RelTol', 1e-2); tc.verifyEqual(expectation_value(gs, mpo), E0, 'RelTol', 1e-2); end end diff --git a/test/TestInfJMpo.m b/test/TestInfJMpo.m index 2988c22..80d23ba 100644 --- a/test/TestInfJMpo.m +++ b/test/TestInfJMpo.m @@ -66,10 +66,12 @@ function test1dHeisenberg(tc) mpo = InfJMpo.Heisenberg('Spin', SU2(3), 'Symmetry', 'SU2'); mpo = [mpo mpo]; - mps = UniformMps.randnc(pspace(mpo), ... - GradedSpace.new(SU2(1:2:5), [5 5 1], false, SU2(1:2:5), [5 5 1], false)); + vspace1 = GradedSpace.new(SU2(1:2:5), [5 5 1], false); + vspace2 = GradedSpace.new(SU2(1:2:5), [5 5 1], false); + mps = initialize_mps(mpo, vspace1, vspace2); - [gs_mps, lambda] = fixedpoint(alg, mpo, mps); + [gs_mps] = fixedpoint(alg, mpo, mps); + lambda = expectation_value(gs_mps, mpo); tc.verifyEqual(lambda / period(mps), -1.40, 'RelTol', 1e-2); end From 3adb2249dda51a8671fbfa804c09207141693078 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sun, 18 Dec 2022 16:03:59 +0100 Subject: [PATCH 100/245] fix test --- test/TestInfJMpo.m | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/TestInfJMpo.m b/test/TestInfJMpo.m index 80d23ba..50e60ae 100644 --- a/test/TestInfJMpo.m +++ b/test/TestInfJMpo.m @@ -49,8 +49,7 @@ function test1dIsing(tc) tc.verifyTrue(isapprox(lambda, -1.27, 'RelTol', 1e-2)) mpo = InfJMpo.Ising(1, 1, 'Symmetry', 'Z2'); - mps = UniformMps.randnc(pspace(mpo), ... - GradedSpace.new(Z2(0, 1), [D D] ./ 2, false)); + mps = initialize_mps(mpo, GradedSpace.new(Z2(0, 1), [D D] ./ 2, false)); [mps2, lambda2] = fixedpoint(alg, mpo, mps); tc.verifyTrue(isapprox(lambda, -1.27, 'RelTol', 1e-2)) From 9907c6beee587917f21f4a5e9a1b85fbe5526c02 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sun, 18 Dec 2022 16:04:15 +0100 Subject: [PATCH 101/245] better error message. --- src/tensors/spaces/GradedSpace.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tensors/spaces/GradedSpace.m b/src/tensors/spaces/GradedSpace.m index 1640170..8a49b6a 100644 --- a/src/tensors/spaces/GradedSpace.m +++ b/src/tensors/spaces/GradedSpace.m @@ -333,7 +333,8 @@ end function bool = le(space1, space2) - assert(isscalar(space1) && isscalar(space2)); + assert(isscalar(space1) && isscalar(space2), 'spaces:scalar', ... + 'method only defined for scalar inputs.'); [lia, locb] = ismember(charges(space1), charges(space2)); if ~all(lia) bool = false; From 80c16898a90124ee67d90eb5a0cd627c9cf4b09c Mon Sep 17 00:00:00 2001 From: leburgel Date: Mon, 19 Dec 2022 14:43:43 +0100 Subject: [PATCH 102/245] Add SparseArray. --- docs/src/lib/tensors.rst | 2 - docs/src/lib/utility.rst | 5 + src/sparse/SparseTensor.m | 14 +- src/utility/linalg/leftorth.m | 12 +- src/utility/linalg/qrpos.m | 16 + src/utility/sparse/SparseArray.m | 1094 +++++++++++++++++++++++++++++ src/utility/sparse/maybesparse_.m | 6 + test/TestSparseArray.m | 11 + 8 files changed, 1136 insertions(+), 24 deletions(-) create mode 100644 src/utility/linalg/qrpos.m create mode 100644 src/utility/sparse/SparseArray.m create mode 100644 src/utility/sparse/maybesparse_.m create mode 100644 test/TestSparseArray.m diff --git a/docs/src/lib/tensors.rst b/docs/src/lib/tensors.rst index 085d0ef..9f5278e 100644 --- a/docs/src/lib/tensors.rst +++ b/docs/src/lib/tensors.rst @@ -37,5 +37,3 @@ Tensors ------- .. autoclass:: src.tensors.Tensor - -.. autoclass:: src.tensors.SpTensor \ No newline at end of file diff --git a/docs/src/lib/utility.rst b/docs/src/lib/utility.rst index 8c77b18..6da2a9d 100644 --- a/docs/src/lib/utility.rst +++ b/docs/src/lib/utility.rst @@ -28,6 +28,11 @@ Permutations .. automodule:: src.utility.permutations +Sparse Arrays +------------- + +.. automodule:: src.utility.sparse + Uninit ------ diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index 64a7f14..0cdeb24 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -172,20 +172,12 @@ function disp(t) dim2str(t.sz), nz); spc = floor(log10(max(double(t.ind), [], 1))) + 1; - if numel(spc) == 1 - fmt = strcat("\t(%", num2str(spc(1)), "u)"); - else - fmt = strcat("\t(%", num2str(spc(1)), "u,"); - for i = 2:numel(spc) - 1 - fmt = strcat(fmt, "%", num2str(spc(i)), "u,"); - end - fmt = strcat(fmt, "%", num2str(spc(end)), "u)"); - end - + fmt_subs = sprintf("%%%du,", spc(1:end-1)); + fmt_subs = sprintf("%s%%%du", fmt_subs, spc(end)); + fmt = sprintf("\t(%s)", fmt_subs); for i = 1:nz fprintf('%s\t\t', compose(fmt, t.ind(i,:))); disp(t.var(i)); - fprintf('\n'); end end diff --git a/src/utility/linalg/leftorth.m b/src/utility/linalg/leftorth.m index 28bd781..ff8fd48 100644 --- a/src/utility/linalg/leftorth.m +++ b/src/utility/linalg/leftorth.m @@ -10,17 +10,7 @@ [Q, R] = qr(A, 0); case 'qrpos' - [Q, R] = qr(A, 0); - if isrow(Q) - Q = Q * sign(R(1)); - R = R * sign(R(1)); - else - D = diag(R); - D(abs(D) < 1e-12) = 1; - D = sign(D); - Q = Q .* D'; - R = D .* R; - end + [Q, R] = qrpos(A, 0); case 'ql' [Q, R] = qr(flip(A, 2), 0); diff --git a/src/utility/linalg/qrpos.m b/src/utility/linalg/qrpos.m new file mode 100644 index 0000000..d6ba3fd --- /dev/null +++ b/src/utility/linalg/qrpos.m @@ -0,0 +1,16 @@ +function [Q, R] = qrpos(A, varargin) +% Positive orthogonal-triangular decomposition. + +[Q, R] = qr(A, varargin{:}); +if isrow(Q) + Q = Q * sign(R(1)); + R = R * sign(R(1)); +else + D = diag(R); + D(abs(D) < 1e-12) = 1; + D = sign(D); + Q = Q .* D'; + R = D .* R; +end + +end diff --git a/src/utility/sparse/SparseArray.m b/src/utility/sparse/SparseArray.m new file mode 100644 index 0000000..efe4545 --- /dev/null +++ b/src/utility/sparse/SparseArray.m @@ -0,0 +1,1094 @@ +classdef SparseArray + % Class for multi-dimensional sparse arrays. + % + % Limited to arrays with a total number of elements of at most 2^48-1. + + %% Properties + properties (Access = private) + var = sparse([]) % (:, 1) double + sz = [] % (1, :) + end + + + %% Constructors + methods + function a = SparseArray(varargin) + % Create a sparse array. + % + % Usage + % ----- + % :code:`a = SparseArray(subs, vals, sz)` + % uses the rows of :code:`subs` and :code:`vals` to generate a sparse array + % :code:`a` of size :code:`sz = [m1 m2 ... mn]`. :code:`subs` is a + % :code:`p` x :code:`n` array specifying the subscripts of the nonzero values + % to be inserted into :code:`a`. The k-th row of :code:`subs` specifies the + % subscripts for the k-th value in :code:`vals`. + % The argument :code:`vals` may be scalar, in which case it is expanded to be + % the same length as :code:`subs`, i.e., it is equivalent to + % :code:`vals * (p, 1)`. + % In the case of duplicate subscripts in :code:`subs`, the corresponding + % values are added. + % + % :code:`a = SparseArray` + % Empty constructor. + % + % :code:`a = SparseArray(b)` + % Copies/converts :code:`b` if it is a :class:`SparseArray`, a dense array or a sparse + % matrix. + % + % :code:`a = SparseArray(b, sz)` + % Copies/converts :code:`b` if it is a :class:`SparseArray`, a dense array or a sparse + % matrix, and sets the size of :code:`a` to :code:`sz` + % + % Example + % ------- + % .. code-block:: matlab + % + % >> subs = [1 1 1; 1 1 3; 2 2 2; 4 4 4; 1 1 1; 1 1 1]; + % >> vals = [0.5; 1.5; 2.5; 3.5; 4.5; 5.5]; + % >> sz = [4 4 4]; + % >> a = SparseArray(subs, vals, sz) %<-- sparse 4x4x4 + + if (nargin == 0) || ((nargin == 1) && isempty(varargin{1})) + % Empty constructor + return; + + elseif nargin == 1 + % Single argument + + source = varargin{1}; + + switch(class(source)) + + % copy constructor + case 'SparseArray' + a.var = source.var; + a.sz = source.sz; + + % sparse matrix, dense array + case {'numeric', 'logical', 'double'} + a.var = reshape(source, [], 1); + if ~issparse(source) + a.var = sparse(a.var); + end + a.sz = size(source); + + otherwise + error('sparse:UnsupportedConstructor', 'Unsupported use of SparseArray constructor.'); + + end + + + elseif nargin == 2 + % Two arguments + + var = varargin{1}; + sz = varargin{2}(:).'; + if isempty(sz) + sz = size(var); + end + if numel(sz) < 2 + sz = [sz, 1]; + end + if numel(var) ~= prod(sz) + error('sparse:ArgumentError', 'Incompatible size vector and input array.'); + end + + switch(class(var)) + + % copy constructor + case 'SparseArray' + a.var = var.var; + a.sz = sz; + + % sparse matrix, dense array + case {'numeric', 'logical', 'double'} + a.var = reshape(var, [], 1); + if ~issparse(var) + a.var = sparse(a.var); + end + a.sz = sz; + + otherwise + error('sparse:UnsupportedConstructor', 'Unsupported use of SparseArray constructor.'); + + end + + + elseif nargin == 3 + % Three arguments + subs = varargin{1}; + vals = varargin{2}; + sz = varargin{3}(:)'; + if numel(sz) < 2 + sz = [sz, 1]; + end + + ind = sub2ind_(sz, subs); + a.var = sparse(ind, ones(size(ind)), vals, prod(sz), 1); + a.sz = sz; + + else + error('sparse:UnsupportedConstructor', 'Unsupported use of SparseArray constructor.'); + end + + end + + end + + + %% Methods + methods + + function a = abs(a) + % Absolute value. + % + % Arguments + % --------- + % a : :class:`SparseArray` + % input array. + % + % Returns + % ------- + % b : :class:`SparseArray` + % output array. + a.var = abs(a.var); + end + + function a = chop(a, tol) + % Set all nonzero values in :class:`SparseArray` who's absolute value is below + % a given threshold to zero. + % + % Arguments + % --------- + % a : :class:`SparseArray` + % input array. + % + % tol : :class:`float` , optional + % threshold tolerance for absolute values of entries, defaults to + % :code:`1e-15`. + % + % Returns + % ------- + % b : :class:`SparseArray` + % sparse array with entries of absolute value below :code:`tol` set to zero. + arguments + a + tol = 1e-15 + end + + [r, c, v] = find(a.var); + drop = abs(v) < tol; + a.var(r(drop), c(drop)) = 0; + end + + function a = conj(a) + % Complex conjugate. + % + % Arguments + % --------- + % a : :class:`SparseArray` + % input array. + % + % Returns + % ------- + % b : :class:`SparseArray` + % output array. + a.var = conj(a.var); + end + + function a = ctranspose(a) + % Complex conjugate transpose. + % + % Only defined for 2D sparse arrays. + % + % Usage + % ----- + % :code:`b = ctranspose(a)` + % + % :code:`b = a'` + % + % Arguments + % --------- + % a : :class:`SparseArray` + % input array. + % + % Returns + % ------- + % b : :class:`SparseArray` + % output array. + assert(ismatrix(a), 'sparse:RankError', 'ctranspose is only defined for 2D sparse arrays.'); + a = permute(conj(a), [2, 1]); + end + + function d = diag(a) + assert(ismatrix(a), 'sparse:RankError', 'diag is only defined for 2D sparse arrays.'); + d = diag(reshape(a.var, a.sz)); + end + + function disp(a) + nz = nnz(a); + + if nz == 0 + fprintf('all-zero %s of size %s\n', class(a), ... + dim2str(a.sz)); + return + end + + fprintf('%s of size %s with %d nonzeros:\n', class(a), ... + dim2str(a.sz), nz); + + if (nz > 1000) + r = input('Big array, do you want to print all nonzeros? (y/n) ', 's'); + if ~strcmpi(r, 'y'), return, end + end + + [subs, ~, vals] = find(a); + spc = floor(log10(max(double(subs), [], 1))) + 1; + fmt_subs = sprintf('%%%du,', spc(1:end-1)); + fmt_subs = sprintf('%s%%%du', fmt_subs, spc(end)); + fmt = sprintf('\t(%s)%%s\n', fmt_subs); + S = evalc('disp(vals)'); % abuse builtin display + S = splitlines(S); + if contains(S{1}, '*') % big numbers + fprintf('%s\n', S{1}); + S = S(2:end); + end + for i = 1:nz + fprintf(fmt, subs(i,:), S{i}); + end + end + + function varargout = eig(a, varargin) + % Find eigenvalues and eigenvectors of a 2D sparse array. + % + % See Also + % -------- + % `Documentation for builtin Matlab eig `_. + assert(ismatrix(a), 'sparse:RankError', 'eig is only defined for 2D sparse arrays.'); + [varargout{1:nargout}] = eig(reshape(a.var, a.sz), varargin{:}); + varargout = cellfun(@maybesparse_, varargout, 'UniformOutput', false); + end + + function varargout = eigs(a, varargin) + % Find a few eigenvalues and eigenvectors of a 2D sparse array. + % + % See Also + % -------- + % `Documentation for builtin Matlab eigs `_. + assert(ismatrix(a), 'sparse:RankError', 'eigs is only defined for 2D sparse arrays.'); + [varargout{1:nargout}] = eigs(reshape(a.var, a.sz), varargin{:}); + varargout = cellfun(@maybesparse_, varargout, 'UniformOutput', false); + end + + function [subs, idx, vals] = find(a) + % Find subscripts of nonzero elements in a sparse array. + % + % Arguments + % --------- + % a : :class:`SparseArray` + % input array + % + % Returns + % ------- + % subs : (:, :) :class:`int` + % subscripts of nonzero array entries. + % idx : (:, 1) :class:`int` + % linear indices of nonzero array entries. + % var : (:, 1) :class:`double` + % values of nonzero array entries. + [idx, ~, vals] = find(a.var); + subs = ind2sub_(a.sz, idx); + end + + function d = full(a) + % Convert a sparse array to a dense array. + % + % Arguments + % --------- + % a : :class:`SparseArray` + % input array. + % + % Returns + % ------- + % d : :class:`double` + % dense output array. + d = reshape(full(a.var), a.sz); + end + + function a = groupind(a, g) + % Group (block) specified sets of contiguous indices. + % + % Arguments + % --------- + % a : :class:`SparseArray` + % input array. + % g : (1, :) :class:`int` + % list of number of contiguous indices to be grouped in each index of the + % output tensor. + % + % Returns + % ------- + % b : :class:`SparseArray` + % output array with grouped indices. + % + % Example + % ------- + % .. code-block:: matlab + % + % >> a = SparseArray.random([2, 3, 4, 5, 6], .1); + % >> b = groupind(a, [3, 2]); %<-- sparse 24x30 + assert(sum(g) == ndims(a), 'sparse:InvalidGrouping', 'Invalid grouping.') + r = zeros(1, length(g)); + offset = 0; + for i = 1:length(g) + r(i) = prod(a.sz(1+offset:g(i)+offset)); + offset = offset + g(i); + end + a = reshape(a, r); + end + + function a = imag(a) + % Complex imaginary part of sparse array. + % + % Arguments + % --------- + % a : :class:`SparseArray` + % input array. + % + % Returns + % ------- + % b : :class:`SparseArray` + % output array with real entries corresponding to the imaginary part of the + % entries of :code:`a`. + a.var = imag(a.var); + end + + function bool = ismatrix(a) + bool = ndims(a) == 2; + end + + function bool = isnumeric(~) + % Determine whether input is numeric. + % + % Arguments + % --------- + % a : :class:`SparseArray` + % input array. + % + % Returns + % ------- + % bool : :class:`logical` + % defaults to :code:`true` for :class:`SparseArray`. + bool = true; + end + + function bool = isscalar(~) + % Determine whether input is scalar. + % + % Arguments + % --------- + % a : :class:`SparseArray` + % input array. + % + % Returns + % ------- + % bool : :class:`logical` + % defaults to :code:`false` for :class:`SparseArray`. + bool = false; + end + + function bool = isrow(a) + % Determine whether input is row vector. + % + % Arguments + % --------- + % a : :class:`SparseArray` + % input array. + % + % Returns + % ------- + % bool : :class:`logical` + % defaults to :code:`false` for :class:`SparseArray`. + bool = ismatrix(a) && size(a, 1) == 1; + end + + function bool = isstruct(~) + bool = false; + end + + function c = ldivide(a, b) + % Elementwise left division for sparse arrays. + % + % :code:`ldivide(a, b)` is called for the syntax :code:`a .\ b` where :code:`a` + % or :code:`b` is a :class:`SparseArray`. :code:`a`and :code:`b` must have the + % same size, unless one is a scalar. + % + % Arguments + % --------- + % a, b : :class:`SparseArray` or :class:`double` + % input arrays to be divided. + % + % Returns + % ------- + % c : :class:`SparseArray` + % elementwise left division of :code:`b` by :code:`a`. + c = rdivide(b, a); + end + + function c = minus(a, b) + % Elementwise subtraction for sparse arrays. + % + % :code:`minus(a, b)` is called for the syntax :code:`a - b` where :code:`a` + % or :code:`b` is a :class:`SparseArray`. :code:`a`and :code:`b` must have the + % same size, unless one of the is scalar. Scalar can be subtracted from a sparse + % array of any size, resulting in a dense array. + % + % Arguments + % --------- + % a, b : :class:`SparseArray` or :class:`double` + % intput arrays. + % + % Returns + % ------- + % c : :class:`SparseArray` or :class:`double` + % output array. + % + % Example + % ------- + % .. code-block:: matlab + % + % >> a = SparseArray.random([4 3 2], .1); + % >> b = SparseArray.random([4 3 2], .1); + % >> a - b %<-- sparse + % >> a - 5 %<-- dense + % >> a - 0 %<-- dense + % >> a - full(a) %<-- dense + if (isa(a, 'double') && ~issparse(a)) || (isa(b, 'double') && ~issparse(b)) + c = full(a) - full(b); + return; + end + c = SparseArray(a); b = SparseArray(b); + c.var = c.var - b.var; + end + + function c = mrdivide(a, b) + % Matrix right division for sparse arrays. + % + % :code:`mrdivide(a, b)` is called for the syntax :code:`a / b` when :code:`a` + % is sparse and :code:`b` is a scalar. + % + % Arguments + % --------- + % a : :class:`SparseArray` + % intput array. + % b : :class:`double` + % scalar to divide by + % + % Returns + % ------- + % c : :class:`SparseArray` + % output array. + % + % Example + % ------- + % .. code-block:: matlab + % + % >> a = SparseArray.random([4 3 2], .1); + % >> a / 3 %<-- sparse + assert(isscalar(b), 'sparse:ScalarDivide', 'SparseArray.mrdivide only supports the scalar case.') + c = a; + c.var = c.var/b; + return; + end + + function c = mtimes(a, b) + % Matrix multiplication for 2D sparse arrays. + % + % :code:`mtimes(a, b)` is called for the syntax :code:`a * b` where :code:`a` + % or :code:`b` is a :class:`SparseArray`. + % + % See Also + % -------- + % `mtimes `_ + if isscalar(a) + c = b; + c.var = a * c.var; + return + elseif isscalar(b) + c = a; + c.var = b * c.var; + return + end + + sz = [size(a,1) size(b,2)]; + + if isa(a, 'SparseArray') + a = reshape(a.var, a.sz); + end + + if isa(b, 'SparseArray') + b = reshape(b.var, b.sz); + end + + % always return sparse array; TODO: make sure this doesn't break things + c = SparseArray(a * b, sz); + end + + function n = ndims(t) + % Number of dimensions of a sparse array. + % + % Note that unlike the `builtin Matlab behavior ` + % trailing singleton dimensions are not ignored. + % + % Example + % ------- + % .. code-block:: matlab + % + % >> a = SparseArray.random([4 3 1], .1); + % >> ndims(a) %<-- returns 3 + n = size(t.sz, 2); + end + + function n = nnz(a) + % Number of nonzero elements in sparse array. + n = nnz(a.var); + end + + function nrm = norm(a) + % Frobenius norm of a sparse array. + nrm = norm(a.var); + end + + function n = numArgumentsFromSubscript(varargin) + n = 1; + end + + function n = numel(a) + % Number of elements in a sparse array. + n = prod(size(a)); + end + + function c = outer(a, b) + % Outer product of two sparse arrays. + % + % Warning + % ------- + % Not :code:`kron`. + varC = kron(b.var, a.var); % ordering is counterintuitive here + c = SparseArray(varC, [size(a), size(b)]); + end + + function a = permute(a, order) + % Permute sparse array dimensions. + if length(a.sz) < length(order) + a = reshape(a, [a.size, ones(1,length(order)-length(a.size))]); + end + if issorted(order) % don't rebuild t if it was a trivial permute + return + end + sz1 = a.sz; + sz2 = sz1(order); + [idx, ~, v] = find(a.var); + idx2 = sub2ind_(sz2, ind2sub_(sz1, idx, order)); + a = SparseArray(sparse(idx2, ones(size(idx2)), v, numel(a.var), 1), sz2); + end + + function c = plus(a, b) + % Elementwise addition for sparse arrays. + % + % :code:`plus(a, b)` is called for the syntax :code:`a + b` when :code:`a` or + % :code:`b` is a sparse array. :code:`a` and :code:`b` must have the same size, + % unless one is a scalar. A scalar can be added to a sparse array of any size. + % + % Example + % ------- + % .. code-block:: matlab + % + % >> a = SparseArray.random([4 3 2], .1); + % >> a + b %<-- sparse + % >> a + 5 %<-- dense + % >> a + 0 %<-- dense + % >> a + full(a) %<-- dense + if (isa(a, 'double') && ~issparse(a)) || (isa(b, 'double') && ~issparse(b)) + c = full(a) + full(b); + return; + end + c = SparseArray(a); b = SparseArray(b); + c.var = c.var + b.var; + end + + function c = power(a, b) + % Elementwise power for sparse array. + assert(isscalar(b), 'sparse:NonScalarPower', 'SparseArray only supports power with a scalar.') + c = a; + c.var = c.var.^b; + end + + function varargout = qr(a, varargin) + % Orthogonal-triangular decomposition for a sparse array. + % + % See Also + % -------- + % `Documentation for builtin Matlab qr `_. + assert(ismatrix(a), 'sparse:RankError', 'qr is only defined for 2D sparse arrays.'); + [varargout{1:nargout}] = qr(reshape(a.var, a.sz), varargin{:}); + varargout = cellfun(@maybesparse_, varargout, 'UniformOutput', false); + end + + function [Q, R] = qrpos(a, varargin) + % Positive orthogonal-triangular decomposition for a sparse array. + assert(ismatrix(a), 'sparse:RankError', 'qrpos is only defined for 2D sparse arrays.'); + [Q, R] = qr(a, varargin{:}); + if isrow(Q) + Q = Q * sign(R(1)); + R = R * sign(R(1)); + else + D = diag(R); + D(abs(D) < 1e-10) = 1; + D = sign(D); + Q = Q * diag(D); % TODO: implement broadcasting for .* + R = diag(D) * R; + end + end + + function c = rdivide(a, b) + % Elementwise right division for sparse arrays. + % + % :code:`rdivide(a, b)` is called for the syntax :code:`a ./ b` where :code:`a` + % or :code:`b` is a :class:`SparseArray`. :code:`a`and :code:`b` must have the + % same size, unless one is a scalar. + % + % Arguments + % --------- + % a, b : :class:`SparseArray` or :class:`double` + % input arrays to be divided. + % + % Returns + % ------- + % c : :class:`SparseArray` + % elementwise left division of :code:`a` by :code:`b`. + % + % Example + % ------- + % .. code-block:: matlab + % + % >> a = SparseArray.random([4 3 2], .1); + % >> a ./ 5 %<-- sparse + % >> 5 ./ a %<-- dense + % >> a ./ full(a) %<-- sparse + % >> full(a) ./ a %<-- dense + if isa(a, 'SparseArray') && isa(b, 'SparseArray') + c = a; + c.var = c.var ./ b.var; + elseif isa(a, 'SparseArray') + c = a.var ./ b; + elseif isa(b, 'SparseArray') + c = a ./ b.var; + end + end + + function b = real(a) + % Complex real part of sparse array. + % + % Arguments + % --------- + % a : :class:`SparseArray` + % input array. + % + % Returns + % ------- + % b : :class:`SparseArray` + % output array with real entries corresponding to the real part of the + % entries of :code:`a`. + b = a; + b.var = real(b.var); + end + + function a = reshape(a, varargin) + % Reshape sparse array. + if nargin == 2 + a.sz = varargin{1}; + else + sz = ones(1, numel(varargin)); + I = []; + for i = 1:numel(varargin) + if isempty(varargin{i}) + I = i; + else + sz(i) = varargin{i}; + end + end + sz(I) = prod(a.sz) / prod(sz); + a.sz = sz; + end + end + + function a = sign(a) + % Signum function. + a.var = sign(a.var); + end + + + function d = size(a, dim) + % Sparse array dimensions. + % + % Usage + % ----- + % :code:`d = size(a)` + % returns the size of the array. + % + % :code:`d = size(a, dim)` + % returns the sizes of the dimensions specified by :code:`dim`, which is + % either a scalar or a vector of dimensions. + if nargin > 1 + d = a.sz(dim); + else + d = a.sz; + end + end + + function b = sparse(a) + % Convert 2D sparse array to a sparse matrix. + assert(ismatrix(a), 'sparse:NotMatrix', sprintf('Cannot convert array of rank %d to sparse matrix.', ndims(a))) + b = reshape(a.var, a.size); + end + + function b = spmatrix(a) + % Convert 2D sparse array to a sparse matrix. + % + % See Also + % -------- + % :code:`sparse` + b = sparse(a); + end + + function b = squeeze(a) + % Remove singleton dimensions from a sparse array. + % + % Usage + % ----- + % :code:`b = squeeze(a)` + % returns a sparse array :code:`b` with the same elements as :code:`a` but + % with all the singleton dimensions removed. + % + % Example + % ------- + % .. code-block:: matlab + % + % >> squeeze(SparseArray.random([2, 1, 3], 0.5)) %<-- returns a 2 x 3 SparseArray + % >> squeeze(SparseArray([1, 1, 1], 1, [1, 1, 1])) %<-- returns a scalar + if sum(a.sz > 1) == 0 + b = full(a.var); + return + end + % always give n x 1 SparseArray in case of only 1 non-singleton dimension, + % consistent with class constructor + b = a; + b.sz = [a.size(a.size>1), ones(1, 2-sum(a.size>1))]; + end + + function a = subsasgn(a, s, rhs) + % Subscripted assignment for sparse array. + error('Not implemented.') + end + + function a_sub = subsref(a, s) + % Subscripted reference for a sparse array. + % + % Usage + % ----- + % :code:`a_sub = a(i)` + % linear indexing for SparseArray with only one non-singleton dimension, + % returns a scalar. + % + % :code:`a_sub = a(i1, i2, ..., iN)` + % where each :code:`in` is an integer index, returns a scalar. + % + % :code:`a_sub = a(R1, R2, ..., RN)` + % where each :code:`Rn` is either a colon, an array of integers representing a + % slice in this dimension or a logical array representing the logical indexing + % of this dimension. Returns a sparse array. + % + % :code:`a_sub = a(S)` + % where :code:`S` is a :code:`1` x :code:`n` array of integer subscripts (for + % dimensions that are indexed) or zeroes (for dimensions that are not + % indexed). Returns a sparse array. + % + % Example + % ------- + % .. code-block:: matlab + % + % >> a = SparseArray([4, 4, 4; 2, 2, 1; 2, 3, 2], [3; 5; 1], [4, 4, 4]); + % >> a(1, 2, 1) %<-- returns zero + % >> a(4, 4, 4) %<-- returns 3 + % >> a(2, :, :) %<-- returns a 1 x 4 x 4 sparse array + % >> a([0, 4, 0]) %<-- returns a 4 x 1 x 4 sparse array + % >> a([4, 4, 0]) %<-- returns a 1 x 1 x 4 sparse array + switch s(1).type + + case '()' % regular subscript indexing + + % linear subscript indexing with only one non-singleton dimension + if (numel(s(1).subs) == 1 && numel(s(1).subs{1}) == 1 && sum(size(a)~=1) == 1) + subs = a.size; + subs(subs~=1) = s(1).subs{1}; + a_sub = full(a.var(sub2ind_(a.size, subs))); + + % regular multi-dimensional subscript indexing + elseif (numel(s(1).subs) == ndims(a)) + + % xtract the subdimensions to be extracted from t + sbs = s(1).subs; + + % Error check that range is valid + okcolon = cellfun(@(x) ischar(x) && x == ':', sbs); + okrange = cellfun(@(x) isnumeric(x) && isreal(x) && ... + ~any(isnan(x)) && ~any(isinf(x)) && ... + isequal(x,round(x)) && all(x > 0), sbs); + oklogical = arrayfun(@(i) islogical(sbs{i}) && ... + length(sbs{i}) == a.size(i), 1:ndims(a)); + if ~all(okcolon | okrange | oklogical) + error('Invalid subscript.'); + end + + % simple case: all dimensions indexed + indexed = cellfun(@isnumeric, sbs) & cellfun(@(x) length(x) == 1, sbs); + if all(indexed) + a_sub = full(a.var(sub2ind_(a.size, [sbs{:}]))); + return; + end + + % convert logical indexing to integer ranges for easy handling + for i = find(oklogical) + sbs{i} = find(sbs{i}); + end + + % extract subscripts and values of nonzero elements + [subs, ~, nz] = find(a); + + % some trickery to do actual slicing + f = true(size(nz)); % auxiliary array of booleans + new_sz = a.sz; % initialize size of sliced tensor + for i = fliplr(1:ndims(a)) + if strcmp(sbs{i}, ':') + continue; % do nothing if not sliced + end + A = sbs{i}; + if length(A) ~= length(unique(A)) + error('Repeated index in position %i.', i); + end + if ~isempty(subs) + % fast intersect for this dimension + B = subs(:, i); + P = false(max(max(A),max(B))+1,1); + P(A+1) = true; + % throw away anything that has no intersect with the slice in this dimension + f = and(f, P(B+1)); + % relabel subscripts in this dimension to match slice + % order + [~, ~, temp] = unique([A(:); subs(f, i)], 'stable'); + subs(f, i) = temp(length(A)+1:end); + % adjust size in this dimension + new_sz(i) = length(A); + end + end + if isempty(subs) + a_sub = SparseArray([], [], new_sz); + else + a_sub = SparseArray(subs(f, :), nz(f), new_sz); + end + + return; + + % hacky indexing using an array + elseif (numel(s(1).subs) == 1) && (numel(s(1).subs{1}) == ndims(a)) + + % extract the subdimensions to be extracted from t + sbs = s(1).subs{1}; + + % error check that range is valid: each subscript must be a nonnegative integer + if ~ (isnumeric(sbs) && isreal(sbs) && ~any(isnan(sbs)) && ~any(isinf(sbs)) ... + && all(sbs >= 0)) + error('Invalid subscript.'); + end + + indexed = sbs ~= 0; + + if all(indexed) % if all dimensions are indexed with integer, return scalar + a_sub = full(a.var(sub2ind_(a.size, sbs))); + return; + end + + new_sz = a.size; + new_sz(indexed) = 1; % set indexed dimensions to 1 + + [subs, ~, nz] = find(a); + good_ind = all(subs(:, indexed) == sbs(indexed), 2); + good_subs = subs(good_ind, :); + good_vals = nz(good_ind); + good_subs(:, indexed) = 1; % set indexed subscripts to 1 for new subs + + a_sub = SparseArray(good_subs, good_vals, new_sz); % construct output + return; + + else + error('sparse:InvalidIndexing', 'Incorrect indexing into SparseArray.') + end + + otherwise + % only overload parentheses indexing + a_sub = builtin('subsref', a, s); + end + end + + function s = sum(a) + % Sum of all elements of a SparseArray. + s = full(sum(a.var)); + end + + function varargout = svd(a, varargin) + % Singular value decomposition. + error('Not supported for sparse arrays.') + end + + function varargout = svds(a, varargin) + % Find a few singular values and vectors. + % See Also + % -------- + % `Documentation for builtin Matlab eig `_. + assert(ismatrix(a), 'sparse:RankError', 'svds is only defined for 2D sparse arrays.'); + [varargout{1:nargout}] = svds(reshape(a.var, a.sz), varargin{:}); + varargout = cellfun(@maybesparse_, varargout, 'UniformOutput', false); + end + + function c = times(a, b) + % Array multiplication for sparse tensors. + % + % :code:`c = times(a, b)` is called for the syntax :code:`a .* b` when :code:`a` + % or :code:`b` is a sparse array. :code:`a` and :code:`b` must have the same + % size, unless one is a scalar. A scalar can be be multiplied by a sparse array + % of any size. + % + % Example + % ------- + % .. code-block:: matlab + % + % >> a = SparseArray.random([4 3 2], .1); + % >> a .* b %<-- sparse + % >> a .* 5 %<-- sparse + % >> a .* 0 %<-- sparse + % >> a .* full(a) %<-- sparse + if isscalar(b) || ~isa(b,'SparseArray') + c = SparseArray(a.var .* b(:), a.sz); + elseif isscalar(a) || ~isa(a,'SparseArray') + c = SparseArray(b.var .* a(:), b.sz); + else + c = SparseArray(b.var .* a.var, b.sz); + end + end + + function a = transpose(a) + % Transpose. + % + % Only defined for 2D sparse arrays. + % + % Usage + % ----- + % :code:`b = transpose(a)` + % + % :code:`b = a.'` + % + % Arguments + % --------- + % a : :class:`SparseArray` + % input array. + % + % Returns + % ------- + % b : :class:`SparseArray` + % output array. + a = permute(a, [2, 1]); + end + + function a = uminus(a) + % Unary minus. + % + % Usage + % ----- + % :code:`b = uminus(a)` + % + % :code:`b = -a` + % + % Arguments + % --------- + % a : :class:`SparseArray` + % input array. + % + % Returns + % ------- + % b : :class:`SparseArray` + % output array. + a.var = -a.var; + end + + function a = uplus(a) + % Unary plus. + % + % Usage + % ----- + % :code:`b = uplus(a)` + % + % :code:`b = +a` + % + % Arguments + % --------- + % a : :class:`SparseArray` + % input array. + % + % Returns + % ------- + % b : :class:`SparseArray` + % output array. + return; + end + end + + methods (Static) + function a = delta(numinds, inddim) + % Create delta- (ghz-) array with given number of indices and index dimension. + % + % Arguments + % --------- + % numinds : :class:`int` + % number of indices of delta-array. + % + % inddim : :class:`int` + % dimension of each index of delta-array. + % + % Returns + % ------- + % a : :class:`SparseArray` + % output delta-array. + a = SparseArray(repmat(1:inddim, numinds, 1)', 1, repmat(inddim, 1, numinds)); + end + + + + function a = random(sz, density) + % Create a random complex sparse array. + % + % Arguments + % --------- + % sz : (1, :) :class:`int` + % size of the sparse array + % density : :class:`double` + % density of nonzero elements (0 < :code:`density` < 1) + a = SparseArray([], [], sz); + a.var = sprandn(numel(a), 1, density); + idx = find(a.var); + a.var(idx) = a.var(idx) + 1i * randn(size(a.var(idx))); + end + end + +end diff --git a/src/utility/sparse/maybesparse_.m b/src/utility/sparse/maybesparse_.m new file mode 100644 index 0000000..d5c0436 --- /dev/null +++ b/src/utility/sparse/maybesparse_.m @@ -0,0 +1,6 @@ +function a = maybesparse_(a) +if issparse(a) + a = SparseArray(a); +end +end + diff --git a/test/TestSparseArray.m b/test/TestSparseArray.m new file mode 100644 index 0000000..5917664 --- /dev/null +++ b/test/TestSparseArray.m @@ -0,0 +1,11 @@ +classdef TestSparseArray < matlab.unittest.TestCase + % Unit tests for sparse arrays. + + properties + tol = 1e-12 + end + + methods (Test) + + end +end From 6a4e07538bb27b26011af996af8092d08fedabc0 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 19 Dec 2022 16:05:58 +0100 Subject: [PATCH 103/245] Sparse Tensor concatenation --- src/sparse/SparseTensor.m | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index 64a7f14..2bc7bc8 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -1,6 +1,8 @@ classdef (InferiorClasses = {?Tensor}) SparseTensor < AbstractTensor % Class for multi-dimensional sparse objects. + %#ok<*PROPLC> + properties (Access = private) ind = [] sz = [] @@ -110,6 +112,9 @@ B = reshape(B, A.sz); end + function A = sparse(A) + end + function s = space(t, i) assert(isscalar(i), 'sparse:argerror', ... 'Can only obtain spaces for single index.'); @@ -794,6 +799,30 @@ function disp(t) [t.ind, p] = sortrows(t.ind, width(t.ind):-1:1); t.var = t.var(p); end + + function t = horzcat(varargin) + t = cat(2, varargin{:}); + end + + function t = vertcat(varargin) + t = cat(1, varargin{:}); + end + + function t = cat(dim, t, varargin) + for i = 1:length(varargin) + t2 = sparse(varargin{i}); + N = max(ndims(t), ndims(t2)); + dimcheck = 1:N; + dimcheck(dim) = []; + assert(isequal(size(t, dimcheck), size(t2, dimcheck)), ... + 'sparse:dimagree', 'incompatible sizes for concatenation.'); + newinds = t2.ind; + newinds(:, dim) = newinds(:, dim) + size(t, dim); + t.var = vertcat(t.var, t2.var); + t.ind = vertcat(t.ind, newinds); + t.sz(dim) = t.sz(dim) + size(t2, dim); + end + end end methods (Static) From 4d36dcdf043296cf0e70a74cdd51f1601b3d57e4 Mon Sep 17 00:00:00 2001 From: sanderdemeyer Date: Tue, 20 Dec 2022 10:32:43 +0100 Subject: [PATCH 104/245] eta to output of vumps en vumps2 fixedpoint --- src/algorithms/Vumps.m | 2 +- src/algorithms/Vumps2.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/algorithms/Vumps.m b/src/algorithms/Vumps.m index 36b7c6c..ae30c37 100644 --- a/src/algorithms/Vumps.m +++ b/src/algorithms/Vumps.m @@ -67,7 +67,7 @@ end end - function [mps, lambda, GL, GR] = fixedpoint(alg, mpo, mps) + function [mps, lambda, GL, GR, eta] = fixedpoint(alg, mpo, mps) if period(mpo) ~= period(mps) error('vumps:argerror', ... diff --git a/src/algorithms/Vumps2.m b/src/algorithms/Vumps2.m index afff73b..c8bbff8 100644 --- a/src/algorithms/Vumps2.m +++ b/src/algorithms/Vumps2.m @@ -69,7 +69,7 @@ end end - function [mps, lambda, GL, GR] = fixedpoint(alg, mpo, mps) + function [mps, lambda, GL, GR, eta] = fixedpoint(alg, mpo, mps) if period(mpo) ~= period(mps) error('vumps:argerror', ... 'periodicity of mpo (%d) should be equal to that of the mps (%d)', ... From e9820b0ad894dea8dc99e439d82a741f23efdec6 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 21 Dec 2022 09:35:22 +0100 Subject: [PATCH 105/245] decompose operator into local tensors. --- src/tensors/AbstractTensor.m | 46 ++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/tensors/AbstractTensor.m b/src/tensors/AbstractTensor.m index 1fef306..a4742c7 100644 --- a/src/tensors/AbstractTensor.m +++ b/src/tensors/AbstractTensor.m @@ -375,6 +375,52 @@ d = norm(A - B); end + + function local_operators = decompose_local_operator(H, kwargs) + % convert a tensor into a product of local operators. + % + % Usage + % ----- + % :code:`local_operators = decompose_local_operator(H, kwargs)`. + % + % Arguments + % --------- + % H : :class:`AbstractTensor` + % tensor representing a local operator on N sites. + % + % Keyword Arguments + % ----------------- + % 'Trunc' : cell + % optional truncation method for the decomposition. See also + % :method:`Tensor.tsvd` + arguments + H + kwargs.Trunc = {'TruncBelow', 1e-14} + end + + assert(mod(nspaces(H), 2) == 0, ... + 'InfJMpo:Argerror', 'local operator must have an even amount of legs.'); + H = repartition(H, nspaces(H) ./ [2 2]); + assert(isequal(H.domain, H.codomain), ... + 'InfJMpo:ArgError', 'local operator must be square.'); + + N = indin(H); + local_operators = cell(1, N); + if N == 1 + local_operators{1} = insert_onespace(insert_onespace(H, 1), 3); + else + [u, s, v] = tsvd(H, [1 N+1], [2:N N+2:2*N], kwargs.Trunc{:}); + local_operators{1} = insert_onespace(tpermute(u * s, [1 3 2], [1 2]), 1); + + for i = 2:N-1 + [u, s, v] = tsvd(v, [1 2 N-i+3], [3:(N-i+2) (N-i+4):nspaces(v)], ... + kwargs.Trunc{:}); + local_operators{i} = tpermute(u * s, [1 2 4 3], [2 2]); + end + + local_operators{N} = insert_onespace(repartition(v, [2 1]), 3); + end + end end end From 2335ac2f29da6bdef0440fda17e8caa5eaa5205c Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 21 Dec 2022 09:35:52 +0100 Subject: [PATCH 106/245] docstring --- src/tensors/spaces/AbstractSpace.m | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tensors/spaces/AbstractSpace.m b/src/tensors/spaces/AbstractSpace.m index 9ce9927..285f9d6 100644 --- a/src/tensors/spaces/AbstractSpace.m +++ b/src/tensors/spaces/AbstractSpace.m @@ -258,6 +258,7 @@ end function spaces = insertone(spaces, i, dual) + % insert a trivial space at position i. arguments spaces i = length(spaces) + 1 From 9168e6a5d0f93a1d6f87470159c045c60f9ac132 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 21 Dec 2022 09:36:09 +0100 Subject: [PATCH 107/245] docstring --- src/tensors/Tensor.m | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 250e4e7..ea83832 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -477,6 +477,7 @@ end function tdst = insert_onespace(tsrc, i, dual) + % insert a trivial space at position i. arguments tsrc i = nspaces(tsrc) From 7ea1f57b021cdb2f25bcc94fad8f231eed98f810 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 21 Dec 2022 09:36:30 +0100 Subject: [PATCH 108/245] speedup cat --- src/tensors/charges/ProductCharge.m | 31 +++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/tensors/charges/ProductCharge.m b/src/tensors/charges/ProductCharge.m index 4241c2e..c8d00fa 100644 --- a/src/tensors/charges/ProductCharge.m +++ b/src/tensors/charges/ProductCharge.m @@ -37,17 +37,28 @@ function a = cat(dim, a, varargin) % Concatenate charges. - - for i = 1:length(varargin) - if isempty(varargin{i}), continue; end - if isempty(a) - a = varargin{i}; - continue; - end - for j = 1:length(a.charges) - a.charges{j} = cat(dim, a.charges{j}, varargin{i}.charges{j}); - end + mask = cellfun(@isempty, varargin); + if isempty(a) + firstnonempty = find(~mask, 1); + a = varargin{firstnonempty}; + mask(firstnonempty) = true; end + + for i = 1:length(a.charges) + catcharges = cellfun(@(x) x.charges{i}, varargin(~mask), 'UniformOutput', false); + a.charges{i} = cat(dim, a.charges{i}, catcharges{:}); + end +% +% for i = 1:length(varargin) +% if isempty(varargin{i}), continue; end +% if isempty(a) +% a = varargin{i}; +% continue; +% end +% for j = 1:length(a.charges) +% a.charges{j} = cat(dim, a.charges{j}, varargin{i}.charges{j}); +% end +% end end function a = horzcat(a, varargin) From dedd95c7bc9d1728140dd3e96f8bf4fa2fef596f Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 2 Jan 2023 10:27:04 +0100 Subject: [PATCH 109/245] Bugfixes transfermatrices with auxlegs --- src/mps/MpoTensor.m | 4 +- src/mps/MpsTensor.m | 4 +- src/mps/UniformMps.m | 40 ++++++++++++++++-- src/tensors/spaces/ComplexSpace.m | 4 ++ src/tensors/spaces/SumSpace.m | 69 +++++++++++++++++++++++++++++++ test/TestFiniteMpo.m | 4 +- test/TestUniformMps.m | 11 ++++- 7 files changed, 127 insertions(+), 9 deletions(-) create mode 100644 src/tensors/spaces/SumSpace.m diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index 1573066..fe48936 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -96,13 +96,13 @@ auxlegs_v = nspaces(v) - 3; auxlegs_l = nspaces(L) - 3; auxlegs_r = nspaces(R) - 3; - auxlegs = auxlegs_v + auxlegs_l + auxlegs_r; + newrank = rank(v); newrank(2) = newrank(2) + auxlegs_l + auxlegs_r; v = contract(v, [1 3 5 (-(1:auxlegs_v) - 3 - auxlegs_l)], ... L, [-1 2 1 (-(1:auxlegs_l) - 3)], ... O, [2 -2 4 3], ... R, [5 4 -3 (-(1:auxlegs_r) - 3 - auxlegs_l - auxlegs_v)], ... - 'Rank', rank(v) + [0 auxlegs]); + 'Rank', newrank); end function v = applympo(varargin) diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index e35b127..94fa534 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -345,12 +345,12 @@ auxlegs_v = nspaces(v) - 2; auxlegs_l = L.alegs; auxlegs_r = R.alegs; - auxlegs = auxlegs_v + auxlegs_l + auxlegs_r; + newrank = rank(v); newrank(2) = newrank(2) + auxlegs_l + auxlegs_r; v = contract(v, [1 3 (-(1:auxlegs_v) - 2 - auxlegs_l)], ... L, [-1 2 1 (-(1:auxlegs_l) - 2)], ... R, [3 2 -2 (-(1:auxlegs_r) - 3 - auxlegs_l - auxlegs_v)], ... - 'Rank', rank(v) + [0 auxlegs]); + 'Rank', newrank); end function rho = applyleft(T, B, rho) diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index 44a28ad..e07a05d 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -451,6 +451,40 @@ end function [V, D] = transfereigs(mps1, mps2, howmany, which, eigopts, kwargs) + % Compute the eigenvalues of the transfer matrix of an mps. + % + % Usage + % ----- + % :code:`[V, D] = transfereigs(mps1, mps2, howmany, which, eigopts, kwargs)` + % + % Arguments + % --------- + % mps1 : :class:`UniformMps` + % input mps for top layer. + % + % mps2 : :class:`UniformMps` + % input mps for bottom layer. Default value equal to `mps1`. + % + % howmany : integer + % number of eigenvectors and eigenvalues to compute. + % + % which : 'char' + % type of eigenvectors to target. + % + % Keyword Arguments + % ----------------- + % eigopts + % see keyword arguments for :method:`eigs`. + % + % Verbosity : integer + % detail level for output. + % + % Type : 'char' + % type of transfer matrix to construct. + % + % Charge : :class:`AbstractCharge` + % charge of eigenvectors to target. + arguments mps1 mps2 = mps1 @@ -479,9 +513,9 @@ v0 = []; end - eigkwargs = [fieldnames(eigopts).'; struct2cell(eigopts).']; - [V, D] = eigsolve(T, v0, howmany, which, ... - eigkwargs{:}, 'Verbosity', kwargs.Verbosity); + eigkwargs.Verbosity = kwargs.Verbosity; + eigkwargs = namedargs2cell(eigopts); + [V, D] = eigsolve(T, v0, howmany, which, eigkwargs{:}); if kwargs.Type(1) == 'r', V = V'; end if nargout < 2, V = D; end diff --git a/src/tensors/spaces/ComplexSpace.m b/src/tensors/spaces/ComplexSpace.m index 1aaa77d..2a3af06 100644 --- a/src/tensors/spaces/ComplexSpace.m +++ b/src/tensors/spaces/ComplexSpace.m @@ -171,6 +171,10 @@ style = FusionStyle.Unique; end + + function space = one(~) + space = ComplexSpace(1, false); + end end diff --git a/src/tensors/spaces/SumSpace.m b/src/tensors/spaces/SumSpace.m new file mode 100644 index 0000000..c1eea84 --- /dev/null +++ b/src/tensors/spaces/SumSpace.m @@ -0,0 +1,69 @@ +classdef SumSpace < AbstractSpace + % Direct product structure for a tensor index. + + %% Constructors + methods + function spaces = SumSpace(subspaces) + % Construct an array of vector spaces. + + arguments (Repeating) + subspaces + end + + if nargin == 0 + args = {}; + else + dual = cell(size(subspaces)); + for i = 1:length(subspaces) + dual{i} = isdual(subspaces{i}(1)); + assert(all(isdual(subspaces{i}) == dual{i}), 'Direct product structure should have all dual or all regular spaces.'); + end + args = [subspaces; dual]; + end + + spaces = spaces@AbstractSpace(args{:}); + end + + function space = one(spaces) + space = SumSpace(arrayfun(@one, spaces(1).dimensions)); + end + + function space = infimum(space1, space2) + assert(isscalar(space1) && isscalar(space2)); + assert(isdual(space1) == isdual(space2)); + space = SumSpace(arrayfun(@infimum, space1.dimensions, space2.dimensions)); + end + end + + + %% Manipulations + methods + function spaces = conj(spaces) + for i = 1:length(spaces) + spaces(i).dual = ~spaces(i).dual; + spaces(i).dimensions = conj(subspaces(spaces(i))); + end + end + end + + %% Utility + methods + function bools = eq(spaces1, spaces2) + assert(isequal(size(spaces1), size(spaces2))); + bools = false(size(spaces1)); + for i = 1:numel(bools) + bools(i) = all(subspaces(spaces1(i)) == subspaces(spaces2(i))); + end + end + + function s = subspaces(space, i) + assert(isscalar(space), ... + 'space:argerror', 'method only defined for scalar inputs.'); + s = space.dimensions; + if nargin > 1 + s = s(i); + end + end + end +end + diff --git a/test/TestFiniteMpo.m b/test/TestFiniteMpo.m index 2b0177d..8fcdc92 100644 --- a/test/TestFiniteMpo.m +++ b/test/TestFiniteMpo.m @@ -14,6 +14,9 @@ function testFixedpoints(tc, mpo) [V, D] = eigsolve(mpo); tc.assertTrue(isapprox(mpo.apply(V), D * V)); + + v2 = insert_onespace(V); + v3 = apply(mpo, v2); end function testProperties(tc, mpo) @@ -36,7 +39,6 @@ function testTransposes(tc, mpo) 'transpose should not change mpo'); tc.assertTrue(isequal(domain(mpo.'), codomain(mpo)')); tc.assertTrue(isequal(codomain(mpo.'), domain(mpo)')); - end end end diff --git a/test/TestUniformMps.m b/test/TestUniformMps.m index c4b3f5a..92ae323 100644 --- a/test/TestUniformMps.m +++ b/test/TestUniformMps.m @@ -75,7 +75,16 @@ function testFixedpoints(tc, A) tc.verifyTrue(isapprox(rhoR, apply(T', rhoR)), ... sprintf('rho_right should be a %c%c fixed point.', top, bot)); end - end + end + end + + function testTransferEigs(tc, A) + mps = UniformMps(A); + + [V, D] = transfereigs(mps, mps, 1, 'largestabs'); + [~, charges] = matrixblocks(V); + [V2, D2] = transfereigs(mps, mps, 1, 'largestabs', 'Charge', one(charges)); + end end end From c3a43ce4b676be93ed2393ea2ff70cb449fbc51e Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 2 Jan 2023 11:01:08 +0100 Subject: [PATCH 110/245] updates for sparse tensors --- src/sparse/SparseTensor.m | 106 +++++++++++++++++++++++++++++++++----- src/tensors/Tensor.m | 2 +- test/TestSparseTensor.m | 52 +++++++------------ test/TestTensor.m | 2 +- 4 files changed, 115 insertions(+), 47 deletions(-) diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index caa635c..d0050a3 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -3,12 +3,18 @@ %#ok<*PROPLC> + properties + codomain + domain + end + properties (Access = private) ind = [] sz = [] var (:, 1) Tensor = Tensor.empty(0, 1); end + %% Constructors methods function t = SparseTensor(varargin) if nargin == 0 || (nargin == 1 && isempty(varargin{1})) @@ -22,6 +28,9 @@ t.sz = source.sz; t.var = source.var; + t.codomain = source.codomain; + t.domain = source.domain; + elseif isa(source, 'Tensor') t.sz = ones(1, nspaces(source(1))); t.sz(1:ndims(source)) = size(source); @@ -29,6 +38,7 @@ t.ind = ind2sub_(t.sz, 1:numel(source)); t.var = source(:); + [t.codomain, t.domain] = deduce_spaces(t); else error('sparse:ArgError', 'Unknown syntax.'); end @@ -42,6 +52,7 @@ t.ind = ind; t.var = var; t.sz = max(ind, [], 1); + [t.codomain, t.domain] = deduce_spaces(t); elseif nargin == 3 % indices, values and size ind = varargin{1}; @@ -61,10 +72,84 @@ end t.ind = ind; t.sz = sz; + [t.codomain, t.domain] = deduce_spaces(t); - else - error('sparse:argerror', 'unknown syntax.'); + elseif nargin == 5 % indices, values, size, codomain, domain + ind = varargin{1}; + if ~isempty(ind) && ~isempty(varargin{2}) + var = reshape(varargin{2}, [], 1); + if isscalar(var), var = repmat(var, size(ind, 1), 1); end + assert(size(ind, 1) == size(var, 1), 'sparse:argerror', ... + 'indices and values must be the same size.'); + sz = reshape(varargin{3}, 1, []); + assert(isempty(ind) || size(ind, 2) == length(sz), 'sparse:argerror', ... + 'number of indices does not match size vector.'); + assert(isempty(ind) || all(max(ind, [], 1) <= sz), 'sparse:argerror', ... + 'indices must not exceed size vector.'); + t.var = var; + else + sz = reshape(varargin{3}, 1, []); + end + t.ind = ind; + t.sz = sz; + t.codomain = varargin{4}; + t.domain = varargin{5}; + end + end + end + + methods (Static) + function t = new(f, codomain, domain, kwargs) + arguments + f + codomain SumSpace + domain SumSpace + kwargs.Density = 1 + end + + sz = zeros(1, length(codomain) + length(domain)); + for i = 1:length(codomain) + sz(i) = length(subspaces(codomain(i))); + end + for i = 1:length(domain) + sz(end+1-i) = length(subspaces(domain(i))); + end + + inds = sort(randperm(prod(sz), round(prod(sz) * kwargs.Density))); + subs = ind2sub_(sz, inds); + for i = length(inds):-1:1 + for j = length(codomain):-1:1 + subcodomain(j) = subspaces(codomain(j), subs(i, j)); + end + for j = length(domain):-1:1 + subdomain(j) = subspaces(domain(j), subs(i, end + 1 - j)); + end + vars(i) = Tensor.new(f, subcodomain, subdomain); + end + t = SparseTensor(subs, vars, sz, codomain, domain); + end + + function t = rand(varargin) + t = SparseTensor.new(@rand, varargin{:}); + end + end + + methods + function [codomain, domain] = deduce_spaces(t) + spaces = cell(1, ndims(t)); + for i = 1:length(spaces) + for j = flip(1:size(t, i)) + idx = find(t.ind(:, i) == j, 1); + if isempty(idx) + error('sparse:argerror', ... + 'Cannot deduce %dth space at index %d.', i, j); + end + spaces{i}(j) = space(t.var(idx), i); + end end + Nout = indout(t.var(1)); + codomain = SumSpace(spaces{1:Nout}); + domain = SumSpace(spaces{(Nout+1):end})'; end function t = permute(t, p) @@ -125,23 +210,18 @@ end end - function n = nspaces(A) - if nnz(A) == 0 - n = ndims(A); - else - n = nspaces(A.var(1)); - end + function n = nspaces(t) + n = length(t.domain) + length(t.codomain); end function n = ndims(A) n = length(A.sz); end - function r = rank(A) - if nnz(A) == 0 - r = [ndims(A) 0]; - else - r = rank(A.var(1)); + function r = rank(t, i) + r = [length(t.codomain) length(t.domain)]; + if nargin > 1 + r = r(i); end end diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index ea83832..53a834c 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -480,7 +480,7 @@ % insert a trivial space at position i. arguments tsrc - i = nspaces(tsrc) + i = nspaces(tsrc) + 1 dual = false end diff --git a/test/TestSparseTensor.m b/test/TestSparseTensor.m index 2137997..7baa364 100644 --- a/test/TestSparseTensor.m +++ b/test/TestSparseTensor.m @@ -5,34 +5,39 @@ tol = 1e-12 end + properties (TestParameter) + spaces = struct(... + 'cartesian', SumSpace(CartesianSpace(2,[],2,[]), CartesianSpace(2,[],1,[]), ... + CartesianSpace(1,[],3,[]), CartesianSpace(2,[],1,[],1,[]), CartesianSpace(1,[],2,[],1,[])) ... + ) + end + methods (Test) - function basic_linear_algebra(tc) - spaces = CartesianSpace.new([2 3 4 5]); - szs = cell(1, 4); - for i = 1:length(szs) - szs{i} = spaces(randperm(length(spaces), randi([2 3]))); - end - t1 = generatesparsetensor([2 2], szs, 0.3); - a = rand(); - + function basic_linear_algebra(tc, spaces) + t1 = SparseTensor.rand(spaces(1:3), spaces(4:5), 'Density', 0.3); tc.verifyTrue(isapprox(norm(t1)^2, dot(t1, t1), ... - 'AbsTol', tc.tol, 'RelTol', tc.tol), 'norm and dot incompatible.'); + 'AbsTol', tc.tol, 'RelTol', tc.tol), 'Norm and dot incompatible.'); + a = rand(); tc.verifyTrue(isapprox(norm(a .* t1), abs(a) * norm(t1), ... 'AbsTol', tc.tol, 'RelTol', tc.tol), ... - 'norm and scalar multiplication incompatible.'); + 'Norm and scalar multiplication incompatible.'); + tc.verifyTrue(isapprox(t1 + t1, 2 .* t1, ... 'AbsTol', tc.tol, 'RelTol', tc.tol), ... - '2t and t+t incompatible.'); + '2*t and t+t incompatible.') tc.verifyTrue(isapprox(-t1, t1 .* (-1), 'AbsTol', tc.tol), ... - '=t and t * (-1) incompatible.'); + '-t and t .* (-1) incompatible.'); + + tc.verifyTrue(isapprox(t1 .* (1/a), t1 ./ a, 'AbsTol', tc.tol), ... + '.* and ./ are incompatible'); + tc.verifyTrue(isapprox(norm(normalize(t1)), 1), ... 'normalize should result in unit norm.'); - t2 = generatesparsetensor([2 2], szs, 0.3); + t2 = SparseTensor.rand(spaces(1:3), spaces(4:5), 'Density', 0.2); b = rand(); - tc.verifyTrue(isapprox(dot(b .* t2, a .* t1), conj(b) * a * dot(t2, t1), ... 'AbsTol', tc.tol, 'RelTol', tc.tol) && ... isapprox(dot(t2, t1), conj(dot(t1, t2)), ... @@ -75,20 +80,3 @@ function permute_via_inner(tc) end end end - -function t = generatesparsetensor(rank, spaces, sparsity) - -sz = cellfun(@length, spaces); -ind = ind2sub_(sz, 1:prod(sz)); - -for i = prod(sz):-1:1 - for j = length(sz):-1:1 - localsz(j) = spaces{j}(ind(i,j)); - end - var(i) = Tensor.rand(localsz(1:rank(1)), localsz(rank(1)+1:end)', 'Rank', rank); -end - -li1 = rand([1, length(var)]) < sparsity; -t = SparseTensor(ind(li1, :), var(li1), sz); - -end \ No newline at end of file diff --git a/test/TestTensor.m b/test/TestTensor.m index 8c938c0..74efbc2 100644 --- a/test/TestTensor.m +++ b/test/TestTensor.m @@ -136,7 +136,7 @@ function permute_via_conversion(tc, spaces) tc.assertTrue(all(dims(t) == size(a, 1:nspaces(t)))); for k = 0:nspaces(t) ps = perms(1:nspaces(t)).'; - for p = ps(:, randperm(size(ps, 2), min(size(ps, 2), 10))) + for p = ps(:, randperm(size(ps, 2), min(size(ps, 2), 5))) t2 = tpermute(t, p.', [k nspaces(t)-k]); a2 = double(t2); tc.assertTrue(all(dims(t2) == size(a2, 1:nspaces(t)))); From 15ca707cff2dbadf0a998887adf114d1c5071e6f Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 2 Jan 2023 23:05:25 +0100 Subject: [PATCH 111/245] possibly fixed some stuffs --- src/mps/FiniteMpo.m | 2 +- src/mps/InfJMpo.m | 42 +++++--- src/mps/InfMpo.m | 2 +- src/mps/MpoTensor.m | 28 ++--- src/sparse/SparseTensor.m | 155 ++++++++++++++++------------ src/tensors/Tensor.m | 59 ++++++++--- src/tensors/spaces/AbstractSpace.m | 23 ++++- src/tensors/spaces/CartesianSpace.m | 9 ++ src/tensors/spaces/ComplexSpace.m | 13 +++ src/tensors/spaces/SumSpace.m | 34 +++++- 10 files changed, 254 insertions(+), 113 deletions(-) diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index 71babf6..5509502 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -65,7 +65,7 @@ end function s = domain(mpo) - N = prod(cellfun(@(x) size(x, 4), mpo.O)); + N = prod(cellfun(@(x) size(x, 4), mpo(1).O)); s = conj(... [rightvspace(mpo(1).L) cellfun(@(x) pspace(x)', mpo(1).O) leftvspace(mpo(1).R)]); end diff --git a/src/mps/InfJMpo.m b/src/mps/InfJMpo.m index a35d932..8817793 100644 --- a/src/mps/InfJMpo.m +++ b/src/mps/InfJMpo.m @@ -33,10 +33,11 @@ if isempty(GL) || isempty(GL{1}) GL = cell(1, period(mps1)); - GL{1} = SparseTensor.zeros(1, size(T(1).O{1}, 2), 1); + GL{1} = SparseTensor.zeros(domain(T), []); pSpace = space(T(1).O{1}(:,:,:,1), 4); - GL{1}(1) = insert_onespace(fixedpoint(mps1, 'l_LL'), ... + fp1 = insert_onespace(fixedpoint(mps1, 'l_LL'), ... 2, ~isdual(pSpace(1))); + GL{1}(1) = repartition(fp1, [nspaces(fp1) 0]); end for i = 2:size(GL{1}, 2) @@ -45,8 +46,8 @@ if iszero(Tdiag) GL{1}(i) = rhs; elseif iseye(T, i) - fp_left = insert_onespace(fixedpoint(mps1, 'l_LL'), ... - 2, isdual(space(rhs, 2))); + fp_left = repartition(insert_onespace(fixedpoint(mps1, 'l_LL'), ... + 2, isdual(space(rhs, 2))), rank(rhs)); fp_right = insert_onespace(fixedpoint(mps1, 'r_LL'), ... 2, ~isdual(space(rhs, 2))); lambda = contract(rhs, 1:3, fp_right, 3:-1:1); @@ -91,10 +92,11 @@ if isempty(GR) || isempty(GR{1}) GR = cell(1, period(mps1)); - GR{1} = SparseTensor.zeros(1, N, 1); + GR{1} = SparseTensor.zeros(domain(T), []); pSpace = space(T(1).O{1}(:, end, :, :), 2); - GR{1}(1, N, 1) = insert_onespace(fixedpoint(mps1, 'r_RR'), ... + fp1 = insert_onespace(fixedpoint(mps1, 'r_RR'), ... 2, isdual(pSpace(end))); + GR{1}(1, N, 1) = repartition(fp1, [nspaces(fp1) 0]); end for i = N-1:-1:1 @@ -105,8 +107,8 @@ elseif iseye(T, i) fp_left = insert_onespace(fixedpoint(mps1, 'l_RR'), ... 2, ~isdual(space(rhs, 2))); - fp_right = insert_onespace(fixedpoint(mps1, 'r_RR'), ... - 2, isdual(space(rhs, 2))); + fp_right = repartition(insert_onespace(fixedpoint(mps1, 'r_RR'), ... + 2, isdual(space(rhs, 2))), rank(rhs)); lambda = contract(rhs, 1:3, fp_left, 3:-1:1); rhs = rhs - lambda * fp_right; @@ -196,11 +198,14 @@ if strcmp(kwargs.Symmetry, 'Z1') pSpace = CartesianSpace.new(2); - S = Tensor([one(pSpace) pSpace], [pSpace one(pSpace)]); + vSpace = one(pSpace); + S = Tensor([vSpace pSpace], [pSpace vSpace]); Sx = fill_matrix(S, sigma_x); Sz = fill_matrix(S, sigma_z); - O = MpoTensor.zeros(3, 1, 3, 1); + cod = SumSpace([vSpace vSpace vSpace], pSpace); + dom = SumSpace(pSpace, [vSpace vSpace vSpace]); + O = MpoTensor.zeros(cod, dom); O(1, 1, 1, 1) = 1; O(3, 1, 3, 1) = 1; O(1, 1, 2, 1) = -J * Sx; @@ -216,7 +221,9 @@ Sx_r = fill_matrix(Tensor([vSpace pSpace], [pSpace trivSpace]), {1 1}); Sz = fill_matrix(Tensor([trivSpace pSpace], [pSpace trivSpace]), {1 -1}); - O = MpoTensor.zeros(3, 1, 3, 1); + cod = SumSpace([one(vSpace) vSpace one(vSpace)], pSpace); + dom = SumSpace(pSpace, [one(vSpace), vSpace, one(vSpace)]); + O = MpoTensor.zeros(cod, dom); O(1, 1, 1, 1) = 1; O(3, 1, 3, 1) = 1; O(1, 1, 2, 1) = -J * Sx_l; @@ -242,16 +249,17 @@ assert(h == 0, 'Magnetic field not invariant under SU2'); pSpace = GradedSpace.new(kwargs.Spin, 1, false); - aSpace = GradedSpace.new(SU2(3), 1, false); - tSpace = one(aSpace); + vSpace = GradedSpace.new(SU2(3), 1, false); + tSpace = one(vSpace); s = spin(kwargs.Spin); - L = Tensor.ones([tSpace pSpace], [pSpace aSpace]); + L = Tensor.ones([tSpace pSpace], [pSpace vSpace]); L = L * (-J(1) * (s^2 + s)); - R = Tensor.ones([aSpace pSpace], [pSpace tSpace]); + R = Tensor.ones([vSpace pSpace], [pSpace tSpace]); - - O = MpoTensor.zeros(3, 1, 3, 1); + cod = SumSpace([one(vSpace) vSpace one(vSpace)], pSpace); + dom = SumSpace(pSpace, [one(vSpace), vSpace, one(vSpace)]); + O = MpoTensor.zeros(cod, dom); O(1, 1, 1, 1) = 1; O(3, 1, 3, 1) = 1; O(1, 1, 2, 1) = L; diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index d9a4f21..255c041 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -84,7 +84,7 @@ vspaces end - pspaces = arrayfun(@(x) pspace(mpo, x), 1:period(mpo), 'UniformOutput', false); + pspaces = arrayfun(@(x) subspaces(pspace(mpo, x)), 1:period(mpo), 'UniformOutput', false); args = [pspaces; vspaces]; mps = UniformMps.randnc(args{:}); end diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index fe48936..98c1fcc 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -204,13 +204,22 @@ iB(1:2) = flip(iB(1:2)); end - A = reshape(tpermute(A, iA, rA), ... + A_ = reshape(tpermute(A, iA, rA), ... [prod(size(A, uncA)) prod(size(A, dimA))]); - B_ = reshape(permute(B.scalars, iB), ... [prod(size(B, dimB)) prod(size(B, uncB))]); - C = C + reshape(A * sparse(B_), szC); + subs = zeros(size(A_, 1), 2); + subs(:, 1) = (1:size(A_, 1)).'; + sz2 = [size(A_, 1) size(B_, 2)]; + [Brows, Bcols, Bvals] = find(B_); + + for i = 1:length(Bvals) + Atmp = A_(:, Brows(i)) .* Bvals(i); + subs(:, 2) = Bcols(i); + idx = sub2ind_(sz2, subs); + C(idx) = C(idx) + Atmp; + end end end end @@ -333,15 +342,10 @@ end methods (Static) - function O = zeros(m, n, o, p) - if nargin == 1 - sz = m; - elseif nargin == 4 - sz = [m n o p]; - else - error('mpotensor:argerror', 'invalid amount of inputs.'); - end - O = MpoTensor(SparseTensor([], [], sz), zeros(sz)); + function O = zeros(codomain, domain) + tensors = SparseTensor.zeros(codomain, domain); + scalars = zeros(size(tensors)); + O = MpoTensor(tensors, scalars); end end end diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index d0050a3..6f521d7 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -89,6 +89,7 @@ t.var = var; else sz = reshape(varargin{3}, 1, []); + ind = double.empty(0, size(sz, 2)); end t.ind = ind; t.sz = sz; @@ -106,17 +107,11 @@ domain SumSpace kwargs.Density = 1 end - - sz = zeros(1, length(codomain) + length(domain)); - for i = 1:length(codomain) - sz(i) = length(subspaces(codomain(i))); - end - for i = 1:length(domain) - sz(end+1-i) = length(subspaces(domain(i))); - end + sz = [nsubspaces(codomain) flip(nsubspaces(domain))]; inds = sort(randperm(prod(sz), round(prod(sz) * kwargs.Density))); subs = ind2sub_(sz, inds); + vars = Tensor.empty(0, 1); for i = length(inds):-1:1 for j = length(codomain):-1:1 subcodomain(j) = subspaces(codomain(j), subs(i, j)); @@ -132,6 +127,15 @@ function t = rand(varargin) t = SparseTensor.new(@rand, varargin{:}); end + + function t = zeros(codomain, domain, kwargs) + arguments + codomain + domain + kwargs.Density = 0 + end + t = SparseTensor.new(@zeros, codomain, domain, 'Density', kwargs.Density); + end end methods @@ -186,8 +190,8 @@ [lia, locb] = ismember(inds, A.ind, 'rows'); B(lia) = A.var(locb(lia)); if ~all(lia) - s = arrayfun(@(i) space(A, i), 1:ndims(A), 'UniformOutput', false); - r = rank(A.var(1)); + s = arrayfun(@(i) subspaces(space(A, i)), 1:ndims(A), 'UniformOutput', false); + r = rank(A); for i = find(~lia).' allspace = arrayfun(@(j) s{j}(inds(i, j)), 1:length(s)); B(i) = Tensor.zeros(allspace(1:r(1)), allspace(r(1)+1:end)'); @@ -200,13 +204,10 @@ function A = sparse(A) end - function s = space(t, i) - assert(isscalar(i), 'sparse:argerror', ... - 'Can only obtain spaces for single index.'); - for j = size(t, i):-1:1 - el = t.var(find(t.ind(:, i) == j, 1)); - assert(~isempty(el), 'sparse:argerror', 'cannot deduce space'); - s(j) = space(t.var(find(t.ind(:, i) == j, 1)), i); + function sp = space(t, inds) + sp = [t.codomain t.domain']; + if nargin > 1 + sp = sp(inds); end end @@ -333,6 +334,8 @@ function disp(t) if ~isempty(a.var) a.var = conj(a.var); end + a.codomain = conj(a.codomain); + a.domain = conj(a.domain); end function a = ctranspose(a) @@ -341,6 +344,7 @@ function disp(t) end a.ind = fliplr(a.ind); a.sz = fliplr(a.sz); + [a.codomain, a.domain] = swapvars(a.codomain, a.domain); end function d = dot(a, b) @@ -485,8 +489,13 @@ function disp(t) if strcmp(dim, 'all') if nnz(A) == 0 - B = A; - B.sz = [1 1]; + for i = flip(1:length(A.codomain)) + cod(i) = SumSpace(subspaces(A.codomain(i), 1)); + end + for i = flip(1:length(A.domain)) + dom(i) = SumSpace(subspaces(A.domain(i), 1)); + end + B = SparseTensor.zeros(cod, dom); return end @@ -619,7 +628,11 @@ function disp(t) end end - C = reshape(SparseTensor(Cind, Cvar, [size(A,1) size(B,2)]), szC); + Ccod = space(A, uncA); + Cdom = space(B, uncB); + + C = SparseTensor(Cind, Cvar, [size(A, 1) size(B, 2)], Ccod, Cdom); + C = reshape(C, szC); if size(Cind, 1) == prod(szC), C = full(C); end end end @@ -675,6 +688,9 @@ function disp(t) t.var(i) = tpermute(t.var(i), p, r); end t = permute(t, p); + sp = space(t, p); + t.codomain = sp(1:r(1)); + t.domain = sp(r(1) + (1:r(2)))'; end function t = repartition(t, r) @@ -685,6 +701,9 @@ function disp(t) t.var = arrayfun(@(x) repartition(x, r), t.var); end end + sp = space(t, p); + t.codomain = sp(1:r(1)); + t.domain = sp(r(1) + (1:r(2)))'; end function t = twist(t, i, inv) @@ -742,47 +761,53 @@ function disp(t) end function t = subsref(t, s) - assert(strcmp(s(1).type, '()'), 'sparse:index', 'only () indexing allowed'); - n = size(s(1).subs, 2); - if n == 1 % linear indexing - I = ind2sub_(t.sz, s(1).subs{1}); - s(1).subs = arrayfun(@(x) I(:,x), 1:width(I), 'UniformOutput',false); - else - assert(n == size(t.sz, 2), 'sparse:index', ... - 'number of indexing indices must match tensor size.'); - end - f = true(size(t.ind, 1), 1); - newsz = zeros(1, size(s(1).subs, 2)); - - for i = 1:size(s(1).subs, 2) - A = s(1).subs{i}; - if strcmp(A, ':') - newsz(i) = t.size(i); - continue; - end - nA = length(A); - if nA ~= length(unique(A)) - error("Repeated index in position %i",i); - end - if ~isempty(t.ind) - B = t.ind(:, i); - P = false(max(max(A), max(B)) + 1, 1); - P(A + 1) = true; - f = and(f, P(B + 1)); - [~, ~, temp] = unique([A(:); t.ind(f, i)], 'stable'); - t.ind(f, i) = temp(nA+1:end); - end - newsz(i) = nA; - end - t.sz = newsz; - if ~isempty(t.ind) - t.ind = t.ind(f, :); - t.var = t.var(f); - end - if length(s) > 1 - assert(isscalar(t)) - t = subsref(t.var, s(2:end)); + switch s(1).type + case '()' + n = size(s(1).subs, 2); + if n == 1 % linear indexing + I = ind2sub_(t.sz, s(1).subs{1}); + s(1).subs = arrayfun(@(x) I(:,x), 1:width(I), 'UniformOutput',false); + else + assert(n == size(t.sz, 2), 'sparse:index', ... + 'number of indexing indices must match tensor size.'); + end + f = true(size(t.ind, 1), 1); + newsz = zeros(1, size(s(1).subs, 2)); + + for i = 1:size(s(1).subs, 2) + A = s(1).subs{i}; + if strcmp(A, ':') + newsz(i) = t.size(i); + continue; + end + nA = length(A); + if nA ~= length(unique(A)) + error("Repeated index in position %i",i); + end + if ~isempty(t.ind) + B = t.ind(:, i); + P = false(max(max(A), max(B)) + 1, 1); + P(A + 1) = true; + f = and(f, P(B + 1)); + [~, ~, temp] = unique([A(:); t.ind(f, i)], 'stable'); + t.ind(f, i) = temp(nA+1:end); + end + newsz(i) = nA; + end + t.sz = newsz; + if ~isempty(t.ind) + t.ind = t.ind(f, :); + t.var = t.var(f); + end + if length(s) > 1 + assert(isscalar(t)) + t = subsref(t.var, s(2:end)); + end + case '.' + t = builtin('subsref', t, s); + otherwise + error('sparse:index', '{} indexing not defined'); end end @@ -806,7 +831,7 @@ function disp(t) subsize(i) = length(s(1).subs{i}); end if nnz(v) == 0, return; end - if isscalar(v), v = repmat(v, subsize); end + if isscalar(v) && prod(subsize) > 1, v = repmat(v, subsize); end subs = combvec(s(1).subs{:}).'; if isempty(t.ind) @@ -897,11 +922,7 @@ function disp(t) end end - methods (Static) - function t = zeros(varargin) - sz = [varargin{:}]; - t = SparseTensor([], [], sz); - end - end + + end diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 53a834c..5f724a9 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -420,11 +420,11 @@ %% Structure methods function n = indin(t) - n = length(t.domain); + n = length(t(1).domain); end function n = indout(t) - n = length(t.codomain); + n = length(t(1).codomain); end function n = nspaces(t) @@ -718,14 +718,20 @@ if isnumeric(A) if issparse(A) && ~all(any(A, 2)) - C = SparseTensor.zeros(szA(1), szB(2)); + [cod, dom] = deduce_spaces(B); + cod2 = cell(size(cod)); + for i = flip(1:length(cod)) + tmp = subspaces(cod(i)); + cod2{i} = tmp(1); + end + C = SparseTensor.zeros(SumSpace(cod2{:}), dom); else C = Tensor.empty(szA(1), szB(2), 0); end for i = 1:szA(1) for j = 1:szB(2) - C(i, j) = sum(A(i, :).' .* B(:, j)); + C(i, j) = sum(A(i, :).' .* B(:, j), 'all'); end end return @@ -733,14 +739,19 @@ if isnumeric(B) if issparse(B) && ~all(any(B, 1)) - C = SparseTensor.zeros(szA(1), szB(2)); - else - C(szA(1), szB(2)) = Tensor(); + [cod, dom] = deduce_spaces(A); + dom2 = cell(size(dom)); + for i = 1:length(dom) + tmp = subspaces(dom(i)); + dom2{i} = tmp(1); + end + C = SparseTensor.zeros(cod, SumSpace(dom2{:})); end - - for i = 1:szA(1) - for j = 1:szB(2) - C(i, j) = sum(A(i, :) .* B(:, j).'); + C(szA(1), szB(2)) = Tensor(); + for i = flip(1:szA(1)) + for j = flip(1:szB(2)) + tmp + C(i, j) = sum(A(i, :) .* B(:, j).', 'all'); end end return @@ -1204,7 +1215,8 @@ if isnumeric(A) if issparse(A) - C = SparseTensor.zeros(size(A)); + [cod, dom] = deduce_spaces(B); + C = SparseTensor.zeros(cod, dom); I = find(A); if isempty(I), return; end C(I) = full(A(I)) .* B(I); @@ -2329,6 +2341,29 @@ type = underlyingType(t(1).var); end + function [codomain, domain] = deduce_spaces(t) + spaces = cell(1, nspaces(t)); + subs = repmat({1}, 1, nspaces(t)); + for i = 1:length(spaces) + for j = flip(1:size(t, i)) + subs{i} = j; + spaces{i}(j) = space(t(subs{:}), i); + end + subs{i} = 1; + end + Nout = indout(t); + if Nout > 0 + codomain = SumSpace(spaces{1:Nout}); + else + codomain = SumSpace([]); + end + if Nout == length(spaces) + domain = SumSpace([]); + else + domain = SumSpace(spaces{(Nout+1):end})'; + end + end + function disp(t, details) if nargin == 1 || isempty(details), details = false; end if isscalar(t) diff --git a/src/tensors/spaces/AbstractSpace.m b/src/tensors/spaces/AbstractSpace.m index 285f9d6..30a5022 100644 --- a/src/tensors/spaces/AbstractSpace.m +++ b/src/tensors/spaces/AbstractSpace.m @@ -1,7 +1,7 @@ classdef (Abstract) AbstractSpace % Abstract structure of a tensor index. - properties (Access = protected) + properties %(Access = protected) dimensions % Specification of the internal dimensions dual (1,1) logical = false % Flag to indicate if the space is a dual space end @@ -116,6 +116,9 @@ error('tensors:AbstractMethod', 'This method should be overloaded.'); end + function s = subspaces(s) + end + function trees = fusiontrees(codomain, domain) % Compute all allowed fusiontrees that connect domain and codomain. If the % spaces have no internal structure than this returns `[]`. @@ -257,6 +260,24 @@ end end + function space = sum(spaces) + % Fuse a list of spaces to the direct sum space. + % + % Arguments + % --------- + % spaces : (1, :) :class:`AbstractSpace` + % Array of input spaces. + % + % Returns + % ------- + % space : (1, 1) :class:`AbstractSpace` + % direct sum space. + space = spaces(1); + for i = 2:length(spaces) + space = space + spaces(i); + end + end + function spaces = insertone(spaces, i, dual) % insert a trivial space at position i. arguments diff --git a/src/tensors/spaces/CartesianSpace.m b/src/tensors/spaces/CartesianSpace.m index 41018c4..728ec97 100644 --- a/src/tensors/spaces/CartesianSpace.m +++ b/src/tensors/spaces/CartesianSpace.m @@ -244,6 +244,15 @@ space = CartesianSpace(prod(dims(spaces)), []); end + + function space = plus(space, space2) + space.dimensions = space.dimensions + space2.dimensions; + end + + function space = sum(spaces) + + space = CartesianSpace(sum(dims(spaces)), []); + end end diff --git a/src/tensors/spaces/ComplexSpace.m b/src/tensors/spaces/ComplexSpace.m index 2a3af06..f73b4fd 100644 --- a/src/tensors/spaces/ComplexSpace.m +++ b/src/tensors/spaces/ComplexSpace.m @@ -218,6 +218,19 @@ assert(isdual(space1) == isdual(space2)); space1.dimensions = min(dims(space1), dims(space2)); end + + function space = plus(space, space2) + + assert(isdual(space) == isdual(space2), ... + 'direct sum only defined when isdual is equal.'); + space.dimensions = space.dimensions + space2.dimensions; + end + + function space = sum(spaces) + assert(all(isdual(spaces) == isdual(spaces(1))), ... + 'direct sum only defined when isdual is equal.'); + space = ComplexSpace(sum(dims(spaces)), isdual(spaces(1))); + end end diff --git a/src/tensors/spaces/SumSpace.m b/src/tensors/spaces/SumSpace.m index c1eea84..937b872 100644 --- a/src/tensors/spaces/SumSpace.m +++ b/src/tensors/spaces/SumSpace.m @@ -1,4 +1,4 @@ -classdef SumSpace < AbstractSpace +classdef (InferiorClasses = {?GradedSpace, ?CartesianSpace, ?ComplexSpace}) SumSpace < AbstractSpace % Direct product structure for a tensor index. %% Constructors @@ -10,7 +10,7 @@ subspaces end - if nargin == 0 + if nargin == 0 || (nargin == 1 && isempty(subspaces{1})) args = {}; else dual = cell(size(subspaces)); @@ -22,6 +22,9 @@ end spaces = spaces@AbstractSpace(args{:}); + if (nargin == 1 && isempty(subspaces{1})) + spaces = spaces.empty(1, 0); + end end function space = one(spaces) @@ -44,6 +47,26 @@ spaces(i).dimensions = conj(subspaces(spaces(i))); end end + + function space = mtimes(space1, space2) + % Fuse two spaces to a single space. + % + % Arguments + % --------- + % space1, space2 : (1,1) :class:`AbstractSpace` + % input spaces. + % + % Returns + % ------- + % space : (1,1) :class:`AbstractSpace` + % fused space. + arguments + space1 SumSpace + space2 SumSpace + end + + space = sum(subspaces(space1)) * sum(subspaces(space2)); + end end %% Utility @@ -64,6 +87,13 @@ s = s(i); end end + + function n = nsubspaces(space) + n = zeros(size(space)); + for i = 1:numel(n) + n(i) = numel(space(i).dimensions); + end + end end end From 2a4151487898dfe18752167eabb773a7fd2e8ab7 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 3 Jan 2023 14:42:28 +0100 Subject: [PATCH 112/245] some more tests fixed? --- src/mps/MpoTensor.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index 98c1fcc..f02ddf8 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -209,17 +209,19 @@ B_ = reshape(permute(B.scalars, iB), ... [prod(size(B, dimB)) prod(size(B, uncB))]); + subs = zeros(size(A_, 1), 2); subs(:, 1) = (1:size(A_, 1)).'; sz2 = [size(A_, 1) size(B_, 2)]; [Brows, Bcols, Bvals] = find(B_); - + C = reshape(C, sz2); for i = 1:length(Bvals) Atmp = A_(:, Brows(i)) .* Bvals(i); subs(:, 2) = Bcols(i); idx = sub2ind_(sz2, subs); C(idx) = C(idx) + Atmp; end + C = reshape(C, szC); end end end From fe56aea099b6a5ab0cf675c6416159cf626c0859 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 3 Jan 2023 14:43:39 +0100 Subject: [PATCH 113/245] remove stray comment --- src/tensors/Tensor.m | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 5f724a9..dcc14c9 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -750,7 +750,6 @@ C(szA(1), szB(2)) = Tensor(); for i = flip(1:szA(1)) for j = flip(1:szB(2)) - tmp C(i, j) = sum(A(i, :) .* B(:, j).', 'all'); end end From 5bd308d971a3ecf254d920fd321163d1c5224d2a Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 3 Jan 2023 14:52:30 +0100 Subject: [PATCH 114/245] bugfix rdivide --- src/sparse/SparseTensor.m | 21 +++++++++++++++------ src/tensors/Tensor.m | 9 ++++++++- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index 6f521d7..3e377f1 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -474,6 +474,13 @@ function disp(t) a.ind = [a.ind; b.ind(~lia, :)]; end + function t = rdivide(t, a) + assert(isnumeric(a), 'method not implemented.'); + if nnz(t) > 0 + t.var = rdivide(t.var, a); + end + end + function B = sum(A, dim) arguments A @@ -694,14 +701,16 @@ function disp(t) end function t = repartition(t, r) + arguments + t + r (1,2) = [nspaces(t) 0] + end + if nnz(t) > 0 - if nargin == 1 - t.var = arrayfun(@repartition, t.var); - else - t.var = arrayfun(@(x) repartition(x, r), t.var); - end + t.var = arrayfun(@(x) repartition(x, r), t.var); end - sp = space(t, p); + + sp = space(t); t.codomain = sp(1:r(1)); t.domain = sp(r(1) + (1:r(2)))'; end diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index dcc14c9..8480453 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -966,7 +966,14 @@ % t : :class:`Tensor` % output tensor. - t.var = rdivide(t.var, a); + if isscalar(a) + for i = 1:numel(t) + t(i).var = rdivide(t(i).var, a); + end + return + end + + error('undefined'); end function C = sum(A, dim) From f31594228077fb29ed8a32331c1699236eb4ce2e Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 3 Jan 2023 14:53:46 +0100 Subject: [PATCH 115/245] fix all tests? --- test/TestSparseTensor.m | 52 +++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/test/TestSparseTensor.m b/test/TestSparseTensor.m index 7baa364..2137997 100644 --- a/test/TestSparseTensor.m +++ b/test/TestSparseTensor.m @@ -5,39 +5,34 @@ tol = 1e-12 end - properties (TestParameter) - spaces = struct(... - 'cartesian', SumSpace(CartesianSpace(2,[],2,[]), CartesianSpace(2,[],1,[]), ... - CartesianSpace(1,[],3,[]), CartesianSpace(2,[],1,[],1,[]), CartesianSpace(1,[],2,[],1,[])) ... - ) - end - methods (Test) - function basic_linear_algebra(tc, spaces) - t1 = SparseTensor.rand(spaces(1:3), spaces(4:5), 'Density', 0.3); + function basic_linear_algebra(tc) + spaces = CartesianSpace.new([2 3 4 5]); + szs = cell(1, 4); + for i = 1:length(szs) + szs{i} = spaces(randperm(length(spaces), randi([2 3]))); + end + t1 = generatesparsetensor([2 2], szs, 0.3); + a = rand(); + tc.verifyTrue(isapprox(norm(t1)^2, dot(t1, t1), ... - 'AbsTol', tc.tol, 'RelTol', tc.tol), 'Norm and dot incompatible.'); + 'AbsTol', tc.tol, 'RelTol', tc.tol), 'norm and dot incompatible.'); - a = rand(); tc.verifyTrue(isapprox(norm(a .* t1), abs(a) * norm(t1), ... 'AbsTol', tc.tol, 'RelTol', tc.tol), ... - 'Norm and scalar multiplication incompatible.'); - + 'norm and scalar multiplication incompatible.'); tc.verifyTrue(isapprox(t1 + t1, 2 .* t1, ... 'AbsTol', tc.tol, 'RelTol', tc.tol), ... - '2*t and t+t incompatible.') + '2t and t+t incompatible.'); tc.verifyTrue(isapprox(-t1, t1 .* (-1), 'AbsTol', tc.tol), ... - '-t and t .* (-1) incompatible.'); - - tc.verifyTrue(isapprox(t1 .* (1/a), t1 ./ a, 'AbsTol', tc.tol), ... - '.* and ./ are incompatible'); - + '=t and t * (-1) incompatible.'); tc.verifyTrue(isapprox(norm(normalize(t1)), 1), ... 'normalize should result in unit norm.'); - t2 = SparseTensor.rand(spaces(1:3), spaces(4:5), 'Density', 0.2); + t2 = generatesparsetensor([2 2], szs, 0.3); b = rand(); + tc.verifyTrue(isapprox(dot(b .* t2, a .* t1), conj(b) * a * dot(t2, t1), ... 'AbsTol', tc.tol, 'RelTol', tc.tol) && ... isapprox(dot(t2, t1), conj(dot(t1, t2)), ... @@ -80,3 +75,20 @@ function permute_via_inner(tc) end end end + +function t = generatesparsetensor(rank, spaces, sparsity) + +sz = cellfun(@length, spaces); +ind = ind2sub_(sz, 1:prod(sz)); + +for i = prod(sz):-1:1 + for j = length(sz):-1:1 + localsz(j) = spaces{j}(ind(i,j)); + end + var(i) = Tensor.rand(localsz(1:rank(1)), localsz(rank(1)+1:end)', 'Rank', rank); +end + +li1 = rand([1, length(var)]) < sparsity; +t = SparseTensor(ind(li1, :), var(li1), sz); + +end \ No newline at end of file From f8939231ff4ebaccac1b54a5af2f947b5c1291ec Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sat, 7 Jan 2023 20:52:49 +0100 Subject: [PATCH 116/245] add boundscheck for subsasgn --- src/sparse/SparseTensor.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index 3e377f1..eeb64e6 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -827,8 +827,11 @@ function disp(t) I = ind2sub_(t.sz, s(1).subs{1}); s(1).subs = arrayfun(@(x) I(:,x), 1:width(I), 'UniformOutput',false); end + assert(length(s(1).subs) == size(t.sz, 2), 'sparse:index', ... 'number of indexing indices must match tensor size.'); + assert(all(t.sz >= cellfun(@max, s(1).subs)), 'sparse:bounds', ... + 'attempting to assign out of bounds'); subsize = zeros(1, size(s(1).subs, 2)); for i = 1:length(subsize) From 0afc1b77358cd8584422e205f4e1d2234c8946c0 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 9 Jan 2023 16:42:42 +0100 Subject: [PATCH 117/245] bugfix twistdual --- src/sparse/SparseTensor.m | 203 ++++++++++++++++++++------------------ 1 file changed, 109 insertions(+), 94 deletions(-) diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index eeb64e6..a48bdc6 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -138,6 +138,8 @@ end end + + %% Utility methods function [codomain, domain] = deduce_spaces(t) spaces = cell(1, ndims(t)); @@ -156,34 +158,7 @@ domain = SumSpace(spaces{(Nout+1):end})'; end - function t = permute(t, p) - if ~isempty(t.ind) - t.ind = t.ind(:, p); - end - t.sz = t.sz(p); - end - function t = reshape(t, varargin) - if nargin == 1 - sz = varargin{1}; - else - hasempty = find(cellfun(@isempty, varargin)); - if isempty(hasempty) - sz = [varargin{:}]; - elseif isscalar(hasempty) - varargin{hasempty} = 1; - sz = [varargin{:}]; - sz(hasempty) = round(numel(t) / prod(sz)); - else - error('Can only accept a single empty size index.'); - end - end - assert(prod(sz) == prod(t.sz), ... - 'sparse:argerror', 'To reshape the number of elements must not change.'); - idx = sub2ind_(t.sz, t.ind); - t.ind = ind2sub_(sz, idx); - t.sz = sz; - end function B = full(A) inds = ind2sub_(A.sz, 1:prod(A.sz)); @@ -733,7 +708,7 @@ function disp(t) inv = false end if nnz(t) > 0 - t.var = twist(t.var, i, inv); + t.var = twistdual(t.var, i, inv); end end @@ -759,6 +734,22 @@ function disp(t) %% Indexing methods + function t = cat(dim, t, varargin) + for i = 1:length(varargin) + t2 = sparse(varargin{i}); + N = max(ndims(t), ndims(t2)); + dimcheck = 1:N; + dimcheck(dim) = []; + assert(isequal(size(t, dimcheck), size(t2, dimcheck)), ... + 'sparse:dimagree', 'incompatible sizes for concatenation.'); + newinds = t2.ind; + newinds(:, dim) = newinds(:, dim) + size(t, dim); + t.var = vertcat(t.var, t2.var); + t.ind = vertcat(t.ind, newinds); + t.sz(dim) = t.sz(dim) + size(t2, dim); + end + end + function i = end(t, k, n) if n == 1 i = prod(t.sz); @@ -769,6 +760,96 @@ function disp(t) i = t.sz(k); end + function [I, J, V] = find(t, k, which) + arguments + t + k = [] + which = 'first' + end + + if isempty(t.ind) + I = []; + J = []; + V = []; + return + end + + [inds, p] = sortrows(t.ind, width(t.ind):-1:1); + + if ~isempty(k) + if strcmp(which, 'first') + inds = inds(1:k, :); + p = p(1:k); + else + inds = inds(end:-1:end-k+1, :); + p = p(end:-1:end-k+1); + end + end + + if nargout < 2 + I = sub2ind_(t.sz, inds); + return + end + + subs = sub2sub([t.sz(1) prod(t.sz(2:end))], t.sz, t.ind); + I = subs(:,1); + J = subs(:,2); + + if nargout > 2 + V = t.var(p); + end + end + + function t = horzcat(varargin) + t = cat(2, varargin{:}); + end + + function t = permute(t, p) + if ~isempty(t.ind) + t.ind = t.ind(:, p); + end + t.sz = t.sz(p); + end + + function t = reshape(t, varargin) + if nargin == 1 + sz = varargin{1}; + else + hasempty = find(cellfun(@isempty, varargin)); + if isempty(hasempty) + sz = [varargin{:}]; + elseif isscalar(hasempty) + varargin{hasempty} = 1; + sz = [varargin{:}]; + sz(hasempty) = round(numel(t) / prod(sz)); + else + error('Can only accept a single empty size index.'); + end + end + assert(prod(sz) == prod(t.sz), ... + 'sparse:argerror', 'To reshape the number of elements must not change.'); + idx = sub2ind_(t.sz, t.ind); + t.ind = ind2sub_(sz, idx); + t.sz = sz; + end + + function t = sortinds(t) + % Sort the non-zero entries by their index. + % + % Arguments + % --------- + % t : :class:`SparseTensor` + % + % Returns + % ------- + % t : :class:`SparseTensor` + % tensor with sorted elements. + + if isempty(t), return; end + [t.ind, p] = sortrows(t.ind, width(t.ind):-1:1); + t.var = t.var(p); + end + function t = subsref(t, s) switch s(1).type @@ -863,75 +944,9 @@ function disp(t) t.sz = max(t.sz, cellfun(@max, s(1).subs)); end - function [I, J, V] = find(t, k, which) - arguments - t - k = [] - which = 'first' - end - - if isempty(t.ind) - I = []; - J = []; - V = []; - return - end - - [inds, p] = sortrows(t.ind, width(t.ind):-1:1); - - if ~isempty(k) - if strcmp(which, 'first') - inds = inds(1:k, :); - p = p(1:k); - else - inds = inds(end:-1:end-k+1, :); - p = p(end:-1:end-k+1); - end - end - - if nargout < 2 - I = sub2ind_(t.sz, inds); - return - end - - subs = sub2sub([t.sz(1) prod(t.sz(2:end))], t.sz, t.ind); - I = subs(:,1); - J = subs(:,2); - - if nargout > 2 - V = t.var(p); - end - end - - function t = sortinds(t) - if isempty(t), return; end - [t.ind, p] = sortrows(t.ind, width(t.ind):-1:1); - t.var = t.var(p); - end - - function t = horzcat(varargin) - t = cat(2, varargin{:}); - end - function t = vertcat(varargin) t = cat(1, varargin{:}); end - - function t = cat(dim, t, varargin) - for i = 1:length(varargin) - t2 = sparse(varargin{i}); - N = max(ndims(t), ndims(t2)); - dimcheck = 1:N; - dimcheck(dim) = []; - assert(isequal(size(t, dimcheck), size(t2, dimcheck)), ... - 'sparse:dimagree', 'incompatible sizes for concatenation.'); - newinds = t2.ind; - newinds(:, dim) = newinds(:, dim) + size(t, dim); - t.var = vertcat(t.var, t2.var); - t.ind = vertcat(t.ind, newinds); - t.sz(dim) = t.sz(dim) + size(t2, dim); - end - end end From bf9331ea5b2e1698e277c0da8c5572e2a572abb9 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 9 Jan 2023 16:46:03 +0100 Subject: [PATCH 118/245] always save mpotensor as sparse tensor --- src/mps/MpoTensor.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index f02ddf8..45c3ac4 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -34,7 +34,7 @@ t.scalars = varargin{1}.scalars; t.tensors = varargin{1}.tensors; elseif isa(varargin{1}, 'Tensor') || isa(varargin{1}, 'SparseTensor') - t.tensors = varargin{1}; + t.tensors = sparse(varargin{1}); t.scalars = zeros(size(t.tensors)); end return From 25fc47799fa24166703c93b184e30cabc46ccbfd Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 9 Jan 2023 19:18:55 +0100 Subject: [PATCH 119/245] Revamp display for spaces. --- src/tensors/charges/AbstractCharge.m | 4 ++ src/tensors/charges/ProductCharge.m | 25 ++-------- src/tensors/spaces/AbstractSpace.m | 23 +++++++++ src/tensors/spaces/CartesianSpace.m | 38 +++++++------- src/tensors/spaces/ComplexSpace.m | 38 +++++++------- src/tensors/spaces/GradedSpace.m | 74 ++++++++++++---------------- src/tensors/spaces/SumSpace.m | 67 ++++++++++++++++++++++++- 7 files changed, 166 insertions(+), 103 deletions(-) diff --git a/src/tensors/charges/AbstractCharge.m b/src/tensors/charges/AbstractCharge.m index e9eab5b..49a0685 100644 --- a/src/tensors/charges/AbstractCharge.m +++ b/src/tensors/charges/AbstractCharge.m @@ -745,5 +745,9 @@ locb = reshape(locb, size(a)); end end + + function s = name(a) + s = class(a); + end end end diff --git a/src/tensors/charges/ProductCharge.m b/src/tensors/charges/ProductCharge.m index c8d00fa..f35319a 100644 --- a/src/tensors/charges/ProductCharge.m +++ b/src/tensors/charges/ProductCharge.m @@ -48,17 +48,6 @@ catcharges = cellfun(@(x) x.charges{i}, varargin(~mask), 'UniformOutput', false); a.charges{i} = cat(dim, a.charges{i}, catcharges{:}); end -% -% for i = 1:length(varargin) -% if isempty(varargin{i}), continue; end -% if isempty(a) -% a = varargin{i}; -% continue; -% end -% for j = 1:length(a.charges) -% a.charges{j} = cat(dim, a.charges{j}, varargin{i}.charges{j}); -% end -% end end function a = horzcat(a, varargin) @@ -363,11 +352,6 @@ end end - %function c = intersect(a, b) - % c = subsref(a, substruct('()', ... - % {mod(find(reshape(a, [], 1) == reshape(b, 1, [])) - 1, length(a)) + 1})); - %end - function [c, ia, ib] = intersect(a, b) c = subsref(a, substruct('()', ... {mod(find(reshape(a, [], 1) == reshape(b, 1, [])) - 1, length(a)) + 1})); @@ -375,7 +359,6 @@ [~,ib] = ismember(c, b); end - function F = Fsymbol(a, b, c, d, e, f) if hasmultiplicity(fusionstyle(a)) error('Not implemented yet.'); @@ -453,10 +436,8 @@ function disp(a) s = string(a); - - fprintf('\t%s (%s) Array:\n', ... - dim2str(size(a)), join(cellfun(@(x)string(class(x)), a.charges), 'x')); + dim2str(size(a)), name(a)); for i = 1:size(a, 1) fprintf('\t\t%s\n', join(s(i, :), ' ')); end @@ -494,6 +475,10 @@ function disp(a) c = ProductCharge(charges{:}); end + function s = name(a) + s = join(cellfun(@(x)string(class(x)), a.charges), 'x'); + end + function bool = ne(a, b) bool = a.charges{1} ~= b.charges{1}; for i = 2:length(a.charges) diff --git a/src/tensors/spaces/AbstractSpace.m b/src/tensors/spaces/AbstractSpace.m index 30a5022..b1d2c8c 100644 --- a/src/tensors/spaces/AbstractSpace.m +++ b/src/tensors/spaces/AbstractSpace.m @@ -366,5 +366,28 @@ hashable = {spaces.dimensions, spaces.dual}; end + + function disp(spaces) + % Custom display of spaces. + + s = settings; + shortformat = strcmp('short', s.matlab.commandwindow.NumericFormat.ActiveValue); + + if isscalar(spaces) + fprintf('\t%s\n\n', string(spaces, 'IncludeDetails', ~shortformat)); + return + end + + sz = size(spaces); + assert(length(sz) == 2); + dim_str = sprintf('%dx%d', sz(1), sz(2)); + fprintf('\t%s Product %s:\n', dim_str, name(spaces)); + for i = 1:length(spaces) + subspacestring = string(spaces(i), ... + 'IncludeType', false, 'IncludeDetails', ~shortformat); + fprintf('\t\t%d.\t%s\n', i, subspacestring); + end + fprintf('\n'); + end end end diff --git a/src/tensors/spaces/CartesianSpace.m b/src/tensors/spaces/CartesianSpace.m index 728ec97..e3ef96f 100644 --- a/src/tensors/spaces/CartesianSpace.m +++ b/src/tensors/spaces/CartesianSpace.m @@ -305,31 +305,31 @@ hashable = [spaces.dimensions]; end - function disp(spaces) - if isscalar(spaces) - fprintf('CartesianSpace of dimension %d\n', spaces.dimensions); - return + function s = string(spaces, kwargs) + arguments + spaces + kwargs.IncludeType = true + kwargs.IncludeDetails = true end - fprintf('%dx%d Product space with elements:\n\n', ... - size(spaces, 1), size(spaces, 2)); - for i = 1:length(spaces) - fprintf('%d.\t', i); - disp(spaces(i)); - fprintf('\n'); - end - end - - function s = string(spaces) if numel(spaces) > 1 - s = strings(size(spaces)); - for i = 1:numel(spaces) - s(i) = string(spaces(i)); - end + kwargs = namedargs2cell(kwargs); + s = arrayfun(@(x) string(x, kwargs{:}), spaces); return end - s = sprintf('CartesianSpace of dimension %d', spaces.dimensions); + dimstring = sprintf("%d", dims(spaces)); + + if kwargs.IncludeType + typestring = name(spaces); + s = sprintf("%s: %s", typestring, dimstring); + else + s = sprintf("%s", dimstring); + end + end + + function s = name(~) + s = "CartesianSpace"; end end end diff --git a/src/tensors/spaces/ComplexSpace.m b/src/tensors/spaces/ComplexSpace.m index f73b4fd..1498cd1 100644 --- a/src/tensors/spaces/ComplexSpace.m +++ b/src/tensors/spaces/ComplexSpace.m @@ -279,34 +279,32 @@ hashable = [spaces.dimensions spaces.isdual]; end - function disp(spaces) - if isscalar(spaces) - fprintf('%s\n', string(spaces)); - return + function s = string(spaces, kwargs) + arguments + spaces + kwargs.IncludeType = true + kwargs.IncludeDetails = true end - sz = size(spaces); - dim_str = sprintf('%dx%d', sz(1), sz(2)); - fprintf('%s ProductSpace with elements:\n\n', dim_str); - for i = 1:length(spaces) - fprintf('%d.\t', i); - disp(spaces(i)); - fprintf('\n'); - end - end - - function s = string(spaces) if numel(spaces) > 1 - s = arrayfun(@string, spaces); + kwargs = namedargs2cell(kwargs); + s = arrayfun(@(x) string(x, kwargs{:}), spaces); return end - if spaces.dual - s = sprintf("Space (%d)*", dims(spaces)); + dimstring = sprintf("%d", dims(spaces)); + if isdual(spaces), dimstring = dimstring + "*"; end + + if kwargs.IncludeType + typestring = name(spaces); + s = sprintf("%s: %s", typestring, dimstring); else - s = sprintf("Space (%d)", dims(spaces)); + s = sprintf("%s", dimstring); end end + + function s = name(~) + s = "ComplexSpace"; + end end - end \ No newline at end of file diff --git a/src/tensors/spaces/GradedSpace.m b/src/tensors/spaces/GradedSpace.m index 8a49b6a..8eb717e 100644 --- a/src/tensors/spaces/GradedSpace.m +++ b/src/tensors/spaces/GradedSpace.m @@ -381,56 +381,40 @@ end methods - function disp(spaces) - % Custom display of spaces. - if isscalar(spaces) - fprintf('%s\n', string(spaces)); -% fprintf('%s GradedSpace of dimension %d:\n', ... -% class(spaces.dimensions.charges), dims(spaces)); -% title_str = strjust(pad(["dual", "charges", "degeneracies"]), 'right'); -% charge_str = strjust(pad([string(spaces.dimensions.charges) -% string(spaces.dimensions.degeneracies)]), 'center'); -% fprintf('\t%s:\t%s\n', title_str(1), string(spaces.dual)); -% fprintf('\t%s:\t%s\n', title_str(2), join(charge_str(1, :), char(9))); -% fprintf('\t%s:\t%s\n', title_str(3), join(charge_str(2, :), char(9))); - return + function s = string(spaces, kwargs) + arguments + spaces + kwargs.IncludeType = true + kwargs.IncludeDetails = true end - sz = size(spaces); - assert(length(sz) == 2); - dim_str = sprintf('%dx%d', sz(1), sz(2)); - fprintf('%s ProductSpace with elements:\n\n', dim_str); - for i = 1:length(spaces) - fprintf('%d.\t', i); - disp(spaces(i)); - fprintf('\n'); - end - end - - function s = string(spaces) -% title_str = strjust(pad(["dual", "charges", "degeneracies"]), 'right'); -% charge_str = strjust(pad([string(spaces.dimensions.charges) -% string(spaces.dimensions.degeneracies)]), 'center'); -% s = sprintf(... -% '%s GradedSpace of dimension %d:\n\t%s:\t%s\n\t%s:\t%s\n\t%s:\t%s\n', ... -% class(spaces.dimensions.charges), dims(spaces), ... -% title_str(1), string(spaces.dual), ... -% title_str(2), join(charge_str(1, :), char(9)), ... -% title_str(3), join(charge_str(2, :), char(9))); if numel(spaces) > 1 - s = arrayfun(@string, spaces); + kwargs = namedargs2cell(kwargs); + s = arrayfun(@(x) string(x, kwargs{:}), spaces); return end - chargestring = join(compose("%s => %d", string(spaces.dimensions.charges).', ... - spaces.dimensions.degeneracies.'), ', '); - - if spaces.dual - s = sprintf("%s Space (%d)*: %s", class(spaces.dimensions.charges), ... - dims(spaces), chargestring); + dimstring = sprintf("%d", dims(spaces)); + if isdual(spaces), dimstring = dimstring + "*"; end + + if kwargs.IncludeType + typestring = name(spaces); + end + + if kwargs.IncludeDetails + chargestring = "(" + join(compose("%s => %d", ... + string(spaces.dimensions.charges).', ... + spaces.dimensions.degeneracies.'), ', ') + ")"; + end + + if kwargs.IncludeType && kwargs.IncludeDetails + s = sprintf("%s: %s %s", typestring, dimstring, chargestring); + elseif kwargs.IncludeType + s = sprintf("%s: %s", typestring, dimstring); + elseif kwargs.IncludeDetails + s = sprintf("%s %s", dimstring, chargestring); else - s = sprintf("%s Space (%d): %s", class(spaces.dimensions.charges), ... - dims(spaces), chargestring); + s = dimstring; end end @@ -445,6 +429,10 @@ function disp(spaces) d = num2cell(dims(gradedspaces)); cartesianspaces = CartesianSpace(d{:}); end + + function s = name(spaces) + s = sprintf("%sSpace", name(spaces(1).dimensions.charges)); + end end diff --git a/src/tensors/spaces/SumSpace.m b/src/tensors/spaces/SumSpace.m index 937b872..91c2a8d 100644 --- a/src/tensors/spaces/SumSpace.m +++ b/src/tensors/spaces/SumSpace.m @@ -55,7 +55,7 @@ % --------- % space1, space2 : (1,1) :class:`AbstractSpace` % input spaces. - % + % % Returns % ------- % space : (1,1) :class:`AbstractSpace` @@ -94,6 +94,71 @@ n(i) = numel(space(i).dimensions); end end + + function [cod, dom] = slice(sumcod, sumdom, I) + assert(length(I) == length(sumcod) + length(sumdom)); + if isempty(sumcod) + cod = []; + else + for i = flip(1:length(sumcod)) + cod(i) = subspaces(sumcod(i), I(i)); + end + end + if isempty(sumdom) + dom = []; + else + for i = flip(1:length(sumdom)) + dom(i) = subspaces(sumdom(i), I(end+1-i)); + end + + end + end + + function disp(spaces) + s = settings; + shortformat = strcmp('short', s.matlab.commandwindow.NumericFormat.ActiveValue); + + if isscalar(spaces) + subsp = subspaces(spaces); + dimstr = num2str(sum(dims(subsp))); + if isdual(spaces), dimstr = dimstr + "*"; end + sz = size(subsp); + szstr = sprintf('%dx%d', sz(1), sz(2)); + fprintf('\t%s %s: %s\n', szstr, name(spaces), dimstr); + if ~shortformat + for i = 1:length(subsp) + fprintf('\t\t%d.\t%s\n', i, string(subsp(i), 'IncludeType', false)); + end + end + fprintf('\n'); + return + end + + sz = size(spaces); + assert(length(sz) == 2); + dim_str = sprintf('%dx%d', sz(1), sz(2)); + fprintf('\t%s Product %s:\n', dim_str, name(spaces)); + for i = 1:length(spaces) + subsp = subspaces(spaces(i)); + dimstr = num2str(sum(dims(subsp))); + if isdual(spaces(i)), dimstr = dimstr + "*"; end + sz = size(subsp); + szstr = sprintf('%dx%d', sz(1), sz(2)); + fprintf('\t\t%d.\t%s: %s\n', i, szstr, dimstr); + + if ~shortformat + for j = 1:length(subsp) + fprintf('\t\t\t%d.\t%s\n', j, ... + string(subsp(j), 'IncludeType', false)); + end + end + end + fprintf('\n'); + end + + function s = name(spaces) + s = sprintf("Sum%s", name(subspaces(spaces(1)))); + end end end From 100af7433b4ad497fdf1af9d138ee4ae1c5f6269 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 9 Jan 2023 19:19:25 +0100 Subject: [PATCH 120/245] update tensors. --- src/sparse/SparseTensor.m | 32 +++++++++++- src/tensors/Tensor.m | 77 ++++++++++++++++++----------- src/tensors/kernels/AbstractBlock.m | 3 ++ 3 files changed, 81 insertions(+), 31 deletions(-) diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index a48bdc6..f6cf552 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -136,6 +136,22 @@ end t = SparseTensor.new(@zeros, codomain, domain, 'Density', kwargs.Density); end + + function t = eye(codomain, domain) + t = SparseTensor.zeros(codomain, domain); + + sz1 = size(t, 1:length(codomain)); + sz2 = size(t, length(codomain) + (1:length(domain))); + n = prod(sz1); + assert(n == prod(sz2)); + + inds = sub2ind_([n n], [1:n; 1:n].'); + for i = flip(1:n) + t.ind(i,:) = ind2sub_(t.sz, inds(i)); + [cod, dom] = slice(codomain, domain, t.ind(i,:)); + t.var(i) = Tensor.eye(cod, dom); + end + end end @@ -158,8 +174,6 @@ domain = SumSpace(spaces{(Nout+1):end})'; end - - function B = full(A) inds = ind2sub_(A.sz, 1:prod(A.sz)); [lia, locb] = ismember(inds, A.ind, 'rows'); @@ -222,7 +236,21 @@ end function disp(t) + r = t.rank; nz = nnz(t); + if nz == 0 + fprintf('all-zero rank (%d, %d) %s:\n', r(1), r(2), class(t)); + else + fprintf('rank (%d, %d) %s with %d nonzeros:\n', r(1), r(2), class(t), nz); + end + s = space(t); + for i = 1:length(s) + fprintf('\t%d.\t', i); + disp(s(i)); + fprintf('\b'); + end + fprintf('\n'); + if nz == 0 fprintf('all-zero %s of size %s\n', class(t), ... dim2str(t.sz)); diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 8480453..f1c2a51 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -60,7 +60,7 @@ varargin); return; end - + % t = Tensor(codomain, domain) if nargin == 2 codomain = varargin{1}; @@ -68,10 +68,28 @@ assert(~isempty(codomain) || ~isempty(domain), ... 'Cannot create (0,0) tensors.'); - t.domain = domain; - t.codomain = codomain; - t.var = AbstractBlock.new(codomain, domain); + if isa(codomain, 'SumSpace') || isa(domain, 'SumSpace') + if isempty(codomain) + sz = flip(nsubspaces(domain)); + elseif isempty(domain) + sz = nsubspaces(codomain); + else + sz = [nsubspaces(codomain) flip(nsubspaces(domain))]; + end + subs = ind2sub_(sz, 1:prod(sz)); + for i = size(subs, 1):-1:1 + [cod, dom] = slice(codomain, domain, subs(i,:)); + t(i).codomain = cod; + t(i).domain = dom; + t(i).var = AbstractBlock.new(cod, dom); + end + else + t.domain = domain; + t.codomain = codomain; + t.var = AbstractBlock.new(codomain, domain); + end + return end @@ -84,7 +102,7 @@ % Usage % ----- % :code:`t = fill_matrix(t, matrices, charges)` - % + % % :code:`t = fill_matrix(t, fun, charges)` % % Arguments @@ -97,7 +115,7 @@ % % fun : :class:`function_handle` % function of signature :code:`fun(dims, charge)` to fill with. - % + % % charges : :class:`AbstractCharge` % optional list of charges to identify the matrix blocks. % @@ -135,7 +153,7 @@ % Usage % ----- % :code:`t = fill_tensor(t, tensors)` - % + % % :code:`t = fill_tensor(t, fun)` % % Arguments @@ -216,7 +234,7 @@ % % Examples % -------- - % :code:`t = similar([], mpsbar, 1, mpo, 4, mps, 1, 'Conj', true)` creates a + % :code:`t = similar([], mpsbar, 1, mpo, 4, mps, 1, 'Conj', true)` creates a % left mps environment tensor. arguments @@ -379,7 +397,7 @@ end end end - + function t = zeros(varargin) t = Tensor.new(@zeros, varargin{:}); end @@ -477,7 +495,7 @@ end function tdst = insert_onespace(tsrc, i, dual) - % insert a trivial space at position i. + % insert a trivial space at position i. arguments tsrc i = nspaces(tsrc) + 1 @@ -522,7 +540,7 @@ methods end - + %% Linear algebra methods @@ -574,7 +592,7 @@ end function d = dot(t1, t2) - % Compute the scalar dot product of two tensors. This is defined as the overlap + % Compute the scalar dot product of two tensors. This is defined as the overlap % of the two tensors, which therefore must have equal domain and codomain. This % function is sesquilinear in its arguments. % @@ -662,7 +680,7 @@ t = inv(t1) * t2; end - + function t = mrdivide(t1, t2) % Right division of tensors. % @@ -985,7 +1003,7 @@ if isscalar(A), C = A; return; end if isempty(dim), dim = find(size(A) ~= 1, 1); end - + if strcmp(dim, 'all') C = A(1); for i = 2:numel(A) @@ -1002,7 +1020,7 @@ end return end - + if dim == 2 C = A(:, 1); for i = 2:size(A, 2) @@ -1144,7 +1162,7 @@ join(string(A_.domain), ' '), ... join(string(B_.codomain), ' ')); end - if ~isempty(A_.codomain) || ~isempty(B_.domain) + if ~isempty(A_.codomain) || ~isempty(B_.domain) med.C = Tensor.zeros(A_.codomain, B_.domain); else med.C = []; @@ -1169,7 +1187,7 @@ for i = rA(1) + (1:rA(2)) if ~isdual(space(A, i)), A = twist(A, i); end end -% A = twist(A, [false(1, length(A.codomain)) ~isdual(A.domain')]); + % A = twist(A, [false(1, length(A.codomain)) ~isdual(A.domain')]); B = tpermute(B, iB, rB); assert(isequal(A.domain, B.codomain), 'tensors:SpaceMismatch', ... @@ -1201,7 +1219,7 @@ % --------- % t : :class:`Tensor` % input tensor. - % + % % a : numeric % input scalar. % @@ -1341,7 +1359,7 @@ inv = false end - if isempty(i) || ~any(i) || istwistless(braidingstyle(t(1))) + if isempty(i) || ~any(i) || istwistless(braidingstyle(t(1))) return end @@ -1507,7 +1525,7 @@ % % alg : char or string % selection of algorithms for the decomposition: - % + % % - 'qr' produces an upper triangular remainder R % - 'qrpos' corrects the diagonal elements of R to be positive. % - 'ql' produces a lower triangular remainder R @@ -1587,7 +1605,7 @@ % % alg : char or string % selection of algorithms for the decomposition: - % + % % - 'rq' produces an upper triangular remainder R % - 'rqpos' corrects the diagonal elements of R to be positive. % - 'lq' produces a lower triangular remainder R @@ -1610,7 +1628,7 @@ alg {mustBeMember(alg, {'rq', 'rqpos', 'lq', 'lqpos', 'polar', 'svd'})} ... = 'rqpos' end - + if isempty(p1), p1 = 1:rank(t, 1); end if isempty(p2), p2 = rank(t, 1) + (1:rank(t,2)); end @@ -1705,7 +1723,7 @@ end function N = rightnull(t, p1, p2, alg, atol) - % Compute the right nullspace of a tensor, such that + % Compute the right nullspace of a tensor, such that % :code:`tpermute(t, [p1 p2], [length(p1) length(p2)]) * N = 0`. % % Arguments @@ -1789,7 +1807,7 @@ % charge. % % TruncTotalDim : int - % truncate such that the total dim of S is not larger than this value. + % truncate such that the total dim of S is not larger than this value. % % TruncBelow : numeric % truncate such that there are no singular values below this value. @@ -1986,7 +2004,7 @@ % Output tensor. % tensor to a scalar power - if isnumeric(Y) && isscalar(Y) + if isnumeric(Y) && isscalar(Y) assert(isequal(X.codomain, X.domain), 'tensors:ArgumentError', ... 'Input tensor should be square.'); mblocks = matrixblocks(X); @@ -2054,7 +2072,7 @@ end - %% + %% methods function bool = isposdef(t) % Test if a tensor is a positive-definite map. Generally, a Hermitian matrix `M` @@ -2121,7 +2139,7 @@ 'RelTol', tol.RelTol, 'AbsTol', tol.AbsTol), t); return end - + mblocks = matrixblocks(t); for i = 1:length(mblocks) if ~isisometry(mblocks{i}, side, 'RelTol', tol.RelTol, 'AbsTol', tol.AbsTol) @@ -2374,11 +2392,12 @@ function disp(t, details) if nargin == 1 || isempty(details), details = false; end if isscalar(t) r = t.rank; - fprintf('Rank (%d, %d) %s:\n\n', r(1), r(2), class(t)); + fprintf('Rank (%d, %d) %s:\n', r(1), r(2), class(t)); s = space(t); for i = 1:length(s) - fprintf('%d.\t', i); + fprintf('\t%d.\t', i); disp(s(i)); + fprintf('\b'); end fprintf('\n'); if details diff --git a/src/tensors/kernels/AbstractBlock.m b/src/tensors/kernels/AbstractBlock.m index fdc707c..6e6a5fd 100644 --- a/src/tensors/kernels/AbstractBlock.m +++ b/src/tensors/kernels/AbstractBlock.m @@ -34,6 +34,9 @@ return end + assert(~isa(codomain, 'SumSpace') && ~isa(domain, 'SumSpace'), ... + 'tensors:argerror', 'Cannot construct tensor with SumSpaces.'); + global cache if isempty(cache), cache = LRU; end From e98a799a7f2fd9d975abb089420fbeac4672b2fa Mon Sep 17 00:00:00 2001 From: sanderdemeyer Date: Tue, 10 Jan 2023 14:50:19 +0100 Subject: [PATCH 121/245] Rankdeficient check fix --- src/mps/UniformMps.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index e07a05d..8d55c09 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -135,9 +135,9 @@ L = length(pspaces); for w = length(pspaces):-1:1 - rankdefficient = vspaces{w} * pspaces{w} < vspaces{next(w, L)} || ... - vspaces{w} > pspaces{w} * vspaces{next(w, L)}; - if rankdefficient + rankdeficient = vspaces{w} * pspaces{w} < vspaces{next(w, L)} || ... + vspaces{w} > pspaces{w}' * vspaces{next(w, L)}; + if rankdeficient error('mps:rank', ... 'Cannot create a full rank mps with given spaces.'); end From 64545bee55dc1f8b96c0fe0da6ea968ac149c9c3 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Fri, 20 Jan 2023 14:29:04 +0100 Subject: [PATCH 122/245] tensor traces --- src/tensors/AbstractTensor.m | 30 +++++++++++++++++++++++++----- src/utility/linalg/contract.m | 15 +++++++++------ src/utility/linalg/tensortrace.m | 20 ++++++++------------ test/TestTensor.m | 16 ++++++++++++++++ 4 files changed, 58 insertions(+), 23 deletions(-) diff --git a/src/tensors/AbstractTensor.m b/src/tensors/AbstractTensor.m index a4742c7..1c1a08f 100644 --- a/src/tensors/AbstractTensor.m +++ b/src/tensors/AbstractTensor.m @@ -304,19 +304,29 @@ kwargs.Debug = false end assert(length(kwargs.Conj) == length(tensors)); + for i = 1:length(tensors) - if length(indices{i}) > 1 - assert(length(unique(indices{i})) == length(indices{i}), ... - 'Tensors:TBA', 'Traces not implemented.'); - end + [i1, i2] = traceinds(indices{i}); + tensors{i} = tensortrace(tensors{i}, i1, i2); + indices{i}([i1 i2]) = []; end debug = kwargs.Debug; % Special case for single input tensor if nargin == 2 - [~, order] = sort(indices{1}, 'descend'); C = tensors{1}; + + if isempty(indices{1}) + assert(isnumeric(C)); + if kwargs.Conj + C = C'; + end + return + end + + [~, order] = sort(indices{1}, 'descend'); + if kwargs.Conj C = tpermute(C', order(length(order):-1:1), kwargs.Rank); else @@ -421,6 +431,16 @@ local_operators{N} = insert_onespace(repartition(v, [2 1]), 3); end end + + function B = tensortrace(A, i1, i2) + if isempty(i1) && isempty(i2), B = A; return; end + assert(length(i1) == length(i2), 'invalid indices'); + + E = A.eye(conj(space(A, i1)), space(A, i2)); + iA = [i1 i2]; + iE = [1:length(i1) length(i1) + (length(i2):-1:1)]; + B = tensorprod(A, E, iA, iE); + end end end diff --git a/src/utility/linalg/contract.m b/src/utility/linalg/contract.m index fe121aa..024b575 100644 --- a/src/utility/linalg/contract.m +++ b/src/utility/linalg/contract.m @@ -46,20 +46,23 @@ assert(length(kwargs.Conj) == length(tensors)); for i = 1:length(tensors) - if length(indices{i}) > 1 - assert(length(unique(indices{i})) == length(indices{i}), ... - 'Tensors:TBA', 'Traces not implemented.'); - end + [i1, i2] = traceinds(indices{i}); + tensors{i} = tensortrace(tensors{i}, i1, i2); + indices{i}([i1 i2]) = []; end debug = kwargs.Debug; % Special case for single input tensor if nargin == 2 - [~, order] = sort(indices{1}, 'descend'); C = tensors{1}; - C = permute(C, order); if kwargs.Conj, C = conj(C); end + + if ~isempty(indices{1}) + [~, order] = sort(indices{1}, 'descend'); + C = permute(C, order); + end + return end diff --git a/src/utility/linalg/tensortrace.m b/src/utility/linalg/tensortrace.m index 35a1f77..2c48c18 100644 --- a/src/utility/linalg/tensortrace.m +++ b/src/utility/linalg/tensortrace.m @@ -1,4 +1,4 @@ -function C = tensortrace(A, ia) +function C = tensortrace(A, i1, i2) % tensortrace - Compute the (partial) trace of a tensor. % [C, ic] = tensortrace(A, ia) % traces over the indices that appear twice in ia. @@ -6,23 +6,19 @@ % [C, ic] = tensortrace(A, ia, ic) % optionally specifies the output indices' order. -arguments - A - ia -end - -[dimA1, dimA2] = traceinds(ia); +if isempty(i1) && isempty(i2), C = A; return; end +assert(length(i1) == length(i2), 'invalid indices'); -szA1 = size(A, dimA1); -szA2 = size(A, dimA2); +szA1 = size(A, i1); +szA2 = size(A, i2); assert(all(szA1 == szA2)); E = reshape(eye(prod(szA1)), [szA1 szA2]); indsA = -(1:ndims(A)); -indsA(dimA1) = 1:length(dimA1); -indsA(dimA2) = (1:length(dimA2)) + length(dimA1); -C = contract(A, indsA, E, [1:length(dimA1) (1:length(dimA2)) + length(dimA1)]); +indsA(i1) = 1:length(i1); +indsA(i2) = (1:length(i2)) + length(i1); +C = contract(A, indsA, E, [1:length(i1) (1:length(i2)) + length(i1)]); end diff --git a/test/TestTensor.m b/test/TestTensor.m index 74efbc2..7fe3917 100644 --- a/test/TestTensor.m +++ b/test/TestTensor.m @@ -197,6 +197,22 @@ function tensorprod_via_conversion(tc, spaces) tc.assertTrue(isapprox(double(AC), contract(double(A), [-1 -2 1], double(C), [1 -3]))); end + function tensortrace(tc, spaces) + t1 = Tensor.randnc(spaces(1:3), spaces(1:3)); + t2 = contract(t1, [-1 -2 1 1 -3 -4], 'Rank', [2 2]); + t3 = contract(t2, [-1 1 1 -2], 'Rank', [1 1]); + t4 = contract(t1, [-1 1 2 2 1 -2], 'Rank', [1 1]); + tc.assertTrue(isapprox(t3, t4, 'AbsTol', tc.tol, 'RelTol', tc.tol)); + + t5 = contract(t1, [1 2 3 3 2 1]); + t6 = contract(t4, [1 1]); + tc.assertTrue(isapprox(t5, t6, 'AbsTol', tc.tol, 'RelTol', tc.tol)); + if istwistless(braidingstyle(spaces)) + t7 = contract(double(t1), [1 2 3 3 2 1]); + tc.assertTrue(isapprox(t6, t7, 'AbsTol', tc.tol, 'RelTol', tc.tol)); + end + end + function contract_order(tc, spaces) A = Tensor.randnc(spaces(1:2), spaces(1)'); r = Tensor.randnc(spaces(1)', spaces(1)'); From cbc5e7f8706aba6aece3ae7068be4353b3dfea62 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Fri, 20 Jan 2023 14:42:41 +0100 Subject: [PATCH 123/245] some fixes --- src/tensors/kernels/AbelianBlock.m | 44 +++--- src/tensors/kernels/MatrixBlock.m | 246 +++++++++++++++-------------- 2 files changed, 152 insertions(+), 138 deletions(-) diff --git a/src/tensors/kernels/AbelianBlock.m b/src/tensors/kernels/AbelianBlock.m index 17a162c..38d3b40 100644 --- a/src/tensors/kernels/AbelianBlock.m +++ b/src/tensors/kernels/AbelianBlock.m @@ -10,7 +10,7 @@ if a == 0 || ... nargin == 4 || ... (isempty(p) && isempty(map)) || ... - (all(p == 1:length(p)) && all(X(1).rank == Y(1).rank)) + (all(p == 1:length(p)) && all(X.rank == Y.rank)) Y = axpby@MatrixBlock(a, X, b, Y); return; @@ -19,8 +19,8 @@ %% General case: addition with permutation % tensor indexing to matrix indexing - rx = X(1).rank; - ry = Y(1).rank; + rx = X.rank; + ry = Y.rank; rrx = rankrange(rx); rry = rankrange(ry); p_eff(rry) = rrx(p); @@ -32,11 +32,11 @@ vars_in = cell(size(map, 1), 1); offset = 0; - Xrowsizes = {X.rowsizes}; - Xcolsizes = {X.colsizes}; - Xtdims = {X.tdims}; - Xvar = {X.var}; - for i = 1:length(X) + Xrowsizes = X.rowsizes; + Xcolsizes = X.colsizes; + Xtdims = X.tdims; + Xvar = X.var; + for i = 1:length(Xvar) rowsz = Xrowsizes{i}; colsz = Xcolsizes{i}; @@ -69,21 +69,21 @@ % inject small tensor blocks if b == 0 offset = 0; - for i = 1:length(Y) - rows = length(Y(i).rowsizes) - 1; - cols = length(Y(i).colsizes) - 1; + for i = 1:length(Y.var) + rows = length(Y.rowsizes{i}) - 1; + cols = length(Y.colsizes{i}) - 1; if rows < cols m = cell(rows, 1); for n = 1:rows m{n} = cat(2, vars_out{offset + n + ((1:cols)-1) * rows}); end - Y(i).var = cat(1, m{:}); + Y.var{i} = cat(1, m{:}); else m = cell(cols, 1); for n = 1:cols m{n} = cat(1, vars_out{offset + (n-1) * rows + (1:rows)}); end - Y(i).var = cat(2, m{:}); + Y.var{i} = cat(2, m{:}); end offset = offset + rows * cols; end @@ -92,7 +92,7 @@ if b == 1 offset = 0; - for i = 1:length(Y) + for i = 1:length(Y.var) rows = length(Y(i).rowsizes) - 1; cols = length(Y(i).colsizes) - 1; if rows < cols @@ -100,13 +100,13 @@ for n = 1:rows m{n} = cat(2, vars_out{offset + n + ((1:cols)-1) * rows}); end - Y(i).var = Y(i).var + cat(1, m{:}); + Y.var{i} = Y.var{i} + cat(1, m{:}); else m = cell(cols, 1); for n = 1:cols m{n} = cat(1, vars_out{offset + (n-1) * rows + (1:rows)}); end - Y(i).var = Y(i).var + cat(2, m{:}); + Y.var{i} = Y.var{i} + cat(2, m{:}); end offset = offset + rows * cols; end @@ -114,28 +114,28 @@ end offset = 0; - for i = 1:length(Y) - rows = length(Y(i).rowsizes) - 1; - cols = length(Y(i).colsizes) - 1; + for i = 1:length(Y.var) + rows = length(Y.rowsizes{i}) - 1; + cols = length(Y.colsizes{i}) - 1; if rows < cols m = cell(rows, 1); for n = 1:rows m{n} = cat(2, vars_out{offset + n + ((1:cols)-1) * rows}); end - Y(i).var = b .* Y(i).var + cat(1, m{:}); + Y.var{i} = b .* Y.var{i} + cat(1, m{:}); else m = cell(cols, 1); for n = 1:cols m{n} = cat(1, vars_out{offset + (n-1) * rows + (1:rows)}); end - Y(i).var = b .* Y(i).var + cat(2, m{:}); + Y.var{i} = b .* Y.var{i} + cat(2, m{:}); end offset = offset + rows * cols; end end function typename = underlyingType(b) - typename = underlyingType(b(1).var); + typename = underlyingType(b.var{1}); end end end diff --git a/src/tensors/kernels/MatrixBlock.m b/src/tensors/kernels/MatrixBlock.m index 28bbf29..91ae84e 100644 --- a/src/tensors/kernels/MatrixBlock.m +++ b/src/tensors/kernels/MatrixBlock.m @@ -41,6 +41,13 @@ splits = split(trees); fuses = fuse(trees); + b.rank = rank; + b.charge = reshape(c, 1, []); + b.var = cell(size(b.charge)); + b.rowsizes = cell(size(b.charge)); + b.colsizes = cell(size(b.charge)); + b.tdims = cell(size(b.charge)); + for i = length(c):-1:1 ids = ic == i; @@ -52,12 +59,10 @@ coldims = mdims(ids, 2); colsizes = [0 cumsum(coldims(ia)).']; - b(i).charge = c(i); - b(i).var = uninit([rowsizes(end) colsizes(end)]); - b(i).rowsizes = rowsizes; - b(i).colsizes = colsizes; - b(i).tdims = tdims(ids, :); - b(i).rank = rank; + b.var{i} = uninit([rowsizes(end) colsizes(end)]); + b.rowsizes{i} = rowsizes; + b.colsizes{i} = colsizes; + b.tdims{i} = tdims(ids, :); end end @@ -100,8 +105,8 @@ %% Special case 2: addition without permutation - rx = X(1).rank; - ry = Y(1).rank; + rx = X.rank; + ry = Y.rank; if nargin == 4 || (isempty(p) && isempty(map)) || ... (all(p == 1:length(p)) && isequal(rx, ry) && ... @@ -117,25 +122,25 @@ if a == 1 && b == -1, Y = X - Y; return; end if a == -1 && b == 1, Y = Y - X; return; end + Yvar = Y.var; + Xvar = X.var; + % general addition + scalar multiplication cases - if a == 1 % b ~= 1 - for i = 1:length(Y) - Y(i).var = Y(i).var .* b + X(i).var; + if a == 1 % b ~= 1 + for i = 1:length(Yvar) + Yvar{i} = Yvar{i} .* b + Xvar{i}; end - return - end - - if b == 1 % a ~= 1 - for i = 1:length(Y) - Y(i).var = Y(i).var + X(i).var .* a; + elseif b == 1 % a ~= 1 + for i = 1:length(Yvar) + Yvar{i} = Yvar{i} + Xvar{i} .* a; + end + else % a ~= [0 1], b ~= [0 1] + for i = 1:length(Yvar) + Yvar{i} = Yvar{i} .* b + Xvar{i} .* a; end - return end - % a ~= [0 1], b ~= [0 1] - for i = 1:length(Y) - Y(i).var = Y(i).var .* b + X(i).var .* a; - end + Y.var = Yvar; return end @@ -153,11 +158,11 @@ vars_in = cell(size(map, 1), 1); offset = 0; - Xrowsizes = {X.rowsizes}; - Xcolsizes = {X.colsizes}; - Xtdims = {X.tdims}; - Xvar = {X.var}; - for i = 1:length(X) + Xrowsizes = X.rowsizes; + Xcolsizes = X.colsizes; + Xtdims = X.tdims; + Xvar = X.var; + for i = 1:length(Xvar) rowsz = Xrowsizes{i}; colsz = Xcolsizes{i}; @@ -192,11 +197,12 @@ end % inject small tensor blocks - Yrowsizes = {Y.rowsizes}; - Ycolsizes = {Y.colsizes}; + Yrowsizes = Y.rowsizes; + Ycolsizes = Y.colsizes; + Yvar = Y.var; if b == 0 offset = 0; - for i = 1:length(Y) + for i = 1:length(Yvar) rows = length(Yrowsizes{i}) - 1; cols = length(Ycolsizes{i}) - 1; if rows < cols @@ -204,22 +210,23 @@ for n = 1:rows m{n} = cat(2, vars_out{offset + n + ((1:cols)-1) * rows}); end - Y(i).var = cat(1, m{:}); + Yvar{i} = cat(1, m{:}); else m = cell(cols, 1); for n = 1:cols m{n} = cat(1, vars_out{offset + (n-1) * rows + (1:rows)}); end - Y(i).var = cat(2, m{:}); + Yvar{i} = cat(2, m{:}); end offset = offset + rows * cols; end + Y.var = Yvar; return end if b == 1 offset = 0; - for i = 1:length(Y) + for i = 1:length(Yvar) rows = length(Yrowsizes{i}) - 1; cols = length(Ycolsizes{i}) - 1; if rows < cols @@ -227,13 +234,13 @@ for n = 1:rows m{n} = cat(2, vars_out{offset + n + ((1:cols)-1) * rows}); end - Y(i).var = Y(i).var + cat(1, m{:}); + Yvar{i} = Yvar{i} + cat(1, m{:}); else m = cell(cols, 1); for n = 1:cols m{n} = cat(1, vars_out{offset + (n-1) * rows + (1:rows)}); end - Y(i).var = Y(i).var + cat(2, m{:}); + Yvar{i} = Yvar{i} + cat(2, m{:}); end offset = offset + rows * cols; end @@ -241,7 +248,7 @@ end offset = 0; - for i = 1:length(Y) + for i = 1:length(Yvar) rows = length(Yrowsizes{i}) - 1; cols = length(Ycolsizes{i}) - 1; if rows < cols @@ -249,13 +256,13 @@ for n = 1:rows m{n} = cat(2, vars_out{offset + n + ((1:cols)-1) * rows}); end - Y(i).var = b .* Y(i).var + cat(1, m{:}); + Yvar{i} = b .* Yvar{i} + cat(1, m{:}); else m = cell(cols, 1); for n = 1:cols m{n} = cat(1, vars_out{offset + (n-1) * rows + (1:rows)}); end - Y(i).var = b .* Y(i).var + cat(2, m{:}); + Yvar{i} = b .* Yvar{i} + cat(2, m{:}); end offset = offset + rows * cols; end @@ -286,48 +293,56 @@ % C : MatrixBlock % Result of computing C = (A .* a) * (B .* b) - Acharge = [A.charge]; - Bcharge = [B.charge]; - Ccharge = [C.charge]; + Acharge = A.charge; + Bcharge = B.charge; + Ccharge = C.charge; if isequal(Acharge, Ccharge) - indA = true(size(A)); - locA = 1:length(A); + indA = true(size(Acharge)); + locA = 1:length(Acharge); else [indA, locA] = ismember_sorted(Ccharge, Acharge); end if isequal(Bcharge, Ccharge) - indB = true(size(B)); - locB = 1:length(B); + indB = true(size(Bcharge)); + locB = 1:length(Bcharge); else [indB, locB] = ismember_sorted(Ccharge, Bcharge); end + Avar = A.var; + Bvar = B.var; + Cvar = C.var; + if nargin == 3 || (a == 1 && b == 1) for i = find(indA & indB) - C(i).var = A(locA(i)).var * B(locB(i)).var; + Cvar{i} = Avar{locA(i)} * Bvar{locB(i)}; end + C.var = Cvar; return end if a == 1 % b ~= 1 for i = find(indA & indB) - C(i).var = (A(locA(i)).var .* a) * B(locB(i)).var; + Cvar{i} = (Avar{locA(i)} .* a) * Bvar{locB(i)}; end + C.var = Cvar; return end if b == 1 % a ~= 1 for i = find(indA & indB) - C(i).var = A(locA(i)).var * (B(locB(i)).var .* b); + Cvar{i} = Avar{locA(i)} * (Bvar{locB(i)} .* b); end + C.var = Cvar; return end % a ~= 1 && b ~= 1 for i = find(indA & indB) - C(i).var = (A(locA(i)).var .* a) * (B(locB(i)).var .* b); + Cvar{i} = (Avar{locA(i)} .* a) * (Bvar{locB(i)} .* b); end + C.var = Cvar; end function tblocks = tensorblocks(b) @@ -344,22 +359,22 @@ % list of non-zero small tensor blocks, in column-major order. nblocks = 0; - for i = 1:length(b) - nblocks = nblocks + size(b(i).tdims, 1); + for i = 1:length(b.var) + nblocks = nblocks + size(b.tdims{i}, 1); end tblocks = cell(nblocks, 1); offset = 0; - p = rankrange(b(1).rank); - for i = 1:length(b) - rowsz = b(i).rowsizes; - colsz = b(i).colsizes; + p = rankrange(b.rank); + for i = 1:length(b.var) + rowsz = b.rowsizes{i}; + colsz = b.colsizes{i}; for k = 1:length(colsz) - 1 for j = 1:length(rowsz) - 1 offset = offset + 1; tblocks{offset} = permute(reshape(... - b(i).var(rowsz(j)+1:rowsz(j+1), colsz(k)+1:colsz(k+1)), ... - b(i).tdims(j + (k-1) * (length(rowsz)-1), p)), ... + b.var{i}(rowsz(j)+1:rowsz(j+1), colsz(k)+1:colsz(k+1)), ... + b.tdims{i}(j + (k-1) * (length(rowsz)-1), p)), ... p); end end @@ -382,52 +397,50 @@ % mcharges : AbstractCharge % list of coupled charges. - mblocks = {b.var}; + mblocks = b.var; if nargout > 1 - mcharges = [b.charge]; + mcharges = b.charge; end end function b = fill_matrix_data(b, vars, charges) if nargin < 3 || isempty(charges) - assert(length(vars) == length(b), ... + assert(length(vars) == length(b.var), ... 'Invalid number of blocks'); - for i = 1:length(b) - b(i).var = vars{i}; + for i = 1:length(b.var) + b.var{i} = vars{i}; end return end - [lia, locb] = ismember(charges, [b.charge]); + [lia, locb] = ismember(charges, b.charge); assert(all(lia)); - for i = 1:length(vars) - b(locb(i)).var = vars{i}; - end + b.var(locb) = vars; end function b = fill_matrix_fun(b, fun, charges) if nargin < 3 || isempty(charges) - for i = 1:length(b) - b(i).var = fun(size(b(i).var)); + for i = 1:length(b.var) + b.var{i} = fun(size(b.var{i})); end else - [lia, locb] = ismember(charges, [b.charge]); + [lia, locb] = ismember(charges, b.charge); for i = locb(lia) - b(i).var = fun(size(b(i).var), b(i).charge); + b.var{i} = fun(size(b.var{i}), b.charge(i)); end end end function b = fill_tensor_data(b, data) ctr = 0; - p = rankrange(b(1).rank); + p = rankrange(b.rank); - for i = 1:length(b) - rowsz = b(i).rowsizes; - colsz = b(i).colsizes; + for i = 1:length(b.var) + rowsz = b.rowsizes{i}; + colsz = b.colsizes{i}; for k = 1:length(colsz) - 1 for j = 1:length(rowsz) - 1 ctr = ctr + 1; - b(i).var(rowsz(j)+1:rowsz(j+1), colsz(k)+1:colsz(k+1)) = ... + b.var{i}(rowsz(j)+1:rowsz(j+1), colsz(k)+1:colsz(k+1)) = ... reshape(permute(data{ctr}, p), ... rowsz(j+1)-rowsz(j), colsz(k+1)-colsz(k)); end @@ -460,8 +473,8 @@ % Y : MatrixBlock % list of output matrices. - for i = 1:length(Y) - Y(i).var = X(i).var - Y(i).var; + for i = 1:length(Y.var) + Y.var{i} = X.var{i} - Y.var{i}; end end @@ -486,8 +499,8 @@ % Y : MatrixBlock % list of output matrices. - for i = 1:length(Y) - Y(i).var = Y(i).var + X(i).var; + for i = 1:length(Y.var) + Y.var{i} = X.var{i} + Y.var{i}; end end @@ -515,8 +528,8 @@ if a == 1, return; end if a == -1, Y = -Y; return; end - for i = 1:length(Y) - Y(i).var = Y(i).var .* a; + for i = 1:length(Y.var) + Y.var{i} = Y.var{i} .* a; end end @@ -544,8 +557,8 @@ if a == 1, return; end if a == -1, Y = -Y; return; end - for i = 1:length(Y) - Y(i).var = Y(i).var ./ a; + for i = 1:length(Y.var) + Y.var{i} = Y.var{i} ./ a; end end @@ -587,13 +600,13 @@ % A : MatrixBlock % list of output matrices. - for i = 1:length(Y) - Y(i).var = -Y(i).var; + for i = 1:length(Y.var) + Y.var{i} = -Y.var{i}; end end function t = underlyingType(X) - t = underlyingType(X(1).var); + t = underlyingType(X.var{1}); end function X = ctranspose(X) @@ -614,52 +627,53 @@ % X : :class:`MatrixBlock` % list of adjoint output matrices. - for i = 1:length(X) - X(i).var = X(i).var'; - [X(i).rowsizes, X(i).colsizes] = swapvars(X(i).rowsizes, X(i).colsizes); - X(i).tdims = fliplr(X(i).tdims); - X(i).rank = fliplr(X(i).rank); + [X.rowsizes, X.colsizes] = swapvars(X.rowsizes, X.colsizes); + X.rank = fliplr(X.rank); + for i = 1:length(X.var) + X.var{i} = X.var{i}'; + X.tdims{i} = fliplr(X.tdims{i}); end end function v = vectorize(X, type) + qdims = sqrt(qdim(X.charge)); switch type case 'complex' - blocks = cell(size(X)); - for i = 1:length(X) - blocks{i} = reshape(X(i).var .* sqrt(qdim(X(i).charge)), [], 1); + blocks = cell(size(X.var)); + for i = 1:length(X.var) + blocks{i} = reshape(X.var{i} .* qdims(i), [], 1); end v = vertcat(blocks{:}); case 'real' - blocks = cell(size(X)); - for i = 1:length(X) - X(i).var = X(i).var .* sqrt(qdim(X(i).charge)); - blocks{i} = [reshape(real(X(i).var), [], 1) - reshape(imag(X(i).var), [], 1)]; + blocks = cell(size(X.var)); + for i = 1:length(X.var) + tmp = X.var{i} .* qdims(i); + blocks{i} = [reshape(real(tmp), [], 1) + reshape(imag(tmp), [], 1)]; end v = vertcat(blocks{:}); end end function X = devectorize(v, X, type) + qdims = sqrt(qdim(X.charge)); switch type case 'complex' ctr = 0; - for i = 1:length(X) - n = numel(X(i).var); - X(i).var = reshape(v(ctr + (1:n)), size(X(i).var)) ./ ... - sqrt(qdim(X(i).charge)); + for i = 1:length(X.var) + n = numel(X.var{i}); + X.var{i} = reshape(v(ctr + (1:n)), size(X.var{i})) ./ qdims(i); ctr = ctr + n; end case 'real' ctr = 0; - for i = 1:length(X) - n = numel(X(i).var); - sz = size(X(i).var); - X(i).var = complex(reshape(v(ctr + (1:n)), sz), ... - reshape(v(ctr + (n + 1:2 * n)), sz)) ./ sqrt(qdim(X(i).charge)); + for i = 1:length(X.var) + n = numel(X.var{i}); + sz = size(X.var{i}); + X.var{i} = complex(reshape(v(ctr + (1:n)), sz), ... + reshape(v(ctr + (n + 1:2 * n)), sz)) ./ qdims(i); ctr = ctr + 2 * n; end end @@ -668,17 +682,17 @@ methods function assertBlocksizes(X) - for i = 1:numel(X) - assert(isequal(size(X(i).var), [X(i).rowsizes(end) X(i).colsizes(end)]), ... + for i = 1:numel(X.var) + assert(isequal(size(X.var{i}), [X.rowsizes{i}(end) X.colsizes{i}(end)]), ... 'kernel:dimerror', 'Wrong size of block'); - rows = length(X(i).rowsizes) - 1; - cols = length(X(i).colsizes) - 1; - matdims = [prod(X(i).tdims(:, 1:X(i).rank(1)), 2) ... - prod(X(i).tdims(:, X(i).rank(1)+1:end), 2)]; + rows = length(X.rowsizes{i}) - 1; + cols = length(X.colsizes{i}) - 1; + matdims = [prod(X.tdims{i}(:, 1:X.rank(1)), 2) ... + prod(X(i).tdims(:, X.rank(1)+1:end), 2)]; for k = 1:cols for j = 1:rows - assert(matdims(j + (k-1) * rows, 1) == X(i).rowsizes(j+1) - X(i).rowsizes(j)); - assert(matdims(j + (k-1) * rows, 2) == X(i).colsizes(k+1) - X(i).colsizes(k)); + assert(matdims(j + (k-1) * rows, 1) == X.rowsizes{i}(j+1) - X.rowsizes{i}(j)); + assert(matdims(j + (k-1) * rows, 2) == X.colsizes{i}(k+1) - X.colsizes{i}(k)); end end end From 6bd963ade0ea80f770bd6c16516e7d55d4a7282d Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 15 Feb 2023 17:30:56 +0100 Subject: [PATCH 124/245] local operator expval --- src/mps/FiniteMpo.m | 20 +++++++++++++++ src/mps/InfMpo.m | 9 ++----- src/mps/MpoTensor.m | 48 +++++++++++++++++++++++++++++++++++ src/mps/UniformMps.m | 23 ++++++++++++++++- src/tensors/spaces/SumSpace.m | 2 +- 5 files changed, 93 insertions(+), 9 deletions(-) diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index 5509502..110dd99 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -204,6 +204,26 @@ R = MpsTensor(Tensor.randnc([pspaces(end)' vspaces(end)], pspaces(end)')); mpo = FiniteMpo(L, O, R); end + + function T = mps_channel_operator(Atop, O, Abot) + arguments + Atop % top mps tensors + O % cell of mpotensors (unrotated) + Abot % bottom mps tensors (unconjugated) + end + + for i = numel(Atop):-1:1 + atop = Atop(i); + o = rot90(O{i}); + twistinds = 1 + find(isdual(space(Atop(i), 2:nspaces(Atop(i)) - 1))); + abot = twist(Abot(i)', twistinds); + + assert(isequal(space(abot, 2), space(o, 1)')); + assert(isequal(space(o, 3), space(atop, 2)')); + + T(i, 1) = FiniteMpo(abot, {o}, atop); + end + end end end diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index 255c041..339d328 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -198,13 +198,8 @@ else A2 = mps2.AR(sites); end - O = cellfun(@rot90, mpo.O(sites), 'UniformOutput', false); - - for i = 1:numel(A2) - T(i, 1) = FiniteMpo(... - twist(A2(i)', 1 + find(isdual(space(A1(i), 2:nspaces(A1(i))-1)))), ... - O(i), A1(i)); - end + O = mpo.O(sites); %#ok + T = FiniteMpo.mps_channel_operator(A1, O, A2); %#ok end function H = AC_hamiltonian(mpo, mps, GL, GR, sites) diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index 45c3ac4..bdc1088 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -349,6 +349,54 @@ scalars = zeros(size(tensors)); O = MpoTensor(tensors, scalars); end + + function local_operators = decompose_local_operator(H, kwargs) + % convert a tensor into a product of local operators. + % + % Usage + % ----- + % :code:`local_operators = MpoTensor.decompose_local_operator(H, kwargs)`. + % + % Arguments + % --------- + % H : :class:`AbstractTensor` + % tensor representing a local operator on N sites. + % + % Keyword Arguments + % ----------------- + % 'Trunc' : cell + % optional truncation method for the decomposition. See also + % :method:`Tensor.tsvd` + arguments + H + kwargs.Trunc = {'TruncBelow', 1e-14} + end + + assert(mod(nspaces(H), 2) == 0, ... + 'MpoTensor:Argerror', 'local operator must have an even amount of legs.'); + H = repartition(H, nspaces(H) ./ [2 2]); + assert(isequal(H.domain, H.codomain), ... + 'MpoTensor:ArgError', 'local operator must be square.'); + + N = indin(H); + local_operators = cell(1, N); + if N == 1 + local_operators{1} = insert_onespace(insert_onespace(H, 1), 3); + else + [u, s, v] = tsvd(H, [1 N+1], [2:N N+2:2*N], kwargs.Trunc{:}); + local_operators{1} = insert_onespace(tpermute(u * s, [1 3 2], [1 2]), 1); + + for i = 2:N-1 + [u, s, v] = tsvd(v, [1 2 N-i+3], [3:(N-i+2) (N-i+4):nspaces(v)], ... + kwargs.Trunc{:}); + local_operators{i} = tpermute(u * s, [1 2 4 3], [2 2]); + end + + local_operators{N} = insert_onespace(repartition(v, [2 1]), 3); + end + + local_operators = cellfun(@MpoTensor, local_operators, 'UniformOutput', false); + end end end diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index 8d55c09..d8409ed 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -564,12 +564,33 @@ E = prod(E); elseif isa(O, 'AbstractTensor') - error('TBA'); + E = local_expectation_value(mps1, O); else error('Unknown operator type (%s)', class(O)); end end + function E = local_expectation_value(mps, O, offset) + arguments + mps + O + offset = 0 % site offset + end + + local_ops = MpoTensor.decompose_local_operator(O); + N = length(local_ops); + + A = [mps.AC(1 + offset) mps.AR(2:(N - 1) + offset)]; + + T = FiniteMpo.mps_channel_operator(A, local_ops, A); + rhoL = insert_onespace(fixedpoint(mps, 'l_LL', offset+ 1), 2, ... + ~isdual(space(T(1).O{1}, 4))); + rhoR = insert_onespace(fixedpoint(mps, 'r_RR', offset + N), 2, ... + ~isdual(space(T(1).O{1}, 2))); + E1 = apply(T, rhoL); + E = contract(E1, 1:3, rhoR, flip(1:3)); + end + function [svals, charges] = schmidt_values(mps, w) arguments mps diff --git a/src/tensors/spaces/SumSpace.m b/src/tensors/spaces/SumSpace.m index 91c2a8d..2f5f506 100644 --- a/src/tensors/spaces/SumSpace.m +++ b/src/tensors/spaces/SumSpace.m @@ -65,7 +65,7 @@ space2 SumSpace end - space = sum(subspaces(space1)) * sum(subspaces(space2)); + space = SumSpace(sum(subspaces(space1)) * sum(subspaces(space2))); end end From 471d627505c36eaf398ddaf2dbbb313e0cbe0966 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 15 Feb 2023 17:37:15 +0100 Subject: [PATCH 125/245] bugfix tensortrace syntax change --- test/TestFusionTree.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/TestFusionTree.m b/test/TestFusionTree.m index 2c63ed3..48e38c9 100644 --- a/test/TestFusionTree.m +++ b/test/TestFusionTree.m @@ -235,7 +235,7 @@ function traces(tc) if f.uncoupled(k, j) ~= conj(f.uncoupled(k, j+1)) assertEqual(tc, norm(c1(k, :)), 0); else - a1 = tensortrace(a1_cell{k}, [1:j-1 j j j+2:f.legs+1]); + a1 = tensortrace(a1_cell{k}, j, j+1); a2 = zeros(size(a1)); [~, col, val] = find(c1(k, :)); for l = 1:length(val) From ca1d7db80ffc725fa6ee788f8e095dd2dc266071 Mon Sep 17 00:00:00 2001 From: leburgel Date: Wed, 15 Feb 2023 17:41:24 +0100 Subject: [PATCH 126/245] Fix(?) docs build --- .readthedocs.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 2164c6b..035079a 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -14,8 +14,8 @@ sphinx: configuration: docs/src/conf.py # Optionally build your docs in additional formats such as PDF and ePub -formats: - - htmlzip +formats: [] + #- htmlzip #- pdf submodules: From d694e4e24cfd289bc2180e682ce5580c36e9515e Mon Sep 17 00:00:00 2001 From: leburgel Date: Mon, 20 Feb 2023 10:59:01 +0100 Subject: [PATCH 127/245] Small sparse array updates --- src/utility/sparse/SparseArray.m | 65 +++++++++++++++++++------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/src/utility/sparse/SparseArray.m b/src/utility/sparse/SparseArray.m index efe4545..cbb8518 100644 --- a/src/utility/sparse/SparseArray.m +++ b/src/utility/sparse/SparseArray.m @@ -33,12 +33,12 @@ % Empty constructor. % % :code:`a = SparseArray(b)` - % Copies/converts :code:`b` if it is a :class:`SparseArray`, a dense array or a sparse - % matrix. + % Copies/converts :code:`b` if it is a :class:`SparseArray`, a dense array or + % a sparse matrix. % % :code:`a = SparseArray(b, sz)` - % Copies/converts :code:`b` if it is a :class:`SparseArray`, a dense array or a sparse - % matrix, and sets the size of :code:`a` to :code:`sz` + % Copies/converts :code:`b` if it is a :class:`SparseArray`, a dense array or + % a sparse matrix, and sets the size of :code:`a` to :code:`sz` % % Example % ------- @@ -293,8 +293,10 @@ function disp(a) % ------- % subs : (:, :) :class:`int` % subscripts of nonzero array entries. + % % idx : (:, 1) :class:`int` % linear indices of nonzero array entries. + % % var : (:, 1) :class:`double` % values of nonzero array entries. [idx, ~, vals] = find(a.var); @@ -409,7 +411,6 @@ function disp(a) % Returns % ------- % bool : :class:`logical` - % defaults to :code:`false` for :class:`SparseArray`. bool = ismatrix(a) && size(a, 1) == 1; end @@ -464,26 +465,24 @@ function disp(a) % >> a - 5 %<-- dense % >> a - 0 %<-- dense % >> a - full(a) %<-- dense - if (isa(a, 'double') && ~issparse(a)) || (isa(b, 'double') && ~issparse(b)) - c = full(a) - full(b); - return; - end - c = SparseArray(a); b = SparseArray(b); - c.var = c.var - b.var; + c = plus(a, -b); end function c = mrdivide(a, b) % Matrix right division for sparse arrays. % - % :code:`mrdivide(a, b)` is called for the syntax :code:`a / b` when :code:`a` - % is sparse and :code:`b` is a scalar. + % Usage + % ----- + % :code:`mrdivide(a, b)` + % + % :code:`a / b` % % Arguments % --------- % a : :class:`SparseArray` % intput array. % b : :class:`double` - % scalar to divide by + % scalar to divide by. % % Returns % ------- @@ -597,10 +596,25 @@ function disp(a) function c = plus(a, b) % Elementwise addition for sparse arrays. % - % :code:`plus(a, b)` is called for the syntax :code:`a + b` when :code:`a` or - % :code:`b` is a sparse array. :code:`a` and :code:`b` must have the same size, + % Usage + % ----- + % + % :code:`plus(a, b)` + % + % :code:`a + b` + % + % :code:`a` and :code:`b` must have the same size, % unless one is a scalar. A scalar can be added to a sparse array of any size. % + % Arguments + % --------- + % a, b : :class:`SparseArray` or :class:`double` + % input arrays. + % + % Returns + % ------- + % + % % Example % ------- % .. code-block:: matlab @@ -618,11 +632,10 @@ function disp(a) c.var = c.var + b.var; end - function c = power(a, b) + function a = power(a, b) % Elementwise power for sparse array. assert(isscalar(b), 'sparse:NonScalarPower', 'SparseArray only supports power with a scalar.') - c = a; - c.var = c.var.^b; + a.var = a.var.^b; end function varargout = qr(a, varargin) @@ -688,7 +701,7 @@ function disp(a) end end - function b = real(a) + function a = real(a) % Complex real part of sparse array. % % Arguments @@ -701,8 +714,7 @@ function disp(a) % b : :class:`SparseArray` % output array with real entries corresponding to the real part of the % entries of :code:`a`. - b = a; - b.var = real(b.var); + a.var = real(a.var); end function a = reshape(a, varargin) @@ -763,7 +775,7 @@ function disp(a) b = sparse(a); end - function b = squeeze(a) + function a = squeeze(a) % Remove singleton dimensions from a sparse array. % % Usage @@ -779,13 +791,12 @@ function disp(a) % >> squeeze(SparseArray.random([2, 1, 3], 0.5)) %<-- returns a 2 x 3 SparseArray % >> squeeze(SparseArray([1, 1, 1], 1, [1, 1, 1])) %<-- returns a scalar if sum(a.sz > 1) == 0 - b = full(a.var); + a = full(a.var); return end % always give n x 1 SparseArray in case of only 1 non-singleton dimension, % consistent with class constructor - b = a; - b.sz = [a.size(a.size>1), ones(1, 2-sum(a.size>1))]; + a.sz = [a.size(a.size>1), ones(1, 2-sum(a.size>1))]; end function a = subsasgn(a, s, rhs) @@ -1008,6 +1019,7 @@ function disp(a) % ------- % b : :class:`SparseArray` % output array. + assert(ismatrix(a), 'sparse:RankError', 'ctranspose is only defined for 2D sparse arrays.'); a = permute(a, [2, 1]); end @@ -1052,6 +1064,7 @@ function disp(a) % output array. return; end + end methods (Static) From 3ef9df1ac4649b14c14b4552f601e1f1a63d5cc1 Mon Sep 17 00:00:00 2001 From: leburgel Date: Mon, 20 Feb 2023 15:48:00 +0100 Subject: [PATCH 128/245] Multi-line fixes --- src/algorithms/Vumps.m | 73 +++++++++++++++++++++++++--------------- src/mps/InfMpo.m | 74 +++++++++++++++++++++-------------------- src/mps/UniformMps.m | 75 +++++++++++++++++++++++------------------- test/TestInfMpo.m | 8 ++++- 4 files changed, 135 insertions(+), 95 deletions(-) diff --git a/src/algorithms/Vumps.m b/src/algorithms/Vumps.m index ae30c37..c0e464f 100644 --- a/src/algorithms/Vumps.m +++ b/src/algorithms/Vumps.m @@ -118,12 +118,16 @@ else sites = 1:period(mps); end - H_AC = AC_hamiltonian(mpo, mps, GL, GR, sites); - AC = mps.AC(sites); + ACs = arrayfun(@(x) x.AC(sites), mps, 'UniformOutput', false); + AC = vertcat(ACs{:}); for i = length(sites):-1:1 - [AC(i).var, ~] = eigsolve(H_AC{i}, AC(sites(i)).var, 1, alg.which, ... + [AC(1, i).var, ~] = eigsolve(H_AC{i}, AC(1, i).var, 1, alg.which, ... kwargs{:}); + + for d = 2:depth(mpo) + AC(d, i).var = H_AC{i}(d).apply(AC(d-1, i).var); + end end end @@ -136,9 +140,15 @@ end H_C = C_hamiltonian(mpo, mps, GL, GR, sites); + Cs = arrayfun(@(x) x.C(sites), mps, 'UniformOutput', false); + C = vertcat(Cs{:}); for i = length(sites):-1:1 - [C(i), ~] = eigsolve(H_C{i}, mps.C(sites(i)), 1, alg.which, ... + [C(1, i), ~] = eigsolve(H_C{i}, C(1, i), 1, alg.which, ... kwargs{:}); + + for d = 2:depth(mpo) + C(d, i) = H_C{i}(d).apply(C(d-1, i)); + end end end @@ -149,10 +159,12 @@ sites = 1:period(mps); end - for i = length(AC):-1:1 - [Q_AC, ~] = leftorth(AC(i), 'polar'); - [Q_C, ~] = leftorth(C(i), 1, 2, 'polar'); - mps.AL(sites(i)) = multiplyright(Q_AC, Q_C'); + for d = size(AC, 1):-1:1 + for i = size(AC, 2):-1:1 + [Q_AC, ~] = leftorth(AC(d, i), 'polar'); + [Q_C, ~] = leftorth(C(d, i), 1, 2, 'polar'); + mps(d).AL(sites(i)) = multiplyright(Q_AC, Q_C'); + end end kwargs = namedargs2cell(alg.alg_canonical); @@ -164,31 +176,40 @@ alg mpo mps - GL = cell(1, period(mps)) - GR = cell(1, period(mps)) + GL = cell(depth(mpo), period(mps)) + GR = cell(depth(mpo), period(mps)) end kwargs = namedargs2cell(alg.alg_environments); - [GL, GR, lambda] = environments(mpo, mps, mps, GL, GR, ... - kwargs{:}); + D = depth(mpo); + lambda = zeros(D, 1); + for d = 1:D + [GL(d, :), GR(d, :), lambda(d)] = environments(mpo.slice(d), mps(d), mps(next(d, D)), GL(d, :), GR(d, :), ... + kwargs{:}); + end + lambda = prod(lambda); end function eta = convergence(alg, mpo, mps, GL, GR) + % TODO: also implement galerkin error H_AC = AC_hamiltonian(mpo, mps, GL, GR); H_C = C_hamiltonian(mpo, mps, GL, GR); - eta = zeros(1, period(mps)); - for w = 1:period(mps) - AC_ = apply(H_AC{w}, mps.AC(w)); - lambda_AC = dot(AC_, mps.AC(w)); - AC_ = normalize(AC_ ./ lambda_AC); - - ww = prev(w, period(mps)); - C_ = apply(H_C{ww}, mps.C(ww)); - lambda_C = dot(C_, mps.C(ww)); - C_ = normalize(C_ ./ lambda_C); - - eta(w) = distance(AC_ , ... - repartition(multiplyleft(mps.AR(w), C_), rank(AC_))); + eta = zeros(depth(mpo), period(mps)); + for d = 1:depth(mpo) + dd = next(d, depth(mpo)); + for w = 1:period(mps) + AC_ = apply(H_AC{w}(d), mps(d).AC(w)); + lambda_AC = dot(AC_, mps(dd).AC(w)); + AC_ = normalize(AC_ ./ lambda_AC); + + ww = prev(w, period(mps)); + C_ = apply(H_C{ww}(d), mps(d).C(ww)); + lambda_C = dot(C_, mps(dd).C(ww)); + C_ = normalize(C_ ./ lambda_C); + + eta(dd, w) = distance(AC_ , ... + repartition(multiplyleft(mps(dd).AR(w), C_), rank(AC_))); + end end eta = max(eta, [], 'all'); end @@ -255,7 +276,7 @@ function plot(alg, iter, mps, eta) axhistory.Children(end).YData(end+1) = eta; end - plot_entanglementspectrum(mps, 1:period(mps), axspectrum); + plot_entanglementspectrum(mps, 1:D, 1:W, axspectrum); drawnow end diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index 339d328..3f765f8 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -13,18 +13,13 @@ O = varargin{1}; if isa(O, 'InfMpo') - for i = numel(O):1:1 - mpo(i, 1).O = O(i, 1).O; - end - mpo = reshape(mpo, size(O)); + mpo.O = O.O; elseif isa(O, 'MpoTensor') mpo.O = {O}; elseif isa(O, 'AbstractTensor') - for i = height(O):-1:1 - mpo(i, 1).O = arrayfun(@MpoTensor, O(i, :), 'UniformOutput', false); - end + mpo.O = arrayfun(@MpoTensor, O, 'UniformOutput', false); elseif iscell(O) mpo.O = O; @@ -36,29 +31,28 @@ methods function p = period(mpo) - p = length(mpo(1).O); + p = size(mpo.O, 2); end function d = depth(mpo) - d = length(mpo); + d = size(mpo.O, 1); end function mpo = block(mpo) if depth(mpo) == 1, return; end - O_ = mpo(1, 1).O; + O_ = mpo.O(1, :); for d = 2:depth(mpo) for w = period(mpo):-1:1 - vspaces = [rightvspace(O_{w})' rightvspace(mpo(d, 1).O{w})']; + vspaces = [rightvspace(O_{w})' rightvspace(mpo.O{d, w})']; fuser(w) = Tensor.eye(vspaces, prod(vspaces)); end for w = 1:period(mpo) - O_{w} = MpoTensor(contract(O_{w}, [1 2 5 -4], mpo(d, 1).O{w}, [3 -2 4 2], ... + O_{w} = MpoTensor(contract(O_{w}, [1 2 5 -4], mpo.O{d, w}, [3 -2 4 2], ... fuser(prev(w, period(mpo)))', [-1 3 1], fuser(w), [5 4 -3], ... 'Rank', [2 2])); end end - mpo = mpo(1, 1); mpo.O = O_; end @@ -76,6 +70,10 @@ mpo = InfMpo(vertcat(Os{:})); end + function out = slice(mpo, d) + out = InfMpo(mpo.O(d, :)); + end + function mps = initialize_mps(mpo, vspaces) arguments mpo @@ -213,10 +211,12 @@ H = cell(1, length(sites)); for i = 1:length(sites) - gl = twistdual(GL{sites(i)}, 1); - gr = GR{next(sites(i), period(mps))}; - gr = twistdual(gr, nspaces(gr)); - H{i} = FiniteMpo(gl, mpo.O(sites(i)), gr); + for d = depth(mpo):-1:1 + gl = twistdual(GL{d, sites(i)}, 1); + gr = GR{d, next(sites(i), period(mps))}; + gr = twistdual(gr, nspaces(gr)); + H{i}(d, 1) = FiniteMpo(gl, mpo.O(d, sites(i)), gr); + end end end @@ -231,17 +231,19 @@ H = cell(1, length(sites)); for i = 1:length(sites) - gl = GL{sites(i)}; - gl = twistdual(gl, 1); - gr = GR{mod1(sites(i) + 2, period(mps))}; - gr = twistdual(gr, nspaces(gr)); - H{i} = FiniteMpo(gl, mpo.O(mod1(sites(i) + [0 1], period(mps))), gr); + for d = depth(mpo):-1:1 + gl = GL{d, sites(i)}; + gl = twistdual(gl, 1); + gr = GR{d, mod1(sites(i) + 2, period(mps))}; + gr = twistdual(gr, nspaces(gr)); + H{i}(d, 1) = FiniteMpo(gl, mpo.O(d, mod1(sites(i) + [0 1], period(mps))), gr); + end end end - function H = C_hamiltonian(~, mps, GL, GR, sites) + function H = C_hamiltonian(mpo, mps, GL, GR, sites) arguments - ~ + mpo mps GL = fixedpoint(transfermatrix(mpo, mps, 'Type', 'LL')) GR = fixedpoint(transfermatrix(mpo, mps, 'Type', 'RR').') @@ -250,20 +252,22 @@ H = cell(1, length(sites)); for i = 1:length(sites) - gl = GL{next(sites(i), period(mps))}; - for j = 1:numel(gl) - if nnz(gl(j)) ~= 0 - gl(j) = twist(gl(j), find(isdual(space(gl(j), 1)))); + for d = depth(mpo):-1:1 + gl = GL{d, next(sites(i), period(mps))}; + for j = 1:numel(gl) + if nnz(gl(j)) ~= 0 + gl(j) = twist(gl(j), find(isdual(space(gl(j), 1)))); + end end - end - gr = GR{next(sites(i), period(mps))}; - for j = 1:numel(gr) - if nnz(gr(j)) ~= 0 - gr(j) = twist(gr(j), find(isdual(space(gr(j), nspaces(gr(j))))) + ... - nspaces(gr(j)) - 1); + gr = GR{d, next(sites(i), period(mps))}; + for j = 1:numel(gr) + if nnz(gr(j)) ~= 0 + gr(j) = twist(gr(j), find(isdual(space(gr(j), nspaces(gr(j))))) + ... + nspaces(gr(j)) - 1); + end end + H{i}(d, 1) = FiniteMpo(gl, {}, gr); end - H{i} = FiniteMpo(gl, {}, gr); end end end diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index d8409ed..69ddd44 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -170,9 +170,7 @@ methods function p = period(mps) % period over which the mps is translation invariant. - for i = numel(mps):-1:1 - p(i) = length(mps(i).AL); - end + p = length(mps(1).AL); end function d = depth(mps) @@ -188,6 +186,14 @@ mps = UniformMps([ALs{:}], [ARs{:}], [Cs{:}], [ACs{:}]); end + function mps = vertcat(varargin) + for i = 2:length(varargin) + assert(period(varargin{1}) == period(varargin{i}), ... + 'Can only stack UniformMps with matching periods.') + end + mps = builtin('vertcat', varargin{:}); + end + function s = leftvspace(mps, w) % return the virtual space to the left of site w. if nargin == 1 || isempty(w), w = 1:period(mps); end @@ -601,43 +607,46 @@ svals = cellfun(@diag, svals, 'UniformOutput', false); end - function plot_entanglementspectrum(mps, w, ax) - if nargin == 1 - w = 1:period(mps); - figure; - ax = gobjects(depth(mps), width(mps)); - for ww = 1:length(w) - ax(1, ww) = subplot(1, length(w), ww); - end - elseif nargin == 2 + function plot_entanglementspectrum(mps, d, w, ax) + arguments + mps + d = 1:depth(mps) + w = 1:period(mps) + ax = [] + end + if isempty(ax) figure; ax = gobjects(depth(mps), width(mps)); - for ww = 1:length(w) - ax(1, ww) = subplot(1, length(w), ww); + for dd = 1:length(d) + for ww = 1:length(w) + ax(dd, ww) = subplot(length(d), length(w), ww + (dd-1)*length(w)); + end end end lim_y = 1; - for ww = 1:length(w) - [svals, charges] = schmidt_values(mps, w(ww)); - ctr = 0; - hold off; - lim_x = 1; - - ticks = zeros(size(svals)); - labels = arrayfun(@string, charges, 'UniformOutput', false); - lengths = cellfun(@length, svals); - ticks = cumsum(lengths); - try - semilogy(ax(1, ww), 1:sum(lengths), vertcat(svals{:}).', '.', 'MarkerSize', 10); - catch - bla + for dd = 1:length(d) + for ww = 1:length(w) + [svals, charges] = schmidt_values(mps(dd), w(ww)); + ctr = 0; + hold off; + lim_x = 1; + + ticks = zeros(size(svals)); + labels = arrayfun(@string, charges, 'UniformOutput', false); + lengths = cellfun(@length, svals); + ticks = cumsum(lengths); + try + semilogy(ax(dd, ww), 1:sum(lengths), vertcat(svals{:}).', '.', 'MarkerSize', 10); + catch + bla + end + set(ax(dd, ww), 'TickLabelInterpreter', 'latex'); + set(ax(dd, ww), 'Xtick', ticks, 'XTickLabel', labels, 'fontsize', 10, ... + 'XtickLabelRotation', 60, 'Xgrid', 'on'); + xlim(ax(dd, ww), [1 - 1e-8 ticks(end) + 1e-8]); + end - set(ax(1, ww), 'TickLabelInterpreter', 'latex'); - set(ax(1, ww), 'Xtick', ticks, 'XTickLabel', labels, 'fontsize', 10, ... - 'XtickLabelRotation', 60, 'Xgrid', 'on'); - xlim(ax(1, ww), [1 - 1e-8 ticks(end) + 1e-8]); - end % for ww = 1:length(w) diff --git a/test/TestInfMpo.m b/test/TestInfMpo.m index a03bfaf..074f582 100644 --- a/test/TestInfMpo.m +++ b/test/TestInfMpo.m @@ -71,7 +71,13 @@ function test2dIsing(tc) mpo = [mpo mpo]; [mps2, lambda] = fixedpoint(alg, mpo, mps); - tc.assertEqual(-log(sqrt(lambda)) / beta, freeEnergyExact, 'RelTol', 1e-5); + tc.assertEqual(-log(lambda) / 2 / beta, freeEnergyExact, 'RelTol', 1e-5); + + mps = [mps; mps]; + mpo = [mpo; mpo]; + + [mps2, lambda] = fixedpoint(alg, mpo, mps); + tc.assertEqual(-log(lambda)/ 4 / beta, freeEnergyExact, 'RelTol', 1e-5); end function test2dfDimer(tc) From d79c0a18105221f9fdb610fd2cf79b67b839f3e5 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 20 Feb 2023 17:02:00 +0100 Subject: [PATCH 129/245] first progress on FiniteMps --- src/mps/FiniteMps.m | 148 +++++++++++++++++++++++++++++++++++ src/mps/MpsTensor.m | 51 +++++++++++- src/tensors/AbstractTensor.m | 4 + src/tensors/Tensor.m | 6 +- 4 files changed, 204 insertions(+), 5 deletions(-) create mode 100644 src/mps/FiniteMps.m diff --git a/src/mps/FiniteMps.m b/src/mps/FiniteMps.m new file mode 100644 index 0000000..987fc4b --- /dev/null +++ b/src/mps/FiniteMps.m @@ -0,0 +1,148 @@ +classdef FiniteMps + % Finite Matrix product states + + properties + A MpsTensor + center + end + + methods + function mps = FiniteMps(varargin) + + if nargin == 0, return; end + if nargin == 1 + mps.A = varargin{1}; + elseif nargin == 2 + mps.A = varargin{1}; + mps.center = varargin{2}; + end + end + + function p = length(mps) + p = length(mps.A); + end + + function mps = movecenter(mps, newcenter, alg) + arguments + mps + newcenter {mustBeInteger} = floor(length(mps) / 2) + alg = 'polar' + end + + assert(newcenter >= 1 && newcenter <= length(mps), ... + 'mps:gauge', ... + sprintf('new center (%d) must be in 1:%d', newcenter, length(mps))); + + for i = max(mps.center, 1):(newcenter - 1) + [mps.A{i}, L] = leftorth(mps.A{i}, alg); + mps.A{i + 1} = multiplyleft(mps.A{i + 1}, L); + end + for i = min(mps.center, length(mps)):-1:(newcenter + 1) + [R, mps.A{i}] = rightorth(mps.A{i}, alg); + mps.A{i - 1} = multiplyright(mps.A{i - 1}, R); + end + mps.center = newcenter; + end + + function T = transfermatrix(mps1, mps2, sites) + arguments + mps1 + mps2 = mps1 + sites = 1:length(mps1) + end + + assert(all(diff(sites) == 1), 'sites must be neighbouring and increasing.'); + T = transfermatrix(mps1.A(sites), mps2.A(sites)); + end + + function o = overlap(mps1, mps2, rholeft, rhoright) + arguments + mps1 + mps2 + rholeft = [] + rhoright = [] + end + + assert(length(mps1) == length(mps2), 'mps should have equal length'); + assert(length(mps1) >= 2, 'edge case to be added'); + + n = floor(length(mps1) / 2); + Tleft = transfermatrix(mps1, mps2, 1:n); + rholeft = apply(Tleft, rholeft); + Tright = transfermatrix(mps1, mps2, n+1:length(mps1)).'; + rhoright = apply(Tright, rhoright); + + o = overlap(rholeft, rhoright); + end + + function n = norm(mps) + if isempty(mps.center) + n = sqrt(abs(overlap(mps, mps))); + return + end + + n = norm(mps.A(mps.center)); + end + + function [mps, n] = normalize(mps) + if isempty(mps.center), mps = movecenter(mps); end + n = norm(mps); + mps.A(mps.center) = mps.A(mps.center) ./ n; + end + + function [svals, charges] = schmidt_values(mps, w) + arguments + mps + w {mustBeInteger} = floor(length(mps) / 2) + end + + assert(0 <= w && w <= length(mps)); + if isempty(mps.center), mps = movecenter(mps, w); end + + if w < mps.center + mps = movecenter(mps, w + 1); + S = tsvd(mps.A(w + 1), 1, 2:nspaces(mps.A(w + 1))); + else + mps = movecenter(mps, w); + S = tsvd(mps.A(w), 1:nspaces(mps.A(w))-1, nspaces(mps.A(w))); + end + [svals, charges] = matrixblocks(S); + svals = cellfun(@diag, svals, 'UniformOutput', false); + end + end + + methods (Static) + function mps = new(fun, vspace1, pspaces, vspaces) + arguments + fun + vspace1 + end + arguments (Repeating) + pspaces + vspaces + end + + if isempty(fun), fun = @randnc; end + + L = length(pspaces); + vspaces = [{vspace1} vspaces]; + + for w = length(pspaces):-1:1 + rankdeficient = vspaces{w} * pspaces{w} < vspaces{w + 1} || ... + vspaces{w} > pspaces{w}' * vspaces{w + 1}; + if rankdeficient + error('mps:rank', ... + 'Cannot create a full rank mps with given spaces.'); + end + + A{w} = Tensor.new(fun, [vspaces{w} pspaces{w}], vspaces{w + 1}); + end + mps = FiniteMps(A); + end + + function mps = randnc(varargin) + mps = FiniteMps.new(@randnc, varargin{:}); + end + end +end + diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index 94fa534..97d3bad 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -115,19 +115,41 @@ function A = plus(varargin) for i = 1:2 if isa(varargin{i}, 'MpsTensor') + alegs = varargin{i}.alegs; varargin{i} = varargin{i}.var; end end - A = plus(varargin{:}); + A = MpsTensor(plus(varargin{:}), alegs); end function A = minus(varargin) for i = 1:2 if isa(varargin{i}, 'MpsTensor') + alegs = varargin{i}.alegs; varargin{i} = varargin{i}.var; end end - A = minus(varargin{:}); + A = MpsTensor(minus(varargin{:}), alegs); + end + + function A = rdivide(varargin) + for i = 1:2 + if isa(varargin{i}, 'MpsTensor') + alegs = varargin{i}.alegs; + varargin{i} = varargin{i}.var; + end + end + A = MpsTensor(rdivide(varargin{:}), alegs); + end + + function A = ldivide(varargin) + for i = 1:2 + if isa(varargin{i}, 'MpsTensor') + alegs = varargin{i}.alegs; + varargin{i} = varargin{i}.var; + end + end + A = MpsTensor(ldivide(varargin{:}), alegs); end function n = norm(A) @@ -339,7 +361,12 @@ arguments L MpsTensor R MpsTensor - v + v = [] + end + + if isempty(v) + v = tracetransfer(L, R); + return end auxlegs_v = nspaces(v) - 2; @@ -353,6 +380,24 @@ 'Rank', newrank); end + function v = tracetransfer(L, R) + arguments + L MpsTensor + R MpsTensor + end + + auxlegs_l = L.alegs; + auxlegs_r = R.alegs; + assert(R.plegs == L.plegs); + plegs = L.plegs; %#ok + newrank = [2 auxlegs_l + auxlegs_r]; + + v = contract(... + L, [-1 1:(plegs + 1) (-(1:auxlegs_l) - 2)], ... + R, [flip(1:(plegs + 1)) -2 (-(1:auxlegs_r) - 3 - auxlegs_l)], ... + 'Rank', newrank); %#ok + end + function rho = applyleft(T, B, rho) if nargin == 2 rho = []; diff --git a/src/tensors/AbstractTensor.m b/src/tensors/AbstractTensor.m index 1c1a08f..33bdee2 100644 --- a/src/tensors/AbstractTensor.m +++ b/src/tensors/AbstractTensor.m @@ -441,6 +441,10 @@ iE = [1:length(i1) length(i1) + (length(i2):-1:1)]; B = tensorprod(A, E, iA, iE); end + + function o = overlap(t1, t2) + o = contract(t1, 1:nspaces(t1), t2, flip(1:nspaces(t1))); + end end end diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index f1c2a51..dedd2fc 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -835,9 +835,11 @@ end end - function t = normalize(t) + function [t, n] = normalize(t) + n = zeros(size(t)); for i = 1:numel(t) - t(i) = t(i) .* (1 / norm(t(i))); + n(i) = norm(t(i)); + t(i) = t(i) .* (1 / n(i)); end end From 4ac56f56936279fa5f0ef88e296aa5f85694250b Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 20 Feb 2023 17:41:50 +0100 Subject: [PATCH 130/245] bugfix slicing/concatenating --- src/mps/InfJMpo.m | 5 ----- src/mps/InfMpo.m | 12 ++++++------ 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/mps/InfJMpo.m b/src/mps/InfJMpo.m index 8817793..f37111f 100644 --- a/src/mps/InfJMpo.m +++ b/src/mps/InfJMpo.m @@ -155,11 +155,6 @@ end end - function mpo = horzcat(varargin) - Os = cellfun(@(x) x.O, varargin, 'UniformOutput', false); - mpo = InfJMpo([Os{:}]); - end - function mpo = plus(a, b) if isa(a, 'InfJMpo') && isnumeric(b) if period(a) > 1 && isscalar(b) diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index 3f765f8..e51ae31 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -60,18 +60,18 @@ s = pspace(mpo.O{x}); end - function mpo = horzcat(varargin) + function mpo = horzcat(mpo, varargin) Os = cellfun(@(x) x.O, varargin, 'UniformOutput', false); - mpo = InfMpo([Os{:}]); + mpo.O = horzcat(mpo.O, Os{:}); end - function mpo = vertcat(varargin) + function mpo = vertcat(mpo, varargin) Os = cellfun(@(x) x.O, varargin, 'UniformOutput', false); - mpo = InfMpo(vertcat(Os{:})); + mpo.O = vertcat(mpo.O, Os{:}); end - function out = slice(mpo, d) - out = InfMpo(mpo.O(d, :)); + function mpo = slice(mpo, d) + mpo.O = mpo.O(d, :); end function mps = initialize_mps(mpo, vspaces) From a8db2318a86ca316b96c3b25aa3f8dcd773cdf29 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 20 Feb 2023 22:45:25 +0100 Subject: [PATCH 131/245] bugfix subsref SparseTensor + various nasty bugs --- src/mps/MpoTensor.m | 43 ++++++++++++++++++++------------------- src/sparse/SparseTensor.m | 20 +++++++++++++----- src/sparse/sub2sub.m | 4 ++-- src/tensors/Tensor.m | 37 +++++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 28 deletions(-) diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index bdc1088..3144995 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -175,16 +175,22 @@ iB(1:2) = flip(iB(1:2)); end - A_ = reshape(permute(A.scalars, iA), ... - [prod(size(A, uncA)) prod(size(A, dimA))]); - B = reshape(tpermute(B, iB, rB), ... - [prod(size(B, dimB)) prod(size(B, uncB))]); + [Ia, Ja, Va] = find(reshape(permute(A.scalars, iA), ... + [prod(size(A, uncA)) prod(size(A, dimA))])); + [Ib, Jb, Vb] = find(reshape(tpermute(B, iB, rB), ... + [prod(size(B, dimB)) prod(size(B, uncB))])); + sz2 = [prod(size(A, uncA)) prod(size(B, uncB))]; - C = C + reshape(sparse(A_) * B, size(C)); + for i = 1:length(Jb) + mask = find(Ja == Ib(i)); + if isempty(mask), continue; end + subs = [Ia(mask) repmat(Jb(i), length(mask), 1)]; + idx = sb2ind_(sz2, subs); + C(idx) = C(idx) + Va(mask) .* Vb(i); + end end else C = tensorprod(A, B.tensors, dimA, dimB, ca, cb); - szC = size(C); if nnz(B.scalars) > 0 assert(sum(dimB == 1 | dimB == 3, 'all') == 1, ... 'Cannot deduce output space unless leg 1 xor leg 3 is connected.'); @@ -204,24 +210,19 @@ iB(1:2) = flip(iB(1:2)); end - A_ = reshape(tpermute(A, iA, rA), ... - [prod(size(A, uncA)) prod(size(A, dimA))]); - B_ = reshape(permute(B.scalars, iB), ... - [prod(size(B, dimB)) prod(size(B, uncB))]); - + [Ia, Ja, Va] = find(reshape(tpermute(A, iA, rA), ... + [prod(size(A, uncA)) prod(size(A, dimA))])); + [Ib, Jb, Vb] = find(reshape(permute(B.scalars, iB), ... + [prod(size(B, dimB)) prod(size(B, uncB))])); + sz2 = [prod(size(A, uncA)) prod(size(B, uncB))]; - subs = zeros(size(A_, 1), 2); - subs(:, 1) = (1:size(A_, 1)).'; - sz2 = [size(A_, 1) size(B_, 2)]; - [Brows, Bcols, Bvals] = find(B_); - C = reshape(C, sz2); - for i = 1:length(Bvals) - Atmp = A_(:, Brows(i)) .* Bvals(i); - subs(:, 2) = Bcols(i); + for i = 1:length(Ia) + mask = find(Ja(i) == Ib); + if isempty(mask), continue; end + subs = [repmat(Ia(i), length(mask), 1) Jb(mask)]; idx = sub2ind_(sz2, subs); - C(idx) = C(idx) + Atmp; + C(idx) = C(idx) + Va(i) .* Vb(mask); end - C = reshape(C, szC); end end end diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index f6cf552..bc1e501 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -639,7 +639,7 @@ function disp(t) end Ccod = space(A, uncA); - Cdom = space(B, uncB); + Cdom = space(B, uncB)'; C = SparseTensor(Cind, Cvar, [size(A, 1) size(B, 2)], Ccod, Cdom); C = reshape(C, szC); @@ -819,7 +819,7 @@ function disp(t) return end - subs = sub2sub([t.sz(1) prod(t.sz(2:end))], t.sz, t.ind); + subs = sub2sub([t.sz(1) prod(t.sz(2:end))], t.sz, inds); I = subs(:,1); J = subs(:,2); @@ -878,8 +878,7 @@ function disp(t) t.var = t.var(p); end - function t = subsref(t, s) - + function varargout = subsref(t, s) switch s(1).type case '()' n = size(s(1).subs, 2); @@ -903,6 +902,12 @@ function disp(t) if nA ~= length(unique(A)) error("Repeated index in position %i",i); end + if i > length(t.codomain) + t.domain(end-(i-length(t.codomain))+1) = ... + SumSpace(subspaces(t.domain(end-(i-length(t.codomain))+1), A)); + else + t.codomain(i) = SumSpace(subspaces(t.codomain(i), A)); + end if ~isempty(t.ind) B = t.ind(:, i); P = false(max(max(A), max(B)) + 1, 1); @@ -922,13 +927,18 @@ function disp(t) assert(isscalar(t)) t = subsref(t.var, s(2:end)); end + varargout{1} = t; case '.' - t = builtin('subsref', t, s); + [varargout{1:nargout}] = builtin('subsref', t, s); otherwise error('sparse:index', '{} indexing not defined'); end end + function n = numArgumentsFromSubscript(t, ~, ~) + n = 1; + end + function t = subsasgn(t, s, v) assert(strcmp(s(1).type, '()'), 'sparse:index', 'only () indexing allowed'); diff --git a/src/sparse/sub2sub.m b/src/sparse/sub2sub.m index c5ed718..29c7338 100644 --- a/src/sparse/sub2sub.m +++ b/src/sparse/sub2sub.m @@ -7,8 +7,8 @@ return; end sub2 = zeros(size(sub1, 1), numel(sz2)); % preallocate new subs -nto1 = sum(cumsum(flip(sz1-1), 2) == 0, 2), length(sz1); % extract number of trailing ones in both sizes -nto2 = sum(cumsum(flip(sz2-1), 2) == 0, 2), length(sz2); +nto1 = sum(cumsum(flip(sz1-1), 2) == 0, 2); % extract number of trailing ones in both sizes +nto2 = sum(cumsum(flip(sz2-1), 2) == 0, 2); pos1_prev = 0; pos2_prev = 0; flag = true; while flag diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index f1c2a51..d7283c3 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -2357,6 +2357,43 @@ %% Utility methods + function [I, J, V] = find(t, k, which) + arguments + t + k = [] + which = 'first' + end + assert(strcmp(which, 'first'), 'not implemented yet') + if ~isempty(k) + assert(k <= numel(t)); + else + k = numel(t); + end + + if isempty(t) + I = []; + J = []; + V = []; + return + end + + if ~isempty(k) + if strcmp(which, 'first') + I = 1:k; + else + I = numel(t):-1:numel(t)+1-k; + end + end + I = reshape(I, [], 1); + if nargout < 2, return; end + + sz = size(t); + subs = ind2sub_([sz(1) prod(sz(2:end))], I); + I = subs(:, 1); + J = subs(:, 2); + V = reshape(t, [], 1); + end + function s = GetMD5_helper(t) s = {t.codomain t.domain}; end From c4949489e8d2919fb4e6760ea37da6229de02745 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 20 Feb 2023 23:29:26 +0100 Subject: [PATCH 132/245] change MpoTensor to use SparseArray --- src/mps/MpoTensor.m | 23 +++++++------ src/sparse/SparseTensor.m | 2 +- src/utility/sparse/SparseArray.m | 55 ++++++++++++++++++++++---------- 3 files changed, 53 insertions(+), 27 deletions(-) diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index 3144995..8581534 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -13,7 +13,7 @@ properties tensors = [] - scalars = [] + scalars SparseArray = [] end methods @@ -34,8 +34,8 @@ t.scalars = varargin{1}.scalars; t.tensors = varargin{1}.tensors; elseif isa(varargin{1}, 'Tensor') || isa(varargin{1}, 'SparseTensor') - t.tensors = sparse(varargin{1}); - t.scalars = zeros(size(t.tensors)); + t.tensors = varargin{1}; + t.scalars = SparseArray.zeros(size(t.tensors, 1:4)); end return end @@ -175,8 +175,8 @@ iB(1:2) = flip(iB(1:2)); end - [Ia, Ja, Va] = find(reshape(permute(A.scalars, iA), ... - [prod(size(A, uncA)) prod(size(A, dimA))])); + [Ia, Ja, Va] = find(spmatrix(reshape(permute(A.scalars, iA), ... + [prod(size(A, uncA)) prod(size(A, dimA))]))); [Ib, Jb, Vb] = find(reshape(tpermute(B, iB, rB), ... [prod(size(B, dimB)) prod(size(B, uncB))])); sz2 = [prod(size(A, uncA)) prod(size(B, uncB))]; @@ -212,8 +212,8 @@ [Ia, Ja, Va] = find(reshape(tpermute(A, iA, rA), ... [prod(size(A, uncA)) prod(size(A, dimA))])); - [Ib, Jb, Vb] = find(reshape(permute(B.scalars, iB), ... - [prod(size(B, dimB)) prod(size(B, uncB))])); + [Ib, Jb, Vb] = find(spmatrix(reshape(permute(B.scalars, iB), ... + [prod(size(B, dimB)) prod(size(B, uncB))]))); sz2 = [prod(size(A, uncA)) prod(size(B, uncB))]; for i = 1:length(Ia) @@ -272,7 +272,12 @@ end function bool = iseye(O) - bool = nnz(O.tensors) == 0 && isequal(O.scalars, eye(size(O.scalars))); + bool = nnz(O.tensors) == 0; + if ~bool, return; end + + scal_mat = reshape(O.scalars, ... + prod(size(O.scalars, 1:2)), prod(size(O.scalars, 3:4))); + bool = isequal(spmatrix(scal_mat), speye(size(scal_mat))); end function n = nnz(O) @@ -347,7 +352,7 @@ methods (Static) function O = zeros(codomain, domain) tensors = SparseTensor.zeros(codomain, domain); - scalars = zeros(size(tensors)); + scalars = SparseArray.zeros(size(tensors)); O = MpoTensor(tensors, scalars); end diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index bc1e501..aa49de1 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -950,7 +950,7 @@ function disp(t) assert(length(s(1).subs) == size(t.sz, 2), 'sparse:index', ... 'number of indexing indices must match tensor size.'); assert(all(t.sz >= cellfun(@max, s(1).subs)), 'sparse:bounds', ... - 'attempting to assign out of bounds'); + 'out of bounds assignment disallowed'); subsize = zeros(1, size(s(1).subs, 2)); for i = 1:length(subsize) diff --git a/src/utility/sparse/SparseArray.m b/src/utility/sparse/SparseArray.m index cbb8518..ef7f37e 100644 --- a/src/utility/sparse/SparseArray.m +++ b/src/utility/sparse/SparseArray.m @@ -418,6 +418,10 @@ function disp(a) bool = false; end + function bool = istriu(a) + bool = istriu(spmatrix(a)); + end + function c = ldivide(a, b) % Elementwise left division for sparse arrays. % @@ -741,22 +745,19 @@ function disp(a) a.var = sign(a.var); end - - function d = size(a, dim) - % Sparse array dimensions. - % - % Usage - % ----- - % :code:`d = size(a)` - % returns the size of the array. - % - % :code:`d = size(a, dim)` - % returns the sizes of the dimensions specified by :code:`dim`, which is - % either a scalar or a vector of dimensions. - if nargin > 1 - d = a.sz(dim); + function varargout = size(a, i) + if nargin == 1 + sz = a.sz; + else + sz = ones(1, max(i)); + sz(1:length(a.sz)) = a.sz; + sz = sz(i); + end + + if nargout <= 1 + varargout = {sz}; else - d = a.sz; + varargout = num2cell(sz); end end @@ -801,7 +802,25 @@ function disp(a) function a = subsasgn(a, s, rhs) % Subscripted assignment for sparse array. - error('Not implemented.') + assert(strcmp(s(1).type, '()'), 'sparse:index', 'only () indexing allowed'); + + if length(s(1).subs) > 1 % non-linear indexing + assert(length(s(1).subs) == size(a.sz, 2), 'sparse:index', ... + 'number of indexing indices must match tensor size.'); + assert(all(a.sz >= cellfun(@max, s(1).subs)), 'sparse:bounds', ... + 'out of bounds assignment disallowed'); + s(1).subs = {sub2ind(a.sz, s(1).subs{:})}; + end + + [I, ~, V] = find(a.var); + [lia, locb] = ismember(s(1).subs{1}, I); + newI = vertcat(I, s(1).subs{1}(~lia)); + newJ = ones(size(newI)); + V(locb(lia)) = rhs(lia); + newV = vertcat(V, rhs(~lia)); + + a.var = sparse(newI, newJ, newV, ... + size(a.var, 1), size(a.var, 2)); end function a_sub = subsref(a, s) @@ -1086,7 +1105,9 @@ function disp(a) a = SparseArray(repmat(1:inddim, numinds, 1)', 1, repmat(inddim, 1, numinds)); end - + function a = zeros(sz) + a = SparseArray([], [], sz); + end function a = random(sz, density) % Create a random complex sparse array. From 0c893c3af8570a4ff8e8f2774936a1301b2b747b Mon Sep 17 00:00:00 2001 From: leburgel Date: Tue, 21 Feb 2023 09:35:57 +0100 Subject: [PATCH 133/245] Change `SparseArray.find` to slightly more default behavior --- src/utility/sparse/SparseArray.m | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/utility/sparse/SparseArray.m b/src/utility/sparse/SparseArray.m index cbb8518..c9802ec 100644 --- a/src/utility/sparse/SparseArray.m +++ b/src/utility/sparse/SparseArray.m @@ -284,6 +284,12 @@ function disp(a) function [subs, idx, vals] = find(a) % Find subscripts of nonzero elements in a sparse array. % + % Usage + % ----- + % :code:`idx = find(a)` + % + % :code:`[subs, idx, vals] = find(a)` + % % Arguments % --------- % a : :class:`SparseArray` @@ -300,6 +306,10 @@ function disp(a) % var : (:, 1) :class:`double` % values of nonzero array entries. [idx, ~, vals] = find(a.var); + if nargout == 1 + subs = idx; + return + end subs = ind2sub_(a.sz, idx); end From b3a7816e7018647fd695dab5de0eb2e71b009ecf Mon Sep 17 00:00:00 2001 From: leburgel Date: Tue, 21 Feb 2023 10:26:21 +0100 Subject: [PATCH 134/245] Bugfix in `sub2sub` --- src/sparse/sub2sub.m | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sparse/sub2sub.m b/src/sparse/sub2sub.m index 29c7338..a1d0a2d 100644 --- a/src/sparse/sub2sub.m +++ b/src/sparse/sub2sub.m @@ -19,7 +19,10 @@ error('Cannot map subscripts to new size as intermediate index exceeds MAXSIZE') end sub2(:, pos2_prev+1:pos2) = ind2sub_(sz2(pos2_prev+1:pos2), sub2ind_(sz1(pos1_prev+1:pos1), sub1(:, pos1_prev+1:pos1))); - if pos2 == numel(sz2) - nto2 || pos1 == numel(sz1) - nto1 + if (isempty(pos2) && numel(sz2) - nto2 == 0) || ... + (isempty(pos1) && numel(sz1) - nto1 == 0) || ... + pos2 == numel(sz2) - nto2 || ... + pos1 == numel(sz1) - nto1 flag = false; else pos1_prev = pos1; From c0b1e7b26c348a2785130da9ab3dd71552b89065 Mon Sep 17 00:00:00 2001 From: leburgel Date: Tue, 21 Feb 2023 19:01:44 +0100 Subject: [PATCH 135/245] First PEPS attempt --- src/mps/FiniteMpo.m | 12 +-- src/mps/InfMpo.m | 12 +-- src/mps/MpoTensor.m | 13 +++ src/mps/MpsTensor.m | 38 ++------ src/mps/PepsSandwich.m | 97 +++++++++++++++++++++ src/mps/PepsTensor.m | 173 +++++++++++++++++++++++++++++++++++++ src/mps/UniformMps.m | 10 +-- src/tensors/Tensor.m | 3 +- src/tensors/spaces/Arrow.m | 4 +- test/TestInfMpo.m | 2 +- 10 files changed, 314 insertions(+), 50 deletions(-) create mode 100644 src/mps/PepsSandwich.m create mode 100644 src/mps/PepsTensor.m diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index 110dd99..f458fed 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -65,13 +65,13 @@ end function s = domain(mpo) - N = prod(cellfun(@(x) size(x, 4), mpo(1).O)); - s = conj(... - [rightvspace(mpo(1).L) cellfun(@(x) pspace(x)', mpo(1).O) leftvspace(mpo(1).R)]); + sO = flip(cellfun(@(x) domainspace(x), mpo(1).O, 'UniformOutput', false)); + s = [leftvspace(mpo(1).R) [sO{:}] rightvspace(mpo(1).L)]'; end function s = codomain(mpo) - s = [leftvspace(mpo(end).L) cellfun(@pspace, mpo(end).O) rightvspace(mpo(end).R)]; + sO = cellfun(@(x) codomainspace(x), mpo(1).O, 'UniformOutput', false); + s = [leftvspace(mpo(end).L) [sO{:}] rightvspace(mpo(end).R)]; end function d = depth(mpo) @@ -218,8 +218,8 @@ twistinds = 1 + find(isdual(space(Atop(i), 2:nspaces(Atop(i)) - 1))); abot = twist(Abot(i)', twistinds); - assert(isequal(space(abot, 2), space(o, 1)')); - assert(isequal(space(o, 3), space(atop, 2)')); + assert(isequal(pspace(abot)', leftvspace(o))); + assert(isequal(rightvspace(o)', pspace(atop))); T(i, 1) = FiniteMpo(abot, {o}, atop); end diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index e51ae31..e37dc7b 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -162,10 +162,10 @@ end for w = 1:period(mps1) - overlap = sqrt(contract(GL{w}, [1 3 2], ... - mps1.C(prev(w, period(mps1))), [2 4], ... - mps2.C(prev(w, period(mps1)))', [5 1], ... - GR{w}, [4 3 5])); + overlap = sqrt(contract(GL{w}, [1, 3:nspaces(GL{w}), 2], ... + mps1.C(prev(w, period(mps1))), [2, nspaces(GL{w})+1], ... + mps2.C(prev(w, period(mps1)))', [nspaces(GL{w})+2, 1], ... + GR{w}, [nspaces(GL{w})+1, flip(3:nspaces(GL{w})), nspaces(GL{w})+2])); GL{w} = GL{w} ./ overlap; GR{w} = GR{w} ./ overlap; end @@ -215,7 +215,9 @@ gl = twistdual(GL{d, sites(i)}, 1); gr = GR{d, next(sites(i), period(mps))}; gr = twistdual(gr, nspaces(gr)); - H{i}(d, 1) = FiniteMpo(gl, mpo.O(d, sites(i)), gr); + gr_better = tpermute(gr, [1, flip(2:nspaces(gr)-1), nspaces(gr)]); + gl_better = tpermute(gl, [1, flip(2:nspaces(gl)-1), nspaces(gl)]); + H{i}(d, 1) = FiniteMpo(gl_better, mpo.O(d, sites(i)), gr_better); end end end diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index 8581534..f1d9b89 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -139,6 +139,11 @@ O.scalars = permute(O.scalars, [2 3 4 1]); end + function O = rot270(O) + O.tensors = tpermute(O.tensors, [4 1 2 3], [2 2]); + O.scalars = permute(O.scalars, [4 1 2 3]); + end + function C = tensorprod(A, B, dimA, dimB, ca, cb, options) arguments A @@ -244,6 +249,14 @@ end function s = pspace(O) + s = domainspace(O)'; + end + + function s = domainspace(O) + s = space(O.tensors, 4); + end + + function s = codomainspace(O) s = space(O.tensors, 2); end diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index 94fa534..b637937 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -347,9 +347,10 @@ auxlegs_r = R.alegs; newrank = rank(v); newrank(2) = newrank(2) + auxlegs_l + auxlegs_r; - v = contract(v, [1 3 (-(1:auxlegs_v) - 2 - auxlegs_l)], ... - L, [-1 2 1 (-(1:auxlegs_l) - 2)], ... - R, [3 2 -2 (-(1:auxlegs_r) - 3 - auxlegs_l - auxlegs_v)], ... + v = contract( ... + v, [1, R.plegs+2, (-(1:auxlegs_v) - 2 - auxlegs_l)], ... + L, [-1, (1:L.plegs)+1, 1 (-(1:auxlegs_l) - (1+L.plegs))], ... + R, [R.plegs+2, (R.plegs:-1:1)+1, -2, (-(1:auxlegs_r) - (2+R.plegs) - auxlegs_l - auxlegs_v)], ... 'Rank', newrank); end @@ -453,36 +454,13 @@ end function A = multiplyleft(A, C) -% A = MpsTensor(repartition(... -% repartition(C, [1 1]) * repartition(A, [1 nspaces(A)-1]), ... -% rank(A)), A.alegs); -% if ~isdual(space(C, 2)) -% C = twist(C, 2); -% end -% if isdual(space(A, 1)), C = twist(C, 2); end - [A.var] = contract(C, [-1 1], [A.var], [1 -2 -3], 'Rank', rank(A)); -% A = MpsTensor(tpermute(... -% multiplyright(MpsTensor(tpermute(A, [3 2 1])), tpermute(C, [2 1])), [3 2 1])); -% A = MpsTensor(multiplyright(A', C')'); -% A = MpsTensor(contract(C, [-1 1], A, [1 -(2:nspaces(A))], 'Rank', rank(A))); + [A.var] = contract(C, [-1 1], ... + [A.var], [1 -((1:A.plegs)+1) -((1:A.alegs+1)+A.plegs+1)], 'Rank', rank(A)); end function A = multiplyright(A, C) -% if A.alegs == 0 -% A = MpsTensor(repartition(... -% repartition(A, [nspaces(A)-1 1]) * repartition(C, [1 1]), ... -% rank(A)), 0); -% return -% end -% if isdual(space(C, 1)), C = twist(C, 1); end - Alegs = nspaces(A); - if A.alegs == 0 - A = contract(A, [-(1:Alegs-1) 1], C, [1 -Alegs], 'Rank', rank(A)); - else - A = contract(A, [-(1:Alegs-1-A.alegs) 1 -(Alegs-A.legs+1:Alegs)], ... - C, [1 Alegs - A.alegs], 'Rank', rank(A)); - end - A = MpsTensor(A); + [A.var] = contract([A.var], [-(1:A.plegs+1) 1 -((1:A.alegs)+A.plegs+2)], ... + C, [1 -(2+A.plegs)], 'Rank', rank(A)); end function C = initializeC(AL, AR) diff --git a/src/mps/PepsSandwich.m b/src/mps/PepsSandwich.m new file mode 100644 index 0000000..cfb15b5 --- /dev/null +++ b/src/mps/PepsSandwich.m @@ -0,0 +1,97 @@ +classdef (InferiorClasses = {?Tensor, ?MpsTensor, ?SparseTensor}) PepsSandwich + % Data structure representing a pair of PEPS tensors in an overlap, which behave as an + % MPO tensor. + + properties + top PepsTensor + bot PepsTensor + end + + + methods + function T = PepsSandwich(top, bot) + arguments + top + bot = conj(top) + end + + if ~isa(top, 'PepsTensor') + top = PepsTensor(top); + end + if ~isa(bot, 'PepsTensor') + bot = PepsTensor(bot); + end + + T.top = top; + T.bot = bot; + end + + function T = rot90(T) + T.top = tpermute(T.top, [1, 3, 4, 5, 2], rank(T.top)); + T.bot = tpermute(T.bot, [1, 3, 4, 5, 2], rank(T.bot)); + end + + function T = rot270(T) + T.top = tpermute(T.top, [1, 5, 2, 3, 4], rank(T.top)); + T.bot = tpermute(T.bot, [1, 5, 2, 3, 4], rank(T.bot)); + end + + function T = transpose(T) + % TODO: test if this is correct when inserted into FiniteMpo + top = T.top; + bot = T.bot; + T.top = tpermute(bot, [1, 4, 5, 2, 3], rank(bot)); + T.bot = tpermute(top, [1, 4, 5, 2, 3], rank(top)); + end + + function T = ctranspose(T) + % TODO: test if this is correct when inserted into FiniteMpo + T.top = conj(tpermute(T.top, [1, 4, 5, 2, 3], rank(T.top))); + T.bot = conj(tpermute(T.bot, [1, 4, 5, 2, 3], rank(T.bot))); + end + + function s = pspace(T) + s = domainspace(T)'; + end + + function s = domainspace(T) + % flipped for consistency + s = [northvspace(T.bot), northvspace(T.top)]; + end + + function s = codomainspace(T) + s = [southvspace(T.top), southvspace(T.bot)]; + end + + function s = rightvspace(T) + % flipped for consistency + s = [eastvspace(T.bot), eastvspace(T.top)]; + end + + function s = leftvspace(T) + s = [westvspace(T.top), westvspace(T.bot)]; + end + + function v = applychannel(T, L, R, v) + arguments + T PepsSandwich + L MpsTensor + R MpsTensor + v + end + auxlegs_v = nspaces(v) - 4; + auxlegs_l = nspaces(L) - 4; + auxlegs_r = nspaces(R) - 4; + newrank = rank(v); newrank(2) = newrank(2) + auxlegs_l + auxlegs_r; + + v = contract(... + v, [1, 4, 3, 7, (-(1:auxlegs_v) - 4 - auxlegs_l)], ... + L, [-1, 2, 5, 1, (-(1:auxlegs_l) - 4)], ... + T.top.var, [6, 5, -2, 8, 4], ... + T.bot.var, [6, 2, -3, 9, 3], ... + R, [7, 8, 9, -4, (-(1:auxlegs_r) - 4 - auxlegs_l - auxlegs_v)], ... + 'Rank', newrank); + end + end +end + diff --git a/src/mps/PepsTensor.m b/src/mps/PepsTensor.m new file mode 100644 index 0000000..234d4af --- /dev/null +++ b/src/mps/PepsTensor.m @@ -0,0 +1,173 @@ +classdef PepsTensor + % Generic PEPS tensor objects that have a notion of virtual and physical legs. + + properties + var + end + + + %% Constructors + methods + function A = PepsTensor(tensor) + arguments + tensor = [] + end + + if iscell(tensor) + for i = length(tensor):-1:1 + A(i) = PepsTensor(tensor{i}); + end + return + end + + if ~isempty(tensor) + A.var = tensor; + end + end + end + + + %% Properties + methods + function s = space(A, varargin) + s = space(A.var, varargin{:}); + end + + function n = nspaces(A) + n = nspaces(A.var); + end + + function s = pspace(A) + s = space(A, 1); + end + + function s = westvspace(A) + s = space(A, 2); + end + + function s = southvspace(A) + s = space(A, 3); + end + + function s = eastvspace(A) + s = space(A, 4); + end + + function s = northvspace(A) + s = space(A, 5); + end + + function s = vspace(A) + s = space(A, 2:5); + end + + function cod = codomain(A) + cod = A.var.codomain; + end + + function dom = domain(A) + dom = A.var.domain; + end + + function r = rank(A) + r = rank(A.var); + end + end + + + %% Linear Algebra + methods + function d = dot(A, B) + % TODO? + end + + function A = repartition(A, varargin) + A.var = repartition(A.var, varargin{:}); + end + + function A = tpermute(A, varargin) + A.var = tpermute(A.var, varargin{:}); + end + + function A = plus(varargin) + for i = 1:2 + if isa(varargin{i}, 'PepsTensor') + varargin{i} = varargin{i}.var; + end + end + A = plus(varargin{:}); + end + + function A = minus(varargin) + for i = 1:2 + if isa(varargin{i}, 'PepsTensor') + varargin{i} = varargin{i}.var; + end + end + A = minus(varargin{:}); + end + + function n = norm(A) + n = norm(A.var); + end + + function A = conj(A) + A.var = conj(A.var); + end + + function A = twist(A, varargin) + A.var = twist(A.var, varargin{:}); + end + + function t = ctranspose(t) + % Compute the adjoint of a tensor. This is defined as swapping the codomain and + % domain, while computing the adjoint of the matrix blocks. + % + % Usage + % ----- + % :code:`t = ctranspose(t)` + % :code:`t = t'` + % + % Arguments + % --------- + % t : :class:`Tensor` + % input tensor. + % + % Returns + % ------- + % t : :class:`Tensor` + % adjoint tensor. + + t.var = t.var'; + + t = permute(t, ndims(t):-1:1); + end + + function C = tensorprod(varargin) + for i = 1:2 + if isa(varargin{i}, 'PepsTensor') + varargin{i} = varargin{i}.var; + end + end + C = tensorprod(varargin{:}); + end + + function type = underlyingType(A) + type = underlyingType(A.var); + end + end + + + %% Converters + methods + function t = Tensor(A) + t = full(A.var); + end + + function t = SparseTensor(A) + t = reshape([A.var], size(A)); + t = sparse(t); + end + end +end + diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index 69ddd44..1f568ab 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -135,8 +135,8 @@ L = length(pspaces); for w = length(pspaces):-1:1 - rankdeficient = vspaces{w} * pspaces{w} < vspaces{next(w, L)} || ... - vspaces{w} > pspaces{w}' * vspaces{next(w, L)}; + rankdeficient = vspaces{w} * prod(pspaces{w}) < vspaces{next(w, L)} || ... + vspaces{w} > prod(pspaces{w})' * vspaces{next(w, L)}; if rankdeficient error('mps:rank', ... 'Cannot create a full rank mps with given spaces.'); @@ -269,7 +269,7 @@ kwargs.Order {mustBeMember(kwargs.Order, {'lr', 'rl'})} = 'lr' end - for i = 1:length(mps) + for i = 1:depth(mps) if strcmp(kwargs.Order, 'rl') [mps(i).AR, ~, ~, eta1] = uniform_rightorth(... mps(i).AR, [], ... @@ -296,7 +296,7 @@ end if kwargs.ComputeAC - for i = 1:height(mps) + for i = 1:depth(mps) for w = period(mps(i)):-1:1 mps(i).AC(w) = multiplyright(mps(i).AL(w), ... mps(i).C(w)); @@ -308,7 +308,7 @@ function mps = diagonalizeC(mps) % gauge transform an mps such that C is diagonal. - for i = 1:height(mps) + for i = 1:depth(mps) for w = 1:period(mps(i)) C_iw = mps(i).C(w); [U, S, V] = tsvd(C_iw, 1, 2); diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index d7283c3..3b80c18 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -353,7 +353,8 @@ spaces = CartesianSpace.new(varargin{1}); elseif isnumeric(varargin{2}) || islogical(varargin{2}) assert(length(varargin{1}) == length(varargin{2})) - spaces = ComplexSpace.new(varargin{1}, varargin{2}); + args = [num2cell(varargin{1}); num2cell(varargin{2})]; + spaces = ComplexSpace.new(args{:}); end if ~isfield(kwargs, 'Rank') diff --git a/src/tensors/spaces/Arrow.m b/src/tensors/spaces/Arrow.m index 83b9af1..7159648 100644 --- a/src/tensors/spaces/Arrow.m +++ b/src/tensors/spaces/Arrow.m @@ -3,8 +3,8 @@ % Enumeration class with directions. enumeration - in (false) - out (true) + in (true) + out (false) end end diff --git a/test/TestInfMpo.m b/test/TestInfMpo.m index 074f582..af51fd9 100644 --- a/test/TestInfMpo.m +++ b/test/TestInfMpo.m @@ -57,7 +57,7 @@ function test2dIsing(tc) D = 16; alg = Vumps('MaxIter', 10); mpo = InfMpo.Ising(beta); - mps = UniformMps.randnc(CartesianSpace.new(2), CartesianSpace.new(D)); + mps = mpo.initialize_mps(CartesianSpace.new(D)); [mps2, lambda] = fixedpoint(alg, mpo, mps); tc.assertEqual(-log(lambda) / beta, freeEnergyExact, 'RelTol', 1e-5); From 9b006ab51c2f41d9a589795231762c587c488e6e Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 22 Feb 2023 00:05:30 +0100 Subject: [PATCH 136/245] add diracdelta --- src/environments/FiniteEnvironment.m | 23 +++++++++++++++++++++++ src/utility/diracdelta.m | 12 ++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 src/environments/FiniteEnvironment.m create mode 100644 src/utility/diracdelta.m diff --git a/src/environments/FiniteEnvironment.m b/src/environments/FiniteEnvironment.m new file mode 100644 index 0000000..d9639a7 --- /dev/null +++ b/src/environments/FiniteEnvironment.m @@ -0,0 +1,23 @@ +classdef FiniteEnvironment + %FINITEENVIRONMENT Summary of this class goes here + % Detailed explanation goes here + + properties + Property1 + end + + methods + function obj = FiniteEnvironment(inputArg1,inputArg2) + %FINITEENVIRONMENT Construct an instance of this class + % Detailed explanation goes here + obj.Property1 = inputArg1 + inputArg2; + end + + function outputArg = method1(obj,inputArg) + %METHOD1 Summary of this method goes here + % Detailed explanation goes here + outputArg = obj.Property1 + inputArg; + end + end +end + diff --git a/src/utility/diracdelta.m b/src/utility/diracdelta.m new file mode 100644 index 0000000..2ed8d80 --- /dev/null +++ b/src/utility/diracdelta.m @@ -0,0 +1,12 @@ +function d = diracdelta(sz) + +assert(all(sz == sz(1))) +d = zeros(sz); + +subs = repmat(1:sz(1), length(sz), 1).'; +idx = sub2ind_(sz, subs); + +d(idx) = 1; + +end + From a45cd768ff7531b66de277b1b612defddb84dbea Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 22 Feb 2023 00:17:39 +0100 Subject: [PATCH 137/245] FiniteMps, DMRG, models map --- src/algorithms/Dmrg.m | 223 +++++++++++++++++++++++++++ src/algorithms/Vumps.m | 1 - src/environments/FiniteEnvironment.m | 52 ++++++- src/models/quantum1dIsing.m | 67 ++++++++ src/models/statmech2DIsing.m | 60 +++++++ src/mps/FiniteMpo.m | 59 ++++++- src/mps/FiniteMps.m | 151 ++++++++++++++++-- src/mps/MpoTensor.m | 17 +- src/tensors/AbstractTensor.m | 42 +++++ src/tensors/Tensor.m | 2 + src/tensors/spaces/AbstractSpace.m | 4 + src/tensors/spaces/CartesianSpace.m | 4 - src/tensors/spaces/SumSpace.m | 15 ++ test/TestFiniteMpo.m | 35 +++++ test/TestFiniteMps.m | 83 ++++++++++ test/TestInfMpo.m | 4 +- 16 files changed, 787 insertions(+), 32 deletions(-) create mode 100644 src/algorithms/Dmrg.m create mode 100644 src/models/quantum1dIsing.m create mode 100644 src/models/statmech2DIsing.m create mode 100644 test/TestFiniteMps.m diff --git a/src/algorithms/Dmrg.m b/src/algorithms/Dmrg.m new file mode 100644 index 0000000..c61025f --- /dev/null +++ b/src/algorithms/Dmrg.m @@ -0,0 +1,223 @@ +classdef Dmrg + % Density Matrix Renormalisation Group algorithm for marix product states. + + properties + tol = 1e-10 + miniter = 5 + maxiter = 100 + verbosity = Verbosity.iter + doplot = false + which = 'largestabs' + + dynamical_tols = false + tol_min = 1e-12 + tol_max = 1e-10 + eigs_tolfactor = 1e-6 + + doSave = false + saveIterations = 1 + saveMethod = 'full' + name = 'DMRG' + end + + methods + function alg = Dmrg(kwargs) + arguments + kwargs.?Dmrg + end + + fields = fieldnames(kwargs); + if ~isempty(fields) + for field = fields.' + alg.(field{1}) = kwargs.(field{1}); + end + end + end + + function [mps, envs, eta] = fixedpoint(alg, mpo, mps, envs) + arguments + alg + mpo + mps + envs = initialize_envs(mpo) + end + + if length(mpo) ~= length(mps) + error('dmrg:argerror', ... + 'length of mpo (%d) should be equal to that of the mps (%d)', ... + length(mpo), length(mps)); + end + + t_total = tic; + disp_init(alg); + + for iter = 1:alg.maxiter + t_iter = tic; + order = sweeporder(alg, length(mps)); + eta = zeros(size(order)); + lambda = zeros(size(order)); + for i = 1:length(order) + pos = order(i); + [mps, envs, lambda(i), eta(i)] = localupdate(alg, mps, mpo, envs, pos); + end + eta = norm(eta, Inf); + if iter > alg.miniter && norm(eta, Inf) < alg.tol + disp_conv(alg, iter, sum(lambda), eta, toc(t_total)); + return + end + + alg = updatetols(alg, iter, eta); + + plot(alg, iter, mps, norm(eta, Inf)); + disp_iter(alg, iter, sum(lambda), eta, toc(t_iter)); + end + + disp_maxiter(alg, iter, sum(lambda), eta, toc(t_total)); + end + + function order = sweeporder(alg, L) + order = [2:L, L-1:-1:1]; +% order = circshift(order, -floor(L / 2)); + order = 2:L-1; + end + + function envs = environments(alg, mpo, mps) + + end + + function [mps, envs, lambda, eta] = localupdate(alg, mps, mpo, envs, pos) + mps = movegaugecenter(mps, pos); + envs = movegaugecenter(envs, mpo, mps, mps, pos); + H_AC = AC_hamiltonian(mpo, mps, envs, pos); + AC = mps.A(pos); + [AC.var, lambda] = eigsolve(H_AC, AC.var, 1, alg.which); + + phase = dot(AC.var, mps.A(pos)); + + eta = distance(AC.var ./ sign(phase), mps.A(pos)); + + mps.A(pos) = AC; + envs = invalidate(envs, pos); + end + end + + %% Option handling + methods + function alg = updatetols(alg, iter, eta) + if alg.dynamical_tols + alg.alg_eigs.Tol = between(alg.tol_min, eta * alg.eigs_tolfactor / iter, ... + alg.tol_max); + alg.alg_canonical.Tol = between(alg.tol_min, ... + eta * alg.canonical_tolfactor / iter, alg.tol_max); + alg.alg_environments.Tol = between(alg.tol_min, ... + eta * alg.environments_tolfactor / iter, alg.tol_max); + + if alg.verbosity > Verbosity.iter + fprintf('Updated subalgorithm tolerances: (%e,\t%e,\t%e)\n', ... + alg.alg_eigs.Tol, alg.alg_canonical.Tol, alg.alg_environments.Tol); + end + end + end + end + + %% Display + methods (Access = private) + function plot(alg, iter, mps, eta) + if ~alg.doplot, return; end + persistent axhistory axspectrum + + D = depth(mps); + W = period(mps); + + if isempty(alg.progressfig) || ~isvalid(alg.progressfig) || iter == 1 + alg.progressfig = figure('Name', 'Vumps'); + axhistory = subplot(D + 1, W, 1:W); + axspectrum = gobjects(D, W); + for d = 1:D, for w = 1:W + axspectrum(d, w) = subplot(D + 1, W, w + d * W); + end, end + linkaxes(axspectrum, 'y'); + end + + + if isempty(axhistory.Children) + semilogy(axhistory, iter, eta, '.', 'Color', colors(1), ... + 'DisplayName', 'Errors', 'MarkerSize', 10); + hold on + ylim(axhistory, [alg.tol / 5, max([1 eta])]); + yline(axhistory, alg.tol, '-', 'Convergence', ... + 'LineWidth', 2); + hold off + else + axhistory.Children(end).XData(end+1) = iter; + axhistory.Children(end).YData(end+1) = eta; + end + + plot_entanglementspectrum(mps, 1:period(mps), axspectrum); + drawnow + end + + function disp_init(alg) + if alg.verbosity < Verbosity.conv, return; end + fprintf('---- VUMPS ----\n'); + end + + function disp_iter(alg, iter, lambda, eta, t) + if alg.verbosity < Verbosity.iter, return; end + + s = settings; + if abs(imag(lambda)) < eps(lambda)^(3/4) * abs(lambda) + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('Vumps %2d:\tE = %-0.4f\terror = %0.1e\t(%s)\n', ... + iter, real(lambda), eta, time2str(t)); + otherwise + fprintf('Vumps %4d:\tE = %-0.15f\terror = %0.4e\t(%s)\n', ... + iter, real(lambda), eta, time2str(t, 's')); + + end + else + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('Vumps %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + otherwise + fprintf('Vumps %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + + end + end + end + + function disp_conv(alg, iter, lambda, eta, t) + if alg.verbosity < Verbosity.conv, return; end + s = settings; + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('Vumps converged %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + otherwise + fprintf('Vumps converged %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + + end + fprintf('---------------\n'); + end + + function disp_maxiter(alg, iter, lambda, eta, t) + if alg.verbosity < Verbosity.warn, return; end + s = settings; + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('Vumps max iterations %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + otherwise + fprintf('Vumps max iterations %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + + end + fprintf('---------------\n'); + end + end +end + diff --git a/src/algorithms/Vumps.m b/src/algorithms/Vumps.m index ae30c37..f9a7812 100644 --- a/src/algorithms/Vumps.m +++ b/src/algorithms/Vumps.m @@ -25,7 +25,6 @@ saveIterations = 1 saveMethod = 'full' name = 'VUMPS' - end properties (Access = private) diff --git a/src/environments/FiniteEnvironment.m b/src/environments/FiniteEnvironment.m index d9639a7..9304f35 100644 --- a/src/environments/FiniteEnvironment.m +++ b/src/environments/FiniteEnvironment.m @@ -3,21 +3,59 @@ % Detailed explanation goes here properties - Property1 + GL + GR end methods - function obj = FiniteEnvironment(inputArg1,inputArg2) + function envs = FiniteEnvironment(varargin) %FINITEENVIRONMENT Construct an instance of this class % Detailed explanation goes here - obj.Property1 = inputArg1 + inputArg2; + if nargin == 0, return; end + if nargin == 2 + envs.GL = varargin{1}; + envs.GR = varargin{2}; + assert(isequal(size(envs.GL), size(envs.GR))); + return + end + error('undefined syntax'); end - function outputArg = method1(obj,inputArg) - %METHOD1 Summary of this method goes here - % Detailed explanation goes here - outputArg = obj.Property1 + inputArg; + function envs = movegaugecenter(envs, mpo, mps1, mps2, pos) + for i = 2:pos + if ~isempty(envs.GL{i}), continue; end + T = transfermatrix(mpo, mps1, mps2, i-1); + envs.GL{i} = apply(T, envs.GL{i-1}); + end + for i = length(mps1):-1:(pos+1) + if ~isempty(envs.GR{i}), continue; end + T = transfermatrix(mpo, mps1, mps2, i).'; + envs.GR{i} = apply(T, envs.GR{i+1}); + end + end + + function envs = invalidate(envs, pos) + envs.GL(pos+1:end) = cell(1, length(envs.GL) - pos); + envs.GR(1:pos) = cell(1, pos); end end + +% methods (Static) +% function envs = initialize(mpo, mps1, mps2) +% arguments +% mpo +% mps1 +% mps2 = mps1 +% end +% +% GL = cell(1, length(mpo) + 1); +% GR = cell(1, length(mpo) + 1); +% +% GL{1} = mpo.L; +% GR{end} = mpo.R; +% +% envs = FiniteEnvironment(GL, GR); +% end +% end end diff --git a/src/models/quantum1dIsing.m b/src/models/quantum1dIsing.m new file mode 100644 index 0000000..5fa4c66 --- /dev/null +++ b/src/models/quantum1dIsing.m @@ -0,0 +1,67 @@ +function mpo = quantum1dIsing(kwargs) +arguments + kwargs.J = 1 + kwargs.h = 1 + kwargs.L = Inf % size of system + kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'Z2'})} = 'Z1' +end + +J = kwargs.J; +h = kwargs.h; + +sigma_x = [0 1; 1 0]; +sigma_z = [1 0; 0 -1]; + +if strcmp(kwargs.Symmetry, 'Z1') + pSpace = CartesianSpace.new(2); + vSpace = one(pSpace); + trivSpace = one(pSpace); + + S = Tensor([vSpace pSpace], [pSpace vSpace]); + Sx = fill_matrix(S, sigma_x); + Sz = fill_matrix(S, sigma_z); + + cod = SumSpace([vSpace vSpace vSpace], pSpace); + dom = SumSpace(pSpace, [vSpace vSpace vSpace]); + O = MpoTensor.zeros(cod, dom); + O(1, 1, 1, 1) = 1; + O(3, 1, 3, 1) = 1; + O(1, 1, 2, 1) = -J * Sx; + O(2, 1, 3, 1) = Sx; + O(1, 1, 3, 1) = (-J * h) * Sz; + +else + pSpace = GradedSpace.new(Z2(0, 1), [1 1], false); + vSpace = GradedSpace.new(Z2(1), 1, false); + trivSpace = one(pSpace); + + Sx_l = fill_matrix(Tensor([trivSpace pSpace], [pSpace vSpace]), {1 1}); + Sx_r = fill_matrix(Tensor([vSpace pSpace], [pSpace trivSpace]), {1 1}); + Sz = fill_matrix(Tensor([trivSpace pSpace], [pSpace trivSpace]), {1 -1}); + + cod = SumSpace([one(vSpace) vSpace one(vSpace)], pSpace); + dom = SumSpace(pSpace, [one(vSpace), vSpace, one(vSpace)]); + O = MpoTensor.zeros(cod, dom); + O(1, 1, 1, 1) = 1; + O(3, 1, 3, 1) = 1; + O(1, 1, 2, 1) = -J * Sx_l; + O(2, 1, 3, 1) = Sx_r; + O(1, 1, 3, 1) = (-J * h) * Sz; +end + +if isfinite(kwargs.L) + r = Tensor.eye([trivSpace trivSpace], trivSpace); + l = r'; + Os = cell(1, kwargs.L); + Os{1} = O(1,:,:,:); + for i = 2:length(Os)-1 + Os{i} = O; + end + Os{end} = O(:,:,end,:); + mpo = FiniteMpo(MpsTensor(l), Os, MpsTensor(r)); +else + mpo = InfJMpo(O); +end + +end + diff --git a/src/models/statmech2DIsing.m b/src/models/statmech2DIsing.m new file mode 100644 index 0000000..0593239 --- /dev/null +++ b/src/models/statmech2DIsing.m @@ -0,0 +1,60 @@ +function O = statmech2DIsing(kwargs) + +arguments + kwargs.beta = log(1 + sqrt(2)) / 2; + kwargs.L = Inf % size of system + kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'Z2'})} = 'Z1' +end + +if ~isfinite(kwargs.L) + O = InfMpo({MpoTensor(bulk_mpo(kwargs.beta, kwargs.Symmetry))}); + +else + r = boundary_mpo(kwargs.beta, kwargs.Symmetry); + l = r'; + o = bulk_mpo(kwargs.beta, kwargs.Symmetry); + + O = FiniteMpo(MpsTensor(l), repmat({MpoTensor(o)}, 1, kwargs.L), MpsTensor(r)); +end + +end + +function O = bulk_mpo(beta, symmetry) + +if strcmp(symmetry, 'Z1') + sz = [2 2 2 2]; + t = sqrtm([exp(beta) exp(-beta); exp(-beta) exp(beta)]); + o = contract(diracdelta(sz), 1:4, t, [-1 1], t, [-2 2], t, [-3 3], t, [-4 4]); + O = fill_tensor(Tensor.zeros(sz), o); + +elseif strcmp(symmetry, 'Z2') + s = GradedSpace.new(Z2(0, 1), [1 1], false); + t = sqrtm(fill_matrix(Tensor(s, s), num2cell(2 * [cosh(beta) sinh(beta)]))); + o = Tensor.ones([s s], [s s]) / 2; + + O = contract(o, 1:4, t, [-1 1], t, [-2 2], t, [3 -3], t, [4 -4], 'Rank', [2 2]); +else + error('invalid symmetry'); +end + +end + +function O = boundary_mpo(beta, symmetry) + +if strcmp(symmetry, 'Z1') + sz = [2 2 2]; + t = sqrtm([exp(beta) exp(-beta); exp(-beta) exp(beta)]); + o = contract(diracdelta(sz), 1:3, t, [-1 1], t, [-2 2], t, [-3 3]); + O = fill_tensor(Tensor.zeros(sz), o); + +elseif strcmp(symmetry, 'Z2') + s = GradedSpace.new(Z2(0, 1), [1 1], false); + t = sqrtm(fill_matrix(Tensor(s, s), num2cell(2 * [cosh(beta) sinh(beta)]))); + o = Tensor.ones([s s], s) / sqrt(2); + + O = contract(o, 1:3, t, [-1 1], t, [-2 2], t, [3 -3], 'Rank', [2 1]); +else + error('invalid symmetry'); +end + +end \ No newline at end of file diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index 110dd99..19a9760 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -2,9 +2,9 @@ % Finite Matrix product operators properties - L MpsTensor + L O - R MpsTensor + R end methods @@ -64,6 +64,61 @@ end end + function mps = initialize_mps(mpo, kwargs) + arguments + mpo + kwargs.MaxVspace + end + + pspaces = arrayfun(@(x) pspace(mpo, x), 1:length(mpo), 'UniformOutput', false); + + vspacefirst = rightvspace(mpo.L)'; + vspacelast = leftvspace(mpo.R); + + newkwargs = namedargs2cell(kwargs); + mps = FiniteMps.randnc(pspaces{:}, 'LeftVspace', vspacefirst, ... + 'RightVspace', vspacelast, newkwargs{:}); + mps = normalize(mps); + end + + function envs = initialize_envs(mpo) + arguments + mpo + end + + GL = cell(1, length(mpo) + 1); + GR = cell(1, length(mpo) + 1); + + GL{1} = mpo.L; + GR{end} = mpo.R; + + envs = FiniteEnvironment(GL, GR); + end + + function T = transfermatrix(mpo, mps1, mps2, sites) + arguments + mpo + mps1 + mps2 = mps1 + sites = 1:length(mps1) + end + + assert(all(diff(sites) == 1), 'sites must be neighbouring and increasing.'); + A1 = mps1.A(sites); + A2 = mps2.A(sites); + O = mpo.O(sites); %#ok + T = FiniteMpo.mps_channel_operator(A1, O, A2); %#ok + end + + function H = AC_hamiltonian(mpo, mps, envs, pos) + envs = movegaugecenter(envs, mpo, mps, mps, pos); + H = FiniteMpo(envs.GL{pos}, mpo.O(pos), envs.GR{pos + 1}); + end + + function s = pspace(mpo, x) + s = pspace(mpo.O{x}); + end + function s = domain(mpo) N = prod(cellfun(@(x) size(x, 4), mpo(1).O)); s = conj(... diff --git a/src/mps/FiniteMps.m b/src/mps/FiniteMps.m index 987fc4b..3c749ee 100644 --- a/src/mps/FiniteMps.m +++ b/src/mps/FiniteMps.m @@ -22,7 +22,7 @@ p = length(mps.A); end - function mps = movecenter(mps, newcenter, alg) + function mps = movegaugecenter(mps, newcenter, alg) arguments mps newcenter {mustBeInteger} = floor(length(mps) / 2) @@ -33,13 +33,21 @@ 'mps:gauge', ... sprintf('new center (%d) must be in 1:%d', newcenter, length(mps))); - for i = max(mps.center, 1):(newcenter - 1) - [mps.A{i}, L] = leftorth(mps.A{i}, alg); - mps.A{i + 1} = multiplyleft(mps.A{i + 1}, L); + if isempty(mps.center) + low = 1; + high = length(mps); + else + low = mps.center; + high = mps.center; + end + + for i = low:(newcenter - 1) + [mps.A(i), L] = leftorth(mps.A(i), alg); + mps.A(i + 1) = multiplyleft(mps.A(i + 1), L); end - for i = min(mps.center, length(mps)):-1:(newcenter + 1) - [R, mps.A{i}] = rightorth(mps.A{i}, alg); - mps.A{i - 1} = multiplyright(mps.A{i - 1}, R); + for i = high:-1:(newcenter + 1) + [R, mps.A(i)] = rightorth(mps.A(i), alg); + [mps.A(i - 1)] = multiplyright(mps.A(i - 1), R); end mps.center = newcenter; end @@ -55,6 +63,31 @@ T = transfermatrix(mps1.A(sites), mps2.A(sites)); end + function rho = fixedpoint(mps, type, w) + arguments + mps + type {mustBeMember(type, {'l', 'r'})} + w = floor(length(mps) / 2); + end + + if strcmp(type, 'l') + if mps.center > w + rho = mps.A.eye(leftvspace(mps, w), leftvspace(mps, w)); + else + T = transfermatrix(mps, mps, mps.center:w); + rho = apply(T, []); + end + else + if mps.center < w + rho = mps.A.eye(rightvspace(mps, w)', rightvspace(mps, w)'); + else + T = transfermatrix(mps, mps, w:mps.center).'; + rho = apply(T, []); + end + end + + end + function o = overlap(mps1, mps2, rholeft, rhoright) arguments mps1 @@ -85,7 +118,7 @@ end function [mps, n] = normalize(mps) - if isempty(mps.center), mps = movecenter(mps); end + if isempty(mps.center), mps = movegaugecenter(mps); end n = norm(mps); mps.A(mps.center) = mps.A(mps.center) ./ n; end @@ -97,35 +130,87 @@ end assert(0 <= w && w <= length(mps)); - if isempty(mps.center), mps = movecenter(mps, w); end + if isempty(mps.center), mps = movegaugecenter(mps, w); end if w < mps.center - mps = movecenter(mps, w + 1); + mps = movegaugecenter(mps, w + 1); S = tsvd(mps.A(w + 1), 1, 2:nspaces(mps.A(w + 1))); else - mps = movecenter(mps, w); + mps = movegaugecenter(mps, w); S = tsvd(mps.A(w), 1:nspaces(mps.A(w))-1, nspaces(mps.A(w))); end [svals, charges] = matrixblocks(S); svals = cellfun(@diag, svals, 'UniformOutput', false); end + + function psi = Tensor(mps) + tensors = num2cell(mps.A); + indices = cell(size(tensors)); + + nout = nspaces(mps.A(1)) - 1; + indices{1} = [-(1:nout), 1]; + + for i = 2:length(indices)-1 + plegs = mps.A(i).plegs; + indices{i} = [i-1 -(1:plegs)-nout i]; + nout = nout + plegs; + end + + plegs = mps.A(end).plegs; + indices{end} = [length(indices)-1 -(1:plegs+1)-nout]; + + args = [tensors; indices]; + psi = contract(args{:}); + end end methods (Static) - function mps = new(fun, vspace1, pspaces, vspaces) + function mps = new(fun, pspaces, kwargs) arguments fun - vspace1 end arguments (Repeating) pspaces - vspaces + end + arguments + kwargs.L + kwargs.MaxVspace + kwargs.LeftVspace = one(pspaces{1}) + kwargs.RightVspace = one(pspaces{1}) end if isempty(fun), fun = @randnc; end + if isfield(kwargs, 'L') + pspaces = repmat(pspaces, 1, kwargs.L); + end + if isfield(kwargs, 'MaxVspace') && ~iscell(kwargs.MaxVspace) + kwargs.MaxVspace = {kwargs.MaxVspace}; + end + L = length(pspaces); - vspaces = [{vspace1} vspaces]; + + vspacesL = cell(1, L + 1); + vspacesL{1} = kwargs.LeftVspace; + for i = 1:L + vspacesL{i+1} = vspacesL{i} * pspaces{i}; + if isfield(kwargs, 'MaxVspace') + vspacesL{i + 1} = infimum(vspacesL{i + 1}, ... + kwargs.MaxVspace{mod1(i + 1, length(kwargs.MaxVspace))}); + end + end + + vspacesR = cell(1, L + 1); + vspacesR{end} = kwargs.RightVspace; + for i = L:-1:1 + vspacesR{i} = pspaces{i}' * vspacesR{i+1}; + if isfield(kwargs, 'MaxVspace') + vspacesR{i} = infimum(vspacesR{i}, ... + kwargs.MaxVspace{mod1(i, length(kwargs.MaxVspace))}); + end + end + + vspaces = cellfun(@infimum, vspacesL, vspacesR, 'UniformOutput', false); for w = length(pspaces):-1:1 rankdeficient = vspaces{w} * pspaces{w} < vspaces{w + 1} || ... @@ -140,6 +225,42 @@ mps = FiniteMps(A); end + + function mps = new_from_spaces(fun, spaces, kwargs) + arguments + fun + end + arguments (Repeating) + spaces + end + arguments + kwargs.MaxVspace + end + + if isfield(kwargs, 'MaxVspace') && ~iscell(kwargs.MaxVspace) + kwargs.MaxVspace = {kwargs.MaxVspace}; + end + + assert(mod(length(spaces) - 1, 2)); + if isempty(fun), fun = @randnc; end + + L = (length(spaces) - 1) / 2; + + for w = L:-1:1 + Vleft = spaces{2 * w - 1}; + P = spaces{2 * w}; + Vright = spaces{2 * w + 1}; + rankdeficient = Vleft * P < Vright || Vleft > P' * Vright; + if rankdeficient + error('mps:rank', ... + 'Cannot create a full rank mps with given spaces.'); + end + A{w} = Tensor.new(fun, [Vleft P], Vright); + end + + mps = FiniteMps(A); + end + function mps = randnc(varargin) mps = FiniteMps.new(@randnc, varargin{:}); end diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index bdc1088..0c564ff 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -20,6 +20,10 @@ function n = nspaces(~) n = 4; end + + function r = rank(t) + r = rank(t.tensors); + end end methods @@ -239,6 +243,9 @@ methods function s = space(O, i) + if nargin == 1 + i = 1:nspaces(O); + end s = space(O.tensors, i); end @@ -314,7 +321,7 @@ i = prod(size(t)); return end - if n > ndims(t) + if k > ndims(t) i = 1; else i = size(t, k); @@ -341,6 +348,14 @@ [varargout{1:nargout}] = size(t.scalars); end end + + function n = ndims(t) + n = ndims(t.scalars); + end + + function disp(O) + builtin('disp', O); + end end methods (Static) diff --git a/src/tensors/AbstractTensor.m b/src/tensors/AbstractTensor.m index 33bdee2..1659f1d 100644 --- a/src/tensors/AbstractTensor.m +++ b/src/tensors/AbstractTensor.m @@ -363,6 +363,48 @@ end end + function disp(t, details) + if nargin == 1 || isempty(details), details = false; end + if isscalar(t) + r = t.rank; + fprintf('Rank (%d, %d) %s:\n', r(1), r(2), class(t)); + s = space(t); + for i = 1:length(s) + fprintf('\t%d.\t', i); + disp(s(i)); + fprintf('\b'); + end + fprintf('\n'); + if details + [blocks, charges] = matrixblocks(t); + for i = 1:length(blocks) + if ~isempty(blocks) + fprintf('charge %s:\n', string(charges(i))); + end + disp(blocks{i}); + end + end + else + fprintf('%s of size %s:\n', class(t), ... + regexprep(mat2str(size(t)), {'\[', '\]', '\s+'}, {'', '', 'x'})); + subs = ind2sub_(size(t), 1:numel(t)); + spc = floor(log10(max(double(subs), [], 1))) + 1; + if numel(spc) == 1 + fmt = strcat("\t(%", num2str(spc(1)), "u)"); + else + fmt = strcat("\t(%", num2str(spc(1)), "u,"); + for i = 2:numel(spc) - 1 + fmt = strcat(fmt, "%", num2str(spc(i)), "u,"); + end + fmt = strcat(fmt, "%", num2str(spc(end)), "u)"); + end + for i = 1:numel(t) + fprintf('%s\t\t', compose(fmt, subs(i, :))); + disp(t(i), details); + end + end + end + function d = distance(A, B) % Compute the Euclidean distance between two tensors. % diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index dedd2fc..de7b239 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -75,6 +75,8 @@ elseif isempty(domain) sz = nsubspaces(codomain); else + if ~isa(codomain, 'SumSpace'), codomain = SumSpace(codomain); end + if ~isa(domain, 'SumSpace'), domain = SumSpace(domain); end sz = [nsubspaces(codomain) flip(nsubspaces(domain))]; end subs = ind2sub_(sz, 1:prod(sz)); diff --git a/src/tensors/spaces/AbstractSpace.m b/src/tensors/spaces/AbstractSpace.m index b1d2c8c..5f61b39 100644 --- a/src/tensors/spaces/AbstractSpace.m +++ b/src/tensors/spaces/AbstractSpace.m @@ -317,6 +317,10 @@ bool = ~le(space1, space2); end + function bool = ge(space1, space2) + bool = le(space2, space1); + end + function bool = isequal(spaces) % Check whether all input spaces are equal. Spaces are considered equal if they % are of same size, and are equal element-wise. For convenience, empty spaces diff --git a/src/tensors/spaces/CartesianSpace.m b/src/tensors/spaces/CartesianSpace.m index e3ef96f..c4e1131 100644 --- a/src/tensors/spaces/CartesianSpace.m +++ b/src/tensors/spaces/CartesianSpace.m @@ -274,10 +274,6 @@ bools = [spaces1.dimensions] == [spaces2.dimensions]; end - function bool = ge(space1, space2) - bool = le(space2, space1); - end - function bool = le(space1, space2) assert(isscalar(space1) && isscalar(space2)); bool = degeneracies(space1) <= degeneracies(space2); diff --git a/src/tensors/spaces/SumSpace.m b/src/tensors/spaces/SumSpace.m index 2f5f506..88b27ca 100644 --- a/src/tensors/spaces/SumSpace.m +++ b/src/tensors/spaces/SumSpace.m @@ -32,6 +32,10 @@ end function space = infimum(space1, space2) + arguments + space1 SumSpace + space2 SumSpace + end assert(isscalar(space1) && isscalar(space2)); assert(isdual(space1) == isdual(space2)); space = SumSpace(arrayfun(@infimum, space1.dimensions, space2.dimensions)); @@ -79,6 +83,17 @@ end end + function bool = le(space1, space2) + arguments + space1 SumSpace + space2 SumSpace + end + + assert(isscalar(space1) && isscalar(space2)); + + bool = le(sum(subspaces(space1)), sum(subspaces(space2))); + end + function s = subspaces(space, i) assert(isscalar(space), ... 'space:argerror', 'method only defined for scalar inputs.'); diff --git a/test/TestFiniteMpo.m b/test/TestFiniteMpo.m index 8fcdc92..4e895c5 100644 --- a/test/TestFiniteMpo.m +++ b/test/TestFiniteMpo.m @@ -8,6 +8,7 @@ FiniteMpo.randnc(ComplexSpace.new(2, false, 4, true, 2, true), ... ComplexSpace.new(2, true, 3, false)) ... ) + end methods (Test) @@ -40,6 +41,40 @@ function testTransposes(tc, mpo) tc.assertTrue(isequal(domain(mpo.'), codomain(mpo)')); tc.assertTrue(isequal(codomain(mpo.'), domain(mpo)')); end + + function test2dIsing(tc) + L = 10; + D = 20; + alg = Dmrg('maxiter', 10, 'which', 'largestabs'); + + mpo = statmech2DIsing('beta', 2, 'L', L); + vspace_max = CartesianSpace.new(D); + mps = initialize_mps(mpo, 'MaxVspace', vspace_max); + + [mps, envs, eta] = fixedpoint(alg, mpo, mps); + + mpo = statmech2DIsing('beta', 2, 'L', L, 'Symmetry', 'Z2'); + vspace_max = GradedSpace.new(Z2(0, 1), D ./ [2 2], false); + mps = initialize_mps(mpo, 'MaxVspace', vspace_max); + [mps, envs, eta] = fixedpoint(alg, mpo, mps); + end + + function test1dIsing(tc) + L = 20; + D = 40; + alg = Dmrg('maxiter', 10, 'which', 'smallestreal'); + + mpo = quantum1dIsing('L', L); + vspace_max = CartesianSpace.new(D); + mps = initialize_mps(mpo, 'MaxVspace', vspace_max); + + [mps, envs, eta] = fixedpoint(alg, mpo, mps); + + mpo = quantum1dIsing('L', L, 'Symmetry', 'Z2'); + vspace_max = GradedSpace.new(Z2(0, 1), D ./ [2 2], false); + mps = initialize_mps(mpo, 'MaxVspace', vspace_max); + [mps, envs, eta] = fixedpoint(alg, mpo, mps); + end end end diff --git a/test/TestFiniteMps.m b/test/TestFiniteMps.m new file mode 100644 index 0000000..b130a63 --- /dev/null +++ b/test/TestFiniteMps.m @@ -0,0 +1,83 @@ +classdef TestFiniteMps < matlab.unittest.TestCase + % Unit tests for uniform matrix product states. + + properties (TestParameter) + A = struct(... + 'trivial', {{Tensor.randnc(CartesianSpace.new([4 2]), CartesianSpace.new(4))}}, ... + 'trivial2', {{Tensor.randnc(CartesianSpace.new([4 2]), CartesianSpace.new(5)), ... + Tensor.randnc(CartesianSpace.new([5 2]), CartesianSpace.new(4))}}, ... + 'trivial3', {{Tensor.randnc(CartesianSpace.new([4 2]), CartesianSpace.new(5)), ... + Tensor.randnc(CartesianSpace.new([5 2]), CartesianSpace.new(6)), ... + Tensor.randnc(CartesianSpace.new([6 2]), CartesianSpace.new(4))}}, ... + 'fermion1', {{Tensor.randnc(... + GradedSpace.new(fZ2(0,1), [2 2], false, fZ2(0,1), [1 1], false), ... + GradedSpace.new(fZ2(0,1), [2 2], false))}}, ... + 'fermion2', {{Tensor.randnc(... + GradedSpace.new(fZ2(0,1), [2 2], true, fZ2(0,1), [1 1], false), ... + GradedSpace.new(fZ2(0,1), [2 2], true))}}, ... + 'fermion3', {{Tensor.randnc(... + GradedSpace.new(fZ2(0,1), [2 2], false, fZ2(0,1), [1 1], true), ... + GradedSpace.new(fZ2(0,1), [2 2], false))}}, ... + 'fermion4', {{Tensor.randnc(... + GradedSpace.new(fZ2(0,1), [2 2], true, fZ2(0,1), [1 1], true), ... + GradedSpace.new(fZ2(0,1), [2 2], true))}}, ... + 'haldane', {{Tensor.randnc(GradedSpace.new(SU2(1:2:5), [5 3 2], false, SU2(2), 1, false), ... + GradedSpace.new(SU2(2:2:6), [5 2 1], false)), ... + Tensor.randnc(GradedSpace.new(SU2(2:2:6), [5 2 1], false, SU2(2), 1, false), ... + GradedSpace.new(SU2(1:2:5), [5 3 2], false))}} ... + ) + end + + properties + tol = 1e-10 + end + + methods (Test) + function testFullMps(tc) + L = 5; + P = CartesianSpace.new(2); + mps = FiniteMps.new([], P, 'L', L); + psi = Tensor(mps); + + tc.verifyEqual(norm(psi), norm(mps), 'RelTol', tc.tol); + tc.verifyEqual(sqrt(abs(overlap(mps, mps))), norm(psi), 'RelTol', tc.tol); + mps_normed = normalize(mps); + tc.verifyEqual(norm(mps_normed), 1, 'RelTol', tc.tol); + tc.verifyEqual(norm(Tensor(mps_normed)), 1, 'RelTol', tc.tol); + + + end + + function testDiagonalC(tc, A) + mps = UniformMps(A); + mps2 = diagonalizeC(mps); + f = fidelity(mps, mps2); + tc.assertTrue(isapprox(f, 1), 'Diagonalizing C should not alter the state.'); + end + + function testFixedpoints(tc, A) + mps = UniformMps(A); + for top = ["L" "R"] + for bot = ["L" "R"] + T = transfermatrix(mps, mps, 'Type', sprintf('%c%c', top, bot)); + rhoL = fixedpoint(mps, sprintf('l_%c%c', top, bot)); + rhoR = fixedpoint(mps, sprintf('r_%c%c', top, bot)); + tc.verifyTrue(isapprox(rhoL, T.apply(rhoL)), ... + sprintf('rho_left should be a %c%c fixed point.', top, bot)); + tc.verifyTrue(isapprox(rhoR, apply(T', rhoR)), ... + sprintf('rho_right should be a %c%c fixed point.', top, bot)); + end + end + end + + function testTransferEigs(tc, A) + mps = UniformMps(A); + + [V, D] = transfereigs(mps, mps, 1, 'largestabs'); + [~, charges] = matrixblocks(V); + [V2, D2] = transfereigs(mps, mps, 1, 'largestabs', 'Charge', one(charges)); + + end + end +end + diff --git a/test/TestInfMpo.m b/test/TestInfMpo.m index a03bfaf..65005c6 100644 --- a/test/TestInfMpo.m +++ b/test/TestInfMpo.m @@ -56,14 +56,14 @@ function test2dIsing(tc) D = 16; alg = Vumps('MaxIter', 10); - mpo = InfMpo.Ising(beta); + mpo = statmech2DIsing('beta', beta, 'Symmetry', 'Z1'); mps = UniformMps.randnc(CartesianSpace.new(2), CartesianSpace.new(D)); [mps2, lambda] = fixedpoint(alg, mpo, mps); tc.assertEqual(-log(lambda) / beta, freeEnergyExact, 'RelTol', 1e-5); mps = UniformMps.randnc(GradedSpace.new(Z2(0, 1), [1 1], false), ... GradedSpace.new(Z2(0, 1), [D D] ./ 2, false)); - mpo = InfMpo.Ising(beta, 'Symmetry', 'Z2'); + mpo = statmech2DIsing('beta', beta, 'Symmetry', 'Z2'); [mps2, lambda] = fixedpoint(alg, mpo, mps); tc.assertEqual(-log(lambda) / beta, freeEnergyExact, 'RelTol', 1e-5); From fc64c0bc67e7ffe235aa3dcd5bd2b011de83adb6 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 22 Feb 2023 11:07:43 +0100 Subject: [PATCH 138/245] Cleanup Dmrg code --- src/algorithms/Dmrg.m | 107 +++++++++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 44 deletions(-) diff --git a/src/algorithms/Dmrg.m b/src/algorithms/Dmrg.m index c61025f..53565b6 100644 --- a/src/algorithms/Dmrg.m +++ b/src/algorithms/Dmrg.m @@ -14,12 +14,19 @@ tol_max = 1e-10 eigs_tolfactor = 1e-6 + sweepstyle {mustBeMember(sweepstyle, {'f2f', 'b2b', 'm2m'})} = 'f2f' + doSave = false saveIterations = 1 saveMethod = 'full' name = 'DMRG' end + properties (Access = private) + alg_eigs = struct('MaxIter', 100, 'KrylovDim', 20); + progressfig + end + methods function alg = Dmrg(kwargs) arguments @@ -32,6 +39,11 @@ alg.(field{1}) = kwargs.(field{1}); end end + + if ~isfield('alg_eigs', kwargs) + alg.alg_eigs.Tol = sqrt(alg.tol_min * alg.tol_max); + alg.alg_eigs.Verbosity = alg.verbosity - 2; + end end function [mps, envs, eta] = fixedpoint(alg, mpo, mps, envs) @@ -53,49 +65,48 @@ for iter = 1:alg.maxiter t_iter = tic; + order = sweeporder(alg, length(mps)); - eta = zeros(size(order)); - lambda = zeros(size(order)); + eta = zeros(size(order)); + lambda = zeros(size(order)); + for i = 1:length(order) - pos = order(i); - [mps, envs, lambda(i), eta(i)] = localupdate(alg, mps, mpo, envs, pos); + [mps, envs, lambda(i), eta(i)] = ... + localupdate(alg, mps, mpo, envs, order(i)); end + eta = norm(eta, Inf); - if iter > alg.miniter && norm(eta, Inf) < alg.tol - disp_conv(alg, iter, sum(lambda), eta, toc(t_total)); + lambda = sum(lambda); + + if iter > alg.miniter && eta < alg.tol + disp_conv(alg, iter, lambda, eta, toc(t_total)); return end alg = updatetols(alg, iter, eta); - plot(alg, iter, mps, norm(eta, Inf)); - disp_iter(alg, iter, sum(lambda), eta, toc(t_iter)); + plot(alg, iter, mps, eta); + disp_iter(alg, iter, lambda, eta, toc(t_iter)); end - disp_maxiter(alg, iter, sum(lambda), eta, toc(t_total)); - end - - function order = sweeporder(alg, L) - order = [2:L, L-1:-1:1]; -% order = circshift(order, -floor(L / 2)); - order = 2:L-1; - end - - function envs = environments(alg, mpo, mps) - + disp_maxiter(alg, iter, lambda, eta, toc(t_total)); end function [mps, envs, lambda, eta] = localupdate(alg, mps, mpo, envs, pos) + % update orthogonality center mps = movegaugecenter(mps, pos); envs = movegaugecenter(envs, mpo, mps, mps, pos); + + % compute update H_AC = AC_hamiltonian(mpo, mps, envs, pos); AC = mps.A(pos); [AC.var, lambda] = eigsolve(H_AC, AC.var, 1, alg.which); + % determine error phase = dot(AC.var, mps.A(pos)); - eta = distance(AC.var ./ sign(phase), mps.A(pos)); + % take step mps.A(pos) = AC; envs = invalidate(envs, pos); end @@ -107,19 +118,27 @@ if alg.dynamical_tols alg.alg_eigs.Tol = between(alg.tol_min, eta * alg.eigs_tolfactor / iter, ... alg.tol_max); - alg.alg_canonical.Tol = between(alg.tol_min, ... - eta * alg.canonical_tolfactor / iter, alg.tol_max); - alg.alg_environments.Tol = between(alg.tol_min, ... - eta * alg.environments_tolfactor / iter, alg.tol_max); - if alg.verbosity > Verbosity.iter - fprintf('Updated subalgorithm tolerances: (%e,\t%e,\t%e)\n', ... - alg.alg_eigs.Tol, alg.alg_canonical.Tol, alg.alg_environments.Tol); + fprintf('Updated eigsolver tolerances: (%e)\n', alg.alg_eigs.Tol); end end end + + function order = sweeporder(alg, L) + switch alg.sweepstyle + case 'f2f' + order = [1:L L-1:-1:2]; + case 'b2b' + order = [L:-1:1 2:L]; + case 'm2m' + order = circshift([2:L, L-1:-1:1], -floor(L / 2)); + otherwise + error('unknown sweepstyle %s', alg.sweepstyle); + end + end end + %% Display methods (Access = private) function plot(alg, iter, mps, eta) @@ -159,7 +178,7 @@ function plot(alg, iter, mps, eta) function disp_init(alg) if alg.verbosity < Verbosity.conv, return; end - fprintf('---- VUMPS ----\n'); + fprintf('---- %s ----\n', alg.name); end function disp_iter(alg, iter, lambda, eta, t) @@ -169,21 +188,21 @@ function disp_iter(alg, iter, lambda, eta, t) if abs(imag(lambda)) < eps(lambda)^(3/4) * abs(lambda) switch s.matlab.commandwindow.NumericFormat.ActiveValue case 'short' - fprintf('Vumps %2d:\tE = %-0.4f\terror = %0.1e\t(%s)\n', ... - iter, real(lambda), eta, time2str(t)); + fprintf('%s %2d:\tE = %-0.4f\terror = %0.1e\t(%s)\n', ... + alg.name, iter, real(lambda), eta, time2str(t)); otherwise - fprintf('Vumps %4d:\tE = %-0.15f\terror = %0.4e\t(%s)\n', ... - iter, real(lambda), eta, time2str(t, 's')); + fprintf('%s %4d:\tE = %-0.15f\terror = %0.4e\t(%s)\n', ... + alg.name, iter, real(lambda), eta, time2str(t, 's')); end else switch s.matlab.commandwindow.NumericFormat.ActiveValue case 'short' - fprintf('Vumps %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... - iter, real(lambda), imag(lambda), eta, time2str(t)); + fprintf('%s %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... + alg.name, iter, real(lambda), imag(lambda), eta, time2str(t)); otherwise - fprintf('Vumps %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... - iter, real(lambda), imag(lambda), eta, time2str(t)); + fprintf('%s %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... + alg.name, iter, real(lambda), imag(lambda), eta, time2str(t)); end end @@ -194,11 +213,11 @@ function disp_conv(alg, iter, lambda, eta, t) s = settings; switch s.matlab.commandwindow.NumericFormat.ActiveValue case 'short' - fprintf('Vumps converged %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... - iter, real(lambda), imag(lambda), eta, time2str(t)); + fprintf('%s converged %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... + alg.name, iter, real(lambda), imag(lambda), eta, time2str(t)); otherwise - fprintf('Vumps converged %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... - iter, real(lambda), imag(lambda), eta, time2str(t)); + fprintf('%s converged %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... + alg.name, iter, real(lambda), imag(lambda), eta, time2str(t)); end fprintf('---------------\n'); @@ -209,11 +228,11 @@ function disp_maxiter(alg, iter, lambda, eta, t) s = settings; switch s.matlab.commandwindow.NumericFormat.ActiveValue case 'short' - fprintf('Vumps max iterations %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... - iter, real(lambda), imag(lambda), eta, time2str(t)); + fprintf('%s max iterations %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... + alg.name, iter, real(lambda), imag(lambda), eta, time2str(t)); otherwise - fprintf('Vumps max iterations %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... - iter, real(lambda), imag(lambda), eta, time2str(t)); + fprintf('%s max iterations %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... + alg.name, iter, real(lambda), imag(lambda), eta, time2str(t)); end fprintf('---------------\n'); From 1d27ea99c9641301614ec1848d2cb3bc94f9a011 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 22 Feb 2023 11:08:17 +0100 Subject: [PATCH 139/245] additional mps-tensor converters --- src/mps/FiniteMps.m | 289 +++++++++++++++++++++++-------------------- src/mps/MpsTensor.m | 38 ++++++ src/tensors/Tensor.m | 5 + test/TestFiniteMps.m | 13 +- 4 files changed, 207 insertions(+), 138 deletions(-) diff --git a/src/mps/FiniteMps.m b/src/mps/FiniteMps.m index 3c749ee..088f179 100644 --- a/src/mps/FiniteMps.m +++ b/src/mps/FiniteMps.m @@ -2,10 +2,11 @@ % Finite Matrix product states properties - A MpsTensor + A (1,:) MpsTensor center end + %% Constructors methods function mps = FiniteMps(varargin) @@ -17,11 +18,155 @@ mps.center = varargin{2}; end end + end + + methods (Static) + function mps = new(fun, pspaces, kwargs) + arguments + fun + end + arguments (Repeating) + pspaces + end + arguments + kwargs.L + kwargs.MaxVspace + kwargs.LeftVspace = one(pspaces{1}) + kwargs.RightVspace = one(pspaces{1}) + end + + if isempty(fun), fun = @randnc; end + + if isfield(kwargs, 'L') + pspaces = repmat(pspaces, 1, kwargs.L); + end + if isfield(kwargs, 'MaxVspace') && ~iscell(kwargs.MaxVspace) + kwargs.MaxVspace = {kwargs.MaxVspace}; + end + + L = length(pspaces); + + vspacesL = cell(1, L + 1); + vspacesL{1} = kwargs.LeftVspace; + for i = 1:L + vspacesL{i+1} = vspacesL{i} * pspaces{i}; + if isfield(kwargs, 'MaxVspace') + vspacesL{i + 1} = infimum(vspacesL{i + 1}, ... + kwargs.MaxVspace{mod1(i + 1, length(kwargs.MaxVspace))}); + end + end + + vspacesR = cell(1, L + 1); + vspacesR{end} = kwargs.RightVspace; + for i = L:-1:1 + vspacesR{i} = pspaces{i}' * vspacesR{i+1}; + if isfield(kwargs, 'MaxVspace') + vspacesR{i} = infimum(vspacesR{i}, ... + kwargs.MaxVspace{mod1(i, length(kwargs.MaxVspace))}); + end + end + + vspaces = cellfun(@infimum, vspacesL, vspacesR, 'UniformOutput', false); + + for w = length(pspaces):-1:1 + rankdeficient = vspaces{w} * pspaces{w} < vspaces{w + 1} || ... + vspaces{w} > pspaces{w}' * vspaces{w + 1}; + if rankdeficient + error('mps:rank', ... + 'Cannot create a full rank mps with given spaces.'); + end + + A{w} = Tensor.new(fun, [vspaces{w} pspaces{w}], vspaces{w + 1}); + end + mps = FiniteMps(A); + end + + function mps = new_from_spaces(fun, spaces, kwargs) + arguments + fun + end + arguments (Repeating) + spaces + end + arguments + kwargs.MaxVspace + end + + if isfield(kwargs, 'MaxVspace') && ~iscell(kwargs.MaxVspace) + kwargs.MaxVspace = {kwargs.MaxVspace}; + end + + assert(mod(length(spaces) - 1, 2)); + if isempty(fun), fun = @randnc; end + + L = (length(spaces) - 1) / 2; + + for w = L:-1:1 + Vleft = spaces{2 * w - 1}; + P = spaces{2 * w}; + Vright = spaces{2 * w + 1}; + rankdeficient = Vleft * P < Vright || Vleft > P' * Vright; + if rankdeficient + error('mps:rank', ... + 'Cannot create a full rank mps with given spaces.'); + end + A{w} = Tensor.new(fun, [Vleft P], Vright); + end + + mps = FiniteMps(A); + end + function mps = randnc(varargin) + mps = FiniteMps.new(@randnc, varargin{:}); + end + end + + + %% Properties + methods function p = length(mps) p = length(mps.A); end + function [svals, charges] = schmidt_values(mps, w) + arguments + mps + w {mustBeInteger} = floor(length(mps) / 2) + end + + assert(0 <= w && w <= length(mps)); + if isempty(mps.center), mps = movegaugecenter(mps, w); end + + if w < mps.center + mps = movegaugecenter(mps, w + 1); + S = tsvd(mps.A(w + 1), 1, 2:nspaces(mps.A(w + 1))); + else + mps = movegaugecenter(mps, w); + S = tsvd(mps.A(w), 1:nspaces(mps.A(w))-1, nspaces(mps.A(w))); + end + [svals, charges] = matrixblocks(S); + svals = cellfun(@diag, svals, 'UniformOutput', false); + end + end + + + %% Derived operators + methods + function T = transfermatrix(mps1, mps2, sites) + arguments + mps1 + mps2 = mps1 + sites = 1:length(mps1) + end + + assert(all(diff(sites) == 1), 'sites must be neighbouring and increasing.'); + T = transfermatrix(mps1.A(sites), mps2.A(sites)); + end + end + + + %% Methods + methods function mps = movegaugecenter(mps, newcenter, alg) arguments mps @@ -52,17 +197,6 @@ mps.center = newcenter; end - function T = transfermatrix(mps1, mps2, sites) - arguments - mps1 - mps2 = mps1 - sites = 1:length(mps1) - end - - assert(all(diff(sites) == 1), 'sites must be neighbouring and increasing.'); - T = transfermatrix(mps1.A(sites), mps2.A(sites)); - end - function rho = fixedpoint(mps, type, w) arguments mps @@ -122,28 +256,14 @@ n = norm(mps); mps.A(mps.center) = mps.A(mps.center) ./ n; end - - function [svals, charges] = schmidt_values(mps, w) - arguments - mps - w {mustBeInteger} = floor(length(mps) / 2) - end - - assert(0 <= w && w <= length(mps)); - if isempty(mps.center), mps = movegaugecenter(mps, w); end - - if w < mps.center - mps = movegaugecenter(mps, w + 1); - S = tsvd(mps.A(w + 1), 1, 2:nspaces(mps.A(w + 1))); - else - mps = movegaugecenter(mps, w); - S = tsvd(mps.A(w), 1:nspaces(mps.A(w))-1, nspaces(mps.A(w))); - end - [svals, charges] = matrixblocks(S); - svals = cellfun(@diag, svals, 'UniformOutput', false); - end - + end + + + %% Converters + methods function psi = Tensor(mps) + % Convert a finite mps to a dense tensor. + tensors = num2cell(mps.A); indices = cell(size(tensors)); @@ -163,107 +283,4 @@ psi = contract(args{:}); end end - - methods (Static) - function mps = new(fun, pspaces, kwargs) - arguments - fun - end - arguments (Repeating) - pspaces - end - arguments - kwargs.L - kwargs.MaxVspace - kwargs.LeftVspace = one(pspaces{1}) - kwargs.RightVspace = one(pspaces{1}) - end - - if isempty(fun), fun = @randnc; end - - if isfield(kwargs, 'L') - pspaces = repmat(pspaces, 1, kwargs.L); - end - if isfield(kwargs, 'MaxVspace') && ~iscell(kwargs.MaxVspace) - kwargs.MaxVspace = {kwargs.MaxVspace}; - end - - L = length(pspaces); - - vspacesL = cell(1, L + 1); - vspacesL{1} = kwargs.LeftVspace; - for i = 1:L - vspacesL{i+1} = vspacesL{i} * pspaces{i}; - if isfield(kwargs, 'MaxVspace') - vspacesL{i + 1} = infimum(vspacesL{i + 1}, ... - kwargs.MaxVspace{mod1(i + 1, length(kwargs.MaxVspace))}); - end - end - - vspacesR = cell(1, L + 1); - vspacesR{end} = kwargs.RightVspace; - for i = L:-1:1 - vspacesR{i} = pspaces{i}' * vspacesR{i+1}; - if isfield(kwargs, 'MaxVspace') - vspacesR{i} = infimum(vspacesR{i}, ... - kwargs.MaxVspace{mod1(i, length(kwargs.MaxVspace))}); - end - end - - vspaces = cellfun(@infimum, vspacesL, vspacesR, 'UniformOutput', false); - - for w = length(pspaces):-1:1 - rankdeficient = vspaces{w} * pspaces{w} < vspaces{w + 1} || ... - vspaces{w} > pspaces{w}' * vspaces{w + 1}; - if rankdeficient - error('mps:rank', ... - 'Cannot create a full rank mps with given spaces.'); - end - - A{w} = Tensor.new(fun, [vspaces{w} pspaces{w}], vspaces{w + 1}); - end - mps = FiniteMps(A); - end - - - function mps = new_from_spaces(fun, spaces, kwargs) - arguments - fun - end - arguments (Repeating) - spaces - end - arguments - kwargs.MaxVspace - end - - if isfield(kwargs, 'MaxVspace') && ~iscell(kwargs.MaxVspace) - kwargs.MaxVspace = {kwargs.MaxVspace}; - end - - assert(mod(length(spaces) - 1, 2)); - if isempty(fun), fun = @randnc; end - - L = (length(spaces) - 1) / 2; - - for w = L:-1:1 - Vleft = spaces{2 * w - 1}; - P = spaces{2 * w}; - Vright = spaces{2 * w + 1}; - rankdeficient = Vleft * P < Vright || Vleft > P' * Vright; - if rankdeficient - error('mps:rank', ... - 'Cannot create a full rank mps with given spaces.'); - end - A{w} = Tensor.new(fun, [Vleft P], Vright); - end - - mps = FiniteMps(A); - end - - function mps = randnc(varargin) - mps = FiniteMps.new(@randnc, varargin{:}); - end - end end - diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index 97d3bad..ba53d89 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -568,5 +568,43 @@ t = sparse(t); end end + + + %% + methods (Static) + function local_tensors = decompose_local_state(psi, kwargs) + % convert a tensor into a product of local operators. + % + % Usage + % ----- + % :code:`local_operators = MpoTensor.decompose_local_operator(H, kwargs)`. + % + % Arguments + % --------- + % H : :class:`AbstractTensor` + % tensor representing a local operator on N sites. + % + % Keyword Arguments + % ----------------- + % 'Trunc' : cell + % optional truncation method for the decomposition. See also + % :method:`Tensor.tsvd` + arguments + psi + kwargs.Trunc = {'TruncBelow', 1e-14} + end + + L = nspaces(psi); + assert(L >= 3, 'argerror', ... + sprintf('state must have at least 3 legs. (%d)', L)); + + local_tensors = cell(1, L - 2); + for i = 1:length(local_tensors)-1 + [u, s, psi] = tsvd(psi, [1 2], [3:nspaces(psi)], kwargs.Trunc{:}); + local_tensors{i} = multiplyright(MpsTensor(u), s); + end + local_tensors{end} = MpsTensor(repartition(psi, [2 1])); + end + end end diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index de7b239..8f214d3 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -2356,6 +2356,11 @@ if ~isempty(tsrc.codomain), tdst.codomain = ComplexSpace(tsrc.codomain); end end end + + function mps = FiniteMps(t, varargin) + A = MpsTensor.decompose_local_state(t, varargin{:}); + mps = FiniteMps(A); + end end diff --git a/test/TestFiniteMps.m b/test/TestFiniteMps.m index b130a63..755789b 100644 --- a/test/TestFiniteMps.m +++ b/test/TestFiniteMps.m @@ -34,18 +34,27 @@ methods (Test) function testFullMps(tc) - L = 5; + L = 12; P = CartesianSpace.new(2); mps = FiniteMps.new([], P, 'L', L); + + % test conversion psi = Tensor(mps); + psi2 = Tensor(FiniteMps(psi)); + tc.verifyTrue(isapprox(psi, psi2, 'RelTol', tc.tol)); + % test norms tc.verifyEqual(norm(psi), norm(mps), 'RelTol', tc.tol); tc.verifyEqual(sqrt(abs(overlap(mps, mps))), norm(psi), 'RelTol', tc.tol); mps_normed = normalize(mps); tc.verifyEqual(norm(mps_normed), 1, 'RelTol', tc.tol); tc.verifyEqual(norm(Tensor(mps_normed)), 1, 'RelTol', tc.tol); - + % test moving orthogonality center + for i = 1:length(mps) + tc.verifyEqual(overlap(movegaugecenter(mps_normed, i), mps_normed), 1, ... + 'RelTol', tc.tol); + end end function testDiagonalC(tc, A) From 025e9d4881347d6a5ab2dd0cbf37af2b0f07b825 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 22 Feb 2023 11:23:07 +0100 Subject: [PATCH 140/245] remove stub code --- src/mps/FiniteMps.m | 1 - test/TestFiniteMps.m | 62 ++------------------------------------------ 2 files changed, 2 insertions(+), 61 deletions(-) diff --git a/src/mps/FiniteMps.m b/src/mps/FiniteMps.m index 088f179..d0c6076 100644 --- a/src/mps/FiniteMps.m +++ b/src/mps/FiniteMps.m @@ -9,7 +9,6 @@ %% Constructors methods function mps = FiniteMps(varargin) - if nargin == 0, return; end if nargin == 1 mps.A = varargin{1}; diff --git a/test/TestFiniteMps.m b/test/TestFiniteMps.m index 755789b..de3eced 100644 --- a/test/TestFiniteMps.m +++ b/test/TestFiniteMps.m @@ -1,32 +1,5 @@ classdef TestFiniteMps < matlab.unittest.TestCase - % Unit tests for uniform matrix product states. - - properties (TestParameter) - A = struct(... - 'trivial', {{Tensor.randnc(CartesianSpace.new([4 2]), CartesianSpace.new(4))}}, ... - 'trivial2', {{Tensor.randnc(CartesianSpace.new([4 2]), CartesianSpace.new(5)), ... - Tensor.randnc(CartesianSpace.new([5 2]), CartesianSpace.new(4))}}, ... - 'trivial3', {{Tensor.randnc(CartesianSpace.new([4 2]), CartesianSpace.new(5)), ... - Tensor.randnc(CartesianSpace.new([5 2]), CartesianSpace.new(6)), ... - Tensor.randnc(CartesianSpace.new([6 2]), CartesianSpace.new(4))}}, ... - 'fermion1', {{Tensor.randnc(... - GradedSpace.new(fZ2(0,1), [2 2], false, fZ2(0,1), [1 1], false), ... - GradedSpace.new(fZ2(0,1), [2 2], false))}}, ... - 'fermion2', {{Tensor.randnc(... - GradedSpace.new(fZ2(0,1), [2 2], true, fZ2(0,1), [1 1], false), ... - GradedSpace.new(fZ2(0,1), [2 2], true))}}, ... - 'fermion3', {{Tensor.randnc(... - GradedSpace.new(fZ2(0,1), [2 2], false, fZ2(0,1), [1 1], true), ... - GradedSpace.new(fZ2(0,1), [2 2], false))}}, ... - 'fermion4', {{Tensor.randnc(... - GradedSpace.new(fZ2(0,1), [2 2], true, fZ2(0,1), [1 1], true), ... - GradedSpace.new(fZ2(0,1), [2 2], true))}}, ... - 'haldane', {{Tensor.randnc(GradedSpace.new(SU2(1:2:5), [5 3 2], false, SU2(2), 1, false), ... - GradedSpace.new(SU2(2:2:6), [5 2 1], false)), ... - Tensor.randnc(GradedSpace.new(SU2(2:2:6), [5 2 1], false, SU2(2), 1, false), ... - GradedSpace.new(SU2(1:2:5), [5 3 2], false))}} ... - ) - end + % Unit tests for finite matrix product states. properties tol = 1e-10 @@ -35,7 +8,7 @@ methods (Test) function testFullMps(tc) L = 12; - P = CartesianSpace.new(2); + P = ComplexSpace.new(2, false); mps = FiniteMps.new([], P, 'L', L); % test conversion @@ -56,37 +29,6 @@ function testFullMps(tc) 'RelTol', tc.tol); end end - - function testDiagonalC(tc, A) - mps = UniformMps(A); - mps2 = diagonalizeC(mps); - f = fidelity(mps, mps2); - tc.assertTrue(isapprox(f, 1), 'Diagonalizing C should not alter the state.'); - end - - function testFixedpoints(tc, A) - mps = UniformMps(A); - for top = ["L" "R"] - for bot = ["L" "R"] - T = transfermatrix(mps, mps, 'Type', sprintf('%c%c', top, bot)); - rhoL = fixedpoint(mps, sprintf('l_%c%c', top, bot)); - rhoR = fixedpoint(mps, sprintf('r_%c%c', top, bot)); - tc.verifyTrue(isapprox(rhoL, T.apply(rhoL)), ... - sprintf('rho_left should be a %c%c fixed point.', top, bot)); - tc.verifyTrue(isapprox(rhoR, apply(T', rhoR)), ... - sprintf('rho_right should be a %c%c fixed point.', top, bot)); - end - end - end - - function testTransferEigs(tc, A) - mps = UniformMps(A); - - [V, D] = transfereigs(mps, mps, 1, 'largestabs'); - [~, charges] = matrixblocks(V); - [V2, D2] = transfereigs(mps, mps, 1, 'largestabs', 'Charge', one(charges)); - - end end end From 9430ead1acb522e19a06f5477b36c71c7d41e102 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 22 Feb 2023 11:41:34 +0100 Subject: [PATCH 141/245] fix some tests --- src/mps/MpsTensor.m | 2 -- src/tensors/AbstractTensor.m | 28 ++++++++++++++++++++++++++++ test/TestInfJMpo.m | 6 +++--- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index ba53d89..ce76874 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -220,7 +220,6 @@ C = tensorprod(varargin{:}); end - function [AL, CL, lambda, eta] = uniform_leftorth(A, CL, kwargs) arguments A @@ -315,7 +314,6 @@ end end - function [AR, CR, lambda, eta] = uniform_rightorth(A, CR, kwargs) arguments A diff --git a/src/tensors/AbstractTensor.m b/src/tensors/AbstractTensor.m index 1659f1d..32287d9 100644 --- a/src/tensors/AbstractTensor.m +++ b/src/tensors/AbstractTensor.m @@ -488,5 +488,33 @@ function disp(t, details) o = contract(t1, 1:nspaces(t1), t2, flip(1:nspaces(t1))); end end + + + %% Contractions + methods + function v = applytransfer(L, R, v) + arguments + L + R + v = [] + end + + if isempty(v) + v = tracetransfer(L, R); + return + end + + auxlegs_v = nspaces(v) - 2; + auxlegs_l = 0; + auxlegs_r = 0; + newrank = rank(v); newrank(2) = newrank(2) + auxlegs_l + auxlegs_r; + + v = contract(v, [1 3 (-(1:auxlegs_v) - 2 - auxlegs_l)], ... + L, [-1 2 1 (-(1:auxlegs_l) - 2)], ... + R, [3 2 -2 (-(1:auxlegs_r) - 3 - auxlegs_l - auxlegs_v)], ... + 'Rank', newrank); + end + + end end diff --git a/test/TestInfJMpo.m b/test/TestInfJMpo.m index 50e60ae..e6f812e 100644 --- a/test/TestInfJMpo.m +++ b/test/TestInfJMpo.m @@ -43,12 +43,12 @@ function testDerivatives(tc, mpo, mps) function test1dIsing(tc) alg = Vumps('which', 'smallestreal', 'maxiter', 5); D = 16; - mpo = InfJMpo.Ising(1, 1); - mps = UniformMps.randnc(CartesianSpace.new(2), CartesianSpace.new(D)); + mpo = quantum1dIsing('J', 1, 'h', 1, 'L', Inf); + mps = initialize_mps(mpo, CartesianSpace.new(D)); [mps2, lambda] = fixedpoint(alg, mpo, mps); tc.verifyTrue(isapprox(lambda, -1.27, 'RelTol', 1e-2)) - mpo = InfJMpo.Ising(1, 1, 'Symmetry', 'Z2'); + mpo = quantum1dIsing('J', 1, 'h', 1, 'L', Inf, 'Symmetry', 'Z2'); mps = initialize_mps(mpo, GradedSpace.new(Z2(0, 1), [D D] ./ 2, false)); [mps2, lambda2] = fixedpoint(alg, mpo, mps); tc.verifyTrue(isapprox(lambda, -1.27, 'RelTol', 1e-2)) From e1539777979042960158c318e314a05f74a341ad Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 22 Feb 2023 14:03:02 +0100 Subject: [PATCH 142/245] cleanup --- src/algorithms/Dmrg.m | 12 +-- src/models/quantum1dHeisenberg.m | 49 ++++++++++ src/models/quantum1dIsing.m | 14 +-- .../{statmech2DIsing.m => statmech2dIsing.m} | 2 +- src/mps/FiniteMpo.m | 4 +- src/mps/InfJMpo.m | 92 ++----------------- src/mps/InfMpo.m | 40 ++------ src/tensors/AbstractTensor.m | 21 +++-- test/TestAlgorithms.m | 88 +++++++++--------- test/TestFiniteMpo.m | 14 +-- 10 files changed, 150 insertions(+), 186 deletions(-) create mode 100644 src/models/quantum1dHeisenberg.m rename src/models/{statmech2DIsing.m => statmech2dIsing.m} (97%) diff --git a/src/algorithms/Dmrg.m b/src/algorithms/Dmrg.m index 53565b6..a10ffaf 100644 --- a/src/algorithms/Dmrg.m +++ b/src/algorithms/Dmrg.m @@ -67,16 +67,16 @@ t_iter = tic; order = sweeporder(alg, length(mps)); - eta = zeros(size(order)); - lambda = zeros(size(order)); + eta = zeros(1, length(mps)); + lambda = zeros(1, length(mps)); - for i = 1:length(order) - [mps, envs, lambda(i), eta(i)] = ... - localupdate(alg, mps, mpo, envs, order(i)); + for pos = sweeporder(alg, length(mps)) + [mps, envs, lambda(pos), eta(pos)] = ... + localupdate(alg, mps, mpo, envs, order(pos)); end eta = norm(eta, Inf); - lambda = sum(lambda); + lambda = sum(lambda) / length(lambda); if iter > alg.miniter && eta < alg.tol disp_conv(alg, iter, lambda, eta, toc(t_total)); diff --git a/src/models/quantum1dHeisenberg.m b/src/models/quantum1dHeisenberg.m new file mode 100644 index 0000000..beff676 --- /dev/null +++ b/src/models/quantum1dHeisenberg.m @@ -0,0 +1,49 @@ +function mpo = quantum1dHeisenberg(kwargs) +arguments + kwargs.Spin = 1 + kwargs.J = 1 + kwargs.h = 0 + kwargs.L = Inf % size of system + kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'U1', 'SU2'})} = 'Z1' +end + +J = kwargs.J; +h = kwargs.h; + +Q = SU2(2 * kwargs.Spin + 1); + +switch kwargs.Symmetry + case 'SU2' + assert(isscalar(J) || all(J == J(1)), ... + 'Different spin couplings not invariant under SU2'); + assert(h == 0, 'Magnetic field not invariant under SU2'); + + pSpace = GradedSpace.new(Q, 1, false); + vSpace = GradedSpace.new(SU2(3), 1, false); + tSpace = one(vSpace); + + s = kwargs.Spin; + L = Tensor.ones([tSpace pSpace], [pSpace vSpace]); + L = L * (-J(1) * (s^2 + s)); + R = Tensor.ones([vSpace pSpace], [pSpace tSpace]); + + cod = SumSpace([one(vSpace) vSpace one(vSpace)], pSpace); + dom = SumSpace(pSpace, [one(vSpace), vSpace, one(vSpace)]); + O = MpoTensor.zeros(cod, dom); + O(1, 1, 1, 1) = 1; + O(3, 1, 3, 1) = 1; + O(1, 1, 2, 1) = L; + O(2, 1, 3, 1) = R; + + otherwise + error('TBA'); +end + +mpo = InfJMpo(O); + +if isfinite(kwargs.L) + mpo = open_boundary_conditions(mpo, L); +end + +end + diff --git a/src/models/quantum1dIsing.m b/src/models/quantum1dIsing.m index 5fa4c66..449aec9 100644 --- a/src/models/quantum1dIsing.m +++ b/src/models/quantum1dIsing.m @@ -49,18 +49,10 @@ O(1, 1, 3, 1) = (-J * h) * Sz; end +mpo = InfJMpo(O); + if isfinite(kwargs.L) - r = Tensor.eye([trivSpace trivSpace], trivSpace); - l = r'; - Os = cell(1, kwargs.L); - Os{1} = O(1,:,:,:); - for i = 2:length(Os)-1 - Os{i} = O; - end - Os{end} = O(:,:,end,:); - mpo = FiniteMpo(MpsTensor(l), Os, MpsTensor(r)); -else - mpo = InfJMpo(O); + mpo = open_boundary_conditions(InfJMpo(O), kwargs.L); end end diff --git a/src/models/statmech2DIsing.m b/src/models/statmech2dIsing.m similarity index 97% rename from src/models/statmech2DIsing.m rename to src/models/statmech2dIsing.m index 0593239..81539ad 100644 --- a/src/models/statmech2DIsing.m +++ b/src/models/statmech2dIsing.m @@ -1,4 +1,4 @@ -function O = statmech2DIsing(kwargs) +function O = statmech2dIsing(kwargs) arguments kwargs.beta = log(1 + sqrt(2)) / 2; diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index 19a9760..015cc92 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -2,9 +2,9 @@ % Finite Matrix product operators properties - L + L MpsTensor O - R + R MpsTensor end methods diff --git a/src/mps/InfJMpo.m b/src/mps/InfJMpo.m index 8817793..060e486 100644 --- a/src/mps/InfJMpo.m +++ b/src/mps/InfJMpo.m @@ -183,92 +183,20 @@ mpo = [mpo; b]; end - end - - methods (Static) - function mpo = Ising(J, h, kwargs) - arguments - J = 1 - h = 1 - kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'Z2'})} = 'Z1' - end + + function finitempo = open_boundary_conditions(mpo, L) + Os = repmat(mpo.O, 1, L); - sigma_x = [0 1; 1 0]; - sigma_z = [1 0; 0 -1]; + Os{1} = Os{1}(1, :, :, :); + Os{end} = Os{end}(:, :, end, :); - if strcmp(kwargs.Symmetry, 'Z1') - pSpace = CartesianSpace.new(2); - vSpace = one(pSpace); - S = Tensor([vSpace pSpace], [pSpace vSpace]); - Sx = fill_matrix(S, sigma_x); - Sz = fill_matrix(S, sigma_z); - - cod = SumSpace([vSpace vSpace vSpace], pSpace); - dom = SumSpace(pSpace, [vSpace vSpace vSpace]); - O = MpoTensor.zeros(cod, dom); - O(1, 1, 1, 1) = 1; - O(3, 1, 3, 1) = 1; - O(1, 1, 2, 1) = -J * Sx; - O(2, 1, 3, 1) = Sx; - O(1, 1, 3, 1) = (-J * h) * Sz; - - else - pSpace = GradedSpace.new(Z2(0, 1), [1 1], false); - vSpace = GradedSpace.new(Z2(1), 1, false); - trivSpace = one(pSpace); - - Sx_l = fill_matrix(Tensor([trivSpace pSpace], [pSpace vSpace]), {1 1}); - Sx_r = fill_matrix(Tensor([vSpace pSpace], [pSpace trivSpace]), {1 1}); - Sz = fill_matrix(Tensor([trivSpace pSpace], [pSpace trivSpace]), {1 -1}); - - cod = SumSpace([one(vSpace) vSpace one(vSpace)], pSpace); - dom = SumSpace(pSpace, [one(vSpace), vSpace, one(vSpace)]); - O = MpoTensor.zeros(cod, dom); - O(1, 1, 1, 1) = 1; - O(3, 1, 3, 1) = 1; - O(1, 1, 2, 1) = -J * Sx_l; - O(2, 1, 3, 1) = Sx_r; - O(1, 1, 3, 1) = (-J * h) * Sz; - end + rspace = subspaces(rightvspace(mpo, period(mpo)), size(Os{end}, 3)); + rightedge = MpsTensor(Tensor.eye([one(rspace) rspace'], one(rspace))); - mpo = InfJMpo(O); - end - - function mpo = Heisenberg(J, h, kwargs) - arguments - J = 1 - h = 0 - kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'U1', 'SU2'})} = 'Z1' - kwargs.Spin = SU2(3) - end + lspace = subspaces(leftvspace(mpo, 1), size(Os{1}, 1)); + leftedge = MpsTensor(Tensor.eye([one(lspace) lspace], one(lspace))'); - switch kwargs.Symmetry - case 'SU2' - assert(isscalar(J) || all(J == J(1)), ... - 'Different spin couplings not invariant under SU2'); - assert(h == 0, 'Magnetic field not invariant under SU2'); - - pSpace = GradedSpace.new(kwargs.Spin, 1, false); - vSpace = GradedSpace.new(SU2(3), 1, false); - tSpace = one(vSpace); - - s = spin(kwargs.Spin); - L = Tensor.ones([tSpace pSpace], [pSpace vSpace]); - L = L * (-J(1) * (s^2 + s)); - R = Tensor.ones([vSpace pSpace], [pSpace tSpace]); - - cod = SumSpace([one(vSpace) vSpace one(vSpace)], pSpace); - dom = SumSpace(pSpace, [one(vSpace), vSpace, one(vSpace)]); - O = MpoTensor.zeros(cod, dom); - O(1, 1, 1, 1) = 1; - O(3, 1, 3, 1) = 1; - O(1, 1, 2, 1) = L; - O(2, 1, 3, 1) = R; - - otherwise - error('TBA'); - end - mpo = InfJMpo(O); + finitempo = FiniteMpo(leftedge, Os, rightedge); end end end diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index 339d328..659462a 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -62,8 +62,16 @@ mpo.O = O_; end - function s = pspace(mpo, x) - s = pspace(mpo.O{x}); + function s = pspace(mpo, w) + s = pspace(mpo.O{w}); + end + + function s = leftvspace(mpo, w) + s = leftvspace(mpo.O{w}); + end + + function s = rightvspace(mpo, w) + s = rightvspace(mpo.O{w}); end function mpo = horzcat(varargin) @@ -269,34 +277,6 @@ end methods (Static) - function mpo = Ising(beta, kwargs) - arguments - beta = log(1 + sqrt(2)) / 2; - kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'Z2'})} = 'Z1' - end - - if strcmp(kwargs.Symmetry, 'Z1') - t = [exp(beta) exp(-beta); exp(-beta) exp(beta)]; - [v, d] = eig(t); - t = v * sqrt(d) * v; - - o = zeros(2, 2, 2, 2); - o(1, 1, 1, 1) = 1; - o(2, 2, 2, 2) = 1; - - o = contract(o, 1:4, t, [-1 1], t, [-2 2], t, [-3 3], t, [-4 4]); - - O = fill_tensor(Tensor.zeros([2 2 2 2]), o); - - else - s = GradedSpace.new(Z2(0, 1), [1 1], false); - O = fill_tensor(Tensor([s s], [s s]), ... - @(~, f) 2 * sqrt(prod(logical(f.uncoupled) .* sinh(beta) + ... - ~logical(f.uncoupled) .* cosh(beta)))); - end - - mpo = InfMpo(O); - end function mpo = fDimer() pspace = GradedSpace.new(fZ2(0, 1), [1 1], false); diff --git a/src/tensors/AbstractTensor.m b/src/tensors/AbstractTensor.m index 32287d9..791444d 100644 --- a/src/tensors/AbstractTensor.m +++ b/src/tensors/AbstractTensor.m @@ -255,7 +255,8 @@ x0_vec = vectorize(x0); sz = size(x0_vec); - + assert(sz(1) >= howmany); + if isa(A, 'function_handle') A_fun = @(x) vectorize(A(devectorize(x, x0))); else @@ -264,11 +265,19 @@ options.KrylovDim = min(sz(1), options.KrylovDim); - [V, D, flag] = eigs(A_fun, sz(1), howmany, sigma, ... - 'Tolerance', options.Tol, 'MaxIterations', options.MaxIter, ... - 'SubspaceDimension', options.KrylovDim, 'IsFunctionSymmetric', ... - options.IsSymmetric, 'StartVector', x0_vec, ... - 'Display', options.Verbosity >= 3); + if howmany > sz(1) - 2 + [V, D, flag] = eigs(A_fun, sz(1), howmany, sigma, ... + 'Tolerance', options.Tol, 'MaxIterations', options.MaxIter, ... + 'IsFunctionSymmetric', ... + options.IsSymmetric, 'StartVector', x0_vec, ... + 'Display', options.Verbosity >= 3); + else + [V, D, flag] = eigs(A_fun, sz(1), howmany, sigma, ... + 'Tolerance', options.Tol, 'MaxIterations', options.MaxIter, ... + 'SubspaceDimension', options.KrylovDim, 'IsFunctionSymmetric', ... + options.IsSymmetric, 'StartVector', x0_vec, ... + 'Display', options.Verbosity >= 3); + end if nargout <= 1 varargout = {D}; diff --git a/test/TestAlgorithms.m b/test/TestAlgorithms.m index 31864a0..7c85e68 100644 --- a/test/TestAlgorithms.m +++ b/test/TestAlgorithms.m @@ -2,7 +2,6 @@ % Unit tests for algorithms properties (TestParameter) - unitcell = {1, 2, 3, 4} alg = {Vumps('which', 'smallestreal', 'maxiter', 5), ... ...IDmrg('which', 'smallestreal', 'maxiter', 5), ... Vumps2('which', 'smallestreal', 'maxiter', 6), ... @@ -11,52 +10,59 @@ symm = {'Z1', 'Z2'} end methods (Test) - function test2dIsing(tc, alg, unitcell, symm) + function test2dIsing(tc, alg, symm) alg.which = 'largestabs'; - tc.assumeTrue(unitcell > 1 || isa(alg, 'IDmrg') || isa(alg, 'Vumps')) - - E0 = 2.5337 ^ unitcell; - if strcmp(symm, 'Z1') - mpo1 = InfMpo.Ising(); - mps1 = initialize_mps(mpo1, CartesianSpace.new(12)); - else - mpo1 = InfMpo.Ising('Symmetry', 'Z2'); - mps1 = initialize_mps(mpo1, GradedSpace.new(Z2(0, 1), [6 6], false)); + for unitcell = 1:4 + if unitcell == 1 && (isa(alg, 'Vumps2') || isa(alg, 'IDmrg2')) + continue; + end + + E0 = 2.5337 ^ unitcell; + mpo1 = statmech2dIsing('Symmetry', symm); + + if strcmp(symm, 'Z1') + mps1 = initialize_mps(mpo1, CartesianSpace.new(12)); + else + mps1 = initialize_mps(mpo1, GradedSpace.new(Z2(0, 1), [6 6], false)); + end + + mpo = mpo1; + mps = mps1; + for i = 2:unitcell + mpo = [mpo mpo1]; + mps = [mps mps1]; + end + + [gs, lambda] = fixedpoint(alg, mpo, mps); + tc.verifyEqual(expectation_value(gs, mpo, gs), E0, 'RelTol', 1e-2); end - - mpo = mpo1; - mps = mps1; - for i = 2:unitcell - mpo = [mpo mpo1]; - mps = [mps mps1]; - end - - [gs, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyEqual(expectation_value(gs, mpo, gs), E0, 'RelTol', 1e-2); end - function test1dIsing(tc, unitcell, alg, symm) + function test1dIsing(tc, alg, symm) alg.which = 'smallestreal'; - tc.assumeTrue(unitcell > 1 || isa(alg, 'IDmrg') || isa(alg, 'Vumps')) - E0 = -1.273 * unitcell; - - if strcmp(symm, 'Z1') - mpo1 = InfJMpo.Ising(); - mps1 = initialize_mps(mpo1, CartesianSpace.new(12)); - else - mpo1 = InfJMpo.Ising('Symmetry', 'Z2'); - mps1 = initialize_mps(mpo1, GradedSpace.new(Z2(0, 1), [6 6], false)); - end - - mpo = mpo1; - mps = mps1; - for i = 2:unitcell - mpo = [mpo mpo1]; - mps = [mps mps1]; + for unitcell = 1:4 + if unitcell == 1 && (isa(alg, 'IDmrg2') || isa(alg, 'Vumps2')) + continue; + end + E0 = -1.273 * unitcell; + + mpo1 = quantum1dIsing('Symmetry', symm); + if strcmp(symm, 'Z1') + mps1 = initialize_mps(mpo1, CartesianSpace.new(12)); + else + mps1 = initialize_mps(mpo1, GradedSpace.new(Z2(0, 1), [6 6], false)); + end + + mpo = mpo1; + mps = mps1; + for i = 2:unitcell + mpo = [mpo mpo1]; + mps = [mps mps1]; + end + + [gs, lambda] = fixedpoint(alg, mpo, mps); + tc.verifyEqual(expectation_value(gs, mpo), E0, 'RelTol', 1e-2); end - - [gs, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyEqual(expectation_value(gs, mpo), E0, 'RelTol', 1e-2); end end end diff --git a/test/TestFiniteMpo.m b/test/TestFiniteMpo.m index 4e895c5..ebb54ed 100644 --- a/test/TestFiniteMpo.m +++ b/test/TestFiniteMpo.m @@ -43,26 +43,26 @@ function testTransposes(tc, mpo) end function test2dIsing(tc) - L = 10; - D = 20; + L = 8; + D = 64; alg = Dmrg('maxiter', 10, 'which', 'largestabs'); - mpo = statmech2DIsing('beta', 2, 'L', L); + mpo = statmech2dIsing('beta', 2, 'L', L); vspace_max = CartesianSpace.new(D); mps = initialize_mps(mpo, 'MaxVspace', vspace_max); [mps, envs, eta] = fixedpoint(alg, mpo, mps); - mpo = statmech2DIsing('beta', 2, 'L', L, 'Symmetry', 'Z2'); + mpo = statmech2dIsing('beta', 2, 'L', L, 'Symmetry', 'Z2'); vspace_max = GradedSpace.new(Z2(0, 1), D ./ [2 2], false); mps = initialize_mps(mpo, 'MaxVspace', vspace_max); [mps, envs, eta] = fixedpoint(alg, mpo, mps); end function test1dIsing(tc) - L = 20; - D = 40; - alg = Dmrg('maxiter', 10, 'which', 'smallestreal'); + L = 8; + D = 64; + alg = Dmrg('miniter', 2, 'maxiter', 5, 'which', 'smallestreal'); mpo = quantum1dIsing('L', L); vspace_max = CartesianSpace.new(D); From 7588be2c184adee4ad012495872964eb897a8b03 Mon Sep 17 00:00:00 2001 From: leburgel Date: Wed, 22 Feb 2023 16:30:42 +0100 Subject: [PATCH 143/245] Multi-site peps, some tests, `GradedSpace` mysteries --- src/mps/FiniteMpo.m | 8 +- src/mps/MpsTensor.m | 4 + src/mps/PepsSandwich.m | 65 ++++++++++++- src/sparse/spblkdiag.m | 81 +++++++++++++++++ src/tensors/FusionTree.m | 2 +- test/TestFiniteMpo.m | 22 ++++- test/TestPeps.m | 191 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 363 insertions(+), 10 deletions(-) create mode 100644 src/sparse/spblkdiag.m create mode 100644 test/TestPeps.m diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index f458fed..c875c99 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -92,10 +92,12 @@ end for d = 1:depth(mpo) - mpo(d).L = mpo(d).L'; + mpo(d).L = tpermute(mpo(d).L', ... + [1, flip(2:nspaces(mpo(d).L)-1), nspaces(mpo(d).L)]); mpo(d).O = cellfun(@ctranspose, mpo(d).O, ... 'UniformOutput', false); - mpo(d).R = mpo(d).R'; + mpo(d).R = tpermute(mpo(d).R', ... + [1, flip(2:nspaces(mpo(d).R)-1), nspaces(mpo(d).R)]); end end @@ -183,7 +185,7 @@ function t = Tensor(mpo) assert(depth(mpo) == 1, 'not implemented for 1 < depth'); N = length(mpo); - inds = arrayfun(@(x) [x -(x+1) (x+1) -(N+x+3)], 1:N, ... + inds = arrayfun(@(x) [x -(x+1) (x+1) -(2*N+4-x)], 1:N, ... 'UniformOutput', false); args = [mpo.O; inds]; t = contract(mpo.L, [-1 1 -(2*N+4)], args{:}, mpo.R, [-(N+3) N+1 -(N+2)], ... diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index b637937..1e19d10 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -189,6 +189,10 @@ t = permute(t, ndims(t):-1:1); end + function A = tpermute(A, varargin) + A.var = tpermute(A.var, varargin{:}); + end + function C = tensorprod(varargin) for i = 1:2 if isa(varargin{i}, 'MpsTensor') diff --git a/src/mps/PepsSandwich.m b/src/mps/PepsSandwich.m index cfb15b5..e58f3bc 100644 --- a/src/mps/PepsSandwich.m +++ b/src/mps/PepsSandwich.m @@ -37,7 +37,6 @@ end function T = transpose(T) - % TODO: test if this is correct when inserted into FiniteMpo top = T.top; bot = T.bot; T.top = tpermute(bot, [1, 4, 5, 2, 3], rank(bot)); @@ -46,8 +45,8 @@ function T = ctranspose(T) % TODO: test if this is correct when inserted into FiniteMpo - T.top = conj(tpermute(T.top, [1, 4, 5, 2, 3], rank(T.top))); - T.bot = conj(tpermute(T.bot, [1, 4, 5, 2, 3], rank(T.bot))); + T.top = tpermute(conj(T.top), [1, 2, 5, 4, 3], rank(T.top)); + T.bot = tpermute(conj(T.bot), [1, 2, 5, 4, 3], rank(T.bot)); end function s = pspace(T) @@ -92,6 +91,66 @@ R, [7, 8, 9, -4, (-(1:auxlegs_r) - 4 - auxlegs_l - auxlegs_v)], ... 'Rank', newrank); end + + function v = applympo(varargin) + % lolz + assert(nargin >= 3) + v = varargin{end}; + R = varargin{end-1}; + L = varargin{end-2}; + O = varargin(1:end-3); + W = length(O); + + auxlegs_v = nspaces(v) - (2*W+2); + if isa(L, 'MpsTensor') + auxlegs_l = L.alegs; + else + auxlegs_l = 0; + end + if isa(R, 'MpsTensor') + auxlegs_r = R.alegs; + else + auxlegs_r = 0; + end + auxlegs = auxlegs_v + auxlegs_l + auxlegs_r; + + inds_top = arrayfun(@(x) [ 5 + 5*(x-1), ... + 2 + 5*(x-1), ... + -(2 + 2*(x-1)), ... + 2 + 5*x, ... + 3 + 5*(x-1)], ... + 1:W, 'UniformOutput', false); + inds_bot = arrayfun(@(x) [ 5 + 5*(x-1), ... + 4 + 5*(x-1), ... + -(3 + 2*(x-1)), ... + 4 + 5*x, ... + 6 + 5*(x-1)], ... + 1:W, 'UniformOutput', false); + tops = cellfun(@(x) x.top.var, O, 'UniformOutput', false); + bots = cellfun(@(x) x.bot.var, O, 'UniformOutput', false); + Oargs = [tops; inds_top; bots; inds_bot]; + v = contract(v, [1, reshape([3, 6]' + 5*(0:W-1), 1, []), 3 + 5*W, (-(1:auxlegs_v) - (2*W+2) - auxlegs_l)], ... + L, [-1, 4, 2, 1, (-(1:auxlegs_l) - (2*W+2))], ... + Oargs{:}, ... + R, [3 + 5*W, 2 + 5*W, 4 + 5*W, -(2*W + 2), (-(1:auxlegs_r) - (2*W+2) - auxlegs_l - auxlegs_v)], ... + 'Rank', rank(v) + [0 auxlegs]); + end + + function t = MpoTensor(T) + fuse_east = Tensor.eye(prod(leftvspace(T)), leftvspace(T)); + fuse_south = Tensor.eye(prod(codomainspace(T)), codomainspace(T)); + fuse_west = Tensor.eye(prod(rightvspace(T))', rightvspace(T)); + fuse_north = Tensor.eye(prod(domainspace(T))', domainspace(T)); + t = MpoTensor(contract(... + T.top.var, [1, 2, 4, 6, 8], ... + T.bot.var, [1, 3, 5, 7, 9], ... + fuse_east, [-1, 3, 2], ... + fuse_south, [-2, 5, 4], ... + fuse_west, [-3, 6, 7], ... + fuse_north, [-4, 8, 9], ... + 'Rank', [2, 2])); + % TODO: test properly + end end end diff --git a/src/sparse/spblkdiag.m b/src/sparse/spblkdiag.m new file mode 100644 index 0000000..05cebb4 --- /dev/null +++ b/src/sparse/spblkdiag.m @@ -0,0 +1,81 @@ +function y = spblkdiag(varargin) +%SPBLKDIAG Sparse block diagonal concatenation of matrix input arguments. +% +% |A 0 .. 0| +% Y = SPBLKDIAG(A,B,...) produces |0 B .. 0| +% |0 0 .. | + +if nargin == 0 + y = []; +else + checkAllAreMatrices(varargin); % Inputs must be all 2-dimensional + X = varargin{1}; + clsX = class(X); + if isobject(X) || ~isnumeric(X) || ~hasSameClass(varargin) + y = X; + if ~strcmp(clsX, 'logical') %#ok + clsX = 'double'; + end + for k = 2:nargin + x = varargin{k}; + [p1,m1] = size(y); + [p2,m2] = size(x); + y = [y zeros(p1,m2,clsX); zeros(p2,m1,clsX) x]; %#ok + end + else + if hasSparse(varargin) + y = matlab.internal.math.blkdiag(varargin{:}); % for sparse double + else + nz = sum(cellfun(@numel, varargin)); + [p, q] = getIndexVectors(varargin); + y = spalloc(p(end), q(end), nz); %Preallocate + for k = 1:nargin + y(p(k)+1:p(k+1),q(k)+1:q(k+1)) = varargin{k}; + end + end + end +end + +% Helper functions + +% check if cellX contains all matrices. +function checkAllAreMatrices(cellX) +for i = 1:numel(cellX) + if ~ismatrix(cellX{i}) + throwAsCaller(MException(message('MATLAB:blkdiag:inputMustBe2D'))); + end +end + +% check if cellX contains all matrices of same class classA. +function tf = hasSameClass(cellX) +clsA = class(cellX{1}); +for i = 2:numel(cellX) + if ~strcmp(clsA, class(cellX{i})) + tf = false; + return + end +end +tf = true; + +% check if cellX contains all sparse matrices. +function tf = hasSparse(cellX) +for i = 1:numel(cellX) + if issparse(cellX{i}) + tf = true; + return + end +end +tf = false; + +% compute the index vector from each matrix. +function [p, q] = getIndexVectors(cellX) +numEl = numel(cellX); +p = zeros(1, numEl+1); +q = zeros(1, numEl+1); +for i = 1:numEl + x = cellX{i}; + p(i+1) = size(x,1); + q(i+1) = size(x,2); +end +p = cumsum(p); +q = cumsum(q); diff --git a/src/tensors/FusionTree.m b/src/tensors/FusionTree.m index bf3c476..91cf0dc 100644 --- a/src/tensors/FusionTree.m +++ b/src/tensors/FusionTree.m @@ -344,7 +344,7 @@ f.charges = vertcat(newcharges{:}); f.isdual(i:i + 1) = f.isdual([i + 1 i]); - c = sparse(blkdiag(blocks{ic2})); + c = spblkdiag(blocks{ic2}); % TODO: make sure this doesn't slow down [f, p] = sort(f); c = c(invperm(order), p); return diff --git a/test/TestFiniteMpo.m b/test/TestFiniteMpo.m index 8fcdc92..626d188 100644 --- a/test/TestFiniteMpo.m +++ b/test/TestFiniteMpo.m @@ -2,11 +2,27 @@ % Unit tests for finite matrix product operators. properties (TestParameter) - mpo = struct('trivial0', ... - FiniteMpo.randnc(ComplexSpace.new(2, false, 3, false), ComplexSpace.new(4, false)), ... + mpo = struct(... + 'trivial0', ... + FiniteMpo.randnc(ComplexSpace.new(2, false, 3, false), ComplexSpace.new(4, false)), ... 'trivial1', ... FiniteMpo.randnc(ComplexSpace.new(2, false, 4, true, 2, true), ... - ComplexSpace.new(2, true, 3, false)) ... + ComplexSpace.new(2, true, 3, false)), ... + 'trivial2', ... + FiniteMpo.randnc(ComplexSpace.new(2, false, 4, true, 4, false, 2, true), ... + ComplexSpace.new(2, true, 3, false, 2, false)), ... + 'U1_0', ... + FiniteMpo.randnc(... + GradedSpace.new(U1(0, 1, -1), [1, 1, 1], false, U1(0, 1, -1), [1, 2, 2], false), ... + GradedSpace.new(U1(0, 1, -1), [2, 2, 2], false)), ... + 'U1_1', ... + FiniteMpo.randnc(... + GradedSpace.new(U1(0, 1, -1), [1, 1, 1], false, U1(0, 1, -1), [1, 2, 2], true, U1(0, 1, -1), [1, 1, 1], false), ... + GradedSpace.new(U1(0, 1, -1), [1, 1, 1], true, U1(0, 1, -1), [2, 2, 2], false)), ... + 'U1_2', ... + FiniteMpo.randnc(... + GradedSpace.new(U1(0, 1, -1), [1, 1, 1], false, U1(0, 1, -1), [1, 2, 2], true, U1(0, 1, -1), [1, 2, 2], false, U1(0, 1, -1), [1, 1, 1], false), ... + GradedSpace.new(U1(0, 1, -1), [1, 1, 1], true, U1(0, 1, -1), [2, 2, 2], false, U1(0, 1, -1), [1, 1, 1], true)) ... ) end diff --git a/test/TestPeps.m b/test/TestPeps.m new file mode 100644 index 0000000..02a9089 --- /dev/null +++ b/test/TestPeps.m @@ -0,0 +1,191 @@ +classdef TestPeps < matlab.unittest.TestCase + % Unit tests for PEPS transfer matrices. + + properties (TestParameter) + spaces = struct(... + 'cartesian', CartesianSpace.new([2, 3, 4, 5]), ... + 'complex', ComplexSpace.new(2, false, 3, false, 4, false, 5, false), ... + 'Z2', GradedSpace.new(Z2(0, 1), [1 1], false, Z2(0, 1), [1 2], false, ... + Z2(0, 1), [2 1], false, Z2(0, 1), [3 3], false), ... + 'fZ2', GradedSpace.new(fZ2(0, 1), [1 1], false, fZ2(0, 1), [1 2], false, ... + fZ2(0, 1), [2 1], false, fZ2(0, 1), [5 5], false), ... + 'U1', GradedSpace.new(U1(0, 1, -1), [1 1 1], false, U1(0, 1, -1), [1 2 2], false, ... + U1(0, 1, -1), [2 1 1], false, U1(0, 1, -1), [2 1 1], false), ... + 'SU2', GradedSpace.new(SU2(1, 2), [1 1], false, SU2(1, 2), [2 1], false, ... + SU2(1, 2), [1 1], false, SU2(1, 2), [2 1], false) ... + ) + depth = {1} + width = {1, 2} + dualdepth = {false, true} + dualwidth = {false, true} + samebot = {true, false} + end + + methods (Test, ParameterCombination='exhaustive') + + function testFiniteMpo(tc, spaces, depth, width, dualdepth, dualwidth, samebot) + tc.assumeTrue(braidingstyle(spaces) ~= BraidingStyle.Fermionic, 'fZ2 test broken') + tc.assumeTrue(depth == 1, 'Test ill-defined for multiline PEPS.') + tc.assumeTrue(width < 3, 'FiniteMpo -> Tensor contraction not reasonable.') + + mpo = tc.random_mpo(spaces, depth, width, dualdepth, dualwidth, samebot); + + % test domain and codomain + mpo_tensor = tc.mpo_to_tensor(mpo); + tc.assertTrue(isequal(mpo.domain, mpo_tensor.domain), ... + 'domain should remain fixed after conversion.'); + tc.assertTrue(isequal(mpo.codomain, mpo_tensor.codomain), ... + 'codomain should remain fixed after conversion.'); + + % test apply + v = initialize_fixedpoint(mpo); + tc.assertTrue(isapprox(mpo.apply(v), mpo_tensor * v)); + + % test transpose + tc.assertTrue(isapprox(tc.mpo_to_tensor(mpo).', tc.mpo_to_tensor(mpo.')), ... + 'transpose should not change mpo'); + tc.assertTrue(isequal(domain(mpo.'), codomain(mpo)')); + tc.assertTrue(isequal(codomain(mpo.'), domain(mpo)')); + + % test ctranspose + tc.assertTrue(isequal(domain(mpo'), codomain(mpo))); + tc.assertTrue(isequal(codomain(mpo'), domain(mpo))); + % GradedSpace: something broken here + tc.assumeTrue(~isa(spaces, 'GradedSpace'), 'ctranspose broken for GradedSpaces.') + tc.assertTrue(isapprox(tc.mpo_to_tensor(mpo)', tc.mpo_to_tensor(mpo')), ... + 'ctranspose should not change mpo'); + end + + function testFixedpoints(tc, spaces, depth, width, dualdepth, dualwidth, samebot) + tc.assumeTrue(braidingstyle(spaces) ~= BraidingStyle.Fermionic, 'fZ2 test broken') + + mpo = tc.random_mpo(spaces, depth, width, dualdepth, dualwidth, samebot); + + [V, D] = eigsolve(mpo); + tc.assertTrue(isapprox(mpo.apply(V), D * V)); + + v2 = insert_onespace(V); + v3 = apply(mpo, v2); + end + + function testDerivatives(tc, spaces, depth, width, dualdepth, dualwidth, samebot) + tc.assumeTrue(braidingstyle(spaces) ~= BraidingStyle.Fermionic, 'fZ2 test broken') + + mpo = tc.random_inf_mpo(spaces, depth, width, dualdepth, dualwidth, samebot); + vspaces = repmat({spaces(end)}, 1, width); + mps = mpo.initialize_mps(vspaces{:}); + + [GL, GR] = environments(mpo, mps, mps); + + H_AC = AC_hamiltonian(mpo, mps, GL, GR); + for i = 1:numel(H_AC) + AC_ = mps.AC(i); + [AC_.var, lambda] = eigsolve(H_AC{i}, mps.AC(i).var, 1, 'largestabs'); + AC_2 = apply(H_AC{i}, AC_); + tc.assertTrue(isapprox(AC_2, lambda * AC_.var)); + end + + H_C = C_hamiltonian(mpo, mps, GL, GR); + for i = 1:numel(H_C) + [C_, lambda] = eigsolve(H_C{i}, mps.C(i), 1, 'largestabs'); + tc.assertTrue(isapprox(apply(H_C{i}, C_), lambda * C_)); + end + end + + function testMpoTensor(tc, spaces, dualdepth, dualwidth) + tc.assumeTrue(braidingstyle(spaces) ~= BraidingStyle.Fermionic, 'fZ2 test broken') + + pspace = spaces(1); horzspace = spaces(2); vertspace = spaces(3); + top = TestPeps.random_peps(pspace, horzspace, vertspace, 1, 1, dualdepth, dualwidth); + bot = TestPeps.random_peps(pspace', vertspace, horzspace, 1, 1, dualdepth, dualwidth); + O = PepsSandwich(top{1}, bot{1}); + t = MpoTensor(O); + end + +end + + methods (Static) + function A = random_peps(pspace, horzspace, vertspace, depth, width, dualdepth, dualwidth) + % spit out random peps unit cell + A = cell(depth, width); + if dualwidth, horzspace = horzspace'; end + if dualdepth, vertspace = vertspace'; end + + if mod(depth, 2) + vertspaces = [vertspace vertspace']; + else + vertspaces = [vertspace vertspace]; + end + + if mod(width, 2) + horzspaces = [horzspace horzspace']; + else + horzspaces = [horzspace horzspace]; + end + + codomainspaces = reshape([vertspaces; horzspaces], 1, []); + + for d = 1:depth + for w = 1:width + dmsp = pspace; + cdmsp = codomainspaces; + if ~mod(depth, 2) && ~mod(d, 2), cdmsp = conj(cdmsp); dmsp = conj(dmsp); end + if ~mod(width, 2) && ~mod(w, 2), cdmsp = conj(cdmsp); dmsp = conj(dmsp); end + A{d, w} = Tensor.randnc(dmsp, cdmsp); + end + end + end + + function T = random_sandwich(pspace, horzspace, vertspace, depth, width, dualdepth, dualwidth, samebot) + top = TestPeps.random_peps(pspace, horzspace, vertspace, depth, width, dualdepth, dualwidth); + if samebot + bot = cellfun(@conj, top, 'UniformOutput', false); + else + bot = TestPeps.random_peps(pspace', horzspace, vertspace, depth, width, dualdepth, dualwidth); + end + T = cellfun(@(t, b) PepsSandwich(t, b), top, bot, 'UniformOutput', false); + end + + function mpo = random_mpo(spaces, depth, width, dualdepth, dualwidth, samebot) + pspace = spaces(1); horzspace = spaces(2); vertspace = spaces(3); vspace = spaces(4); + O = TestPeps.random_sandwich(pspace, horzspace, vertspace, depth, width, dualdepth, dualwidth, samebot); + for d = depth:-1:1 + L = MpsTensor(Tensor.randnc([vspace leftvspace(O{d, 1})'], vspace)); + R = MpsTensor(Tensor.randnc([vspace rightvspace(O{d, end})'], vspace)); + mpo(d, 1) = FiniteMpo(L, O(d, :), R); + end + end + + function mpo = random_inf_mpo(spaces, depth, width, dualdepth, dualwidth, samebot) + pspace = spaces(1); horzspace = spaces(2); vertspace = spaces(3); + O = TestPeps.random_sandwich(pspace, horzspace, vertspace, depth, width, dualdepth, dualwidth, samebot); + mpo = InfMpo(O); + end + + function t = mpo_to_tensor(mpo) + % contract FiniteMpo{PepsSandwich} to tensor... + assert(depth(mpo) == 1, 'not implemented for 1 < depth'); + W = length(mpo); + inds_top = arrayfun(@(x) [ 3 + 3*(x-1), ... + 1 + 3*(x-1), ... + -(2 + 2*(x-1)), ... + 1 + 3*x, ... + -(4*W + 5 - 2*x)], ... + 1:W, 'UniformOutput', false); + inds_bot = arrayfun(@(x) [ 3 + 3*(x-1), ... + 2 + 3*(x-1), ... + -(3 + 2*(x-1)), ... + 2 + 3*x, ... + -(4*W + 4 - 2*x)], ... + 1:W, 'UniformOutput', false); + tops = cellfun(@(x) x.top.var, mpo.O, 'UniformOutput', false); + bots = cellfun(@(x) x.bot.var, mpo.O, 'UniformOutput', false); + args = [tops; inds_top; bots; inds_bot]; + t = contract(mpo.L, [-1, 2, 1, -(4*W + 4)], args{:}, mpo.R, [-(2*W + 3), 1 + 3*W, 2 + 3*W, -(2 + 2*W)], ... + 'Rank', [2*W+2, 2*W+2]); + + end + + end +end + From 4abdc827f578749e4f0d9504f3679ab5f9862e50 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 22 Feb 2023 20:50:16 +0100 Subject: [PATCH 144/245] add contraction sequence check --- src/tensors/AbstractTensor.m | 25 + src/utility/linalg/contract.m | 17 + src/utility/linalg/contractcost.m | 73 ++ src/utility/netcon.m | 1308 +++++++++++++++++++++++++++++ 4 files changed, 1423 insertions(+) create mode 100644 src/utility/linalg/contractcost.m create mode 100644 src/utility/netcon.m diff --git a/src/tensors/AbstractTensor.m b/src/tensors/AbstractTensor.m index 1c1a08f..d8bc720 100644 --- a/src/tensors/AbstractTensor.m +++ b/src/tensors/AbstractTensor.m @@ -302,9 +302,27 @@ kwargs.Conj (1, :) logical = false(size(tensors)) kwargs.Rank = [] kwargs.Debug = false + kwargs.CheckOptimal = false end + assert(length(kwargs.Conj) == length(tensors)); + if kwargs.CheckOptimal + legcosts = zeros(2, 0); + for i = 1:length(indices) + legcosts = [legcosts [indices{i}; dims(tensors{i})]]; + end + legcosts = unique(legcosts.', 'rows'); + + currentcost = contractcost(indices, legcosts); + [sequence, cost] = netcon(indices, 1, 1, currentcost, 1, legcosts); + + if cost < currentcost + warning('suboptimal contraction order.\n optimal: %s', ... + num2str(sequence)); + end + end + for i = 1:length(tensors) [i1, i2] = traceinds(indices{i}); tensors{i} = tensortrace(tensors{i}, i1, i2); @@ -441,6 +459,13 @@ iE = [1:length(i1) length(i1) + (length(i2):-1:1)]; B = tensorprod(A, E, iA, iE); end + + function sz = dims(t, inds) + sz = dims([t.codomain, t.domain']); + if nargin > 1 + sz = sz(inds); + end + end end end diff --git a/src/utility/linalg/contract.m b/src/utility/linalg/contract.m index 024b575..7a3c1b3 100644 --- a/src/utility/linalg/contract.m +++ b/src/utility/linalg/contract.m @@ -41,10 +41,27 @@ kwargs.Conj (1, :) logical = false(size(tensors)) kwargs.Rank = [] kwargs.Debug = false + kwargs.CheckOptimal = false end assert(length(kwargs.Conj) == length(tensors)); +if kwargs.CheckOptimal + legcosts = zeros(2, 0); + for i = 1:length(indices) + legcosts = [legcosts [indices{i}; size(tensors{i}, 1:length(indices{i}))]]; + end + legcosts = unique(legcosts.', 'rows'); + + currentcost = contractcost(indices, legcosts); + [sequence, cost] = netcon(indices, 1, 1, currentcost, 1, legcosts); + + if cost < currentcost + warning('suboptimal contraction order.\n optimal: %s', ... + num2str(sequence)); + end +end + for i = 1:length(tensors) [i1, i2] = traceinds(indices{i}); tensors{i} = tensortrace(tensors{i}, i1, i2); diff --git a/src/utility/linalg/contractcost.m b/src/utility/linalg/contractcost.m new file mode 100644 index 0000000..1a7b0b7 --- /dev/null +++ b/src/utility/linalg/contractcost.m @@ -0,0 +1,73 @@ +function cost = contractcost(indices, legCosts) + +cost = 0; + +allInds = horzcat(indices{:}); +numCont = max(allInds); + +table = zeros(numCont, 2); +for ii = 1:length(indices) + for jj = indices{ii}(indices{ii} > 0) + if table(jj, 1) == 0 + table(jj, 1) = ii; + else + table(jj, 2) = ii; + end + end +end + + +%% Do contractions +ctr = 1; +contlist = 1:numCont; + +while ~isempty(contlist) + i1 = table(contlist(1), 1); + i2 = table(contlist(1), 2); + + if i1 + i2 == 0 + contlist(1) = []; + continue; + else + assert(i1 ~= i2, 'Tensor:isOptimalContract', 'Traces are not implemented.'); + end + + labels1 = indices{i1}; + labels2 = indices{i2}; + + [pos1, pos2] = contractinds(labels1, labels2); + unc1 = 1:length(labels1); unc1(pos1) = []; + unc2 = 1:length(labels2); unc2(pos2) = []; + contracting = labels1(pos1); + + % cost is dim(unc1) * dim(pos1) * dim(unc2) + dims1 = zeros(1, length(unc1)); + for ii = 1:length(unc1) + label = labels1(unc1(ii)); + dims1(ii) = legCosts(legCosts(:, 1) == label, 2); + end + dims2 = zeros(1, length(pos1)); + for ii = 1:length(pos1) + label = labels1(pos1(ii)); + dims2(ii) = legCosts(legCosts(:, 1) == label, 2); + end + dims3 = zeros(1, length(unc2)); + for ii = 1:length(unc2) + label = labels2(unc2(ii)); + dims3(ii) = legCosts(legCosts(:, 1) == label, 2); + end + + cost = cost + prod([dims1 dims2 dims3]); + + % update remaining contractions + indices{i1} = [indices{i1}(unc1) indices{i2}(unc2)]; + indices(i2) = []; + table(table == i2) = i1; + table(table > i2) = table(table > i2) - 1; + contlist = contlist(~ismember(contlist, contracting)); + + ctr = ctr + 1; +end + + +end diff --git a/src/utility/netcon.m b/src/utility/netcon.m new file mode 100644 index 0000000..4e18b31 --- /dev/null +++ b/src/utility/netcon.m @@ -0,0 +1,1308 @@ +function [sequence, cost] = netcon(legLinks,verbosity,costType,muCap,allowOPs,legCosts) +% function [sequence cost] = netcon(legLinks,verbosity,costType,muCap,allowOPs,legCosts) +% Finds most efficient way to contract a tensor network +% v2.00 by Robert N. C. Pfeifer 2014 +% Contact: rpfeifer.public@gmail.com, robert.pfeifer@mq.edu.au +% +% Parameters: (defaults) +% - legLinks: Labelling of tensor indices. (required) +% - verbosity: 0: Quiet. 1: State final result. 2: State intermediate and final results. 3: Also display object sizes during pairwise contractions. (2) +% - costType: 1: Absolute value. 2: Multiple and power of chi. (2) +% - muCap: Initially restrict search to sequences having a cost of muCap (if costType==1) or O(X^muCap) (if costType==2). This value will increment +% automatically if required, and it is recommended that it be left at the default value of 1. (1) +% - allowOPs: Allow contraction sequences including outer products: 0/false: No. 1/true: Yes. (true) +% - legCosts: For costType==1: nx2 table. A row reading [a b] assigns a dimension of b to index a. Default: 2 for all legs. +% For costType==2: nx3 table. A row reading [a b c] assigns a dimension of bX^c to index a, for unspecified X. Default: 1X^1 for all legs. +arguments + legLinks + verbosity = 2 % 0: No displayed output. 1: Display final result. 2: Display progress reports. + costType = 2 % 1: Absolute value. 2: Multiple and power of chi. + muCap = 1 + allowOPs = 1 + legCosts = [] +end + +% Benchmarking examples +% --------------------- +% 3:1 1D MERA: +% tic;netcon({[-1 1 2 3],[2 4 5 6],[1 5 7 -3],[3 8 4 9],[6 9 7 10],[-2 8 11 12],[10 11 12 -4]},0,2,1,1);toc +% All in MATLAB, no OPs: 0.041s +% All in MATLAB, with OPs: 0.054s +% Using C++, no OPs: 0.0019s +% Using C++, with OPs: 0.0019s +% +% 9:1 2D MERA: +% tic;netcon({[1 4 5 6 7 8],[2 9 10 11 12 13],[3 14 15 16 17 18],[-6 9 23 24 25 26],[-5 5 19 20 21 22],[8 14 27 28 29 30],[12 15 31 32 33 34],[22 25 28 31 35 36 37 38],[-4 20 23 35 39 40 41 42],[42 36 37 38 43 44 45 46],[41 24 44 26 47 48],[19 40 21 43 49 50],[27 45 29 30 51 52],[46 32 33 34 53 54],[-2 -3 39 49 47 55],[4 50 6 7 51 56],[48 10 11 53 13 57],[52 54 16 17 18 58],[55 56 57 58 -1 1 2 3]},0,2,1,1);toc +% All in MATLAB, no OPs: 5.4s +% All in MATLAB, with OPs: 6.1s +% Using C++, no OPs: 0.066s +% Using C++, with OPs: 0.069s +% +% 4:1 2D MERA: +% tic;netcon({[64 72 73 19 22],[65 74 75 23 76],[66 77 20 79 28],[67 21 24 29 34],[68 25 78 35 80],[69 81 30 83 84],[70 31 36 85 86],[71 37 82 87 88],[-5 19 20 21 1 2 4 5],[22 23 24 25 3 26 6 27],[28 29 30 31 7 8 32 33],[34 35 36 37 9 38 39 40],[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18],[10 11 13 14 41 42 43 44],[12 26 15 27 47 48 49 50],[16 17 32 33 53 54 55 56],[18 38 39 40 60 61 62 63],[-2 -3 -4 41 89],[72 73 42 47 90],[74 75 48 76 45],[77 43 79 53 46],[44 49 54 60 51],[50 78 61 80 52],[81 55 83 84 57],[56 62 85 86 58],[63 82 87 88 59],[89 90 45 46 51 52 57 58 59 -1 64 65 66 67 68 69 70 71]},0,2,1,1);toc +% All in MATLAB, no OPs: 2486s (41.4m) +% All in MATLAB, with OPs: 3919.6s (65.3m) +% Using C++, no OPs: 36.12s +% Using C++, with OPs: 36.49s + + +% Changes in v2.00: +% ----------------- +% Changed algorithm to breadth-first search instead of dynamic programming. +% Made handling of subnetworks iterative, not recursive. +% Made displayed output more user-friendly for networks composed of disjoint subnetworks. +% Added warning, not error, for tensors or subnetworks of trivial dimension (i.e. just a number). +% Added more vigorous exclusion of unnecessary outer products. + +% Changes in v1.01: +% ----------------- +% Improved structure of code. +% Fixed bug returning wrong cost when trivial indices present and non-consecutive numbering of positive indices. +% Removed keepTriv option. +% Fixed omission of trailing zeros in sequence when network is disjoint and all contractions are traces. +% Corrected helptext. + + +% Check input data (and strip trivial indices from legLinks) +% ================ +if ~isempty(legCosts) + [trivialindices legCosts legLinks] = checkInputData(legLinks,verbosity,costType,muCap,allowOPs,legCosts); +else + [trivialindices legCosts legLinks] = checkInputData(legLinks,verbosity,costType,muCap,allowOPs); +end + +% Divide network into disjoint subnets +% ==================================== +subnetlist = zeros(1,numel(legLinks)); +subnetcounter = 1; +while any(subnetlist==0) + flags = findSubnetIncluding(legLinks,find(subnetlist==0,1,'first')); + subnetlist(flags) = subnetcounter; + subnetcounter = subnetcounter + 1; +end +subnetcounter = subnetcounter-1; + +% Evaluate contraction sequences for disjoint subnets +% =================================================== +sequence = cell(1,subnetcounter); +cost = cell(1,subnetcounter); +freelegs = cell(1,subnetcounter); +donetrace = false(1,subnetcounter); +for a=1:subnetcounter + if verbosity > 0 + disp(' '); + if subnetcounter > 1 + disp(['Subnet ' num2str(a) ':']); + end + if verbosity > 1 + disp(['Tensors: ' unpaddednum2str(find(subnetlist==a))]); + end + end + [sequence{a} cost{a} freelegs{a} donetrace(a)] = netcon_getsubnetcost(legLinks(subnetlist==a),verbosity,costType,muCap,legCosts,allowOPs); +end +donetrace = any(donetrace); + +% Perform outer products of subnets, add costs, and merge sequences +% ================================================================= +[sequence cost] = performOPs(sequence,cost,freelegs,legCosts,costType,verbosity); +sequence = [sequence trivialindices]; % Append tracing over trivial indices on final object + +% Display result +% ============== +if verbosity>0 + if ~isempty(trivialindices) && verbosity > 1 + disp(' '); + disp(['Restore summed trivial indices: ' unpaddednum2str(trivialindices)]); + end + disp(' '); + if verbosity > 1 && subnetcounter > 1 + disp('Entire network:'); + end + netcon_displayresult(sequence,cost,donetrace,costType); +end +end + +function [sequence cost] = performOPs(sequence,cost,freelegs,legCosts,costType,verbosity) +% Renumber freelegs by position and trim index label list off legCosts +for a=1:numel(freelegs) + for b=1:numel(freelegs{a}) + freelegs{a}(b) = find(legCosts(:,1)==freelegs{a}(b)); + end +end +legCosts = legCosts(:,2:end); +% Perform outer products +if costType==2 + tensormulsize = ones(size(freelegs)); + tensorchisize = zeros(size(freelegs)); + for a=1:numel(freelegs) + for b=1:numel(freelegs{a}) + tensormulsize(a) = tensormulsize(a)*legCosts(freelegs{a}(b),1); + tensorchisize(a) = tensorchisize(a)+legCosts(freelegs{a}(b),2); + end + end + if any(tensormulsize==1 & tensorchisize==0) && numel(freelegs)>1 + if sum(tensormulsize==1 & tensorchisize==0)==1 + if verbosity > 0 + disp(' '); + end + warning('netcon:trivialsubnet',['Subnet ' unpaddednum2str(find(tensormulsize==1 & tensorchisize==0)) ' reduces to a single number. Since this subnet is not the entire network, contraction sequence may not be optimal.']); + else + if verbosity > 0 + disp(' '); + end + warning('netcon:trivialsubnet',['More than one subnet reduces to a single number: Contraction sequence may not be optimal. List of subnets reducing to a single number: ' unpaddednum2str(find(tensormulsize==1 & tensorchisize==0))]); + end + end + while numel(tensorchisize)>1 + % Sort tensors + ptr = 1; + while (ptrtensorchisize(ptr+1) || (tensorchisize(ptr)==tensorchisize(ptr+1) && tensormulsize(ptr)>tensormulsize(ptr+1)) + t = tensorchisize(ptr+1); tensorchisize(ptr+1) = tensorchisize(ptr); tensorchisize(ptr) = t; + t = tensormulsize(ptr+1); tensormulsize(ptr+1) = tensormulsize(ptr); tensormulsize(ptr) = t; + t = sequence{ptr}; sequence{ptr} = sequence{ptr+1}; sequence{ptr+1} = t; + t = cost{ptr}; cost{ptr} = cost{ptr+1}; cost{ptr+1} = t; + ptr = ptr - 1; + if ptr==0 + ptr = 2; + end + else + ptr = ptr + 1; + end + end + % Combine smallest two tensors + outerproductmul = tensormulsize(1) * tensormulsize(2); + outerproductpower = tensorchisize(1) + tensorchisize(2); + if numel(cost{1})1 + % Sort tensors + tensorsize = sort(tensorsize,'ascend'); + % Combine smallest two tensors + outerproduct = tensorsize(1) * tensorsize(2); + cost{1} = cost{1} + outerproduct; + tensorsize = [outerproduct tensorsize(3:end)]; + cost{1} = cost{1} + cost{2}; + sequence{1} = [sequence{1} sequence{2}]; + cost = cost([1 3:end]); + sequence = sequence([1 3:end]); + end +end +sequence = sequence{1}; +sequence = [sequence zeros(1,numel(freelegs)-1)]; % Add outer products between disjoint subnets to the contraction sequence +cost = cost{1}; +end + +function [sequence cost negindices donetrace] = netcon_getsubnetcost(legLinks,verbosity,costType,muCap,legCosts,allowOPs) +% Performs any traces on single tensors +% Checks if network is trivial +% If the network is not trivial, invokes netcon_nondisj to evaluate cost for the network + +% Get index lists for this subnet and trim legCosts +% ================================================= +[posindices negindices] = getIndexLists(cell2mat(legLinks)); + +% Any contraction to do? +% ====================== +if numel(legLinks)<2 + % List of tensors has only one entry + % ================================== + if ~isempty(posindices) + % One tensor, with traces - do them, then go to "Finished contracting" + if costType==2 + cost = []; + else + cost = 0; + end + sequence = posindices; + donetrace = true; + if verbosity > 0 + disp('Tracing costs only'); + end + % Go to "Finished contracting" + else + % Nothing to do + sequence = []; + if costType==2 + cost = []; + else + cost = 0; + end + donetrace = false; + if verbosity > 0 + disp('Network is trivial'); + end + % One tensor, no traces - go to "Finished contracting" + end +else + % >1 tensor, tracing and/or contraction to do + % =========================================== + % Make positive indices consecutive + % Make negative indices consecutive following on from positive indices + % Truncate cost table and remove indexing column + % (Note original labelling for legs may be recovered from posindices and negindices) + [legLinks legCosts] = reprocessLegLinks(legLinks,posindices,negindices,legCosts); + + % Determine any traces (legs with both ends on a single tensor) and eliminate + % =========================================================================== + [tracedindices donetrace legLinks legCosts linkposindices] = eliminateTraces(legLinks,posindices,legCosts); + + if isempty(linkposindices) + % No positive indices after tracing + sequence = posindices(tracedindices); + if costType==2 + cost = []; + else + cost = 0; + end + else + % Find best sequence + % ================== + % Invoke sequence finder for traceless non-disjoint network + for a=1:numel(legLinks) + legLinks{a} = int32(abs(legLinks{a})); + end + legCosts = double(legCosts); + verbosity = double(verbosity); + costType = double(costType); + muCap = double(muCap); + allowOPs = double(allowOPs); + posindices = double(posindices); + tracedindices = int32(tracedindices); + [sequence cost] = netcon_nondisj(legLinks,legCosts,verbosity,costType,muCap,allowOPs,posindices,tracedindices); +% try +% [sequence cost] = netcon_nondisj_cpp(legLinks,legCosts,verbosity,costType,muCap,allowOPs,posindices,tracedindices); +% catch ME +% if isequal(ME.identifier,'MATLAB:UndefinedFunction') +% [sequence cost] = netcon_nondisj(legLinks,legCosts,verbosity,costType,muCap,allowOPs,posindices,tracedindices); +% else +% rethrow(ME); +% end +% end + % Reinsert indices traced at the beginning of the contraction process + sequence = [tracedindices sequence]; + % Translate sequence back into user's original leg labels + sequence(sequence>0) = posindices(sequence(sequence>0)); + end +end +end + +function [tracedindices donetrace legLinks legCosts posindices] = eliminateTraces(legLinks,posindices,legCosts) +tracedindices = []; +donetrace = false; +% For each tensor: +for a=1:numel(legLinks) + % - Find all tracing legs on that tensor, note the indices, and delete them. + % - Tracing can be performed over all traced indices on a tensor simultaneously by combining them - no need to sort + b = 1; + while b + legLinks{a}(legLinks{a}==legLinks{a}(b)) = []; + else + b = b + 1; + end + end +end +% Then: +if ~isempty(tracedindices) + donetrace = true; + % - Delete removed rows from leg cost list + legCosts(tracedindices,:) = []; + % - Renumber all positive and negative legs consecutively again + renumindices = 1:numel(posindices); + renumindices(tracedindices) = []; + posindices(tracedindices) = []; + for a=1:numel(legLinks) + for b=1:numel(legLinks{a}) + if legLinks{a}(b)>0 + legLinks{a}(b) = find(renumindices==legLinks{a}(b)); + else + legLinks{a}(b) = legLinks{a}(b) + numel(tracedindices); + end + end + end +end +end + +function [legLinks legCosts] = reprocessLegLinks(legLinks,posindices,negindices,legCosts) +% Renumber all positive and negative indices consecutively +% ======================================================== +for a=1:numel(legLinks) + for b=find(legLinks{a}>0) + legLinks{a}(b) = find(posindices==legLinks{a}(b)); + end + for b=find(legLinks{a}<0) + legLinks{a}(b) = -find(negindices==legLinks{a}(b))-numel(posindices); + end +end + +% Assemble truncated cost table +% ============================= +for a=size(legCosts,1):-1:1 + if ~any([posindices negindices]==legCosts(a,1)) + legCosts(a,:) = []; + end +end +legCosts = legCosts(:,2:end); +end + +function cost = addCosts(cost1,cost2,costType) +if costType==1 + cost = cost1 + cost2; +else + cost = zeros(1,max([numel(cost1) numel(cost2)])); + cost(1:numel(cost1)) = reshape(cost1,1,numel(cost1)); + cost(1:numel(cost2)) = cost(1:numel(cost2)) + reshape(cost2,1,numel(cost2)); +end +end + +function flags = findSubnetIncluding(legLinks,pos) +new = true; +flags = zeros(1,numel(legLinks)); % 0: May not be in subnet +flags(pos) = 1; % 1: Follow connections off this tensor +while new + new = false; + fromlist = find(flags==1); + flags(flags==1) = 2; % 2: Have followed all connections from this tensor + for checkfrom = fromlist + for checkto = find(flags==0) + for x = 1:numel(legLinks{checkfrom}) + if any(legLinks{checkto}==legLinks{checkfrom}(x)) + flags(checkto) = true; + new = true; + end + end + end + end +end +flags = flags~=0; +end + +function [trivialindices legCosts legLinks] = checkInputData(legLinks,verbosity,costType,muCap,allowOPs,legCosts) +% Check data sizes +if ~iscell(legLinks) + error('legLinks must be a cell array') +end +if numel(legLinks)==0 + error('legLinks may not be empty') +end +if ~isnumeric(verbosity) || ~any([0 1 2 3]==verbosity) + error('verbosity must be 0, 1, 2 or 3') +end +if ~isnumeric(muCap) || numel(muCap)~=1 || ~isreal(muCap) || muCap<=0 + error('muCap must be a real positive number'); +end +if ~isnumeric(costType) || ~any([1 2]==costType) + error('costType must be either 1 or 2') +end +if (~isnumeric(allowOPs) && ~islogical(allowOPs)) || ~any([0 1]==allowOPs) + error('allowOPs must be either 0 or 1'); +end +for a=1:numel(legLinks) + if ~isnumeric(legLinks{a}) + error('Entries in legLinks must be numeric arrays') + end +end +if size(legLinks,1)~=1 || size(legLinks,2)~=numel(legLinks) + error('Array of index connections (legLinks) has incorrect dimension - should be 1xn') +end +for a=1:numel(legLinks) + if size(legLinks{a},1)~=1 || size(legLinks{a},2)~=numel(legLinks{a}) + error(['legLinks entry ' num2str(a) ' has wrong dimension - should be 1xn']); + end + if isempty(legLinks{a}) + error(['Empty list of indices on tensor ' num2str(a)]) + end +end +allindices = cell2mat(legLinks); +if any(allindices==0) + error('Zero entry in index list') +elseif any(imag(allindices)~=0) + error('Complex entry in index list') +elseif any(int32(allindices)~=allindices) + error('Non-integer entry in legLinks'); +end +posindices = sort(allindices(allindices>0),'ascend'); +negindices = sort(allindices(allindices<0),'descend'); +if ~isempty(posindices) + % Test all positive indices occur exactly twice + if mod(numel(posindices),2)~=0 + maxposindex = posindices(end); + posindices = posindices(1:end-1); + end + flags = (posindices(1:2:numel(posindices))-posindices(2:2:numel(posindices)))~=0; + if any(flags) + errorpos = 2*find(flags~=0,1,'first')-1; + if errorpos>1 && posindices(errorpos-1)==posindices(errorpos) + error(['Error in index list: Index ' num2str(posindices(errorpos)) ' appears more than twice']); + else + error(['Error in index list: Index ' num2str(posindices(errorpos)) ' only appears once']); + end + end + if exist('maxposindex','var') + if posindices(end)==maxposindex + error(['Error in index list: Index ' num2str(maxposindex) ' appears more than twice']); + else + error(['Error in index list: Index ' num2str(maxposindex) ' only appears once']); + end + end + posindices = posindices(1:2:numel(posindices)); % List of all positive indices, sorted ascending, each appearing once. + flags = posindices(1:end-1)==posindices(2:end); + if any(flags) + errorpos = find(flags,1,'first'); + error(['Error in index list: Index ' num2str(posindices(errorpos)) ' appears more than twice']); + end +else + posindices = []; +end +% Test all negative indices occur exactly once +flags = negindices(1:end-1)==negindices(2:end); +if any(flags) + errorpos = find(flags,1,'first'); + error(['Error in index list: Index ' num2str(negindices(errorpos)) ' appears more than once']); +end + +% Check leg size data +trivialindices = []; +if ~exist('legCosts','var') + if costType==1 + % Create basic leg costs list (all 2) + legCosts = [[posindices.';negindices.'] 2*ones(numel(posindices)+numel(negindices),1)]; + else + % Create basic leg costs list (all Chi^1) + legCosts = [[posindices.';negindices.'] ones(numel(posindices)+numel(negindices),2)]; + end +else + if ~isnumeric(legCosts) + error('legCosts must be numeric') + end + % Check valid leg costs list, & process + if ndims(legCosts)>2 || any(legCosts(:,1)==0) || size(legCosts,2)~=2+(costType==2) || any(legCosts(:,2)<=0) + error('Bad index dimensions list (error in legCosts: type ''help netcon'' for specifications)') + end + if costType==2 + if any(legCosts(:,3)<0) + error('For index dimensions of the form aX^b, values of b less than 0 are not supported') + end + if any(legCosts(:,2)<=0) + error('For index dimensions of the form aX^b, values of a less than or equal to 0 are not supported') + end + else + if any(legCosts(:,2)<=0) + error('Index dimensions less than or equal to 0 are not supported') + end + end + [t1 ix1] = sort(legCosts(legCosts(:,1)>0,1)); + [t2 ix2] = sort(legCosts(legCosts(:,1)<0,1),'descend'); + t1 = reshape(t1,1,[]); + t2 = reshape(t2,1,[]); + flag = t1(1:end-1)==t1(2:end); + if any(flag) + error(['Dimension of index ' num2str(t1(find(flag,1))) ' specified twice']) + end + if ~(isempty(t1) && isempty(posindices)) % Necessary as a 1x0 matrix is not the same as a 0x0 matrix + if ~isequal(t1,posindices) + error(['Dimension not specified for all positive indices. Supplied: ' num2str(t1) ' Required: ' num2str(posindices)]) + end + end + flag = t2(1:end-1)==t2(2:end); + if any(flag) + error(['Dimension of index ' num2str(t2(find(flag,1))) ' specified twice']) + end + if ~(isempty(t2) && isempty(negindices)) + if ~isequal(t2,negindices) + error(['Dimension not specified for all negative indices. Supplied: ' num2str(t2) ' Required: ' num2str(negindices)]) + end + end + + % Store list of any summed trivial indices to be stripped + if costType==1 + trivialindices = legCosts(legCosts(:,2)==1,1); + else + trivialindices = legCosts(legCosts(:,2)==1 & legCosts(:,3)==0,1); + end + trivialindices = reshape(sort(trivialindices(trivialindices>0),'descend'),1,[]); + + % Strip trivial indices + for a=numel(trivialindices):-1:1 + for b=1:numel(legLinks) + if sum(legLinks{b}==trivialindices(a))==2 + trivialindices(a) = []; % Do not strip trivial single-tensor traces (not that this actually matters either way) + else + legLinks{b}(legLinks{b}==trivialindices(a)) = []; + end + end + end + if ~isempty(trivialindices) && verbosity > 1 + disp(' '); + disp(['Ignore summed trivial indices: ' unpaddednum2str(trivialindices)]); + end + + % Order leg costs list + t1 = legCosts(legCosts(:,1)>0,2:end); + t1 = t1(ix1,:); + t2 = legCosts(legCosts(:,1)<0,2:end); + t2 = t2(ix2,:); + legCosts = [[posindices.';negindices.'] [t1;t2]]; +end +end + +function [posindices negindices] = getIndexLists(allindices) +posindices = sort(allindices(allindices>0),'ascend'); +negindices = sort(allindices(allindices<0),'descend'); +posindices = posindices(1:2:end); +end + +function netcon_displayresult(sequence,cost,donetrace,costType) +% Displays nicely-formatted final output +t = ['Best sequence: ' unpaddednum2str(sequence)]; +if isempty(sequence) + t = [t '']; +end +disp(t); +if isempty(cost) || isequal(cost,0) + if donetrace + disp('Cost: Tracing costs only'); + else + disp('Cost: 0'); + end +else + t = 'Cost: '; + if costType==2 + for a=numel(cost):-1:2 + t = [t num2str(cost(a)) 'X^' num2str(a-1) ' + ']; %#ok + end + t = [t num2str(cost(1)) 'X^0']; + else + t = [t num2str(cost)]; + end + if donetrace + t = [t ' + tracing costs']; + end + disp(t); +end +end + +function str = unpaddednum2str(row) +str = []; +for a=row(1:end-1) + str = [str num2str(a) ' ']; %#ok +end +if ~isempty(row) + str = [str num2str(row(end))]; +end +end + +% Functions used in the pure-MATLAB version only: + +function [sequence cost] = netcon_nondisj(legLinks,legCosts,verbosity,costType,muCap,allowOPs,posindices,tracedindices) +% Set up initial data +% =================== +numtensors = numel(legLinks); +if costType==1 + zerocost = 0; +else + zerocost = []; +end +allowOPs = (allowOPs==1); + +% This code uses the nomenclature of Appendix G, in which a tensor which may be contracted with the result of an outer product [e.g. C in Fig.4(a)] is denoted X. + +% Structure of "objects": objects{numElements}{positionInList}{legFlags,tensorFlags,sequenceToBuild,costToBuild,isOP,OPmaxdim,allIn} +% legFlags: Legs present on object +% tensorFlags: Tensor constituents of object +% sequenceToBuild: Cheapest identified contraction sequence for constructing this object +% costToBuild: Minimum identified cost of constructing this object +% isOP: Indicates whether the contraction which yielded this object was an outer product +% OPmaxdim: If isOP==true, then OPmaxdim gives the dimension of the largest constituent tensor +% allIn: If this tensor is an object X which may be contracted with an outer product, allIn is the dimension of the tensor which contributed no external legs to X, i.e. xi_C in Fig.5(c). + +objects = cell(1,numtensors); +objects{1} = cell(1,numtensors); +tensorflags = false(1,numtensors); +numleglabels = size(legCosts,1); +legflags = false(1,numleglabels); + +% ### Create lists used in enforcing Sec. II.B.2.c (structure of tensor X contractable with an outer product) +tensorXlegs = {}; +tensorXflags = []; +tensorXdims = {}; +% ### End of creating lists + +for a=1:numtensors + tensorflags(a) = true; + legflags(abs(legLinks{a})) = true; + objects{1}{a} = {legflags,tensorflags,[],zerocost,false,[],zeros(1,costType)}; + + % ### Set up initial list data used in enforcing Sec. II.B.2.c + if allowOPs + [tensorXlegs tensorXdims tensorXflags] = addToTensorXlist(tensorXlegs,tensorXdims,tensorXflags,legflags,Inf*ones(1,costType),costType,true); + end + % ### End of setting up linked list data + + tensorflags(a) = false; + legflags(abs(legLinks{a})) = false; +end + +% ### Set up initial list data used in enforcing Sec. II.B.2.c (continued) +tensorXflags = zeros(1,numel(tensorXflags)); +% ### End of setting up initial linked list data (continued) + +for a=2:numtensors + objects{a} = {}; +end + +newobjectflags = cell(1,numtensors); +newobjectflags{1} = true(1,numtensors); + +oldmuCap = 0; +newmuCap = Inf; + +done = false; +while ~done + if verbosity>0 + if oldmuCap~=muCap + if costType==1 + disp(['Looking for solutions with maximum cost of ' num2str(muCap)]); + else + disp(['Looking for solutions of cost O(X^' num2str(muCap) ')']); + end + end + end + for numInObjects = 2:numtensors + if verbosity > 2 + disp(['Pairwise contractions (AB) involving ' num2str(numInObjects) ' fundamental tensors:']); + end + for numInPieceOne = 1:floor(numInObjects/2) + numInPieceTwo = numInObjects - numInPieceOne; + if verbosity > 2 + disp(['A contains ' num2str(numInPieceOne) ', B contains ' num2str(numInPieceTwo)'.']); + pause(0.01); + end + if numel(objects{numInPieceOne})>0 && numel(objects{numInPieceTwo})>0 + + % Iterate over pairings: Iterate over object 1 + for a = 1:numel(objects{numInPieceOne}) + + % Get data for object 1 + obj1data = objects{numInPieceOne}{a}; + % obj1data: legs, tensors, seqToBuild, costToBuild, isOP, maxdimOP, allIn + legs1 = obj1data{1}; + tensorsIn1 = obj1data{2}; + seq1 = obj1data{3}; + costToBuild1 = obj1data{4}; + isOP1 = obj1data{5}; + OPmaxdim1 = obj1data{6}; + allIn1 = obj1data{7}; + isnew1 = newobjectflags{numInPieceOne}(a); + + % Iterate over pairings: Iterate over object 2 + if numInPieceOne == numInPieceTwo + obj2list = a+1:numel(objects{numInPieceTwo}); + else + obj2list = 1:numel(objects{numInPieceTwo}); + end + for b = obj2list + + % Check object 1 and object 2 don't share any common tensors (which would then appear twice in the resulting network) + if ~any(objects{numInPieceOne}{a}{2} & objects{numInPieceTwo}{b}{2}) + + % Get data for object 2 + obj2data = objects{numInPieceTwo}{b}; + % obj2data: legs, tensors, seqToBuild, costToBuild, isOP, maxdimOP, allIn + legs2 = obj2data{1}; + tensorsIn2 = obj2data{2}; + seq2 = obj2data{3}; + costToBuild2 = obj2data{4}; + isOP2 = obj2data{5}; + OPmaxdim2 = obj2data{6}; + allIn2 = obj2data{7}; + isnew2 = newobjectflags{numInPieceTwo}(b); + + commonlegs = legs1 & legs2; % Common legs + freelegs = xor(legs1,legs2); + freelegs1 = legs1 & freelegs; + freelegs2 = legs2 & freelegs; + commonlegsflag = any(commonlegs); + + isOK = allowOPs || commonlegsflag; % Exclude outer products if allowOPs is not set + + thisTensorXflag = -1; + % ### Enforce Sec. II.B.2.b,c,d (only perform outer product if there is a known tensor X with appropriate structure; only contract resulting object in another outer product or with an appropriate tensor X; enforce index dimension constraints) + if isOK && ~commonlegsflag + % It's an outer product. Check if a suitable tensor X exists yet to contract with this outer product [Fig.5(c) & Eq.(25)]. + if oldmuCap == muCap + % Pass for new X's + if isnew1 || isnew2 + % Using a new object - allowed to contract with old and new X's + % Note - the while/end construction is faster than using "find" + Xstart = 1; + Xend = 1; + while Xend<=numel(tensorXflags) && tensorXflags(Xend)~=2; + Xend = Xend + 1; + end + else + % Made from old objects - only allowed to contract with new X's. Already had a chance to contract with old X's + % on the previous pass so repeating these is unnecessary. + Xstart = 1; + while Xstart<=numel(tensorXflags) && tensorXflags(Xstart)~=1; + Xstart = Xstart + 1; + end + Xend = Xstart; + while Xend<=numel(tensorXflags) && tensorXflags(Xend)==1; + Xend = Xend + 1; + end + end + else + % Old X's only on this pass + Xstart = 1; + Xend = 1; + while Xend<=numel(tensorXflags) && tensorXflags(Xend)==0; + Xend = Xend + 1; + end + end + for x = Xstart:Xend-1 + if all(tensorXlegs{x}(freelegs)) + % IIB2c: xi_C > xi_A (25) + if isGreaterThan_sd(tensorXdims{a},getProdLegDims(freelegs,legCosts,costType),costType) + % IIB2b: xi_C > xi_D && xi_C > xi_E: (16) + if isGreaterThan_sd(getProdLegDims(tensorXlegs{x}&~freelegs,legCosts,costType),getProdLegDims(freelegs1,legCosts,costType),costType) + if isGreaterThan_sd(getProdLegDims(tensorXlegs{x}&~freelegs,legCosts,costType),getProdLegDims(freelegs2,legCosts,costType),costType) + thisTensorXflag = tensorXflags(x); + break; + end + end + end + end + end + isOK = thisTensorXflag~=-1; + end + + % If either constituent is the result of an outer product, check that it is being contracted with an appropriate tensor + % [either this is a contraction over all indices, or this is an outer product with a tensor of larger total dimension than + % either constituent of the previous outer product, and Eqs. (16), (25), and (26) are satisfied]. + if isOK && (isOP1 || isOP2) + % Post-OP. This contraction only allowed if it is also an outer product, or if only one object is an outer product, it involves all indices on that tensor, and the other object satisfies the relevant conditions. + isOK = xor(isOP1,isOP2) || ~commonlegsflag; % If contracting over common indices, only one object may be an outer product + if isOK + if commonlegsflag + % This contraction is not itself an outer product + % Conditions on outer product object: + if isOP1 + % Check all-indices condition: + if isequal(commonlegs,legs1) + % Check free legs on contracting tensor are larger than summing legs going to each component of outer product [Eq. (16)] + isOK = isGreaterThan_sd(getProdLegDims(freelegs,legCosts,costType),OPmaxdim1,costType); % IIB2b: xi_C > xi_D, xi_C > xi_E (16) + else + isOK = false; + end + else + % Check all-indices condition: + if isequal(commonlegs,legs2) + % Check free legs on contracting tensor are larger than summing legs going to each component of outer + % product [Eq. (16)] + isOK = isGreaterThan_sd(getProdLegDims(freelegs,legCosts,costType),OPmaxdim2,costType); % IIB2b: xi_C > xi_D, xi_C > xi_E (16) + else + isOK = false; + end + end + % Conditions on X: Ensure X is fundamental or acceptably-constructed (note: structure is checked by requiring + % non-zero value of allIn) + if isOK + if isOP1 + % Tensor 2 is X + if numInPieceTwo > 1 + % Tensor 2 is not fundamental + % Check tensor 2 is constructed in an acceptable fashion [Fig. 5(c) and Eqs. (25) and (26)] + isOK = isGreaterThan_sd(allIn2,getProdLegDims(freelegs,legCosts,costType),costType); % IIB2c: xi_C > xi_D (26) + if isOK + isOK = isGreaterThan_sd(allIn2,getProdLegDims(freelegs1,legCosts,costType),costType); % IIB2c: xi_C > xi_A (25) + end + end + else + % Tensor 1 is X + if numInPieceOne > 1 + % Tensor 1 is not fundamental + % Check tensor 1 is constructed in an acceptable fashion [Fig. 5(c) and Eqs. (25) and (26)] + isOK = isGreaterThan_sd(allIn1,getProdLegDims(freelegs,legCosts,costType),costType); % IIB2c: xi_C > xi_D (26) + if isOK + isOK = isGreaterThan_sd(allIn1,getProdLegDims(freelegs2,legCosts,costType),costType); % IIB2c: xi_C > xi_A (25) + end + end + end + end + else + % This contraction is an outer product. If either constituent is an outer product, check that both tensors + % within that object are not larger than the third tensor with which they are now being contracted. + if isOP1 + isOK = ~isGreaterThan_sd(OPmaxdim1,getProdLegDims(freelegs2,legCosts,costType),costType); % IIB2b: xi_C >= xi_A, xi_C >= xi_B (20) + end + if isOK && isOP2 + isOK = ~isGreaterThan_sd(OPmaxdim2,getProdLegDims(freelegs1,legCosts,costType),costType); % IIB2b: xi_C >= xi_A, xi_C >= xi_B (20) + end + end + end + end + % ### End of enforcing Sec. II.B.2.b,c,d (only perform outer product if there is a known tensor X with appropriate structure; only contract resulting object in another outer product or with an appropriate tensor X; enforce index dimension constraints) + + % If contraction is not prohibited, check cost is acceptable (<=muCap and, if not involving new objects, >oldmuCap) + if isOK + % If constructing an outer product which may contract with a new X, do not exclude on basis of low cost: Hence + % isnew1||isnew2||thisTensorXflag>0 + [newCost isOK] = getBuildCost(freelegs,commonlegs,legCosts,costType,oldmuCap,muCap,isnew1||isnew2||thisTensorXflag>0,costToBuild1,costToBuild2); + if ~isOK + newmuCap = min([newmuCap newCost]); + end + end + + % If cost is OK, compare with previous best known cost for constructing this object + if isOK + % Get involved tensors + tensorsInNew = tensorsIn1 | tensorsIn2; + % Find if previously constructed + objectPtr = 0; + for x=1:numel(objects{numInObjects}) + if isequal(objects{numInObjects}{x}{2},tensorsInNew) + objectPtr = x; + break; + end + end + isnew = objectPtr==0; + if isnew + % Is a new construction + objectPtr = numel(objects{numInObjects})+1; + else + % Compare new cost with best-so-far cost for construction of this object + isOK = isLessThan(newCost,objects{numInObjects}{objectPtr}{4},costType); + end + + % ### If appropriate, update tensorXlist (list of tensors which can be contracted with objects created by outer product) + if allowOPs + if isOK + % New tensor or new best cost + E_is_2 = ~any(freelegs2) && any(freelegs1); + if (~any(freelegs1) && any(freelegs2)) || E_is_2 + % New best sequence consistent with Fig.5(c). + % Determine the value of allIn, which corresponds to xi_C. (This is used in determining valid tensors X to contract with outer products). + if E_is_2 + allIn = getProdLegDims(legs2,legCosts,costType); + else + allIn = getProdLegDims(legs1,legCosts,costType); + end + % Add to tensor X list for outer products (or if already there, update the value of allIn): + if isnew + if isGreaterThan_sd(dimSquared(allIn,costType),getProdLegDims(freelegs,legCosts,costType),costType) % Enforce Eq.(27) + [tensorXlegs tensorXdims tensorXflags] = addToTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs,allIn,costType,isnew); + end + else + if isGreaterThan_sd(dimSquared(allIn,costType),getProdLegDims(freelegs,legCosts,costType),costType) % Enforce Eq.(27) + [tensorXlegs tensorXdims tensorXflags] = addToTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs,allIn,costType,isnew,~isequal(objects{numInObjects}{objectPtr}{7},zeros(1,costType))); + elseif ~isequal(objects{numInObjects}{objectPtr}{7},zeros(1,costType)) % Only need to invoke removeFromTensorXList if there might actually be an entry + [tensorXlegs tensorXdims tensorXflags] = removeFromTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs); + end + end + else + % This tensor is not an eligible tensor X for an outer product: Store a dummy value in allIn to indicate this + allIn = zeros(1,costType); + % Best cost and not consistent with Fig.5(c): Ensure does not appear in tensorXlist. Active removal only + % required if object is not new, and previous best sequence was consistent with Fig.5(c), so allIn is not a + % dummy on the old entry. + if ~isnew && ~isequal(objects{numInObjects}{objectPtr}{7},zeros(1,costType)) + [tensorXlegs tensorXdims tensorXflags] = removeFromTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs); + end + end + elseif isequal(newCost,objects{numInObjects}{objectPtr}{4}) + % Equal-best cost to a known sequence for the same tensor + if ~isequal(objects{numInObjects}{objectPtr}{7},zeros(1,costType)) + % Previous best sequence was consistent with Fig.5(c) so tensor may appear in the provisional environments list + E_is_2 = ~any(freelegs2) && any(freelegs1); + if (~any(freelegs1) && any(freelegs2)) || E_is_2 + % Determine the value of allIn, which corresponds to xi_C in Fig.5(c). + if E_is_2 + allIn = getProdLegDims(legs2,legCosts,costType); + else + allIn = getProdLegDims(legs1,legCosts,costType); + end + % If smaller than previous value, update the value of allIn: + if isGreaterThan_sd(objects{numInObjects}{objectPtr}{7},allIn,costType) + if isGreaterThan_sd(dimSquared(allIn,costType),getProdLegDims(freelegs,legCosts,costType),costType) % Enforce Eq.(27) + [tensorXlegs tensorXdims tensorXflags] = updateTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs,allIn); + else + [tensorXlegs tensorXdims tensorXflags] = removeFromTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs); + end + end + else + % Found best-equal sequence not consistent with Fig.5(c) + [tensorXlegs tensorXdims tensorXflags] = removeFromTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs); + end + %else: There already exists a best-known-cost sequence for the tensor which is not consistent with Fig.5(c), and isOK=false. Tensor does not appear in tensorXlist. No need to assign allIn. Now returning to start of main loop. + end + % else: Sequence is not capable of updating tensorXlist (not better cost, not equal cost). Also, isOK=false. No need to assign allIn. Now returning to start of main loop. + end + else + % Not doing outer products. Store a dummy value in allIn, which is never used. + allIn = []; + end + % ### Done updating tensorXlist (list of tensors which can be contracted with objects created by outer product) + end + + if isOK + % Either no previous construction, or this one is better + if ~any(commonlegs) + % ### This construction is an outer product. Note dimension of larger of the two participating tensors in newmaxdim. (This is used in enforcing index-dimension-related constraints.) + newmaxdim = getProdLegDims(legs1,legCosts,costType); + newmaxdim2 = getProdLegDims(legs2,legCosts,costType); + if isGreaterThan_sd(newmaxdim2,newmaxdim,costType) + newmaxdim = newmaxdim2; + end + % ### End recording dimension of larger of the two participating tensors in newmaxdim. + thisIsOP = true; + newseq = [seq1 seq2 0]; + else + % This construction is not an outer product. + if isOP1 + newseq = [seq2 seq1 find(commonlegs)]; + else + newseq = [seq1 seq2 find(commonlegs)]; + end + thisIsOP = false; + % ### This construction is not an outer product. Therefore store a dummy value in maxdim. (For outer products, maxdim records the dimension of the larger participating tensor, to assist in enforcing index-dimension-related constraints.) + newmaxdim = []; + % ### End storing dummy value in maxdim + end + + % Update objects{} with this construction + objects{numInObjects}{objectPtr} = {freelegs,tensorsInNew,newseq,newCost,thisIsOP,newmaxdim,allIn}; + % ### Note 1: If this tensor has the structure of Fig.5(c) and so is capable of being contracted with an outer product object, |E| is recorded in allIn (otherwise this is a dummy value). + % ### Note 2: If this tensor is constructed by outer product, the dimension of the largest participating tensor is recorded in newmaxdim. (This is used in enforcing index-dimension-related constraints.) Otherwise, this is a dummy value. + + % Flag as a new construction + newobjectflags{numInObjects}(objectPtr) = true; + + % If top level, display result + if numInObjects == numtensors + % ### If a valid contraction sequence has been found, there is no need to perform any contraction sequence more expensive than this. Set muCap accordingly. + if costType==1 + muCap = newCost; + else + muCap = numel(newCost)-1; + end + % ### Done setting muCap accordingly. + if verbosity > 1 + displayInterimCostAndSequence(newCost,newseq,costType,posindices,tracedindices); + end + end + end + end + end + end + end + end + end + + % ### Finished searching if an object has been constructed which contains all tensors, and no new outer products have been enabled on the last pass (all(tensorXflags<2)==true, indicating that there are no new entries in the list of tensors which can be contracted with outer products). + done = numel(objects{numtensors})~=0 && (all(tensorXflags<2) || ~allowOPs); % Final object has been constructed, and if outer products are allowed, also no new X's have recently been constructed + + if ~done + if all(tensorXflags<2) + % ### All X tensors have been present for an entire pass, so all permitted outer products at this cost have already been constructed. + % Increment muCap, update oldmuCap + if costType==1 + if newmuCap < muCap * max([min(legCosts) 2]) + newmuCap = muCap * max([min(legCosts) 2]); + end + end + oldmuCap = muCap; + muCap = newmuCap; + newmuCap = Inf; + else + % ### New X tensors generated this pass (some tensor X flags==2). Do another pass with same cost limit, to construct newly-allowed objects (i.e. sequences of affordable cost including at least one new outer product). + % ### This is achieved by updating oldmuCap only. Now only outer products and contractions involving newly-created tensors will satisfy mu_0 < mu <= muCap. + oldmuCap = muCap; + end + % Clear all new object flags + for a=1:numel(newobjectflags) + newobjectflags{a} = false(1,numel(newobjectflags{a})); + end + % ### Update tensor X flags (2 -> 1 -> 0): + % ### 2: Newly created this pass becomes + % ### 1: Created last pass; allow construction of cheap objects which contract with this, as they may have previously been excluded due to lack of a valid tensor X. 1 becomes... + % ### 0: Old tensor X. Standard costing rules apply. + % ### Delete redundant entries in tensorXlist (e.g. if A has a subset of the legs on B, and an equal or lower value of allIn (i.e. |E| in Fig.5(c))) + [tensorXlegs tensorXdims tensorXflags] = mergeTensorXlist(tensorXlegs,tensorXdims,tensorXflags,costType); + % ### Done updating tensor X flags + else + if numel(objects{numtensors})~=1 + error('Inappropriate number of objects containing all tensors!') + end + end +end + +% Extract final result +sequence = int32(objects{numtensors}{1}{3}); +cost = objects{numtensors}{1}{4}; +end + +function [newCost isOK] = getBuildCost(freelegs,commonlegs,legCosts,costType,oldmuCap,muCap,isnew,costToBuild1,costToBuild2) +% Get fusion cost +allLegs = freelegs | commonlegs; +isOK = true; +if costType==1 + % Is cost too high (>muCap)? + newCost = prod(legCosts(allLegs)) + costToBuild1 + costToBuild2; + if newCost > muCap + isOK = false; + end + + % Is cost too low (not made from new objects, and <=oldmuCap: This construction has been done before) + if isOK && ~isnew + if newCost <= oldmuCap + isOK = false; + newCost = Inf; + end + end +else + % Is cost too high (>muCap)? + fusionpower = sum(legCosts(allLegs,2)); + if fusionpower > muCap + isOK = false; + newCost = fusionpower; + end + + % Is cost too low (not made from new objects, and <=oldmuCap: This construction has been done before) + if isOK && ~isnew + if fusionpower <= oldmuCap + isOK = false; + newCost = Inf; + end + end + + % If cost OK, determine total cost of construction + if isOK + newCostLen = max([numel(costToBuild1) numel(costToBuild2) fusionpower+1]); + newCost = zeros(1,newCostLen); + newCost(1:numel(costToBuild1)) = costToBuild1; + if ~isempty(costToBuild2) + newCost(1:numel(costToBuild2)) = newCost(1:numel(costToBuild2)) + costToBuild2; + end + newCost(fusionpower+1) = newCost(fusionpower+1) + prod(legCosts(allLegs,1)); + end +end +end + +function flag = isLessThan(cost1,cost2,costType) +% Compares two full network costs +if costType==1 + flag = cost1 < cost2; +else + if numel(cost1) < numel(cost2) + flag = true; + elseif numel(cost1) > numel(cost2) + flag = false; + else + flag = false; + for a = numel(cost2):-1:1 + if cost1(a) < cost2(a) + flag = true; + break; + elseif cost1(a) > cost2(a) + break; + end + end + end +end +end + +function flag = isGreaterThan_sd(cost1,cost2,costType) +% Compares two single-index costs +if costType==1 + flag = cost1 > cost2; +else + flag = false; + if cost1(2) > cost2(2) + flag = true; + elseif cost1(2) == cost2(2) + if cost1(1) > cost2(1) + flag = true; + end + end +end +end + +function dim = getProdLegDims(freelegs,legCosts,costType) +if costType==1 + dim = prod(legCosts(freelegs)); +else + dim = [prod(legCosts(freelegs,1)) sum(legCosts(freelegs,2))]; +end +end + +function dim = dimSquared(dim,costType) +if costType==1 + dim = dim*dim; +else + dim = [dim(1)*dim(1) dim(2)*2]; +end +end + +function displayInterimCostAndSequence(cost,seq,costType,posindices,tracedindices) +% Displays cost and sequence +dispseq = [tracedindices seq]; +dispseq(dispseq>0) = posindices(dispseq(dispseq>0)); +disp(['Sequence: ' unpaddednum2str(dispseq)]); +if costType==1 + t = ['Cost: ' num2str(cost)]; +else + t = 'Cost: '; + for a = numel(cost):-1:2 + t = [t num2str(cost(a)) 'X^' num2str(a-1) ' + ']; %#ok + end + t = [t num2str(cost(1)) 'X^0']; +end +if ~isempty(tracedindices) + t = [t ' + tracing costs']; +end +disp(t); +end + +function [tensorXlegs tensorXdims tensorXflags] = addToTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs,allIn,costType,isnew,oldMayHaveEntry) +% Constructed a new tensor for tensorXlist, or a known tensor at same or better cost. +% If legs exactly match a known non-provisional entry, consider as a possible tighter bound on allIn. +% Otherwise, consider for provisional list if not made redundant by any non-provisional entries. +% Add to provisional list if not made redundant by any non-provisional entries. +consider = true; +ptr = find(tensorXflags==1,1,'last'); +for a=ptr:-1:1 + if isequal(tensorXlegs{a},freelegs) + % If legs exactly match a non-provisional entry, update value of allIn. + % If allIn for this entry just got increased, associated constraints have been relaxed. Flag this updated entry as provisional to trigger another pass. + if isGreaterThan_sd(allIn,tensorXdims{a},costType) + tensorXdims{a} = allIn; + tensorXflags(a) = 2; + % Flags are always in ascending order: Move re-flagged entry to end of list + tensorXdims = tensorXdims([1:a-1 a+1:end a]); + tensorXlegs = tensorXlegs([1:a-1 a+1:end a]); + tensorXflags = tensorXflags([1:a-1 a+1:end a]); + else + tensorXdims{a} = allIn; + end + consider = false; + break; + else + % Check to see if made redundant by existing non-provisional entry + if all(tensorXlegs{a}(freelegs)) && ~isGreaterThan_sd(allIn,tensorXdims{a},costType) + % All legs in freelegs are in overlap with tensorXlegs{a}, and dimension of absorbed tensor is not greater: Proposed new entry is redundant. + % (Greater dimension is allowed as it would mean more permissive bounds for a subset of legs) + consider = false; + if ~isnew + if oldMayHaveEntry + % This tensor: Excluded from list. + % Previous, higer-cost contraction sequence may have successfully made an entry. Remove it. + [tensorXlegs tensorXdims tensorXflags] = removeFromTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs); + end + end + break; + end + end +end +if consider + % Tensor not excluded after comparison with non-provisional entries: + % If legs exactly match another provisional entry, new submission is a cheaper sequence so keep only the new value of allIn. + % (This is the same subnetwork but with a better cost, and possibly a different value of \xi_C.) + if ~isnew + if oldMayHaveEntry + for a=ptr+1:numel(tensorXlegs) + if isequal(tensorXlegs{a},freelegs) + tensorXdims{a} = allIn; + consider = false; + break; + end + end + end + end + % Otherwise: This is a new tensorXlist entry + if consider + tensorXlegs{end+1} = freelegs; + tensorXflags(end+1) = 2; + tensorXdims{end+1} = allIn; + end +end +end + +function [tensorXlegs tensorXdims tensorXflags] = updateTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs,allIn) +% Found new contraction sequence for an existing entry, but with a smaller value of allIn. Update the stored value to this value. +% (This is the same subnetwork, but the \xi_C constraint has been tightened.) +for a=numel(tensorXlegs):-1:1 + if isequal(freelegs,tensorXlegs{a}) + tensorXdims{a} = allIn; + break; + end +end +% If the original entry was provisional and redundant, this one is too. No match will be found (as the redundant tensor was never recorded), and that's OK. +% If the original entry was not provisional and redundant, it has now been updated. +end + +function [tensorXlegs tensorXdims tensorXflags] = removeFromTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs) +% Remove this tensor from tensorXlist. +% Cannot restrict checking just to provisional portion, because might need to remove a confirmed tensor's data from the list in the scenario described in footnote 2. +for a=numel(tensorXlegs):-1:1 + if isequal(freelegs,tensorXlegs{a}) + tensorXlegs(a) = []; + tensorXflags(a) = []; + tensorXdims(a) = []; + break; + end +end +end + +function [tensorXlegs tensorXdims tensorXflags] = mergeTensorXlist(tensorXlegs,tensorXdims,tensorXflags,costType) +% Merge provisional tensors into main list and decrease all nonzero flags by one. +% For each provisional entry in turn, check if it makes redundant or is made redundant by any other provisional entries, and if it makes redundant any +% non-provisional entries. If so, delete the redundant entries. + +% Decrease non-zero tensorXflags +tensorXflags(tensorXflags>0) = tensorXflags(tensorXflags>0)-1; + +ptr = find(tensorXflags==1,1,'first'); +for a=numel(tensorXlegs):-1:ptr + % Permit provisional tensors to be eliminated by other provisional tensors (elimination by non-provisional tensors was done earlier) + for b=[ptr:a-1 a+1:numel(tensorXlegs)] + if all(tensorXlegs{b}(tensorXlegs{a})) && ~isGreaterThan_sd(tensorXdims{a},tensorXdims{b},costType) % All legs in tensorXlegs{a} overlap with tensorXlegs{b}, and dimension of absorbed tensor is not greater: tensorXlegs{a} is redundant. + tensorXlegs(a) = []; + tensorXflags(a) = []; + tensorXdims(a) = []; + break; + end + end +end + +for a=ptr-1:-1:1 + % Now permit non-provisional tensors to be eliminated by provisional tensors, as these are now confirmed + for b=ptr:numel(tensorXlegs) + if all(tensorXlegs{b}(tensorXlegs{a})) && ~isGreaterThan_sd(tensorXdims{a},tensorXdims{b},costType) % All legs in tensorXlegs{a} overlap with tensorXlegs{b}, and dimension of absorbed tensor is not greater: tensorXlegs{a} is redundant. + tensorXlegs(a) = []; + tensorXflags(a) = []; + tensorXdims(a) = []; + break; + end + end +end +end From e897c83299906f05acfc1b8e99bb66ad21310b4e Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 22 Feb 2023 20:50:45 +0100 Subject: [PATCH 145/245] Add (co)domain getters for MpoTensor --- src/mps/MpoTensor.m | 10 +++++++++- src/tensors/Tensor.m | 7 +------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index 8581534..d37fdbb 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -20,6 +20,14 @@ function n = nspaces(~) n = 4; end + + function dom = domain(t) + dom = t.tensors.domain; + end + + function cod = codomain(t) + cod = t.tensors.codomain; + end end methods @@ -102,7 +110,7 @@ L, [-1 2 1 (-(1:auxlegs_l) - 3)], ... O, [2 -2 4 3], ... R, [5 4 -3 (-(1:auxlegs_r) - 3 - auxlegs_l - auxlegs_v)], ... - 'Rank', newrank); + 'Rank', newrank, 'CheckOptimal', true); end function v = applympo(varargin) diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index d7283c3..0ec76b3 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -456,12 +456,7 @@ end end - function sz = dims(t, inds) - sz = dims([t.codomain, t.domain']); - if nargin > 1 - sz = sz(inds); - end - end + function sp = space(t, inds) sp = [t.codomain t.domain']; From d1154ee11cdcc4f66cf67d2955f158a180659f22 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 22 Feb 2023 22:24:55 +0100 Subject: [PATCH 146/245] bugfix Tensor ctranspose + add test to prevent --- src/tensors/kernels/MatrixBlock.m | 10 ++++++++-- test/TestTensor.m | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/tensors/kernels/MatrixBlock.m b/src/tensors/kernels/MatrixBlock.m index 91ae84e..b0ff81a 100644 --- a/src/tensors/kernels/MatrixBlock.m +++ b/src/tensors/kernels/MatrixBlock.m @@ -627,12 +627,18 @@ % X : :class:`MatrixBlock` % list of adjoint output matrices. - [X.rowsizes, X.colsizes] = swapvars(X.rowsizes, X.colsizes); X.rank = fliplr(X.rank); for i = 1:length(X.var) X.var{i} = X.var{i}'; - X.tdims{i} = fliplr(X.tdims{i}); + ncols = length(X.colsizes{i}) - 1; + nrows = length(X.rowsizes{i}) - 1; + X.tdims{i} = reshape(permute(... + reshape(fliplr(X.tdims{i}), nrows, ncols, []), ... + [2 1 3]), ... + ncols * nrows, []); end + + [X.rowsizes, X.colsizes] = swapvars(X.rowsizes, X.colsizes); end function v = vectorize(X, type) diff --git a/test/TestTensor.m b/test/TestTensor.m index 7fe3917..08ce982 100644 --- a/test/TestTensor.m +++ b/test/TestTensor.m @@ -76,6 +76,20 @@ function basic_linear_algebra(tc, spaces) 'Dot should be sesquilinear.'); end + function transpose_via_conversion(tc, spaces) + tc.assumeTrue(istwistless(braidingstyle(spaces))); + + t = Tensor.ones(spaces(1:3), spaces(4:5)); + tdagger = t'; + tc.assertTrue(isequal(t.domain, tdagger.codomain)); + tc.assertTrue(isequal(t.codomain, tdagger.domain)); + + tc.assertEqual(flip(dims(tdagger)), dims(t)); + tc.assertEqual(conj(double(t)), double(conj(t)), ... + 'AbsTol', tc.tol, 'RelTol', tc.tol, ... + sprintf('conj(double(t)) should be double(conj(t)). (%e)', distance(conj(double(t)), double(conj(t))))); + end + function matrix_functions(tc, spaces) for i = 1:3 t = Tensor.randnc(spaces(1:i), spaces(1:i)); From 030e898a3c6f2c39eeed4c8f632361ad963c79d3 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 22 Feb 2023 22:43:09 +0100 Subject: [PATCH 147/245] bugfix applympo with auxleg on v --- src/mps/MpoTensor.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index 260782a..7580c3f 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -127,7 +127,7 @@ else auxlegs_r = 0; end - auxlegs = auxlegs_v + auxlegs_l + auxlegs_r; + auxlegs_extra = auxlegs_l + auxlegs_r; Oinds = arrayfun(@(x) [2*x-2 -x 2*x 2*x-1], 2:N-1, 'UniformOutput', false); O = [varargin(1:end-3); Oinds]; @@ -135,7 +135,7 @@ L, [-1 2 1 (-(1:auxlegs_l) - N)], ... O{:}, ... R, [2*N-1 2*N-2 -N (-(1:auxlegs_r) - N - auxlegs_l - auxlegs_v)], ... - 'Rank', rank(v) + [0 auxlegs]); + 'Rank', rank(v) + [0 auxlegs_extra]); end function O = rot90(O) From 37a20488faeaace4689350fd6a61458740393570 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 22 Feb 2023 23:19:49 +0100 Subject: [PATCH 148/245] improve netcon implementation remove stray check in applychannel --- src/mps/MpoTensor.m | 2 +- src/mps/MpsTensor.m | 4 ++++ src/tensors/AbstractTensor.m | 9 +++++---- src/tensors/Tensor.m | 9 ++++++--- src/tensors/spaces/SumSpace.m | 6 ++++++ 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index 8572467..394c3f2 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -114,7 +114,7 @@ L, [-1 2 1 (-(1:auxlegs_l) - 3)], ... O, [2 -2 4 3], ... R, [5 4 -3 (-(1:auxlegs_r) - 3 - auxlegs_l - auxlegs_v)], ... - 'Rank', newrank, 'CheckOptimal', true); + 'Rank', newrank); end function v = applympo(varargin) diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index ce76874..ba737af 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -550,6 +550,10 @@ function type = underlyingType(A) type = underlyingType(A.var); end + + function disp(t) + builtin('disp', t); + end end diff --git a/src/tensors/AbstractTensor.m b/src/tensors/AbstractTensor.m index 03ba2fd..2b69009 100644 --- a/src/tensors/AbstractTensor.m +++ b/src/tensors/AbstractTensor.m @@ -324,11 +324,12 @@ legcosts = unique(legcosts.', 'rows'); currentcost = contractcost(indices, legcosts); - [sequence, cost] = netcon(indices, 1, 1, currentcost, 1, legcosts); + [sequence, cost] = netcon(indices, 0, 1, currentcost, 1, legcosts); if cost < currentcost - warning('suboptimal contraction order.\n optimal: %s', ... - num2str(sequence)); + warning('suboptimal contraction order.\n current (%d): %s\n optimal(%d): %s', ... + currentcost, num2str(1:max(legcosts(:,1))), ... + cost, num2str(sequence)); end end @@ -512,7 +513,7 @@ function disp(t, details) end function sz = dims(t, inds) - sz = dims([t.codomain, t.domain']); + sz = dims(space(t)); if nargin > 1 sz = sz(inds); end diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 5579a3c..c515960 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -458,10 +458,13 @@ end end - - function sp = space(t, inds) - sp = [t.codomain t.domain']; + if isscalar(t) + sp = [t.codomain t.domain']; + else + [cod, dom] = deduce_spaces(t); + sp = [cod, dom']; + end if nargin > 1 sp = sp(inds); end diff --git a/src/tensors/spaces/SumSpace.m b/src/tensors/spaces/SumSpace.m index 88b27ca..456551d 100644 --- a/src/tensors/spaces/SumSpace.m +++ b/src/tensors/spaces/SumSpace.m @@ -75,6 +75,12 @@ %% Utility methods + function d = dims(spaces) + for i = length(spaces):-1:1 + d(i) = sum(dims(subspaces(spaces(i)))); + end + end + function bools = eq(spaces1, spaces2) assert(isequal(size(spaces1), size(spaces2))); bools = false(size(spaces1)); From 566ecc91ae3e3a70c90ff459a5880e6e96a42dff Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 22 Feb 2023 23:21:23 +0100 Subject: [PATCH 149/245] reduce weigth test --- test/TestAlgorithms.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/TestAlgorithms.m b/test/TestAlgorithms.m index 7c85e68..2a5f279 100644 --- a/test/TestAlgorithms.m +++ b/test/TestAlgorithms.m @@ -12,7 +12,7 @@ methods (Test) function test2dIsing(tc, alg, symm) alg.which = 'largestabs'; - for unitcell = 1:4 + for unitcell = 1:3 if unitcell == 1 && (isa(alg, 'Vumps2') || isa(alg, 'IDmrg2')) continue; end @@ -40,7 +40,7 @@ function test2dIsing(tc, alg, symm) function test1dIsing(tc, alg, symm) alg.which = 'smallestreal'; - for unitcell = 1:4 + for unitcell = 1:3 if unitcell == 1 && (isa(alg, 'IDmrg2') || isa(alg, 'Vumps2')) continue; end From cc6edbc7490bbca96b984cd70230909b086ccaa5 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 23 Feb 2023 09:29:43 +0100 Subject: [PATCH 150/245] apply bugfix --- src/tensors/kernels/MatrixBlock.m | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/tensors/kernels/MatrixBlock.m b/src/tensors/kernels/MatrixBlock.m index 91ae84e..b0ff81a 100644 --- a/src/tensors/kernels/MatrixBlock.m +++ b/src/tensors/kernels/MatrixBlock.m @@ -627,12 +627,18 @@ % X : :class:`MatrixBlock` % list of adjoint output matrices. - [X.rowsizes, X.colsizes] = swapvars(X.rowsizes, X.colsizes); X.rank = fliplr(X.rank); for i = 1:length(X.var) X.var{i} = X.var{i}'; - X.tdims{i} = fliplr(X.tdims{i}); + ncols = length(X.colsizes{i}) - 1; + nrows = length(X.rowsizes{i}) - 1; + X.tdims{i} = reshape(permute(... + reshape(fliplr(X.tdims{i}), nrows, ncols, []), ... + [2 1 3]), ... + ncols * nrows, []); end + + [X.rowsizes, X.colsizes] = swapvars(X.rowsizes, X.colsizes); end function v = vectorize(X, type) From 1e4de27c9d2e41d275e08e48db1a981d1a8ccd20 Mon Sep 17 00:00:00 2001 From: leburgel Date: Thu, 23 Feb 2023 13:57:23 +0100 Subject: [PATCH 151/245] Fix more broken tests --- src/mps/PepsSandwich.m | 4 ++-- src/tensors/AbstractTensor.m | 6 +++--- src/tensors/Tensor.m | 1 + src/tensors/charges/FusionStyle.m | 8 ++++---- test/TestPeps.m | 31 +++++++++++++++++++------------ 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/mps/PepsSandwich.m b/src/mps/PepsSandwich.m index e58f3bc..ff170a1 100644 --- a/src/mps/PepsSandwich.m +++ b/src/mps/PepsSandwich.m @@ -112,7 +112,7 @@ else auxlegs_r = 0; end - auxlegs = auxlegs_v + auxlegs_l + auxlegs_r; + auxlegs_extra = auxlegs_l + auxlegs_r; inds_top = arrayfun(@(x) [ 5 + 5*(x-1), ... 2 + 5*(x-1), ... @@ -133,7 +133,7 @@ L, [-1, 4, 2, 1, (-(1:auxlegs_l) - (2*W+2))], ... Oargs{:}, ... R, [3 + 5*W, 2 + 5*W, 4 + 5*W, -(2*W + 2), (-(1:auxlegs_r) - (2*W+2) - auxlegs_l - auxlegs_v)], ... - 'Rank', rank(v) + [0 auxlegs]); + 'Rank', rank(v) + [0 auxlegs_extra]); end function t = MpoTensor(T) diff --git a/src/tensors/AbstractTensor.m b/src/tensors/AbstractTensor.m index 791444d..ecb85d7 100644 --- a/src/tensors/AbstractTensor.m +++ b/src/tensors/AbstractTensor.m @@ -270,13 +270,13 @@ 'Tolerance', options.Tol, 'MaxIterations', options.MaxIter, ... 'IsFunctionSymmetric', ... options.IsSymmetric, 'StartVector', x0_vec, ... - 'Display', options.Verbosity >= 3); + 'Display', options.Verbosity >= 3, 'FailureTreatment', 'keep'); else [V, D, flag] = eigs(A_fun, sz(1), howmany, sigma, ... 'Tolerance', options.Tol, 'MaxIterations', options.MaxIter, ... 'SubspaceDimension', options.KrylovDim, 'IsFunctionSymmetric', ... options.IsSymmetric, 'StartVector', x0_vec, ... - 'Display', options.Verbosity >= 3); + 'Display', options.Verbosity >= 3, 'FailureTreatment', 'keep'); end if nargout <= 1 @@ -294,7 +294,7 @@ varargout{3} = flag; end - if flag && options.Verbosity > 0 + if flag warning('eigsolve did not converge.'); elseif ~flag && options.Verbosity > 1 fprintf('eigsolve converged.\n'); diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index ab3e6bb..8a68ebe 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -2196,6 +2196,7 @@ return end end + bool = true; end function bool = iszero(t) diff --git a/src/tensors/charges/FusionStyle.m b/src/tensors/charges/FusionStyle.m index d4cc419..6d7bc92 100644 --- a/src/tensors/charges/FusionStyle.m +++ b/src/tensors/charges/FusionStyle.m @@ -1,4 +1,4 @@ -classdef FusionStyle +classdef FusionStyle < uint8 % FusionStyle - The fusion product behaviour of charges. % This represents the possibilities for the decomposition of the fusion product of two charges. % @@ -7,9 +7,9 @@ % Generic - Multiple outputs. enumeration - Unique - Simple - Generic + Unique (0) + Simple (1) + Generic (2) end methods diff --git a/test/TestPeps.m b/test/TestPeps.m index 02a9089..0661746 100644 --- a/test/TestPeps.m +++ b/test/TestPeps.m @@ -11,8 +11,8 @@ fZ2(0, 1), [2 1], false, fZ2(0, 1), [5 5], false), ... 'U1', GradedSpace.new(U1(0, 1, -1), [1 1 1], false, U1(0, 1, -1), [1 2 2], false, ... U1(0, 1, -1), [2 1 1], false, U1(0, 1, -1), [2 1 1], false), ... - 'SU2', GradedSpace.new(SU2(1, 2), [1 1], false, SU2(1, 2), [2 1], false, ... - SU2(1, 2), [1 1], false, SU2(1, 2), [2 1], false) ... + 'SU2', GradedSpace.new(SU2(1, 2), [1 1], false, SU2(2), [2], false, ... + SU2(2), [1], false, SU2(1, 2), [2 1], false) ... ) depth = {1} width = {1, 2} @@ -26,10 +26,22 @@ function testFiniteMpo(tc, spaces, depth, width, dualdepth, dualwidth, samebot) tc.assumeTrue(braidingstyle(spaces) ~= BraidingStyle.Fermionic, 'fZ2 test broken') tc.assumeTrue(depth == 1, 'Test ill-defined for multiline PEPS.') - tc.assumeTrue(width < 3, 'FiniteMpo -> Tensor contraction not reasonable.') mpo = tc.random_mpo(spaces, depth, width, dualdepth, dualwidth, samebot); + % test transpose (co)domain + tc.assertTrue(isequal(domain(mpo.'), codomain(mpo)')); + tc.assertTrue(isequal(codomain(mpo.'), domain(mpo)')); + + % test ctranspose(co)domain + tc.assertTrue(isequal(domain(mpo'), codomain(mpo))); + tc.assertTrue(isequal(codomain(mpo'), domain(mpo))); + + + % test tensor behavior + tc.assumeTrue(width < 3 || (fusionstyle(spaces) <= FusionStyle.Unique || width < 2), ... + 'FiniteMpo -> Tensor contraction not reasonable.') + % test domain and codomain mpo_tensor = tc.mpo_to_tensor(mpo); tc.assertTrue(isequal(mpo.domain, mpo_tensor.domain), ... @@ -44,14 +56,8 @@ function testFiniteMpo(tc, spaces, depth, width, dualdepth, dualwidth, samebot) % test transpose tc.assertTrue(isapprox(tc.mpo_to_tensor(mpo).', tc.mpo_to_tensor(mpo.')), ... 'transpose should not change mpo'); - tc.assertTrue(isequal(domain(mpo.'), codomain(mpo)')); - tc.assertTrue(isequal(codomain(mpo.'), domain(mpo)')); % test ctranspose - tc.assertTrue(isequal(domain(mpo'), codomain(mpo))); - tc.assertTrue(isequal(codomain(mpo'), domain(mpo))); - % GradedSpace: something broken here - tc.assumeTrue(~isa(spaces, 'GradedSpace'), 'ctranspose broken for GradedSpaces.') tc.assertTrue(isapprox(tc.mpo_to_tensor(mpo)', tc.mpo_to_tensor(mpo')), ... 'ctranspose should not change mpo'); end @@ -61,7 +67,7 @@ function testFixedpoints(tc, spaces, depth, width, dualdepth, dualwidth, samebot mpo = tc.random_mpo(spaces, depth, width, dualdepth, dualwidth, samebot); - [V, D] = eigsolve(mpo); + [V, D] = eigsolve(mpo, 'MaxIter', 1e3, 'KrylovDim', 32); tc.assertTrue(isapprox(mpo.apply(V), D * V)); v2 = insert_onespace(V); @@ -82,13 +88,13 @@ function testDerivatives(tc, spaces, depth, width, dualdepth, dualwidth, samebot AC_ = mps.AC(i); [AC_.var, lambda] = eigsolve(H_AC{i}, mps.AC(i).var, 1, 'largestabs'); AC_2 = apply(H_AC{i}, AC_); - tc.assertTrue(isapprox(AC_2, lambda * AC_.var)); + tc.assertTrue(isapprox(AC_2, lambda * AC_.var, 'RelTol', 1e-6)); end H_C = C_hamiltonian(mpo, mps, GL, GR); for i = 1:numel(H_C) [C_, lambda] = eigsolve(H_C{i}, mps.C(i), 1, 'largestabs'); - tc.assertTrue(isapprox(apply(H_C{i}, C_), lambda * C_)); + tc.assertTrue(isapprox(apply(H_C{i}, C_), lambda * C_, 'RelTol', 1e-6)); end end @@ -132,6 +138,7 @@ function testMpoTensor(tc, spaces, dualdepth, dualwidth) if ~mod(depth, 2) && ~mod(d, 2), cdmsp = conj(cdmsp); dmsp = conj(dmsp); end if ~mod(width, 2) && ~mod(w, 2), cdmsp = conj(cdmsp); dmsp = conj(dmsp); end A{d, w} = Tensor.randnc(dmsp, cdmsp); + A{d, w} = A{d, w} / norm(A{d, w}); end end end From 61457f25392ac4f9fa6bc15489f4c41155c54ebe Mon Sep 17 00:00:00 2001 From: sanderdemeyer Date: Thu, 23 Feb 2023 18:17:49 +0100 Subject: [PATCH 152/245] Time and trunctotdim fix --- src/algorithms/Vumps.m | 11 +++++++---- src/algorithms/Vumps2.m | 12 +++++++++--- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/algorithms/Vumps.m b/src/algorithms/Vumps.m index ae30c37..4526ea6 100644 --- a/src/algorithms/Vumps.m +++ b/src/algorithms/Vumps.m @@ -67,7 +67,7 @@ end end - function [mps, lambda, GL, GR, eta] = fixedpoint(alg, mpo, mps) + function [mps, lambda, GL, GR, eta, time] = fixedpoint(alg, mpo, mps) if period(mpo) ~= period(mps) error('vumps:argerror', ... @@ -93,6 +93,7 @@ if iter > alg.miniter && eta < alg.tol disp_conv(alg, iter, lambda, eta, toc(t_total)); + time = toc(t_total); return end alg = updatetols(alg, iter, eta); @@ -100,10 +101,10 @@ disp_iter(alg, iter, lambda, eta, toc(t_iter)); if alg.doSave && mod(iter, alg.saveIterations) == 0 - save_iteration(alg, mps, lambda, iter); + save_iteration(alg, mps, lambda, iter, eta, toc(t_total)); end end - + time = toc(t_total); disp_maxiter(alg, iter, lambda, eta, toc(t_total)); end end @@ -321,13 +322,15 @@ function disp_maxiter(alg, iter, lambda, eta, t) fprintf('---------------\n'); end - function save_iteration(alg, mps, lambda, iter) + function save_iteration(alg, mps, lambda, iter, eta, t) fileName = alg.name; fileData = struct; fileData.mps = mps; fileData.lambda = lambda; fileData.iteration = iter; + fileData.eta = eta; + fileData.time = t; % save %if exist(fileName,'file') diff --git a/src/algorithms/Vumps2.m b/src/algorithms/Vumps2.m index c8bbff8..86d87ed 100644 --- a/src/algorithms/Vumps2.m +++ b/src/algorithms/Vumps2.m @@ -18,7 +18,8 @@ environments_tolfactor = 1e-6 trunc = {'TruncTotalDim', 100} - + notrunc = false + multiAC {mustBeMember(multiAC, {'sequential'})} = 'sequential' dynamical_multiAC = false; tol_multiAC = Inf @@ -106,7 +107,7 @@ disp_iter(alg, iter, lambda, eta, toc(t_iter)); if alg.doSave && mod(iter, alg.saveIterations) == 0 - save_iteration(alg, mps, lambda, iter); + save_iteration(alg, mps, lambda, iter, eta); end end @@ -162,6 +163,10 @@ [Q_AC, ~] = leftorth(AC2(i), 'polar'); [Q_C, ~] = leftorth(C(i), 1, 2, 'polar'); AL = multiplyright(Q_AC, Q_C'); + if alg.notrunc + assert(isempty(alg.trunc) || strcmp(alg.trunc{1}, 'TruncTotalDim'), 'tba', 'notrunc only defined in combination with TruncTotalDim'); + alg.trunc{2} = max(alg.trunc{2}, dims(rightvspace(mps, sites(i)))); + end [AL1, C, AL2] = tsvd(AL.var, [1 2], [3 4], alg.trunc{:}); mps.AL(sites(i)) = multiplyright(MpsTensor(AL1), C); mps.AL(next(sites(i), period(mps))) = AL2; @@ -338,13 +343,14 @@ function disp_maxiter(alg, iter, lambda, eta, t) fprintf('---------------\n'); end - function save_iteration(alg, mps, lambda, iter) + function save_iteration(alg, mps, lambda, iter, eta) fileName = alg.name; fileData = struct; fileData.mps = mps; fileData.lambda = lambda; fileData.iteration = iter; + fileData.eta = eta; % save %if exist(fileName,'file') From 5ffdae99f7cc42f27f1287a9bd692bc7b7f32ba9 Mon Sep 17 00:00:00 2001 From: sanderdemeyer Date: Thu, 23 Feb 2023 19:44:45 +0100 Subject: [PATCH 153/245] Revert "Merge branch 'dev' of https://github.com/quantumghent/TensorTrack into dev" This reverts commit 4bc15ddd95d089de1e0c192891360a229af79975, reversing changes made to 61457f25392ac4f9fa6bc15489f4c41155c54ebe. --- src/algorithms/Dmrg.m | 242 ----- src/algorithms/Vumps.m | 74 +- src/environments/FiniteEnvironment.m | 61 -- src/models/quantum1dHeisenberg.m | 49 - src/models/quantum1dIsing.m | 59 -- src/models/statmech2dIsing.m | 60 -- src/mps/FiniteMpo.m | 55 -- src/mps/FiniteMps.m | 285 ------ src/mps/InfJMpo.m | 97 +- src/mps/InfMpo.m | 122 +-- src/mps/MpoTensor.m | 84 +- src/mps/MpsTensor.m | 95 +- src/mps/UniformMps.m | 75 +- src/sparse/SparseTensor.m | 22 +- src/sparse/sub2sub.m | 9 +- src/tensors/AbstractTensor.m | 122 +-- src/tensors/Tensor.m | 64 +- src/tensors/spaces/AbstractSpace.m | 4 - src/tensors/spaces/CartesianSpace.m | 4 + src/tensors/spaces/SumSpace.m | 21 - src/utility/diracdelta.m | 12 - src/utility/linalg/contract.m | 17 - src/utility/linalg/contractcost.m | 73 -- src/utility/netcon.m | 1308 -------------------------- src/utility/sparse/SparseArray.m | 65 +- test/TestAlgorithms.m | 88 +- test/TestFiniteMpo.m | 35 - test/TestFiniteMps.m | 34 - test/TestInfJMpo.m | 6 +- test/TestInfMpo.m | 12 +- 30 files changed, 343 insertions(+), 2911 deletions(-) delete mode 100644 src/algorithms/Dmrg.m delete mode 100644 src/environments/FiniteEnvironment.m delete mode 100644 src/models/quantum1dHeisenberg.m delete mode 100644 src/models/quantum1dIsing.m delete mode 100644 src/models/statmech2dIsing.m delete mode 100644 src/mps/FiniteMps.m delete mode 100644 src/utility/diracdelta.m delete mode 100644 src/utility/linalg/contractcost.m delete mode 100644 src/utility/netcon.m delete mode 100644 test/TestFiniteMps.m diff --git a/src/algorithms/Dmrg.m b/src/algorithms/Dmrg.m deleted file mode 100644 index a10ffaf..0000000 --- a/src/algorithms/Dmrg.m +++ /dev/null @@ -1,242 +0,0 @@ -classdef Dmrg - % Density Matrix Renormalisation Group algorithm for marix product states. - - properties - tol = 1e-10 - miniter = 5 - maxiter = 100 - verbosity = Verbosity.iter - doplot = false - which = 'largestabs' - - dynamical_tols = false - tol_min = 1e-12 - tol_max = 1e-10 - eigs_tolfactor = 1e-6 - - sweepstyle {mustBeMember(sweepstyle, {'f2f', 'b2b', 'm2m'})} = 'f2f' - - doSave = false - saveIterations = 1 - saveMethod = 'full' - name = 'DMRG' - end - - properties (Access = private) - alg_eigs = struct('MaxIter', 100, 'KrylovDim', 20); - progressfig - end - - methods - function alg = Dmrg(kwargs) - arguments - kwargs.?Dmrg - end - - fields = fieldnames(kwargs); - if ~isempty(fields) - for field = fields.' - alg.(field{1}) = kwargs.(field{1}); - end - end - - if ~isfield('alg_eigs', kwargs) - alg.alg_eigs.Tol = sqrt(alg.tol_min * alg.tol_max); - alg.alg_eigs.Verbosity = alg.verbosity - 2; - end - end - - function [mps, envs, eta] = fixedpoint(alg, mpo, mps, envs) - arguments - alg - mpo - mps - envs = initialize_envs(mpo) - end - - if length(mpo) ~= length(mps) - error('dmrg:argerror', ... - 'length of mpo (%d) should be equal to that of the mps (%d)', ... - length(mpo), length(mps)); - end - - t_total = tic; - disp_init(alg); - - for iter = 1:alg.maxiter - t_iter = tic; - - order = sweeporder(alg, length(mps)); - eta = zeros(1, length(mps)); - lambda = zeros(1, length(mps)); - - for pos = sweeporder(alg, length(mps)) - [mps, envs, lambda(pos), eta(pos)] = ... - localupdate(alg, mps, mpo, envs, order(pos)); - end - - eta = norm(eta, Inf); - lambda = sum(lambda) / length(lambda); - - if iter > alg.miniter && eta < alg.tol - disp_conv(alg, iter, lambda, eta, toc(t_total)); - return - end - - alg = updatetols(alg, iter, eta); - - plot(alg, iter, mps, eta); - disp_iter(alg, iter, lambda, eta, toc(t_iter)); - end - - disp_maxiter(alg, iter, lambda, eta, toc(t_total)); - end - - function [mps, envs, lambda, eta] = localupdate(alg, mps, mpo, envs, pos) - % update orthogonality center - mps = movegaugecenter(mps, pos); - envs = movegaugecenter(envs, mpo, mps, mps, pos); - - % compute update - H_AC = AC_hamiltonian(mpo, mps, envs, pos); - AC = mps.A(pos); - [AC.var, lambda] = eigsolve(H_AC, AC.var, 1, alg.which); - - % determine error - phase = dot(AC.var, mps.A(pos)); - eta = distance(AC.var ./ sign(phase), mps.A(pos)); - - % take step - mps.A(pos) = AC; - envs = invalidate(envs, pos); - end - end - - %% Option handling - methods - function alg = updatetols(alg, iter, eta) - if alg.dynamical_tols - alg.alg_eigs.Tol = between(alg.tol_min, eta * alg.eigs_tolfactor / iter, ... - alg.tol_max); - if alg.verbosity > Verbosity.iter - fprintf('Updated eigsolver tolerances: (%e)\n', alg.alg_eigs.Tol); - end - end - end - - function order = sweeporder(alg, L) - switch alg.sweepstyle - case 'f2f' - order = [1:L L-1:-1:2]; - case 'b2b' - order = [L:-1:1 2:L]; - case 'm2m' - order = circshift([2:L, L-1:-1:1], -floor(L / 2)); - otherwise - error('unknown sweepstyle %s', alg.sweepstyle); - end - end - end - - - %% Display - methods (Access = private) - function plot(alg, iter, mps, eta) - if ~alg.doplot, return; end - persistent axhistory axspectrum - - D = depth(mps); - W = period(mps); - - if isempty(alg.progressfig) || ~isvalid(alg.progressfig) || iter == 1 - alg.progressfig = figure('Name', 'Vumps'); - axhistory = subplot(D + 1, W, 1:W); - axspectrum = gobjects(D, W); - for d = 1:D, for w = 1:W - axspectrum(d, w) = subplot(D + 1, W, w + d * W); - end, end - linkaxes(axspectrum, 'y'); - end - - - if isempty(axhistory.Children) - semilogy(axhistory, iter, eta, '.', 'Color', colors(1), ... - 'DisplayName', 'Errors', 'MarkerSize', 10); - hold on - ylim(axhistory, [alg.tol / 5, max([1 eta])]); - yline(axhistory, alg.tol, '-', 'Convergence', ... - 'LineWidth', 2); - hold off - else - axhistory.Children(end).XData(end+1) = iter; - axhistory.Children(end).YData(end+1) = eta; - end - - plot_entanglementspectrum(mps, 1:period(mps), axspectrum); - drawnow - end - - function disp_init(alg) - if alg.verbosity < Verbosity.conv, return; end - fprintf('---- %s ----\n', alg.name); - end - - function disp_iter(alg, iter, lambda, eta, t) - if alg.verbosity < Verbosity.iter, return; end - - s = settings; - if abs(imag(lambda)) < eps(lambda)^(3/4) * abs(lambda) - switch s.matlab.commandwindow.NumericFormat.ActiveValue - case 'short' - fprintf('%s %2d:\tE = %-0.4f\terror = %0.1e\t(%s)\n', ... - alg.name, iter, real(lambda), eta, time2str(t)); - otherwise - fprintf('%s %4d:\tE = %-0.15f\terror = %0.4e\t(%s)\n', ... - alg.name, iter, real(lambda), eta, time2str(t, 's')); - - end - else - switch s.matlab.commandwindow.NumericFormat.ActiveValue - case 'short' - fprintf('%s %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... - alg.name, iter, real(lambda), imag(lambda), eta, time2str(t)); - otherwise - fprintf('%s %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... - alg.name, iter, real(lambda), imag(lambda), eta, time2str(t)); - - end - end - end - - function disp_conv(alg, iter, lambda, eta, t) - if alg.verbosity < Verbosity.conv, return; end - s = settings; - switch s.matlab.commandwindow.NumericFormat.ActiveValue - case 'short' - fprintf('%s converged %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... - alg.name, iter, real(lambda), imag(lambda), eta, time2str(t)); - otherwise - fprintf('%s converged %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... - alg.name, iter, real(lambda), imag(lambda), eta, time2str(t)); - - end - fprintf('---------------\n'); - end - - function disp_maxiter(alg, iter, lambda, eta, t) - if alg.verbosity < Verbosity.warn, return; end - s = settings; - switch s.matlab.commandwindow.NumericFormat.ActiveValue - case 'short' - fprintf('%s max iterations %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... - alg.name, iter, real(lambda), imag(lambda), eta, time2str(t)); - otherwise - fprintf('%s max iterations %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... - alg.name, iter, real(lambda), imag(lambda), eta, time2str(t)); - - end - fprintf('---------------\n'); - end - end -end - diff --git a/src/algorithms/Vumps.m b/src/algorithms/Vumps.m index 18d1752..4526ea6 100644 --- a/src/algorithms/Vumps.m +++ b/src/algorithms/Vumps.m @@ -25,6 +25,7 @@ saveIterations = 1 saveMethod = 'full' name = 'VUMPS' + end properties (Access = private) @@ -118,16 +119,12 @@ else sites = 1:period(mps); end + H_AC = AC_hamiltonian(mpo, mps, GL, GR, sites); - ACs = arrayfun(@(x) x.AC(sites), mps, 'UniformOutput', false); - AC = vertcat(ACs{:}); + AC = mps.AC(sites); for i = length(sites):-1:1 - [AC(1, i).var, ~] = eigsolve(H_AC{i}, AC(1, i).var, 1, alg.which, ... + [AC(i).var, ~] = eigsolve(H_AC{i}, AC(sites(i)).var, 1, alg.which, ... kwargs{:}); - - for d = 2:depth(mpo) - AC(d, i).var = H_AC{i}(d).apply(AC(d-1, i).var); - end end end @@ -140,15 +137,9 @@ end H_C = C_hamiltonian(mpo, mps, GL, GR, sites); - Cs = arrayfun(@(x) x.C(sites), mps, 'UniformOutput', false); - C = vertcat(Cs{:}); for i = length(sites):-1:1 - [C(1, i), ~] = eigsolve(H_C{i}, C(1, i), 1, alg.which, ... + [C(i), ~] = eigsolve(H_C{i}, mps.C(sites(i)), 1, alg.which, ... kwargs{:}); - - for d = 2:depth(mpo) - C(d, i) = H_C{i}(d).apply(C(d-1, i)); - end end end @@ -159,12 +150,10 @@ sites = 1:period(mps); end - for d = size(AC, 1):-1:1 - for i = size(AC, 2):-1:1 - [Q_AC, ~] = leftorth(AC(d, i), 'polar'); - [Q_C, ~] = leftorth(C(d, i), 1, 2, 'polar'); - mps(d).AL(sites(i)) = multiplyright(Q_AC, Q_C'); - end + for i = length(AC):-1:1 + [Q_AC, ~] = leftorth(AC(i), 'polar'); + [Q_C, ~] = leftorth(C(i), 1, 2, 'polar'); + mps.AL(sites(i)) = multiplyright(Q_AC, Q_C'); end kwargs = namedargs2cell(alg.alg_canonical); @@ -176,40 +165,31 @@ alg mpo mps - GL = cell(depth(mpo), period(mps)) - GR = cell(depth(mpo), period(mps)) + GL = cell(1, period(mps)) + GR = cell(1, period(mps)) end kwargs = namedargs2cell(alg.alg_environments); - D = depth(mpo); - lambda = zeros(D, 1); - for d = 1:D - [GL(d, :), GR(d, :), lambda(d)] = environments(mpo.slice(d), mps(d), mps(next(d, D)), GL(d, :), GR(d, :), ... - kwargs{:}); - end - lambda = prod(lambda); + [GL, GR, lambda] = environments(mpo, mps, mps, GL, GR, ... + kwargs{:}); end function eta = convergence(alg, mpo, mps, GL, GR) - % TODO: also implement galerkin error H_AC = AC_hamiltonian(mpo, mps, GL, GR); H_C = C_hamiltonian(mpo, mps, GL, GR); - eta = zeros(depth(mpo), period(mps)); - for d = 1:depth(mpo) - dd = next(d, depth(mpo)); - for w = 1:period(mps) - AC_ = apply(H_AC{w}(d), mps(d).AC(w)); - lambda_AC = dot(AC_, mps(dd).AC(w)); - AC_ = normalize(AC_ ./ lambda_AC); - - ww = prev(w, period(mps)); - C_ = apply(H_C{ww}(d), mps(d).C(ww)); - lambda_C = dot(C_, mps(dd).C(ww)); - C_ = normalize(C_ ./ lambda_C); - - eta(dd, w) = distance(AC_ , ... - repartition(multiplyleft(mps(dd).AR(w), C_), rank(AC_))); - end + eta = zeros(1, period(mps)); + for w = 1:period(mps) + AC_ = apply(H_AC{w}, mps.AC(w)); + lambda_AC = dot(AC_, mps.AC(w)); + AC_ = normalize(AC_ ./ lambda_AC); + + ww = prev(w, period(mps)); + C_ = apply(H_C{ww}, mps.C(ww)); + lambda_C = dot(C_, mps.C(ww)); + C_ = normalize(C_ ./ lambda_C); + + eta(w) = distance(AC_ , ... + repartition(multiplyleft(mps.AR(w), C_), rank(AC_))); end eta = max(eta, [], 'all'); end @@ -276,7 +256,7 @@ function plot(alg, iter, mps, eta) axhistory.Children(end).YData(end+1) = eta; end - plot_entanglementspectrum(mps, 1:D, 1:W, axspectrum); + plot_entanglementspectrum(mps, 1:period(mps), axspectrum); drawnow end diff --git a/src/environments/FiniteEnvironment.m b/src/environments/FiniteEnvironment.m deleted file mode 100644 index 9304f35..0000000 --- a/src/environments/FiniteEnvironment.m +++ /dev/null @@ -1,61 +0,0 @@ -classdef FiniteEnvironment - %FINITEENVIRONMENT Summary of this class goes here - % Detailed explanation goes here - - properties - GL - GR - end - - methods - function envs = FiniteEnvironment(varargin) - %FINITEENVIRONMENT Construct an instance of this class - % Detailed explanation goes here - if nargin == 0, return; end - if nargin == 2 - envs.GL = varargin{1}; - envs.GR = varargin{2}; - assert(isequal(size(envs.GL), size(envs.GR))); - return - end - error('undefined syntax'); - end - - function envs = movegaugecenter(envs, mpo, mps1, mps2, pos) - for i = 2:pos - if ~isempty(envs.GL{i}), continue; end - T = transfermatrix(mpo, mps1, mps2, i-1); - envs.GL{i} = apply(T, envs.GL{i-1}); - end - for i = length(mps1):-1:(pos+1) - if ~isempty(envs.GR{i}), continue; end - T = transfermatrix(mpo, mps1, mps2, i).'; - envs.GR{i} = apply(T, envs.GR{i+1}); - end - end - - function envs = invalidate(envs, pos) - envs.GL(pos+1:end) = cell(1, length(envs.GL) - pos); - envs.GR(1:pos) = cell(1, pos); - end - end - -% methods (Static) -% function envs = initialize(mpo, mps1, mps2) -% arguments -% mpo -% mps1 -% mps2 = mps1 -% end -% -% GL = cell(1, length(mpo) + 1); -% GR = cell(1, length(mpo) + 1); -% -% GL{1} = mpo.L; -% GR{end} = mpo.R; -% -% envs = FiniteEnvironment(GL, GR); -% end -% end -end - diff --git a/src/models/quantum1dHeisenberg.m b/src/models/quantum1dHeisenberg.m deleted file mode 100644 index beff676..0000000 --- a/src/models/quantum1dHeisenberg.m +++ /dev/null @@ -1,49 +0,0 @@ -function mpo = quantum1dHeisenberg(kwargs) -arguments - kwargs.Spin = 1 - kwargs.J = 1 - kwargs.h = 0 - kwargs.L = Inf % size of system - kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'U1', 'SU2'})} = 'Z1' -end - -J = kwargs.J; -h = kwargs.h; - -Q = SU2(2 * kwargs.Spin + 1); - -switch kwargs.Symmetry - case 'SU2' - assert(isscalar(J) || all(J == J(1)), ... - 'Different spin couplings not invariant under SU2'); - assert(h == 0, 'Magnetic field not invariant under SU2'); - - pSpace = GradedSpace.new(Q, 1, false); - vSpace = GradedSpace.new(SU2(3), 1, false); - tSpace = one(vSpace); - - s = kwargs.Spin; - L = Tensor.ones([tSpace pSpace], [pSpace vSpace]); - L = L * (-J(1) * (s^2 + s)); - R = Tensor.ones([vSpace pSpace], [pSpace tSpace]); - - cod = SumSpace([one(vSpace) vSpace one(vSpace)], pSpace); - dom = SumSpace(pSpace, [one(vSpace), vSpace, one(vSpace)]); - O = MpoTensor.zeros(cod, dom); - O(1, 1, 1, 1) = 1; - O(3, 1, 3, 1) = 1; - O(1, 1, 2, 1) = L; - O(2, 1, 3, 1) = R; - - otherwise - error('TBA'); -end - -mpo = InfJMpo(O); - -if isfinite(kwargs.L) - mpo = open_boundary_conditions(mpo, L); -end - -end - diff --git a/src/models/quantum1dIsing.m b/src/models/quantum1dIsing.m deleted file mode 100644 index 449aec9..0000000 --- a/src/models/quantum1dIsing.m +++ /dev/null @@ -1,59 +0,0 @@ -function mpo = quantum1dIsing(kwargs) -arguments - kwargs.J = 1 - kwargs.h = 1 - kwargs.L = Inf % size of system - kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'Z2'})} = 'Z1' -end - -J = kwargs.J; -h = kwargs.h; - -sigma_x = [0 1; 1 0]; -sigma_z = [1 0; 0 -1]; - -if strcmp(kwargs.Symmetry, 'Z1') - pSpace = CartesianSpace.new(2); - vSpace = one(pSpace); - trivSpace = one(pSpace); - - S = Tensor([vSpace pSpace], [pSpace vSpace]); - Sx = fill_matrix(S, sigma_x); - Sz = fill_matrix(S, sigma_z); - - cod = SumSpace([vSpace vSpace vSpace], pSpace); - dom = SumSpace(pSpace, [vSpace vSpace vSpace]); - O = MpoTensor.zeros(cod, dom); - O(1, 1, 1, 1) = 1; - O(3, 1, 3, 1) = 1; - O(1, 1, 2, 1) = -J * Sx; - O(2, 1, 3, 1) = Sx; - O(1, 1, 3, 1) = (-J * h) * Sz; - -else - pSpace = GradedSpace.new(Z2(0, 1), [1 1], false); - vSpace = GradedSpace.new(Z2(1), 1, false); - trivSpace = one(pSpace); - - Sx_l = fill_matrix(Tensor([trivSpace pSpace], [pSpace vSpace]), {1 1}); - Sx_r = fill_matrix(Tensor([vSpace pSpace], [pSpace trivSpace]), {1 1}); - Sz = fill_matrix(Tensor([trivSpace pSpace], [pSpace trivSpace]), {1 -1}); - - cod = SumSpace([one(vSpace) vSpace one(vSpace)], pSpace); - dom = SumSpace(pSpace, [one(vSpace), vSpace, one(vSpace)]); - O = MpoTensor.zeros(cod, dom); - O(1, 1, 1, 1) = 1; - O(3, 1, 3, 1) = 1; - O(1, 1, 2, 1) = -J * Sx_l; - O(2, 1, 3, 1) = Sx_r; - O(1, 1, 3, 1) = (-J * h) * Sz; -end - -mpo = InfJMpo(O); - -if isfinite(kwargs.L) - mpo = open_boundary_conditions(InfJMpo(O), kwargs.L); -end - -end - diff --git a/src/models/statmech2dIsing.m b/src/models/statmech2dIsing.m deleted file mode 100644 index 81539ad..0000000 --- a/src/models/statmech2dIsing.m +++ /dev/null @@ -1,60 +0,0 @@ -function O = statmech2dIsing(kwargs) - -arguments - kwargs.beta = log(1 + sqrt(2)) / 2; - kwargs.L = Inf % size of system - kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'Z2'})} = 'Z1' -end - -if ~isfinite(kwargs.L) - O = InfMpo({MpoTensor(bulk_mpo(kwargs.beta, kwargs.Symmetry))}); - -else - r = boundary_mpo(kwargs.beta, kwargs.Symmetry); - l = r'; - o = bulk_mpo(kwargs.beta, kwargs.Symmetry); - - O = FiniteMpo(MpsTensor(l), repmat({MpoTensor(o)}, 1, kwargs.L), MpsTensor(r)); -end - -end - -function O = bulk_mpo(beta, symmetry) - -if strcmp(symmetry, 'Z1') - sz = [2 2 2 2]; - t = sqrtm([exp(beta) exp(-beta); exp(-beta) exp(beta)]); - o = contract(diracdelta(sz), 1:4, t, [-1 1], t, [-2 2], t, [-3 3], t, [-4 4]); - O = fill_tensor(Tensor.zeros(sz), o); - -elseif strcmp(symmetry, 'Z2') - s = GradedSpace.new(Z2(0, 1), [1 1], false); - t = sqrtm(fill_matrix(Tensor(s, s), num2cell(2 * [cosh(beta) sinh(beta)]))); - o = Tensor.ones([s s], [s s]) / 2; - - O = contract(o, 1:4, t, [-1 1], t, [-2 2], t, [3 -3], t, [4 -4], 'Rank', [2 2]); -else - error('invalid symmetry'); -end - -end - -function O = boundary_mpo(beta, symmetry) - -if strcmp(symmetry, 'Z1') - sz = [2 2 2]; - t = sqrtm([exp(beta) exp(-beta); exp(-beta) exp(beta)]); - o = contract(diracdelta(sz), 1:3, t, [-1 1], t, [-2 2], t, [-3 3]); - O = fill_tensor(Tensor.zeros(sz), o); - -elseif strcmp(symmetry, 'Z2') - s = GradedSpace.new(Z2(0, 1), [1 1], false); - t = sqrtm(fill_matrix(Tensor(s, s), num2cell(2 * [cosh(beta) sinh(beta)]))); - o = Tensor.ones([s s], s) / sqrt(2); - - O = contract(o, 1:3, t, [-1 1], t, [-2 2], t, [3 -3], 'Rank', [2 1]); -else - error('invalid symmetry'); -end - -end \ No newline at end of file diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index 015cc92..110dd99 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -64,61 +64,6 @@ end end - function mps = initialize_mps(mpo, kwargs) - arguments - mpo - kwargs.MaxVspace - end - - pspaces = arrayfun(@(x) pspace(mpo, x), 1:length(mpo), 'UniformOutput', false); - - vspacefirst = rightvspace(mpo.L)'; - vspacelast = leftvspace(mpo.R); - - newkwargs = namedargs2cell(kwargs); - mps = FiniteMps.randnc(pspaces{:}, 'LeftVspace', vspacefirst, ... - 'RightVspace', vspacelast, newkwargs{:}); - mps = normalize(mps); - end - - function envs = initialize_envs(mpo) - arguments - mpo - end - - GL = cell(1, length(mpo) + 1); - GR = cell(1, length(mpo) + 1); - - GL{1} = mpo.L; - GR{end} = mpo.R; - - envs = FiniteEnvironment(GL, GR); - end - - function T = transfermatrix(mpo, mps1, mps2, sites) - arguments - mpo - mps1 - mps2 = mps1 - sites = 1:length(mps1) - end - - assert(all(diff(sites) == 1), 'sites must be neighbouring and increasing.'); - A1 = mps1.A(sites); - A2 = mps2.A(sites); - O = mpo.O(sites); %#ok - T = FiniteMpo.mps_channel_operator(A1, O, A2); %#ok - end - - function H = AC_hamiltonian(mpo, mps, envs, pos) - envs = movegaugecenter(envs, mpo, mps, mps, pos); - H = FiniteMpo(envs.GL{pos}, mpo.O(pos), envs.GR{pos + 1}); - end - - function s = pspace(mpo, x) - s = pspace(mpo.O{x}); - end - function s = domain(mpo) N = prod(cellfun(@(x) size(x, 4), mpo(1).O)); s = conj(... diff --git a/src/mps/FiniteMps.m b/src/mps/FiniteMps.m deleted file mode 100644 index d0c6076..0000000 --- a/src/mps/FiniteMps.m +++ /dev/null @@ -1,285 +0,0 @@ -classdef FiniteMps - % Finite Matrix product states - - properties - A (1,:) MpsTensor - center - end - - %% Constructors - methods - function mps = FiniteMps(varargin) - if nargin == 0, return; end - if nargin == 1 - mps.A = varargin{1}; - elseif nargin == 2 - mps.A = varargin{1}; - mps.center = varargin{2}; - end - end - end - - methods (Static) - function mps = new(fun, pspaces, kwargs) - arguments - fun - end - arguments (Repeating) - pspaces - end - arguments - kwargs.L - kwargs.MaxVspace - kwargs.LeftVspace = one(pspaces{1}) - kwargs.RightVspace = one(pspaces{1}) - end - - if isempty(fun), fun = @randnc; end - - if isfield(kwargs, 'L') - pspaces = repmat(pspaces, 1, kwargs.L); - end - if isfield(kwargs, 'MaxVspace') && ~iscell(kwargs.MaxVspace) - kwargs.MaxVspace = {kwargs.MaxVspace}; - end - - L = length(pspaces); - - vspacesL = cell(1, L + 1); - vspacesL{1} = kwargs.LeftVspace; - for i = 1:L - vspacesL{i+1} = vspacesL{i} * pspaces{i}; - if isfield(kwargs, 'MaxVspace') - vspacesL{i + 1} = infimum(vspacesL{i + 1}, ... - kwargs.MaxVspace{mod1(i + 1, length(kwargs.MaxVspace))}); - end - end - - vspacesR = cell(1, L + 1); - vspacesR{end} = kwargs.RightVspace; - for i = L:-1:1 - vspacesR{i} = pspaces{i}' * vspacesR{i+1}; - if isfield(kwargs, 'MaxVspace') - vspacesR{i} = infimum(vspacesR{i}, ... - kwargs.MaxVspace{mod1(i, length(kwargs.MaxVspace))}); - end - end - - vspaces = cellfun(@infimum, vspacesL, vspacesR, 'UniformOutput', false); - - for w = length(pspaces):-1:1 - rankdeficient = vspaces{w} * pspaces{w} < vspaces{w + 1} || ... - vspaces{w} > pspaces{w}' * vspaces{w + 1}; - if rankdeficient - error('mps:rank', ... - 'Cannot create a full rank mps with given spaces.'); - end - - A{w} = Tensor.new(fun, [vspaces{w} pspaces{w}], vspaces{w + 1}); - end - mps = FiniteMps(A); - end - - function mps = new_from_spaces(fun, spaces, kwargs) - arguments - fun - end - arguments (Repeating) - spaces - end - arguments - kwargs.MaxVspace - end - - if isfield(kwargs, 'MaxVspace') && ~iscell(kwargs.MaxVspace) - kwargs.MaxVspace = {kwargs.MaxVspace}; - end - - assert(mod(length(spaces) - 1, 2)); - if isempty(fun), fun = @randnc; end - - L = (length(spaces) - 1) / 2; - - for w = L:-1:1 - Vleft = spaces{2 * w - 1}; - P = spaces{2 * w}; - Vright = spaces{2 * w + 1}; - rankdeficient = Vleft * P < Vright || Vleft > P' * Vright; - if rankdeficient - error('mps:rank', ... - 'Cannot create a full rank mps with given spaces.'); - end - A{w} = Tensor.new(fun, [Vleft P], Vright); - end - - mps = FiniteMps(A); - end - - function mps = randnc(varargin) - mps = FiniteMps.new(@randnc, varargin{:}); - end - end - - - %% Properties - methods - function p = length(mps) - p = length(mps.A); - end - - function [svals, charges] = schmidt_values(mps, w) - arguments - mps - w {mustBeInteger} = floor(length(mps) / 2) - end - - assert(0 <= w && w <= length(mps)); - if isempty(mps.center), mps = movegaugecenter(mps, w); end - - if w < mps.center - mps = movegaugecenter(mps, w + 1); - S = tsvd(mps.A(w + 1), 1, 2:nspaces(mps.A(w + 1))); - else - mps = movegaugecenter(mps, w); - S = tsvd(mps.A(w), 1:nspaces(mps.A(w))-1, nspaces(mps.A(w))); - end - [svals, charges] = matrixblocks(S); - svals = cellfun(@diag, svals, 'UniformOutput', false); - end - end - - - %% Derived operators - methods - function T = transfermatrix(mps1, mps2, sites) - arguments - mps1 - mps2 = mps1 - sites = 1:length(mps1) - end - - assert(all(diff(sites) == 1), 'sites must be neighbouring and increasing.'); - T = transfermatrix(mps1.A(sites), mps2.A(sites)); - end - end - - - %% Methods - methods - function mps = movegaugecenter(mps, newcenter, alg) - arguments - mps - newcenter {mustBeInteger} = floor(length(mps) / 2) - alg = 'polar' - end - - assert(newcenter >= 1 && newcenter <= length(mps), ... - 'mps:gauge', ... - sprintf('new center (%d) must be in 1:%d', newcenter, length(mps))); - - if isempty(mps.center) - low = 1; - high = length(mps); - else - low = mps.center; - high = mps.center; - end - - for i = low:(newcenter - 1) - [mps.A(i), L] = leftorth(mps.A(i), alg); - mps.A(i + 1) = multiplyleft(mps.A(i + 1), L); - end - for i = high:-1:(newcenter + 1) - [R, mps.A(i)] = rightorth(mps.A(i), alg); - [mps.A(i - 1)] = multiplyright(mps.A(i - 1), R); - end - mps.center = newcenter; - end - - function rho = fixedpoint(mps, type, w) - arguments - mps - type {mustBeMember(type, {'l', 'r'})} - w = floor(length(mps) / 2); - end - - if strcmp(type, 'l') - if mps.center > w - rho = mps.A.eye(leftvspace(mps, w), leftvspace(mps, w)); - else - T = transfermatrix(mps, mps, mps.center:w); - rho = apply(T, []); - end - else - if mps.center < w - rho = mps.A.eye(rightvspace(mps, w)', rightvspace(mps, w)'); - else - T = transfermatrix(mps, mps, w:mps.center).'; - rho = apply(T, []); - end - end - - end - - function o = overlap(mps1, mps2, rholeft, rhoright) - arguments - mps1 - mps2 - rholeft = [] - rhoright = [] - end - - assert(length(mps1) == length(mps2), 'mps should have equal length'); - assert(length(mps1) >= 2, 'edge case to be added'); - - n = floor(length(mps1) / 2); - Tleft = transfermatrix(mps1, mps2, 1:n); - rholeft = apply(Tleft, rholeft); - Tright = transfermatrix(mps1, mps2, n+1:length(mps1)).'; - rhoright = apply(Tright, rhoright); - - o = overlap(rholeft, rhoright); - end - - function n = norm(mps) - if isempty(mps.center) - n = sqrt(abs(overlap(mps, mps))); - return - end - - n = norm(mps.A(mps.center)); - end - - function [mps, n] = normalize(mps) - if isempty(mps.center), mps = movegaugecenter(mps); end - n = norm(mps); - mps.A(mps.center) = mps.A(mps.center) ./ n; - end - end - - - %% Converters - methods - function psi = Tensor(mps) - % Convert a finite mps to a dense tensor. - - tensors = num2cell(mps.A); - indices = cell(size(tensors)); - - nout = nspaces(mps.A(1)) - 1; - indices{1} = [-(1:nout), 1]; - - for i = 2:length(indices)-1 - plegs = mps.A(i).plegs; - indices{i} = [i-1 -(1:plegs)-nout i]; - nout = nout + plegs; - end - - plegs = mps.A(end).plegs; - indices{end} = [length(indices)-1 -(1:plegs+1)-nout]; - - args = [tensors; indices]; - psi = contract(args{:}); - end - end -end diff --git a/src/mps/InfJMpo.m b/src/mps/InfJMpo.m index c05fff7..8817793 100644 --- a/src/mps/InfJMpo.m +++ b/src/mps/InfJMpo.m @@ -155,6 +155,11 @@ end end + function mpo = horzcat(varargin) + Os = cellfun(@(x) x.O, varargin, 'UniformOutput', false); + mpo = InfJMpo([Os{:}]); + end + function mpo = plus(a, b) if isa(a, 'InfJMpo') && isnumeric(b) if period(a) > 1 && isscalar(b) @@ -178,20 +183,92 @@ mpo = [mpo; b]; end - - function finitempo = open_boundary_conditions(mpo, L) - Os = repmat(mpo.O, 1, L); + end + + methods (Static) + function mpo = Ising(J, h, kwargs) + arguments + J = 1 + h = 1 + kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'Z2'})} = 'Z1' + end - Os{1} = Os{1}(1, :, :, :); - Os{end} = Os{end}(:, :, end, :); + sigma_x = [0 1; 1 0]; + sigma_z = [1 0; 0 -1]; - rspace = subspaces(rightvspace(mpo, period(mpo)), size(Os{end}, 3)); - rightedge = MpsTensor(Tensor.eye([one(rspace) rspace'], one(rspace))); + if strcmp(kwargs.Symmetry, 'Z1') + pSpace = CartesianSpace.new(2); + vSpace = one(pSpace); + S = Tensor([vSpace pSpace], [pSpace vSpace]); + Sx = fill_matrix(S, sigma_x); + Sz = fill_matrix(S, sigma_z); + + cod = SumSpace([vSpace vSpace vSpace], pSpace); + dom = SumSpace(pSpace, [vSpace vSpace vSpace]); + O = MpoTensor.zeros(cod, dom); + O(1, 1, 1, 1) = 1; + O(3, 1, 3, 1) = 1; + O(1, 1, 2, 1) = -J * Sx; + O(2, 1, 3, 1) = Sx; + O(1, 1, 3, 1) = (-J * h) * Sz; + + else + pSpace = GradedSpace.new(Z2(0, 1), [1 1], false); + vSpace = GradedSpace.new(Z2(1), 1, false); + trivSpace = one(pSpace); + + Sx_l = fill_matrix(Tensor([trivSpace pSpace], [pSpace vSpace]), {1 1}); + Sx_r = fill_matrix(Tensor([vSpace pSpace], [pSpace trivSpace]), {1 1}); + Sz = fill_matrix(Tensor([trivSpace pSpace], [pSpace trivSpace]), {1 -1}); + + cod = SumSpace([one(vSpace) vSpace one(vSpace)], pSpace); + dom = SumSpace(pSpace, [one(vSpace), vSpace, one(vSpace)]); + O = MpoTensor.zeros(cod, dom); + O(1, 1, 1, 1) = 1; + O(3, 1, 3, 1) = 1; + O(1, 1, 2, 1) = -J * Sx_l; + O(2, 1, 3, 1) = Sx_r; + O(1, 1, 3, 1) = (-J * h) * Sz; + end - lspace = subspaces(leftvspace(mpo, 1), size(Os{1}, 1)); - leftedge = MpsTensor(Tensor.eye([one(lspace) lspace], one(lspace))'); + mpo = InfJMpo(O); + end + + function mpo = Heisenberg(J, h, kwargs) + arguments + J = 1 + h = 0 + kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'U1', 'SU2'})} = 'Z1' + kwargs.Spin = SU2(3) + end - finitempo = FiniteMpo(leftedge, Os, rightedge); + switch kwargs.Symmetry + case 'SU2' + assert(isscalar(J) || all(J == J(1)), ... + 'Different spin couplings not invariant under SU2'); + assert(h == 0, 'Magnetic field not invariant under SU2'); + + pSpace = GradedSpace.new(kwargs.Spin, 1, false); + vSpace = GradedSpace.new(SU2(3), 1, false); + tSpace = one(vSpace); + + s = spin(kwargs.Spin); + L = Tensor.ones([tSpace pSpace], [pSpace vSpace]); + L = L * (-J(1) * (s^2 + s)); + R = Tensor.ones([vSpace pSpace], [pSpace tSpace]); + + cod = SumSpace([one(vSpace) vSpace one(vSpace)], pSpace); + dom = SumSpace(pSpace, [one(vSpace), vSpace, one(vSpace)]); + O = MpoTensor.zeros(cod, dom); + O(1, 1, 1, 1) = 1; + O(3, 1, 3, 1) = 1; + O(1, 1, 2, 1) = L; + O(2, 1, 3, 1) = R; + + otherwise + error('TBA'); + end + mpo = InfJMpo(O); end end end diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index ef85d9d..339d328 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -13,13 +13,18 @@ O = varargin{1}; if isa(O, 'InfMpo') - mpo.O = O.O; + for i = numel(O):1:1 + mpo(i, 1).O = O(i, 1).O; + end + mpo = reshape(mpo, size(O)); elseif isa(O, 'MpoTensor') mpo.O = {O}; elseif isa(O, 'AbstractTensor') - mpo.O = arrayfun(@MpoTensor, O, 'UniformOutput', false); + for i = height(O):-1:1 + mpo(i, 1).O = arrayfun(@MpoTensor, O(i, :), 'UniformOutput', false); + end elseif iscell(O) mpo.O = O; @@ -31,55 +36,44 @@ methods function p = period(mpo) - p = size(mpo.O, 2); + p = length(mpo(1).O); end function d = depth(mpo) - d = size(mpo.O, 1); + d = length(mpo); end function mpo = block(mpo) if depth(mpo) == 1, return; end - O_ = mpo.O(1, :); + O_ = mpo(1, 1).O; for d = 2:depth(mpo) for w = period(mpo):-1:1 - vspaces = [rightvspace(O_{w})' rightvspace(mpo.O{d, w})']; + vspaces = [rightvspace(O_{w})' rightvspace(mpo(d, 1).O{w})']; fuser(w) = Tensor.eye(vspaces, prod(vspaces)); end for w = 1:period(mpo) - O_{w} = MpoTensor(contract(O_{w}, [1 2 5 -4], mpo.O{d, w}, [3 -2 4 2], ... + O_{w} = MpoTensor(contract(O_{w}, [1 2 5 -4], mpo(d, 1).O{w}, [3 -2 4 2], ... fuser(prev(w, period(mpo)))', [-1 3 1], fuser(w), [5 4 -3], ... 'Rank', [2 2])); end end + mpo = mpo(1, 1); mpo.O = O_; end - function s = pspace(mpo, w) - s = pspace(mpo.O{w}); - end - - function s = leftvspace(mpo, w) - s = leftvspace(mpo.O{w}); - end - - function s = rightvspace(mpo, w) - s = rightvspace(mpo.O{w}); + function s = pspace(mpo, x) + s = pspace(mpo.O{x}); end - function mpo = horzcat(mpo, varargin) + function mpo = horzcat(varargin) Os = cellfun(@(x) x.O, varargin, 'UniformOutput', false); - mpo.O = horzcat(mpo.O, Os{:}); + mpo = InfMpo([Os{:}]); end - function mpo = vertcat(mpo, varargin) + function mpo = vertcat(varargin) Os = cellfun(@(x) x.O, varargin, 'UniformOutput', false); - mpo.O = vertcat(mpo.O, Os{:}); - end - - function mpo = slice(mpo, d) - mpo.O = mpo.O(d, :); + mpo = InfMpo(vertcat(Os{:})); end function mps = initialize_mps(mpo, vspaces) @@ -219,12 +213,10 @@ H = cell(1, length(sites)); for i = 1:length(sites) - for d = depth(mpo):-1:1 - gl = twistdual(GL{d, sites(i)}, 1); - gr = GR{d, next(sites(i), period(mps))}; - gr = twistdual(gr, nspaces(gr)); - H{i}(d, 1) = FiniteMpo(gl, mpo.O(d, sites(i)), gr); - end + gl = twistdual(GL{sites(i)}, 1); + gr = GR{next(sites(i), period(mps))}; + gr = twistdual(gr, nspaces(gr)); + H{i} = FiniteMpo(gl, mpo.O(sites(i)), gr); end end @@ -239,19 +231,17 @@ H = cell(1, length(sites)); for i = 1:length(sites) - for d = depth(mpo):-1:1 - gl = GL{d, sites(i)}; - gl = twistdual(gl, 1); - gr = GR{d, mod1(sites(i) + 2, period(mps))}; - gr = twistdual(gr, nspaces(gr)); - H{i}(d, 1) = FiniteMpo(gl, mpo.O(d, mod1(sites(i) + [0 1], period(mps))), gr); - end + gl = GL{sites(i)}; + gl = twistdual(gl, 1); + gr = GR{mod1(sites(i) + 2, period(mps))}; + gr = twistdual(gr, nspaces(gr)); + H{i} = FiniteMpo(gl, mpo.O(mod1(sites(i) + [0 1], period(mps))), gr); end end - function H = C_hamiltonian(mpo, mps, GL, GR, sites) + function H = C_hamiltonian(~, mps, GL, GR, sites) arguments - mpo + ~ mps GL = fixedpoint(transfermatrix(mpo, mps, 'Type', 'LL')) GR = fixedpoint(transfermatrix(mpo, mps, 'Type', 'RR').') @@ -260,27 +250,53 @@ H = cell(1, length(sites)); for i = 1:length(sites) - for d = depth(mpo):-1:1 - gl = GL{d, next(sites(i), period(mps))}; - for j = 1:numel(gl) - if nnz(gl(j)) ~= 0 - gl(j) = twist(gl(j), find(isdual(space(gl(j), 1)))); - end + gl = GL{next(sites(i), period(mps))}; + for j = 1:numel(gl) + if nnz(gl(j)) ~= 0 + gl(j) = twist(gl(j), find(isdual(space(gl(j), 1)))); end - gr = GR{d, next(sites(i), period(mps))}; - for j = 1:numel(gr) - if nnz(gr(j)) ~= 0 - gr(j) = twist(gr(j), find(isdual(space(gr(j), nspaces(gr(j))))) + ... - nspaces(gr(j)) - 1); - end + end + gr = GR{next(sites(i), period(mps))}; + for j = 1:numel(gr) + if nnz(gr(j)) ~= 0 + gr(j) = twist(gr(j), find(isdual(space(gr(j), nspaces(gr(j))))) + ... + nspaces(gr(j)) - 1); end - H{i}(d, 1) = FiniteMpo(gl, {}, gr); end + H{i} = FiniteMpo(gl, {}, gr); end end end methods (Static) + function mpo = Ising(beta, kwargs) + arguments + beta = log(1 + sqrt(2)) / 2; + kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'Z2'})} = 'Z1' + end + + if strcmp(kwargs.Symmetry, 'Z1') + t = [exp(beta) exp(-beta); exp(-beta) exp(beta)]; + [v, d] = eig(t); + t = v * sqrt(d) * v; + + o = zeros(2, 2, 2, 2); + o(1, 1, 1, 1) = 1; + o(2, 2, 2, 2) = 1; + + o = contract(o, 1:4, t, [-1 1], t, [-2 2], t, [-3 3], t, [-4 4]); + + O = fill_tensor(Tensor.zeros([2 2 2 2]), o); + + else + s = GradedSpace.new(Z2(0, 1), [1 1], false); + O = fill_tensor(Tensor([s s], [s s]), ... + @(~, f) 2 * sqrt(prod(logical(f.uncoupled) .* sinh(beta) + ... + ~logical(f.uncoupled) .* cosh(beta)))); + end + + mpo = InfMpo(O); + end function mpo = fDimer() pspace = GradedSpace.new(fZ2(0, 1), [1 1], false); diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index 394c3f2..bdc1088 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -13,25 +13,13 @@ properties tensors = [] - scalars SparseArray = [] + scalars = [] end methods function n = nspaces(~) n = 4; end - - function dom = domain(t) - dom = t.tensors.domain; - end - - function cod = codomain(t) - cod = t.tensors.codomain; - end - - function r = rank(t) - r = rank(t.tensors); - end end methods @@ -46,8 +34,8 @@ t.scalars = varargin{1}.scalars; t.tensors = varargin{1}.tensors; elseif isa(varargin{1}, 'Tensor') || isa(varargin{1}, 'SparseTensor') - t.tensors = varargin{1}; - t.scalars = SparseArray.zeros(size(t.tensors, 1:4)); + t.tensors = sparse(varargin{1}); + t.scalars = zeros(size(t.tensors)); end return end @@ -187,22 +175,16 @@ iB(1:2) = flip(iB(1:2)); end - [Ia, Ja, Va] = find(spmatrix(reshape(permute(A.scalars, iA), ... - [prod(size(A, uncA)) prod(size(A, dimA))]))); - [Ib, Jb, Vb] = find(reshape(tpermute(B, iB, rB), ... - [prod(size(B, dimB)) prod(size(B, uncB))])); - sz2 = [prod(size(A, uncA)) prod(size(B, uncB))]; + A_ = reshape(permute(A.scalars, iA), ... + [prod(size(A, uncA)) prod(size(A, dimA))]); + B = reshape(tpermute(B, iB, rB), ... + [prod(size(B, dimB)) prod(size(B, uncB))]); - for i = 1:length(Jb) - mask = find(Ja == Ib(i)); - if isempty(mask), continue; end - subs = [Ia(mask) repmat(Jb(i), length(mask), 1)]; - idx = sb2ind_(sz2, subs); - C(idx) = C(idx) + Va(mask) .* Vb(i); - end + C = C + reshape(sparse(A_) * B, size(C)); end else C = tensorprod(A, B.tensors, dimA, dimB, ca, cb); + szC = size(C); if nnz(B.scalars) > 0 assert(sum(dimB == 1 | dimB == 3, 'all') == 1, ... 'Cannot deduce output space unless leg 1 xor leg 3 is connected.'); @@ -222,19 +204,24 @@ iB(1:2) = flip(iB(1:2)); end - [Ia, Ja, Va] = find(reshape(tpermute(A, iA, rA), ... - [prod(size(A, uncA)) prod(size(A, dimA))])); - [Ib, Jb, Vb] = find(spmatrix(reshape(permute(B.scalars, iB), ... - [prod(size(B, dimB)) prod(size(B, uncB))]))); - sz2 = [prod(size(A, uncA)) prod(size(B, uncB))]; + A_ = reshape(tpermute(A, iA, rA), ... + [prod(size(A, uncA)) prod(size(A, dimA))]); + B_ = reshape(permute(B.scalars, iB), ... + [prod(size(B, dimB)) prod(size(B, uncB))]); + - for i = 1:length(Ia) - mask = find(Ja(i) == Ib); - if isempty(mask), continue; end - subs = [repmat(Ia(i), length(mask), 1) Jb(mask)]; + subs = zeros(size(A_, 1), 2); + subs(:, 1) = (1:size(A_, 1)).'; + sz2 = [size(A_, 1) size(B_, 2)]; + [Brows, Bcols, Bvals] = find(B_); + C = reshape(C, sz2); + for i = 1:length(Bvals) + Atmp = A_(:, Brows(i)) .* Bvals(i); + subs(:, 2) = Bcols(i); idx = sub2ind_(sz2, subs); - C(idx) = C(idx) + Va(i) .* Vb(mask); + C(idx) = C(idx) + Atmp; end + C = reshape(C, szC); end end end @@ -252,9 +239,6 @@ methods function s = space(O, i) - if nargin == 1 - i = 1:nspaces(O); - end s = space(O.tensors, i); end @@ -287,12 +271,7 @@ end function bool = iseye(O) - bool = nnz(O.tensors) == 0; - if ~bool, return; end - - scal_mat = reshape(O.scalars, ... - prod(size(O.scalars, 1:2)), prod(size(O.scalars, 3:4))); - bool = isequal(spmatrix(scal_mat), speye(size(scal_mat))); + bool = nnz(O.tensors) == 0 && isequal(O.scalars, eye(size(O.scalars))); end function n = nnz(O) @@ -335,7 +314,7 @@ i = prod(size(t)); return end - if k > ndims(t) + if n > ndims(t) i = 1; else i = size(t, k); @@ -362,20 +341,12 @@ [varargout{1:nargout}] = size(t.scalars); end end - - function n = ndims(t) - n = ndims(t.scalars); - end - - function disp(O) - builtin('disp', O); - end end methods (Static) function O = zeros(codomain, domain) tensors = SparseTensor.zeros(codomain, domain); - scalars = SparseArray.zeros(size(tensors)); + scalars = zeros(size(tensors)); O = MpoTensor(tensors, scalars); end @@ -428,3 +399,4 @@ function disp(O) end end end + diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index ba737af..94fa534 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -115,41 +115,19 @@ function A = plus(varargin) for i = 1:2 if isa(varargin{i}, 'MpsTensor') - alegs = varargin{i}.alegs; varargin{i} = varargin{i}.var; end end - A = MpsTensor(plus(varargin{:}), alegs); + A = plus(varargin{:}); end function A = minus(varargin) for i = 1:2 if isa(varargin{i}, 'MpsTensor') - alegs = varargin{i}.alegs; varargin{i} = varargin{i}.var; end end - A = MpsTensor(minus(varargin{:}), alegs); - end - - function A = rdivide(varargin) - for i = 1:2 - if isa(varargin{i}, 'MpsTensor') - alegs = varargin{i}.alegs; - varargin{i} = varargin{i}.var; - end - end - A = MpsTensor(rdivide(varargin{:}), alegs); - end - - function A = ldivide(varargin) - for i = 1:2 - if isa(varargin{i}, 'MpsTensor') - alegs = varargin{i}.alegs; - varargin{i} = varargin{i}.var; - end - end - A = MpsTensor(ldivide(varargin{:}), alegs); + A = minus(varargin{:}); end function n = norm(A) @@ -220,6 +198,7 @@ C = tensorprod(varargin{:}); end + function [AL, CL, lambda, eta] = uniform_leftorth(A, CL, kwargs) arguments A @@ -314,6 +293,7 @@ end end + function [AR, CR, lambda, eta] = uniform_rightorth(A, CR, kwargs) arguments A @@ -359,12 +339,7 @@ arguments L MpsTensor R MpsTensor - v = [] - end - - if isempty(v) - v = tracetransfer(L, R); - return + v end auxlegs_v = nspaces(v) - 2; @@ -378,24 +353,6 @@ 'Rank', newrank); end - function v = tracetransfer(L, R) - arguments - L MpsTensor - R MpsTensor - end - - auxlegs_l = L.alegs; - auxlegs_r = R.alegs; - assert(R.plegs == L.plegs); - plegs = L.plegs; %#ok - newrank = [2 auxlegs_l + auxlegs_r]; - - v = contract(... - L, [-1 1:(plegs + 1) (-(1:auxlegs_l) - 2)], ... - R, [flip(1:(plegs + 1)) -2 (-(1:auxlegs_r) - 3 - auxlegs_l)], ... - 'Rank', newrank); %#ok - end - function rho = applyleft(T, B, rho) if nargin == 2 rho = []; @@ -550,10 +507,6 @@ function type = underlyingType(A) type = underlyingType(A.var); end - - function disp(t) - builtin('disp', t); - end end @@ -570,43 +523,5 @@ function disp(t) t = sparse(t); end end - - - %% - methods (Static) - function local_tensors = decompose_local_state(psi, kwargs) - % convert a tensor into a product of local operators. - % - % Usage - % ----- - % :code:`local_operators = MpoTensor.decompose_local_operator(H, kwargs)`. - % - % Arguments - % --------- - % H : :class:`AbstractTensor` - % tensor representing a local operator on N sites. - % - % Keyword Arguments - % ----------------- - % 'Trunc' : cell - % optional truncation method for the decomposition. See also - % :method:`Tensor.tsvd` - arguments - psi - kwargs.Trunc = {'TruncBelow', 1e-14} - end - - L = nspaces(psi); - assert(L >= 3, 'argerror', ... - sprintf('state must have at least 3 legs. (%d)', L)); - - local_tensors = cell(1, L - 2); - for i = 1:length(local_tensors)-1 - [u, s, psi] = tsvd(psi, [1 2], [3:nspaces(psi)], kwargs.Trunc{:}); - local_tensors{i} = multiplyright(MpsTensor(u), s); - end - local_tensors{end} = MpsTensor(repartition(psi, [2 1])); - end - end end diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index 69ddd44..d8409ed 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -170,7 +170,9 @@ methods function p = period(mps) % period over which the mps is translation invariant. - p = length(mps(1).AL); + for i = numel(mps):-1:1 + p(i) = length(mps(i).AL); + end end function d = depth(mps) @@ -186,14 +188,6 @@ mps = UniformMps([ALs{:}], [ARs{:}], [Cs{:}], [ACs{:}]); end - function mps = vertcat(varargin) - for i = 2:length(varargin) - assert(period(varargin{1}) == period(varargin{i}), ... - 'Can only stack UniformMps with matching periods.') - end - mps = builtin('vertcat', varargin{:}); - end - function s = leftvspace(mps, w) % return the virtual space to the left of site w. if nargin == 1 || isempty(w), w = 1:period(mps); end @@ -607,46 +601,43 @@ svals = cellfun(@diag, svals, 'UniformOutput', false); end - function plot_entanglementspectrum(mps, d, w, ax) - arguments - mps - d = 1:depth(mps) - w = 1:period(mps) - ax = [] - end - if isempty(ax) + function plot_entanglementspectrum(mps, w, ax) + if nargin == 1 + w = 1:period(mps); figure; ax = gobjects(depth(mps), width(mps)); - for dd = 1:length(d) - for ww = 1:length(w) - ax(dd, ww) = subplot(length(d), length(w), ww + (dd-1)*length(w)); - end + for ww = 1:length(w) + ax(1, ww) = subplot(1, length(w), ww); + end + elseif nargin == 2 + figure; + ax = gobjects(depth(mps), width(mps)); + for ww = 1:length(w) + ax(1, ww) = subplot(1, length(w), ww); end end lim_y = 1; - for dd = 1:length(d) - for ww = 1:length(w) - [svals, charges] = schmidt_values(mps(dd), w(ww)); - ctr = 0; - hold off; - lim_x = 1; - - ticks = zeros(size(svals)); - labels = arrayfun(@string, charges, 'UniformOutput', false); - lengths = cellfun(@length, svals); - ticks = cumsum(lengths); - try - semilogy(ax(dd, ww), 1:sum(lengths), vertcat(svals{:}).', '.', 'MarkerSize', 10); - catch - bla - end - set(ax(dd, ww), 'TickLabelInterpreter', 'latex'); - set(ax(dd, ww), 'Xtick', ticks, 'XTickLabel', labels, 'fontsize', 10, ... - 'XtickLabelRotation', 60, 'Xgrid', 'on'); - xlim(ax(dd, ww), [1 - 1e-8 ticks(end) + 1e-8]); - + for ww = 1:length(w) + [svals, charges] = schmidt_values(mps, w(ww)); + ctr = 0; + hold off; + lim_x = 1; + + ticks = zeros(size(svals)); + labels = arrayfun(@string, charges, 'UniformOutput', false); + lengths = cellfun(@length, svals); + ticks = cumsum(lengths); + try + semilogy(ax(1, ww), 1:sum(lengths), vertcat(svals{:}).', '.', 'MarkerSize', 10); + catch + bla end + set(ax(1, ww), 'TickLabelInterpreter', 'latex'); + set(ax(1, ww), 'Xtick', ticks, 'XTickLabel', labels, 'fontsize', 10, ... + 'XtickLabelRotation', 60, 'Xgrid', 'on'); + xlim(ax(1, ww), [1 - 1e-8 ticks(end) + 1e-8]); + end % for ww = 1:length(w) diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index aa49de1..f6cf552 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -639,7 +639,7 @@ function disp(t) end Ccod = space(A, uncA); - Cdom = space(B, uncB)'; + Cdom = space(B, uncB); C = SparseTensor(Cind, Cvar, [size(A, 1) size(B, 2)], Ccod, Cdom); C = reshape(C, szC); @@ -819,7 +819,7 @@ function disp(t) return end - subs = sub2sub([t.sz(1) prod(t.sz(2:end))], t.sz, inds); + subs = sub2sub([t.sz(1) prod(t.sz(2:end))], t.sz, t.ind); I = subs(:,1); J = subs(:,2); @@ -878,7 +878,8 @@ function disp(t) t.var = t.var(p); end - function varargout = subsref(t, s) + function t = subsref(t, s) + switch s(1).type case '()' n = size(s(1).subs, 2); @@ -902,12 +903,6 @@ function disp(t) if nA ~= length(unique(A)) error("Repeated index in position %i",i); end - if i > length(t.codomain) - t.domain(end-(i-length(t.codomain))+1) = ... - SumSpace(subspaces(t.domain(end-(i-length(t.codomain))+1), A)); - else - t.codomain(i) = SumSpace(subspaces(t.codomain(i), A)); - end if ~isempty(t.ind) B = t.ind(:, i); P = false(max(max(A), max(B)) + 1, 1); @@ -927,18 +922,13 @@ function disp(t) assert(isscalar(t)) t = subsref(t.var, s(2:end)); end - varargout{1} = t; case '.' - [varargout{1:nargout}] = builtin('subsref', t, s); + t = builtin('subsref', t, s); otherwise error('sparse:index', '{} indexing not defined'); end end - function n = numArgumentsFromSubscript(t, ~, ~) - n = 1; - end - function t = subsasgn(t, s, v) assert(strcmp(s(1).type, '()'), 'sparse:index', 'only () indexing allowed'); @@ -950,7 +940,7 @@ function disp(t) assert(length(s(1).subs) == size(t.sz, 2), 'sparse:index', ... 'number of indexing indices must match tensor size.'); assert(all(t.sz >= cellfun(@max, s(1).subs)), 'sparse:bounds', ... - 'out of bounds assignment disallowed'); + 'attempting to assign out of bounds'); subsize = zeros(1, size(s(1).subs, 2)); for i = 1:length(subsize) diff --git a/src/sparse/sub2sub.m b/src/sparse/sub2sub.m index a1d0a2d..c5ed718 100644 --- a/src/sparse/sub2sub.m +++ b/src/sparse/sub2sub.m @@ -7,8 +7,8 @@ return; end sub2 = zeros(size(sub1, 1), numel(sz2)); % preallocate new subs -nto1 = sum(cumsum(flip(sz1-1), 2) == 0, 2); % extract number of trailing ones in both sizes -nto2 = sum(cumsum(flip(sz2-1), 2) == 0, 2); +nto1 = sum(cumsum(flip(sz1-1), 2) == 0, 2), length(sz1); % extract number of trailing ones in both sizes +nto2 = sum(cumsum(flip(sz2-1), 2) == 0, 2), length(sz2); pos1_prev = 0; pos2_prev = 0; flag = true; while flag @@ -19,10 +19,7 @@ error('Cannot map subscripts to new size as intermediate index exceeds MAXSIZE') end sub2(:, pos2_prev+1:pos2) = ind2sub_(sz2(pos2_prev+1:pos2), sub2ind_(sz1(pos1_prev+1:pos1), sub1(:, pos1_prev+1:pos1))); - if (isempty(pos2) && numel(sz2) - nto2 == 0) || ... - (isempty(pos1) && numel(sz1) - nto1 == 0) || ... - pos2 == numel(sz2) - nto2 || ... - pos1 == numel(sz1) - nto1 + if pos2 == numel(sz2) - nto2 || pos1 == numel(sz1) - nto1 flag = false; else pos1_prev = pos1; diff --git a/src/tensors/AbstractTensor.m b/src/tensors/AbstractTensor.m index 2b69009..1c1a08f 100644 --- a/src/tensors/AbstractTensor.m +++ b/src/tensors/AbstractTensor.m @@ -255,8 +255,7 @@ x0_vec = vectorize(x0); sz = size(x0_vec); - assert(sz(1) >= howmany); - + if isa(A, 'function_handle') A_fun = @(x) vectorize(A(devectorize(x, x0))); else @@ -265,19 +264,11 @@ options.KrylovDim = min(sz(1), options.KrylovDim); - if howmany > sz(1) - 2 - [V, D, flag] = eigs(A_fun, sz(1), howmany, sigma, ... - 'Tolerance', options.Tol, 'MaxIterations', options.MaxIter, ... - 'IsFunctionSymmetric', ... - options.IsSymmetric, 'StartVector', x0_vec, ... - 'Display', options.Verbosity >= 3); - else - [V, D, flag] = eigs(A_fun, sz(1), howmany, sigma, ... - 'Tolerance', options.Tol, 'MaxIterations', options.MaxIter, ... - 'SubspaceDimension', options.KrylovDim, 'IsFunctionSymmetric', ... - options.IsSymmetric, 'StartVector', x0_vec, ... - 'Display', options.Verbosity >= 3); - end + [V, D, flag] = eigs(A_fun, sz(1), howmany, sigma, ... + 'Tolerance', options.Tol, 'MaxIterations', options.MaxIter, ... + 'SubspaceDimension', options.KrylovDim, 'IsFunctionSymmetric', ... + options.IsSymmetric, 'StartVector', x0_vec, ... + 'Display', options.Verbosity >= 3); if nargout <= 1 varargout = {D}; @@ -311,28 +302,9 @@ kwargs.Conj (1, :) logical = false(size(tensors)) kwargs.Rank = [] kwargs.Debug = false - kwargs.CheckOptimal = false end - assert(length(kwargs.Conj) == length(tensors)); - if kwargs.CheckOptimal - legcosts = zeros(2, 0); - for i = 1:length(indices) - legcosts = [legcosts [indices{i}; dims(tensors{i})]]; - end - legcosts = unique(legcosts.', 'rows'); - - currentcost = contractcost(indices, legcosts); - [sequence, cost] = netcon(indices, 0, 1, currentcost, 1, legcosts); - - if cost < currentcost - warning('suboptimal contraction order.\n current (%d): %s\n optimal(%d): %s', ... - currentcost, num2str(1:max(legcosts(:,1))), ... - cost, num2str(sequence)); - end - end - for i = 1:length(tensors) [i1, i2] = traceinds(indices{i}); tensors{i} = tensortrace(tensors{i}, i1, i2); @@ -391,48 +363,6 @@ end end - function disp(t, details) - if nargin == 1 || isempty(details), details = false; end - if isscalar(t) - r = t.rank; - fprintf('Rank (%d, %d) %s:\n', r(1), r(2), class(t)); - s = space(t); - for i = 1:length(s) - fprintf('\t%d.\t', i); - disp(s(i)); - fprintf('\b'); - end - fprintf('\n'); - if details - [blocks, charges] = matrixblocks(t); - for i = 1:length(blocks) - if ~isempty(blocks) - fprintf('charge %s:\n', string(charges(i))); - end - disp(blocks{i}); - end - end - else - fprintf('%s of size %s:\n', class(t), ... - regexprep(mat2str(size(t)), {'\[', '\]', '\s+'}, {'', '', 'x'})); - subs = ind2sub_(size(t), 1:numel(t)); - spc = floor(log10(max(double(subs), [], 1))) + 1; - if numel(spc) == 1 - fmt = strcat("\t(%", num2str(spc(1)), "u)"); - else - fmt = strcat("\t(%", num2str(spc(1)), "u,"); - for i = 2:numel(spc) - 1 - fmt = strcat(fmt, "%", num2str(spc(i)), "u,"); - end - fmt = strcat(fmt, "%", num2str(spc(end)), "u)"); - end - for i = 1:numel(t) - fprintf('%s\t\t', compose(fmt, subs(i, :))); - disp(t(i), details); - end - end - end - function d = distance(A, B) % Compute the Euclidean distance between two tensors. % @@ -511,44 +441,6 @@ function disp(t, details) iE = [1:length(i1) length(i1) + (length(i2):-1:1)]; B = tensorprod(A, E, iA, iE); end - - function sz = dims(t, inds) - sz = dims(space(t)); - if nargin > 1 - sz = sz(inds); - end - end - - function o = overlap(t1, t2) - o = contract(t1, 1:nspaces(t1), t2, flip(1:nspaces(t1))); - end - end - - - %% Contractions - methods - function v = applytransfer(L, R, v) - arguments - L - R - v = [] - end - - if isempty(v) - v = tracetransfer(L, R); - return - end - - auxlegs_v = nspaces(v) - 2; - auxlegs_l = 0; - auxlegs_r = 0; - newrank = rank(v); newrank(2) = newrank(2) + auxlegs_l + auxlegs_r; - - v = contract(v, [1 3 (-(1:auxlegs_v) - 2 - auxlegs_l)], ... - L, [-1 2 1 (-(1:auxlegs_l) - 2)], ... - R, [3 2 -2 (-(1:auxlegs_r) - 3 - auxlegs_l - auxlegs_v)], ... - 'Rank', newrank); - end - end end + diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index c515960..f1c2a51 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -75,8 +75,6 @@ elseif isempty(domain) sz = nsubspaces(codomain); else - if ~isa(codomain, 'SumSpace'), codomain = SumSpace(codomain); end - if ~isa(domain, 'SumSpace'), domain = SumSpace(domain); end sz = [nsubspaces(codomain) flip(nsubspaces(domain))]; end subs = ind2sub_(sz, 1:prod(sz)); @@ -458,13 +456,15 @@ end end - function sp = space(t, inds) - if isscalar(t) - sp = [t.codomain t.domain']; - else - [cod, dom] = deduce_spaces(t); - sp = [cod, dom']; + function sz = dims(t, inds) + sz = dims([t.codomain, t.domain']); + if nargin > 1 + sz = sz(inds); end + end + + function sp = space(t, inds) + sp = [t.codomain t.domain']; if nargin > 1 sp = sp(inds); end @@ -835,11 +835,9 @@ end end - function [t, n] = normalize(t) - n = zeros(size(t)); + function t = normalize(t) for i = 1:numel(t) - n(i) = norm(t(i)); - t(i) = t(i) .* (1 / n(i)); + t(i) = t(i) .* (1 / norm(t(i))); end end @@ -2354,53 +2352,11 @@ if ~isempty(tsrc.codomain), tdst.codomain = ComplexSpace(tsrc.codomain); end end end - - function mps = FiniteMps(t, varargin) - A = MpsTensor.decompose_local_state(t, varargin{:}); - mps = FiniteMps(A); - end end %% Utility methods - function [I, J, V] = find(t, k, which) - arguments - t - k = [] - which = 'first' - end - assert(strcmp(which, 'first'), 'not implemented yet') - if ~isempty(k) - assert(k <= numel(t)); - else - k = numel(t); - end - - if isempty(t) - I = []; - J = []; - V = []; - return - end - - if ~isempty(k) - if strcmp(which, 'first') - I = 1:k; - else - I = numel(t):-1:numel(t)+1-k; - end - end - I = reshape(I, [], 1); - if nargout < 2, return; end - - sz = size(t); - subs = ind2sub_([sz(1) prod(sz(2:end))], I); - I = subs(:, 1); - J = subs(:, 2); - V = reshape(t, [], 1); - end - function s = GetMD5_helper(t) s = {t.codomain t.domain}; end diff --git a/src/tensors/spaces/AbstractSpace.m b/src/tensors/spaces/AbstractSpace.m index 5f61b39..b1d2c8c 100644 --- a/src/tensors/spaces/AbstractSpace.m +++ b/src/tensors/spaces/AbstractSpace.m @@ -317,10 +317,6 @@ bool = ~le(space1, space2); end - function bool = ge(space1, space2) - bool = le(space2, space1); - end - function bool = isequal(spaces) % Check whether all input spaces are equal. Spaces are considered equal if they % are of same size, and are equal element-wise. For convenience, empty spaces diff --git a/src/tensors/spaces/CartesianSpace.m b/src/tensors/spaces/CartesianSpace.m index c4e1131..e3ef96f 100644 --- a/src/tensors/spaces/CartesianSpace.m +++ b/src/tensors/spaces/CartesianSpace.m @@ -274,6 +274,10 @@ bools = [spaces1.dimensions] == [spaces2.dimensions]; end + function bool = ge(space1, space2) + bool = le(space2, space1); + end + function bool = le(space1, space2) assert(isscalar(space1) && isscalar(space2)); bool = degeneracies(space1) <= degeneracies(space2); diff --git a/src/tensors/spaces/SumSpace.m b/src/tensors/spaces/SumSpace.m index 456551d..2f5f506 100644 --- a/src/tensors/spaces/SumSpace.m +++ b/src/tensors/spaces/SumSpace.m @@ -32,10 +32,6 @@ end function space = infimum(space1, space2) - arguments - space1 SumSpace - space2 SumSpace - end assert(isscalar(space1) && isscalar(space2)); assert(isdual(space1) == isdual(space2)); space = SumSpace(arrayfun(@infimum, space1.dimensions, space2.dimensions)); @@ -75,12 +71,6 @@ %% Utility methods - function d = dims(spaces) - for i = length(spaces):-1:1 - d(i) = sum(dims(subspaces(spaces(i)))); - end - end - function bools = eq(spaces1, spaces2) assert(isequal(size(spaces1), size(spaces2))); bools = false(size(spaces1)); @@ -89,17 +79,6 @@ end end - function bool = le(space1, space2) - arguments - space1 SumSpace - space2 SumSpace - end - - assert(isscalar(space1) && isscalar(space2)); - - bool = le(sum(subspaces(space1)), sum(subspaces(space2))); - end - function s = subspaces(space, i) assert(isscalar(space), ... 'space:argerror', 'method only defined for scalar inputs.'); diff --git a/src/utility/diracdelta.m b/src/utility/diracdelta.m deleted file mode 100644 index 2ed8d80..0000000 --- a/src/utility/diracdelta.m +++ /dev/null @@ -1,12 +0,0 @@ -function d = diracdelta(sz) - -assert(all(sz == sz(1))) -d = zeros(sz); - -subs = repmat(1:sz(1), length(sz), 1).'; -idx = sub2ind_(sz, subs); - -d(idx) = 1; - -end - diff --git a/src/utility/linalg/contract.m b/src/utility/linalg/contract.m index 7a3c1b3..024b575 100644 --- a/src/utility/linalg/contract.m +++ b/src/utility/linalg/contract.m @@ -41,27 +41,10 @@ kwargs.Conj (1, :) logical = false(size(tensors)) kwargs.Rank = [] kwargs.Debug = false - kwargs.CheckOptimal = false end assert(length(kwargs.Conj) == length(tensors)); -if kwargs.CheckOptimal - legcosts = zeros(2, 0); - for i = 1:length(indices) - legcosts = [legcosts [indices{i}; size(tensors{i}, 1:length(indices{i}))]]; - end - legcosts = unique(legcosts.', 'rows'); - - currentcost = contractcost(indices, legcosts); - [sequence, cost] = netcon(indices, 1, 1, currentcost, 1, legcosts); - - if cost < currentcost - warning('suboptimal contraction order.\n optimal: %s', ... - num2str(sequence)); - end -end - for i = 1:length(tensors) [i1, i2] = traceinds(indices{i}); tensors{i} = tensortrace(tensors{i}, i1, i2); diff --git a/src/utility/linalg/contractcost.m b/src/utility/linalg/contractcost.m deleted file mode 100644 index 1a7b0b7..0000000 --- a/src/utility/linalg/contractcost.m +++ /dev/null @@ -1,73 +0,0 @@ -function cost = contractcost(indices, legCosts) - -cost = 0; - -allInds = horzcat(indices{:}); -numCont = max(allInds); - -table = zeros(numCont, 2); -for ii = 1:length(indices) - for jj = indices{ii}(indices{ii} > 0) - if table(jj, 1) == 0 - table(jj, 1) = ii; - else - table(jj, 2) = ii; - end - end -end - - -%% Do contractions -ctr = 1; -contlist = 1:numCont; - -while ~isempty(contlist) - i1 = table(contlist(1), 1); - i2 = table(contlist(1), 2); - - if i1 + i2 == 0 - contlist(1) = []; - continue; - else - assert(i1 ~= i2, 'Tensor:isOptimalContract', 'Traces are not implemented.'); - end - - labels1 = indices{i1}; - labels2 = indices{i2}; - - [pos1, pos2] = contractinds(labels1, labels2); - unc1 = 1:length(labels1); unc1(pos1) = []; - unc2 = 1:length(labels2); unc2(pos2) = []; - contracting = labels1(pos1); - - % cost is dim(unc1) * dim(pos1) * dim(unc2) - dims1 = zeros(1, length(unc1)); - for ii = 1:length(unc1) - label = labels1(unc1(ii)); - dims1(ii) = legCosts(legCosts(:, 1) == label, 2); - end - dims2 = zeros(1, length(pos1)); - for ii = 1:length(pos1) - label = labels1(pos1(ii)); - dims2(ii) = legCosts(legCosts(:, 1) == label, 2); - end - dims3 = zeros(1, length(unc2)); - for ii = 1:length(unc2) - label = labels2(unc2(ii)); - dims3(ii) = legCosts(legCosts(:, 1) == label, 2); - end - - cost = cost + prod([dims1 dims2 dims3]); - - % update remaining contractions - indices{i1} = [indices{i1}(unc1) indices{i2}(unc2)]; - indices(i2) = []; - table(table == i2) = i1; - table(table > i2) = table(table > i2) - 1; - contlist = contlist(~ismember(contlist, contracting)); - - ctr = ctr + 1; -end - - -end diff --git a/src/utility/netcon.m b/src/utility/netcon.m deleted file mode 100644 index 4e18b31..0000000 --- a/src/utility/netcon.m +++ /dev/null @@ -1,1308 +0,0 @@ -function [sequence, cost] = netcon(legLinks,verbosity,costType,muCap,allowOPs,legCosts) -% function [sequence cost] = netcon(legLinks,verbosity,costType,muCap,allowOPs,legCosts) -% Finds most efficient way to contract a tensor network -% v2.00 by Robert N. C. Pfeifer 2014 -% Contact: rpfeifer.public@gmail.com, robert.pfeifer@mq.edu.au -% -% Parameters: (defaults) -% - legLinks: Labelling of tensor indices. (required) -% - verbosity: 0: Quiet. 1: State final result. 2: State intermediate and final results. 3: Also display object sizes during pairwise contractions. (2) -% - costType: 1: Absolute value. 2: Multiple and power of chi. (2) -% - muCap: Initially restrict search to sequences having a cost of muCap (if costType==1) or O(X^muCap) (if costType==2). This value will increment -% automatically if required, and it is recommended that it be left at the default value of 1. (1) -% - allowOPs: Allow contraction sequences including outer products: 0/false: No. 1/true: Yes. (true) -% - legCosts: For costType==1: nx2 table. A row reading [a b] assigns a dimension of b to index a. Default: 2 for all legs. -% For costType==2: nx3 table. A row reading [a b c] assigns a dimension of bX^c to index a, for unspecified X. Default: 1X^1 for all legs. -arguments - legLinks - verbosity = 2 % 0: No displayed output. 1: Display final result. 2: Display progress reports. - costType = 2 % 1: Absolute value. 2: Multiple and power of chi. - muCap = 1 - allowOPs = 1 - legCosts = [] -end - -% Benchmarking examples -% --------------------- -% 3:1 1D MERA: -% tic;netcon({[-1 1 2 3],[2 4 5 6],[1 5 7 -3],[3 8 4 9],[6 9 7 10],[-2 8 11 12],[10 11 12 -4]},0,2,1,1);toc -% All in MATLAB, no OPs: 0.041s -% All in MATLAB, with OPs: 0.054s -% Using C++, no OPs: 0.0019s -% Using C++, with OPs: 0.0019s -% -% 9:1 2D MERA: -% tic;netcon({[1 4 5 6 7 8],[2 9 10 11 12 13],[3 14 15 16 17 18],[-6 9 23 24 25 26],[-5 5 19 20 21 22],[8 14 27 28 29 30],[12 15 31 32 33 34],[22 25 28 31 35 36 37 38],[-4 20 23 35 39 40 41 42],[42 36 37 38 43 44 45 46],[41 24 44 26 47 48],[19 40 21 43 49 50],[27 45 29 30 51 52],[46 32 33 34 53 54],[-2 -3 39 49 47 55],[4 50 6 7 51 56],[48 10 11 53 13 57],[52 54 16 17 18 58],[55 56 57 58 -1 1 2 3]},0,2,1,1);toc -% All in MATLAB, no OPs: 5.4s -% All in MATLAB, with OPs: 6.1s -% Using C++, no OPs: 0.066s -% Using C++, with OPs: 0.069s -% -% 4:1 2D MERA: -% tic;netcon({[64 72 73 19 22],[65 74 75 23 76],[66 77 20 79 28],[67 21 24 29 34],[68 25 78 35 80],[69 81 30 83 84],[70 31 36 85 86],[71 37 82 87 88],[-5 19 20 21 1 2 4 5],[22 23 24 25 3 26 6 27],[28 29 30 31 7 8 32 33],[34 35 36 37 9 38 39 40],[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18],[10 11 13 14 41 42 43 44],[12 26 15 27 47 48 49 50],[16 17 32 33 53 54 55 56],[18 38 39 40 60 61 62 63],[-2 -3 -4 41 89],[72 73 42 47 90],[74 75 48 76 45],[77 43 79 53 46],[44 49 54 60 51],[50 78 61 80 52],[81 55 83 84 57],[56 62 85 86 58],[63 82 87 88 59],[89 90 45 46 51 52 57 58 59 -1 64 65 66 67 68 69 70 71]},0,2,1,1);toc -% All in MATLAB, no OPs: 2486s (41.4m) -% All in MATLAB, with OPs: 3919.6s (65.3m) -% Using C++, no OPs: 36.12s -% Using C++, with OPs: 36.49s - - -% Changes in v2.00: -% ----------------- -% Changed algorithm to breadth-first search instead of dynamic programming. -% Made handling of subnetworks iterative, not recursive. -% Made displayed output more user-friendly for networks composed of disjoint subnetworks. -% Added warning, not error, for tensors or subnetworks of trivial dimension (i.e. just a number). -% Added more vigorous exclusion of unnecessary outer products. - -% Changes in v1.01: -% ----------------- -% Improved structure of code. -% Fixed bug returning wrong cost when trivial indices present and non-consecutive numbering of positive indices. -% Removed keepTriv option. -% Fixed omission of trailing zeros in sequence when network is disjoint and all contractions are traces. -% Corrected helptext. - - -% Check input data (and strip trivial indices from legLinks) -% ================ -if ~isempty(legCosts) - [trivialindices legCosts legLinks] = checkInputData(legLinks,verbosity,costType,muCap,allowOPs,legCosts); -else - [trivialindices legCosts legLinks] = checkInputData(legLinks,verbosity,costType,muCap,allowOPs); -end - -% Divide network into disjoint subnets -% ==================================== -subnetlist = zeros(1,numel(legLinks)); -subnetcounter = 1; -while any(subnetlist==0) - flags = findSubnetIncluding(legLinks,find(subnetlist==0,1,'first')); - subnetlist(flags) = subnetcounter; - subnetcounter = subnetcounter + 1; -end -subnetcounter = subnetcounter-1; - -% Evaluate contraction sequences for disjoint subnets -% =================================================== -sequence = cell(1,subnetcounter); -cost = cell(1,subnetcounter); -freelegs = cell(1,subnetcounter); -donetrace = false(1,subnetcounter); -for a=1:subnetcounter - if verbosity > 0 - disp(' '); - if subnetcounter > 1 - disp(['Subnet ' num2str(a) ':']); - end - if verbosity > 1 - disp(['Tensors: ' unpaddednum2str(find(subnetlist==a))]); - end - end - [sequence{a} cost{a} freelegs{a} donetrace(a)] = netcon_getsubnetcost(legLinks(subnetlist==a),verbosity,costType,muCap,legCosts,allowOPs); -end -donetrace = any(donetrace); - -% Perform outer products of subnets, add costs, and merge sequences -% ================================================================= -[sequence cost] = performOPs(sequence,cost,freelegs,legCosts,costType,verbosity); -sequence = [sequence trivialindices]; % Append tracing over trivial indices on final object - -% Display result -% ============== -if verbosity>0 - if ~isempty(trivialindices) && verbosity > 1 - disp(' '); - disp(['Restore summed trivial indices: ' unpaddednum2str(trivialindices)]); - end - disp(' '); - if verbosity > 1 && subnetcounter > 1 - disp('Entire network:'); - end - netcon_displayresult(sequence,cost,donetrace,costType); -end -end - -function [sequence cost] = performOPs(sequence,cost,freelegs,legCosts,costType,verbosity) -% Renumber freelegs by position and trim index label list off legCosts -for a=1:numel(freelegs) - for b=1:numel(freelegs{a}) - freelegs{a}(b) = find(legCosts(:,1)==freelegs{a}(b)); - end -end -legCosts = legCosts(:,2:end); -% Perform outer products -if costType==2 - tensormulsize = ones(size(freelegs)); - tensorchisize = zeros(size(freelegs)); - for a=1:numel(freelegs) - for b=1:numel(freelegs{a}) - tensormulsize(a) = tensormulsize(a)*legCosts(freelegs{a}(b),1); - tensorchisize(a) = tensorchisize(a)+legCosts(freelegs{a}(b),2); - end - end - if any(tensormulsize==1 & tensorchisize==0) && numel(freelegs)>1 - if sum(tensormulsize==1 & tensorchisize==0)==1 - if verbosity > 0 - disp(' '); - end - warning('netcon:trivialsubnet',['Subnet ' unpaddednum2str(find(tensormulsize==1 & tensorchisize==0)) ' reduces to a single number. Since this subnet is not the entire network, contraction sequence may not be optimal.']); - else - if verbosity > 0 - disp(' '); - end - warning('netcon:trivialsubnet',['More than one subnet reduces to a single number: Contraction sequence may not be optimal. List of subnets reducing to a single number: ' unpaddednum2str(find(tensormulsize==1 & tensorchisize==0))]); - end - end - while numel(tensorchisize)>1 - % Sort tensors - ptr = 1; - while (ptrtensorchisize(ptr+1) || (tensorchisize(ptr)==tensorchisize(ptr+1) && tensormulsize(ptr)>tensormulsize(ptr+1)) - t = tensorchisize(ptr+1); tensorchisize(ptr+1) = tensorchisize(ptr); tensorchisize(ptr) = t; - t = tensormulsize(ptr+1); tensormulsize(ptr+1) = tensormulsize(ptr); tensormulsize(ptr) = t; - t = sequence{ptr}; sequence{ptr} = sequence{ptr+1}; sequence{ptr+1} = t; - t = cost{ptr}; cost{ptr} = cost{ptr+1}; cost{ptr+1} = t; - ptr = ptr - 1; - if ptr==0 - ptr = 2; - end - else - ptr = ptr + 1; - end - end - % Combine smallest two tensors - outerproductmul = tensormulsize(1) * tensormulsize(2); - outerproductpower = tensorchisize(1) + tensorchisize(2); - if numel(cost{1})1 - % Sort tensors - tensorsize = sort(tensorsize,'ascend'); - % Combine smallest two tensors - outerproduct = tensorsize(1) * tensorsize(2); - cost{1} = cost{1} + outerproduct; - tensorsize = [outerproduct tensorsize(3:end)]; - cost{1} = cost{1} + cost{2}; - sequence{1} = [sequence{1} sequence{2}]; - cost = cost([1 3:end]); - sequence = sequence([1 3:end]); - end -end -sequence = sequence{1}; -sequence = [sequence zeros(1,numel(freelegs)-1)]; % Add outer products between disjoint subnets to the contraction sequence -cost = cost{1}; -end - -function [sequence cost negindices donetrace] = netcon_getsubnetcost(legLinks,verbosity,costType,muCap,legCosts,allowOPs) -% Performs any traces on single tensors -% Checks if network is trivial -% If the network is not trivial, invokes netcon_nondisj to evaluate cost for the network - -% Get index lists for this subnet and trim legCosts -% ================================================= -[posindices negindices] = getIndexLists(cell2mat(legLinks)); - -% Any contraction to do? -% ====================== -if numel(legLinks)<2 - % List of tensors has only one entry - % ================================== - if ~isempty(posindices) - % One tensor, with traces - do them, then go to "Finished contracting" - if costType==2 - cost = []; - else - cost = 0; - end - sequence = posindices; - donetrace = true; - if verbosity > 0 - disp('Tracing costs only'); - end - % Go to "Finished contracting" - else - % Nothing to do - sequence = []; - if costType==2 - cost = []; - else - cost = 0; - end - donetrace = false; - if verbosity > 0 - disp('Network is trivial'); - end - % One tensor, no traces - go to "Finished contracting" - end -else - % >1 tensor, tracing and/or contraction to do - % =========================================== - % Make positive indices consecutive - % Make negative indices consecutive following on from positive indices - % Truncate cost table and remove indexing column - % (Note original labelling for legs may be recovered from posindices and negindices) - [legLinks legCosts] = reprocessLegLinks(legLinks,posindices,negindices,legCosts); - - % Determine any traces (legs with both ends on a single tensor) and eliminate - % =========================================================================== - [tracedindices donetrace legLinks legCosts linkposindices] = eliminateTraces(legLinks,posindices,legCosts); - - if isempty(linkposindices) - % No positive indices after tracing - sequence = posindices(tracedindices); - if costType==2 - cost = []; - else - cost = 0; - end - else - % Find best sequence - % ================== - % Invoke sequence finder for traceless non-disjoint network - for a=1:numel(legLinks) - legLinks{a} = int32(abs(legLinks{a})); - end - legCosts = double(legCosts); - verbosity = double(verbosity); - costType = double(costType); - muCap = double(muCap); - allowOPs = double(allowOPs); - posindices = double(posindices); - tracedindices = int32(tracedindices); - [sequence cost] = netcon_nondisj(legLinks,legCosts,verbosity,costType,muCap,allowOPs,posindices,tracedindices); -% try -% [sequence cost] = netcon_nondisj_cpp(legLinks,legCosts,verbosity,costType,muCap,allowOPs,posindices,tracedindices); -% catch ME -% if isequal(ME.identifier,'MATLAB:UndefinedFunction') -% [sequence cost] = netcon_nondisj(legLinks,legCosts,verbosity,costType,muCap,allowOPs,posindices,tracedindices); -% else -% rethrow(ME); -% end -% end - % Reinsert indices traced at the beginning of the contraction process - sequence = [tracedindices sequence]; - % Translate sequence back into user's original leg labels - sequence(sequence>0) = posindices(sequence(sequence>0)); - end -end -end - -function [tracedindices donetrace legLinks legCosts posindices] = eliminateTraces(legLinks,posindices,legCosts) -tracedindices = []; -donetrace = false; -% For each tensor: -for a=1:numel(legLinks) - % - Find all tracing legs on that tensor, note the indices, and delete them. - % - Tracing can be performed over all traced indices on a tensor simultaneously by combining them - no need to sort - b = 1; - while b - legLinks{a}(legLinks{a}==legLinks{a}(b)) = []; - else - b = b + 1; - end - end -end -% Then: -if ~isempty(tracedindices) - donetrace = true; - % - Delete removed rows from leg cost list - legCosts(tracedindices,:) = []; - % - Renumber all positive and negative legs consecutively again - renumindices = 1:numel(posindices); - renumindices(tracedindices) = []; - posindices(tracedindices) = []; - for a=1:numel(legLinks) - for b=1:numel(legLinks{a}) - if legLinks{a}(b)>0 - legLinks{a}(b) = find(renumindices==legLinks{a}(b)); - else - legLinks{a}(b) = legLinks{a}(b) + numel(tracedindices); - end - end - end -end -end - -function [legLinks legCosts] = reprocessLegLinks(legLinks,posindices,negindices,legCosts) -% Renumber all positive and negative indices consecutively -% ======================================================== -for a=1:numel(legLinks) - for b=find(legLinks{a}>0) - legLinks{a}(b) = find(posindices==legLinks{a}(b)); - end - for b=find(legLinks{a}<0) - legLinks{a}(b) = -find(negindices==legLinks{a}(b))-numel(posindices); - end -end - -% Assemble truncated cost table -% ============================= -for a=size(legCosts,1):-1:1 - if ~any([posindices negindices]==legCosts(a,1)) - legCosts(a,:) = []; - end -end -legCosts = legCosts(:,2:end); -end - -function cost = addCosts(cost1,cost2,costType) -if costType==1 - cost = cost1 + cost2; -else - cost = zeros(1,max([numel(cost1) numel(cost2)])); - cost(1:numel(cost1)) = reshape(cost1,1,numel(cost1)); - cost(1:numel(cost2)) = cost(1:numel(cost2)) + reshape(cost2,1,numel(cost2)); -end -end - -function flags = findSubnetIncluding(legLinks,pos) -new = true; -flags = zeros(1,numel(legLinks)); % 0: May not be in subnet -flags(pos) = 1; % 1: Follow connections off this tensor -while new - new = false; - fromlist = find(flags==1); - flags(flags==1) = 2; % 2: Have followed all connections from this tensor - for checkfrom = fromlist - for checkto = find(flags==0) - for x = 1:numel(legLinks{checkfrom}) - if any(legLinks{checkto}==legLinks{checkfrom}(x)) - flags(checkto) = true; - new = true; - end - end - end - end -end -flags = flags~=0; -end - -function [trivialindices legCosts legLinks] = checkInputData(legLinks,verbosity,costType,muCap,allowOPs,legCosts) -% Check data sizes -if ~iscell(legLinks) - error('legLinks must be a cell array') -end -if numel(legLinks)==0 - error('legLinks may not be empty') -end -if ~isnumeric(verbosity) || ~any([0 1 2 3]==verbosity) - error('verbosity must be 0, 1, 2 or 3') -end -if ~isnumeric(muCap) || numel(muCap)~=1 || ~isreal(muCap) || muCap<=0 - error('muCap must be a real positive number'); -end -if ~isnumeric(costType) || ~any([1 2]==costType) - error('costType must be either 1 or 2') -end -if (~isnumeric(allowOPs) && ~islogical(allowOPs)) || ~any([0 1]==allowOPs) - error('allowOPs must be either 0 or 1'); -end -for a=1:numel(legLinks) - if ~isnumeric(legLinks{a}) - error('Entries in legLinks must be numeric arrays') - end -end -if size(legLinks,1)~=1 || size(legLinks,2)~=numel(legLinks) - error('Array of index connections (legLinks) has incorrect dimension - should be 1xn') -end -for a=1:numel(legLinks) - if size(legLinks{a},1)~=1 || size(legLinks{a},2)~=numel(legLinks{a}) - error(['legLinks entry ' num2str(a) ' has wrong dimension - should be 1xn']); - end - if isempty(legLinks{a}) - error(['Empty list of indices on tensor ' num2str(a)]) - end -end -allindices = cell2mat(legLinks); -if any(allindices==0) - error('Zero entry in index list') -elseif any(imag(allindices)~=0) - error('Complex entry in index list') -elseif any(int32(allindices)~=allindices) - error('Non-integer entry in legLinks'); -end -posindices = sort(allindices(allindices>0),'ascend'); -negindices = sort(allindices(allindices<0),'descend'); -if ~isempty(posindices) - % Test all positive indices occur exactly twice - if mod(numel(posindices),2)~=0 - maxposindex = posindices(end); - posindices = posindices(1:end-1); - end - flags = (posindices(1:2:numel(posindices))-posindices(2:2:numel(posindices)))~=0; - if any(flags) - errorpos = 2*find(flags~=0,1,'first')-1; - if errorpos>1 && posindices(errorpos-1)==posindices(errorpos) - error(['Error in index list: Index ' num2str(posindices(errorpos)) ' appears more than twice']); - else - error(['Error in index list: Index ' num2str(posindices(errorpos)) ' only appears once']); - end - end - if exist('maxposindex','var') - if posindices(end)==maxposindex - error(['Error in index list: Index ' num2str(maxposindex) ' appears more than twice']); - else - error(['Error in index list: Index ' num2str(maxposindex) ' only appears once']); - end - end - posindices = posindices(1:2:numel(posindices)); % List of all positive indices, sorted ascending, each appearing once. - flags = posindices(1:end-1)==posindices(2:end); - if any(flags) - errorpos = find(flags,1,'first'); - error(['Error in index list: Index ' num2str(posindices(errorpos)) ' appears more than twice']); - end -else - posindices = []; -end -% Test all negative indices occur exactly once -flags = negindices(1:end-1)==negindices(2:end); -if any(flags) - errorpos = find(flags,1,'first'); - error(['Error in index list: Index ' num2str(negindices(errorpos)) ' appears more than once']); -end - -% Check leg size data -trivialindices = []; -if ~exist('legCosts','var') - if costType==1 - % Create basic leg costs list (all 2) - legCosts = [[posindices.';negindices.'] 2*ones(numel(posindices)+numel(negindices),1)]; - else - % Create basic leg costs list (all Chi^1) - legCosts = [[posindices.';negindices.'] ones(numel(posindices)+numel(negindices),2)]; - end -else - if ~isnumeric(legCosts) - error('legCosts must be numeric') - end - % Check valid leg costs list, & process - if ndims(legCosts)>2 || any(legCosts(:,1)==0) || size(legCosts,2)~=2+(costType==2) || any(legCosts(:,2)<=0) - error('Bad index dimensions list (error in legCosts: type ''help netcon'' for specifications)') - end - if costType==2 - if any(legCosts(:,3)<0) - error('For index dimensions of the form aX^b, values of b less than 0 are not supported') - end - if any(legCosts(:,2)<=0) - error('For index dimensions of the form aX^b, values of a less than or equal to 0 are not supported') - end - else - if any(legCosts(:,2)<=0) - error('Index dimensions less than or equal to 0 are not supported') - end - end - [t1 ix1] = sort(legCosts(legCosts(:,1)>0,1)); - [t2 ix2] = sort(legCosts(legCosts(:,1)<0,1),'descend'); - t1 = reshape(t1,1,[]); - t2 = reshape(t2,1,[]); - flag = t1(1:end-1)==t1(2:end); - if any(flag) - error(['Dimension of index ' num2str(t1(find(flag,1))) ' specified twice']) - end - if ~(isempty(t1) && isempty(posindices)) % Necessary as a 1x0 matrix is not the same as a 0x0 matrix - if ~isequal(t1,posindices) - error(['Dimension not specified for all positive indices. Supplied: ' num2str(t1) ' Required: ' num2str(posindices)]) - end - end - flag = t2(1:end-1)==t2(2:end); - if any(flag) - error(['Dimension of index ' num2str(t2(find(flag,1))) ' specified twice']) - end - if ~(isempty(t2) && isempty(negindices)) - if ~isequal(t2,negindices) - error(['Dimension not specified for all negative indices. Supplied: ' num2str(t2) ' Required: ' num2str(negindices)]) - end - end - - % Store list of any summed trivial indices to be stripped - if costType==1 - trivialindices = legCosts(legCosts(:,2)==1,1); - else - trivialindices = legCosts(legCosts(:,2)==1 & legCosts(:,3)==0,1); - end - trivialindices = reshape(sort(trivialindices(trivialindices>0),'descend'),1,[]); - - % Strip trivial indices - for a=numel(trivialindices):-1:1 - for b=1:numel(legLinks) - if sum(legLinks{b}==trivialindices(a))==2 - trivialindices(a) = []; % Do not strip trivial single-tensor traces (not that this actually matters either way) - else - legLinks{b}(legLinks{b}==trivialindices(a)) = []; - end - end - end - if ~isempty(trivialindices) && verbosity > 1 - disp(' '); - disp(['Ignore summed trivial indices: ' unpaddednum2str(trivialindices)]); - end - - % Order leg costs list - t1 = legCosts(legCosts(:,1)>0,2:end); - t1 = t1(ix1,:); - t2 = legCosts(legCosts(:,1)<0,2:end); - t2 = t2(ix2,:); - legCosts = [[posindices.';negindices.'] [t1;t2]]; -end -end - -function [posindices negindices] = getIndexLists(allindices) -posindices = sort(allindices(allindices>0),'ascend'); -negindices = sort(allindices(allindices<0),'descend'); -posindices = posindices(1:2:end); -end - -function netcon_displayresult(sequence,cost,donetrace,costType) -% Displays nicely-formatted final output -t = ['Best sequence: ' unpaddednum2str(sequence)]; -if isempty(sequence) - t = [t '']; -end -disp(t); -if isempty(cost) || isequal(cost,0) - if donetrace - disp('Cost: Tracing costs only'); - else - disp('Cost: 0'); - end -else - t = 'Cost: '; - if costType==2 - for a=numel(cost):-1:2 - t = [t num2str(cost(a)) 'X^' num2str(a-1) ' + ']; %#ok - end - t = [t num2str(cost(1)) 'X^0']; - else - t = [t num2str(cost)]; - end - if donetrace - t = [t ' + tracing costs']; - end - disp(t); -end -end - -function str = unpaddednum2str(row) -str = []; -for a=row(1:end-1) - str = [str num2str(a) ' ']; %#ok -end -if ~isempty(row) - str = [str num2str(row(end))]; -end -end - -% Functions used in the pure-MATLAB version only: - -function [sequence cost] = netcon_nondisj(legLinks,legCosts,verbosity,costType,muCap,allowOPs,posindices,tracedindices) -% Set up initial data -% =================== -numtensors = numel(legLinks); -if costType==1 - zerocost = 0; -else - zerocost = []; -end -allowOPs = (allowOPs==1); - -% This code uses the nomenclature of Appendix G, in which a tensor which may be contracted with the result of an outer product [e.g. C in Fig.4(a)] is denoted X. - -% Structure of "objects": objects{numElements}{positionInList}{legFlags,tensorFlags,sequenceToBuild,costToBuild,isOP,OPmaxdim,allIn} -% legFlags: Legs present on object -% tensorFlags: Tensor constituents of object -% sequenceToBuild: Cheapest identified contraction sequence for constructing this object -% costToBuild: Minimum identified cost of constructing this object -% isOP: Indicates whether the contraction which yielded this object was an outer product -% OPmaxdim: If isOP==true, then OPmaxdim gives the dimension of the largest constituent tensor -% allIn: If this tensor is an object X which may be contracted with an outer product, allIn is the dimension of the tensor which contributed no external legs to X, i.e. xi_C in Fig.5(c). - -objects = cell(1,numtensors); -objects{1} = cell(1,numtensors); -tensorflags = false(1,numtensors); -numleglabels = size(legCosts,1); -legflags = false(1,numleglabels); - -% ### Create lists used in enforcing Sec. II.B.2.c (structure of tensor X contractable with an outer product) -tensorXlegs = {}; -tensorXflags = []; -tensorXdims = {}; -% ### End of creating lists - -for a=1:numtensors - tensorflags(a) = true; - legflags(abs(legLinks{a})) = true; - objects{1}{a} = {legflags,tensorflags,[],zerocost,false,[],zeros(1,costType)}; - - % ### Set up initial list data used in enforcing Sec. II.B.2.c - if allowOPs - [tensorXlegs tensorXdims tensorXflags] = addToTensorXlist(tensorXlegs,tensorXdims,tensorXflags,legflags,Inf*ones(1,costType),costType,true); - end - % ### End of setting up linked list data - - tensorflags(a) = false; - legflags(abs(legLinks{a})) = false; -end - -% ### Set up initial list data used in enforcing Sec. II.B.2.c (continued) -tensorXflags = zeros(1,numel(tensorXflags)); -% ### End of setting up initial linked list data (continued) - -for a=2:numtensors - objects{a} = {}; -end - -newobjectflags = cell(1,numtensors); -newobjectflags{1} = true(1,numtensors); - -oldmuCap = 0; -newmuCap = Inf; - -done = false; -while ~done - if verbosity>0 - if oldmuCap~=muCap - if costType==1 - disp(['Looking for solutions with maximum cost of ' num2str(muCap)]); - else - disp(['Looking for solutions of cost O(X^' num2str(muCap) ')']); - end - end - end - for numInObjects = 2:numtensors - if verbosity > 2 - disp(['Pairwise contractions (AB) involving ' num2str(numInObjects) ' fundamental tensors:']); - end - for numInPieceOne = 1:floor(numInObjects/2) - numInPieceTwo = numInObjects - numInPieceOne; - if verbosity > 2 - disp(['A contains ' num2str(numInPieceOne) ', B contains ' num2str(numInPieceTwo)'.']); - pause(0.01); - end - if numel(objects{numInPieceOne})>0 && numel(objects{numInPieceTwo})>0 - - % Iterate over pairings: Iterate over object 1 - for a = 1:numel(objects{numInPieceOne}) - - % Get data for object 1 - obj1data = objects{numInPieceOne}{a}; - % obj1data: legs, tensors, seqToBuild, costToBuild, isOP, maxdimOP, allIn - legs1 = obj1data{1}; - tensorsIn1 = obj1data{2}; - seq1 = obj1data{3}; - costToBuild1 = obj1data{4}; - isOP1 = obj1data{5}; - OPmaxdim1 = obj1data{6}; - allIn1 = obj1data{7}; - isnew1 = newobjectflags{numInPieceOne}(a); - - % Iterate over pairings: Iterate over object 2 - if numInPieceOne == numInPieceTwo - obj2list = a+1:numel(objects{numInPieceTwo}); - else - obj2list = 1:numel(objects{numInPieceTwo}); - end - for b = obj2list - - % Check object 1 and object 2 don't share any common tensors (which would then appear twice in the resulting network) - if ~any(objects{numInPieceOne}{a}{2} & objects{numInPieceTwo}{b}{2}) - - % Get data for object 2 - obj2data = objects{numInPieceTwo}{b}; - % obj2data: legs, tensors, seqToBuild, costToBuild, isOP, maxdimOP, allIn - legs2 = obj2data{1}; - tensorsIn2 = obj2data{2}; - seq2 = obj2data{3}; - costToBuild2 = obj2data{4}; - isOP2 = obj2data{5}; - OPmaxdim2 = obj2data{6}; - allIn2 = obj2data{7}; - isnew2 = newobjectflags{numInPieceTwo}(b); - - commonlegs = legs1 & legs2; % Common legs - freelegs = xor(legs1,legs2); - freelegs1 = legs1 & freelegs; - freelegs2 = legs2 & freelegs; - commonlegsflag = any(commonlegs); - - isOK = allowOPs || commonlegsflag; % Exclude outer products if allowOPs is not set - - thisTensorXflag = -1; - % ### Enforce Sec. II.B.2.b,c,d (only perform outer product if there is a known tensor X with appropriate structure; only contract resulting object in another outer product or with an appropriate tensor X; enforce index dimension constraints) - if isOK && ~commonlegsflag - % It's an outer product. Check if a suitable tensor X exists yet to contract with this outer product [Fig.5(c) & Eq.(25)]. - if oldmuCap == muCap - % Pass for new X's - if isnew1 || isnew2 - % Using a new object - allowed to contract with old and new X's - % Note - the while/end construction is faster than using "find" - Xstart = 1; - Xend = 1; - while Xend<=numel(tensorXflags) && tensorXflags(Xend)~=2; - Xend = Xend + 1; - end - else - % Made from old objects - only allowed to contract with new X's. Already had a chance to contract with old X's - % on the previous pass so repeating these is unnecessary. - Xstart = 1; - while Xstart<=numel(tensorXflags) && tensorXflags(Xstart)~=1; - Xstart = Xstart + 1; - end - Xend = Xstart; - while Xend<=numel(tensorXflags) && tensorXflags(Xend)==1; - Xend = Xend + 1; - end - end - else - % Old X's only on this pass - Xstart = 1; - Xend = 1; - while Xend<=numel(tensorXflags) && tensorXflags(Xend)==0; - Xend = Xend + 1; - end - end - for x = Xstart:Xend-1 - if all(tensorXlegs{x}(freelegs)) - % IIB2c: xi_C > xi_A (25) - if isGreaterThan_sd(tensorXdims{a},getProdLegDims(freelegs,legCosts,costType),costType) - % IIB2b: xi_C > xi_D && xi_C > xi_E: (16) - if isGreaterThan_sd(getProdLegDims(tensorXlegs{x}&~freelegs,legCosts,costType),getProdLegDims(freelegs1,legCosts,costType),costType) - if isGreaterThan_sd(getProdLegDims(tensorXlegs{x}&~freelegs,legCosts,costType),getProdLegDims(freelegs2,legCosts,costType),costType) - thisTensorXflag = tensorXflags(x); - break; - end - end - end - end - end - isOK = thisTensorXflag~=-1; - end - - % If either constituent is the result of an outer product, check that it is being contracted with an appropriate tensor - % [either this is a contraction over all indices, or this is an outer product with a tensor of larger total dimension than - % either constituent of the previous outer product, and Eqs. (16), (25), and (26) are satisfied]. - if isOK && (isOP1 || isOP2) - % Post-OP. This contraction only allowed if it is also an outer product, or if only one object is an outer product, it involves all indices on that tensor, and the other object satisfies the relevant conditions. - isOK = xor(isOP1,isOP2) || ~commonlegsflag; % If contracting over common indices, only one object may be an outer product - if isOK - if commonlegsflag - % This contraction is not itself an outer product - % Conditions on outer product object: - if isOP1 - % Check all-indices condition: - if isequal(commonlegs,legs1) - % Check free legs on contracting tensor are larger than summing legs going to each component of outer product [Eq. (16)] - isOK = isGreaterThan_sd(getProdLegDims(freelegs,legCosts,costType),OPmaxdim1,costType); % IIB2b: xi_C > xi_D, xi_C > xi_E (16) - else - isOK = false; - end - else - % Check all-indices condition: - if isequal(commonlegs,legs2) - % Check free legs on contracting tensor are larger than summing legs going to each component of outer - % product [Eq. (16)] - isOK = isGreaterThan_sd(getProdLegDims(freelegs,legCosts,costType),OPmaxdim2,costType); % IIB2b: xi_C > xi_D, xi_C > xi_E (16) - else - isOK = false; - end - end - % Conditions on X: Ensure X is fundamental or acceptably-constructed (note: structure is checked by requiring - % non-zero value of allIn) - if isOK - if isOP1 - % Tensor 2 is X - if numInPieceTwo > 1 - % Tensor 2 is not fundamental - % Check tensor 2 is constructed in an acceptable fashion [Fig. 5(c) and Eqs. (25) and (26)] - isOK = isGreaterThan_sd(allIn2,getProdLegDims(freelegs,legCosts,costType),costType); % IIB2c: xi_C > xi_D (26) - if isOK - isOK = isGreaterThan_sd(allIn2,getProdLegDims(freelegs1,legCosts,costType),costType); % IIB2c: xi_C > xi_A (25) - end - end - else - % Tensor 1 is X - if numInPieceOne > 1 - % Tensor 1 is not fundamental - % Check tensor 1 is constructed in an acceptable fashion [Fig. 5(c) and Eqs. (25) and (26)] - isOK = isGreaterThan_sd(allIn1,getProdLegDims(freelegs,legCosts,costType),costType); % IIB2c: xi_C > xi_D (26) - if isOK - isOK = isGreaterThan_sd(allIn1,getProdLegDims(freelegs2,legCosts,costType),costType); % IIB2c: xi_C > xi_A (25) - end - end - end - end - else - % This contraction is an outer product. If either constituent is an outer product, check that both tensors - % within that object are not larger than the third tensor with which they are now being contracted. - if isOP1 - isOK = ~isGreaterThan_sd(OPmaxdim1,getProdLegDims(freelegs2,legCosts,costType),costType); % IIB2b: xi_C >= xi_A, xi_C >= xi_B (20) - end - if isOK && isOP2 - isOK = ~isGreaterThan_sd(OPmaxdim2,getProdLegDims(freelegs1,legCosts,costType),costType); % IIB2b: xi_C >= xi_A, xi_C >= xi_B (20) - end - end - end - end - % ### End of enforcing Sec. II.B.2.b,c,d (only perform outer product if there is a known tensor X with appropriate structure; only contract resulting object in another outer product or with an appropriate tensor X; enforce index dimension constraints) - - % If contraction is not prohibited, check cost is acceptable (<=muCap and, if not involving new objects, >oldmuCap) - if isOK - % If constructing an outer product which may contract with a new X, do not exclude on basis of low cost: Hence - % isnew1||isnew2||thisTensorXflag>0 - [newCost isOK] = getBuildCost(freelegs,commonlegs,legCosts,costType,oldmuCap,muCap,isnew1||isnew2||thisTensorXflag>0,costToBuild1,costToBuild2); - if ~isOK - newmuCap = min([newmuCap newCost]); - end - end - - % If cost is OK, compare with previous best known cost for constructing this object - if isOK - % Get involved tensors - tensorsInNew = tensorsIn1 | tensorsIn2; - % Find if previously constructed - objectPtr = 0; - for x=1:numel(objects{numInObjects}) - if isequal(objects{numInObjects}{x}{2},tensorsInNew) - objectPtr = x; - break; - end - end - isnew = objectPtr==0; - if isnew - % Is a new construction - objectPtr = numel(objects{numInObjects})+1; - else - % Compare new cost with best-so-far cost for construction of this object - isOK = isLessThan(newCost,objects{numInObjects}{objectPtr}{4},costType); - end - - % ### If appropriate, update tensorXlist (list of tensors which can be contracted with objects created by outer product) - if allowOPs - if isOK - % New tensor or new best cost - E_is_2 = ~any(freelegs2) && any(freelegs1); - if (~any(freelegs1) && any(freelegs2)) || E_is_2 - % New best sequence consistent with Fig.5(c). - % Determine the value of allIn, which corresponds to xi_C. (This is used in determining valid tensors X to contract with outer products). - if E_is_2 - allIn = getProdLegDims(legs2,legCosts,costType); - else - allIn = getProdLegDims(legs1,legCosts,costType); - end - % Add to tensor X list for outer products (or if already there, update the value of allIn): - if isnew - if isGreaterThan_sd(dimSquared(allIn,costType),getProdLegDims(freelegs,legCosts,costType),costType) % Enforce Eq.(27) - [tensorXlegs tensorXdims tensorXflags] = addToTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs,allIn,costType,isnew); - end - else - if isGreaterThan_sd(dimSquared(allIn,costType),getProdLegDims(freelegs,legCosts,costType),costType) % Enforce Eq.(27) - [tensorXlegs tensorXdims tensorXflags] = addToTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs,allIn,costType,isnew,~isequal(objects{numInObjects}{objectPtr}{7},zeros(1,costType))); - elseif ~isequal(objects{numInObjects}{objectPtr}{7},zeros(1,costType)) % Only need to invoke removeFromTensorXList if there might actually be an entry - [tensorXlegs tensorXdims tensorXflags] = removeFromTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs); - end - end - else - % This tensor is not an eligible tensor X for an outer product: Store a dummy value in allIn to indicate this - allIn = zeros(1,costType); - % Best cost and not consistent with Fig.5(c): Ensure does not appear in tensorXlist. Active removal only - % required if object is not new, and previous best sequence was consistent with Fig.5(c), so allIn is not a - % dummy on the old entry. - if ~isnew && ~isequal(objects{numInObjects}{objectPtr}{7},zeros(1,costType)) - [tensorXlegs tensorXdims tensorXflags] = removeFromTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs); - end - end - elseif isequal(newCost,objects{numInObjects}{objectPtr}{4}) - % Equal-best cost to a known sequence for the same tensor - if ~isequal(objects{numInObjects}{objectPtr}{7},zeros(1,costType)) - % Previous best sequence was consistent with Fig.5(c) so tensor may appear in the provisional environments list - E_is_2 = ~any(freelegs2) && any(freelegs1); - if (~any(freelegs1) && any(freelegs2)) || E_is_2 - % Determine the value of allIn, which corresponds to xi_C in Fig.5(c). - if E_is_2 - allIn = getProdLegDims(legs2,legCosts,costType); - else - allIn = getProdLegDims(legs1,legCosts,costType); - end - % If smaller than previous value, update the value of allIn: - if isGreaterThan_sd(objects{numInObjects}{objectPtr}{7},allIn,costType) - if isGreaterThan_sd(dimSquared(allIn,costType),getProdLegDims(freelegs,legCosts,costType),costType) % Enforce Eq.(27) - [tensorXlegs tensorXdims tensorXflags] = updateTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs,allIn); - else - [tensorXlegs tensorXdims tensorXflags] = removeFromTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs); - end - end - else - % Found best-equal sequence not consistent with Fig.5(c) - [tensorXlegs tensorXdims tensorXflags] = removeFromTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs); - end - %else: There already exists a best-known-cost sequence for the tensor which is not consistent with Fig.5(c), and isOK=false. Tensor does not appear in tensorXlist. No need to assign allIn. Now returning to start of main loop. - end - % else: Sequence is not capable of updating tensorXlist (not better cost, not equal cost). Also, isOK=false. No need to assign allIn. Now returning to start of main loop. - end - else - % Not doing outer products. Store a dummy value in allIn, which is never used. - allIn = []; - end - % ### Done updating tensorXlist (list of tensors which can be contracted with objects created by outer product) - end - - if isOK - % Either no previous construction, or this one is better - if ~any(commonlegs) - % ### This construction is an outer product. Note dimension of larger of the two participating tensors in newmaxdim. (This is used in enforcing index-dimension-related constraints.) - newmaxdim = getProdLegDims(legs1,legCosts,costType); - newmaxdim2 = getProdLegDims(legs2,legCosts,costType); - if isGreaterThan_sd(newmaxdim2,newmaxdim,costType) - newmaxdim = newmaxdim2; - end - % ### End recording dimension of larger of the two participating tensors in newmaxdim. - thisIsOP = true; - newseq = [seq1 seq2 0]; - else - % This construction is not an outer product. - if isOP1 - newseq = [seq2 seq1 find(commonlegs)]; - else - newseq = [seq1 seq2 find(commonlegs)]; - end - thisIsOP = false; - % ### This construction is not an outer product. Therefore store a dummy value in maxdim. (For outer products, maxdim records the dimension of the larger participating tensor, to assist in enforcing index-dimension-related constraints.) - newmaxdim = []; - % ### End storing dummy value in maxdim - end - - % Update objects{} with this construction - objects{numInObjects}{objectPtr} = {freelegs,tensorsInNew,newseq,newCost,thisIsOP,newmaxdim,allIn}; - % ### Note 1: If this tensor has the structure of Fig.5(c) and so is capable of being contracted with an outer product object, |E| is recorded in allIn (otherwise this is a dummy value). - % ### Note 2: If this tensor is constructed by outer product, the dimension of the largest participating tensor is recorded in newmaxdim. (This is used in enforcing index-dimension-related constraints.) Otherwise, this is a dummy value. - - % Flag as a new construction - newobjectflags{numInObjects}(objectPtr) = true; - - % If top level, display result - if numInObjects == numtensors - % ### If a valid contraction sequence has been found, there is no need to perform any contraction sequence more expensive than this. Set muCap accordingly. - if costType==1 - muCap = newCost; - else - muCap = numel(newCost)-1; - end - % ### Done setting muCap accordingly. - if verbosity > 1 - displayInterimCostAndSequence(newCost,newseq,costType,posindices,tracedindices); - end - end - end - end - end - end - end - end - end - - % ### Finished searching if an object has been constructed which contains all tensors, and no new outer products have been enabled on the last pass (all(tensorXflags<2)==true, indicating that there are no new entries in the list of tensors which can be contracted with outer products). - done = numel(objects{numtensors})~=0 && (all(tensorXflags<2) || ~allowOPs); % Final object has been constructed, and if outer products are allowed, also no new X's have recently been constructed - - if ~done - if all(tensorXflags<2) - % ### All X tensors have been present for an entire pass, so all permitted outer products at this cost have already been constructed. - % Increment muCap, update oldmuCap - if costType==1 - if newmuCap < muCap * max([min(legCosts) 2]) - newmuCap = muCap * max([min(legCosts) 2]); - end - end - oldmuCap = muCap; - muCap = newmuCap; - newmuCap = Inf; - else - % ### New X tensors generated this pass (some tensor X flags==2). Do another pass with same cost limit, to construct newly-allowed objects (i.e. sequences of affordable cost including at least one new outer product). - % ### This is achieved by updating oldmuCap only. Now only outer products and contractions involving newly-created tensors will satisfy mu_0 < mu <= muCap. - oldmuCap = muCap; - end - % Clear all new object flags - for a=1:numel(newobjectflags) - newobjectflags{a} = false(1,numel(newobjectflags{a})); - end - % ### Update tensor X flags (2 -> 1 -> 0): - % ### 2: Newly created this pass becomes - % ### 1: Created last pass; allow construction of cheap objects which contract with this, as they may have previously been excluded due to lack of a valid tensor X. 1 becomes... - % ### 0: Old tensor X. Standard costing rules apply. - % ### Delete redundant entries in tensorXlist (e.g. if A has a subset of the legs on B, and an equal or lower value of allIn (i.e. |E| in Fig.5(c))) - [tensorXlegs tensorXdims tensorXflags] = mergeTensorXlist(tensorXlegs,tensorXdims,tensorXflags,costType); - % ### Done updating tensor X flags - else - if numel(objects{numtensors})~=1 - error('Inappropriate number of objects containing all tensors!') - end - end -end - -% Extract final result -sequence = int32(objects{numtensors}{1}{3}); -cost = objects{numtensors}{1}{4}; -end - -function [newCost isOK] = getBuildCost(freelegs,commonlegs,legCosts,costType,oldmuCap,muCap,isnew,costToBuild1,costToBuild2) -% Get fusion cost -allLegs = freelegs | commonlegs; -isOK = true; -if costType==1 - % Is cost too high (>muCap)? - newCost = prod(legCosts(allLegs)) + costToBuild1 + costToBuild2; - if newCost > muCap - isOK = false; - end - - % Is cost too low (not made from new objects, and <=oldmuCap: This construction has been done before) - if isOK && ~isnew - if newCost <= oldmuCap - isOK = false; - newCost = Inf; - end - end -else - % Is cost too high (>muCap)? - fusionpower = sum(legCosts(allLegs,2)); - if fusionpower > muCap - isOK = false; - newCost = fusionpower; - end - - % Is cost too low (not made from new objects, and <=oldmuCap: This construction has been done before) - if isOK && ~isnew - if fusionpower <= oldmuCap - isOK = false; - newCost = Inf; - end - end - - % If cost OK, determine total cost of construction - if isOK - newCostLen = max([numel(costToBuild1) numel(costToBuild2) fusionpower+1]); - newCost = zeros(1,newCostLen); - newCost(1:numel(costToBuild1)) = costToBuild1; - if ~isempty(costToBuild2) - newCost(1:numel(costToBuild2)) = newCost(1:numel(costToBuild2)) + costToBuild2; - end - newCost(fusionpower+1) = newCost(fusionpower+1) + prod(legCosts(allLegs,1)); - end -end -end - -function flag = isLessThan(cost1,cost2,costType) -% Compares two full network costs -if costType==1 - flag = cost1 < cost2; -else - if numel(cost1) < numel(cost2) - flag = true; - elseif numel(cost1) > numel(cost2) - flag = false; - else - flag = false; - for a = numel(cost2):-1:1 - if cost1(a) < cost2(a) - flag = true; - break; - elseif cost1(a) > cost2(a) - break; - end - end - end -end -end - -function flag = isGreaterThan_sd(cost1,cost2,costType) -% Compares two single-index costs -if costType==1 - flag = cost1 > cost2; -else - flag = false; - if cost1(2) > cost2(2) - flag = true; - elseif cost1(2) == cost2(2) - if cost1(1) > cost2(1) - flag = true; - end - end -end -end - -function dim = getProdLegDims(freelegs,legCosts,costType) -if costType==1 - dim = prod(legCosts(freelegs)); -else - dim = [prod(legCosts(freelegs,1)) sum(legCosts(freelegs,2))]; -end -end - -function dim = dimSquared(dim,costType) -if costType==1 - dim = dim*dim; -else - dim = [dim(1)*dim(1) dim(2)*2]; -end -end - -function displayInterimCostAndSequence(cost,seq,costType,posindices,tracedindices) -% Displays cost and sequence -dispseq = [tracedindices seq]; -dispseq(dispseq>0) = posindices(dispseq(dispseq>0)); -disp(['Sequence: ' unpaddednum2str(dispseq)]); -if costType==1 - t = ['Cost: ' num2str(cost)]; -else - t = 'Cost: '; - for a = numel(cost):-1:2 - t = [t num2str(cost(a)) 'X^' num2str(a-1) ' + ']; %#ok - end - t = [t num2str(cost(1)) 'X^0']; -end -if ~isempty(tracedindices) - t = [t ' + tracing costs']; -end -disp(t); -end - -function [tensorXlegs tensorXdims tensorXflags] = addToTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs,allIn,costType,isnew,oldMayHaveEntry) -% Constructed a new tensor for tensorXlist, or a known tensor at same or better cost. -% If legs exactly match a known non-provisional entry, consider as a possible tighter bound on allIn. -% Otherwise, consider for provisional list if not made redundant by any non-provisional entries. -% Add to provisional list if not made redundant by any non-provisional entries. -consider = true; -ptr = find(tensorXflags==1,1,'last'); -for a=ptr:-1:1 - if isequal(tensorXlegs{a},freelegs) - % If legs exactly match a non-provisional entry, update value of allIn. - % If allIn for this entry just got increased, associated constraints have been relaxed. Flag this updated entry as provisional to trigger another pass. - if isGreaterThan_sd(allIn,tensorXdims{a},costType) - tensorXdims{a} = allIn; - tensorXflags(a) = 2; - % Flags are always in ascending order: Move re-flagged entry to end of list - tensorXdims = tensorXdims([1:a-1 a+1:end a]); - tensorXlegs = tensorXlegs([1:a-1 a+1:end a]); - tensorXflags = tensorXflags([1:a-1 a+1:end a]); - else - tensorXdims{a} = allIn; - end - consider = false; - break; - else - % Check to see if made redundant by existing non-provisional entry - if all(tensorXlegs{a}(freelegs)) && ~isGreaterThan_sd(allIn,tensorXdims{a},costType) - % All legs in freelegs are in overlap with tensorXlegs{a}, and dimension of absorbed tensor is not greater: Proposed new entry is redundant. - % (Greater dimension is allowed as it would mean more permissive bounds for a subset of legs) - consider = false; - if ~isnew - if oldMayHaveEntry - % This tensor: Excluded from list. - % Previous, higer-cost contraction sequence may have successfully made an entry. Remove it. - [tensorXlegs tensorXdims tensorXflags] = removeFromTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs); - end - end - break; - end - end -end -if consider - % Tensor not excluded after comparison with non-provisional entries: - % If legs exactly match another provisional entry, new submission is a cheaper sequence so keep only the new value of allIn. - % (This is the same subnetwork but with a better cost, and possibly a different value of \xi_C.) - if ~isnew - if oldMayHaveEntry - for a=ptr+1:numel(tensorXlegs) - if isequal(tensorXlegs{a},freelegs) - tensorXdims{a} = allIn; - consider = false; - break; - end - end - end - end - % Otherwise: This is a new tensorXlist entry - if consider - tensorXlegs{end+1} = freelegs; - tensorXflags(end+1) = 2; - tensorXdims{end+1} = allIn; - end -end -end - -function [tensorXlegs tensorXdims tensorXflags] = updateTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs,allIn) -% Found new contraction sequence for an existing entry, but with a smaller value of allIn. Update the stored value to this value. -% (This is the same subnetwork, but the \xi_C constraint has been tightened.) -for a=numel(tensorXlegs):-1:1 - if isequal(freelegs,tensorXlegs{a}) - tensorXdims{a} = allIn; - break; - end -end -% If the original entry was provisional and redundant, this one is too. No match will be found (as the redundant tensor was never recorded), and that's OK. -% If the original entry was not provisional and redundant, it has now been updated. -end - -function [tensorXlegs tensorXdims tensorXflags] = removeFromTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs) -% Remove this tensor from tensorXlist. -% Cannot restrict checking just to provisional portion, because might need to remove a confirmed tensor's data from the list in the scenario described in footnote 2. -for a=numel(tensorXlegs):-1:1 - if isequal(freelegs,tensorXlegs{a}) - tensorXlegs(a) = []; - tensorXflags(a) = []; - tensorXdims(a) = []; - break; - end -end -end - -function [tensorXlegs tensorXdims tensorXflags] = mergeTensorXlist(tensorXlegs,tensorXdims,tensorXflags,costType) -% Merge provisional tensors into main list and decrease all nonzero flags by one. -% For each provisional entry in turn, check if it makes redundant or is made redundant by any other provisional entries, and if it makes redundant any -% non-provisional entries. If so, delete the redundant entries. - -% Decrease non-zero tensorXflags -tensorXflags(tensorXflags>0) = tensorXflags(tensorXflags>0)-1; - -ptr = find(tensorXflags==1,1,'first'); -for a=numel(tensorXlegs):-1:ptr - % Permit provisional tensors to be eliminated by other provisional tensors (elimination by non-provisional tensors was done earlier) - for b=[ptr:a-1 a+1:numel(tensorXlegs)] - if all(tensorXlegs{b}(tensorXlegs{a})) && ~isGreaterThan_sd(tensorXdims{a},tensorXdims{b},costType) % All legs in tensorXlegs{a} overlap with tensorXlegs{b}, and dimension of absorbed tensor is not greater: tensorXlegs{a} is redundant. - tensorXlegs(a) = []; - tensorXflags(a) = []; - tensorXdims(a) = []; - break; - end - end -end - -for a=ptr-1:-1:1 - % Now permit non-provisional tensors to be eliminated by provisional tensors, as these are now confirmed - for b=ptr:numel(tensorXlegs) - if all(tensorXlegs{b}(tensorXlegs{a})) && ~isGreaterThan_sd(tensorXdims{a},tensorXdims{b},costType) % All legs in tensorXlegs{a} overlap with tensorXlegs{b}, and dimension of absorbed tensor is not greater: tensorXlegs{a} is redundant. - tensorXlegs(a) = []; - tensorXflags(a) = []; - tensorXdims(a) = []; - break; - end - end -end -end diff --git a/src/utility/sparse/SparseArray.m b/src/utility/sparse/SparseArray.m index 88ebd5b..cbb8518 100644 --- a/src/utility/sparse/SparseArray.m +++ b/src/utility/sparse/SparseArray.m @@ -284,12 +284,6 @@ function disp(a) function [subs, idx, vals] = find(a) % Find subscripts of nonzero elements in a sparse array. % - % Usage - % ----- - % :code:`idx = find(a)` - % - % :code:`[subs, idx, vals] = find(a)` - % % Arguments % --------- % a : :class:`SparseArray` @@ -306,10 +300,6 @@ function disp(a) % var : (:, 1) :class:`double` % values of nonzero array entries. [idx, ~, vals] = find(a.var); - if nargout == 1 - subs = idx; - return - end subs = ind2sub_(a.sz, idx); end @@ -428,10 +418,6 @@ function disp(a) bool = false; end - function bool = istriu(a) - bool = istriu(spmatrix(a)); - end - function c = ldivide(a, b) % Elementwise left division for sparse arrays. % @@ -755,19 +741,22 @@ function disp(a) a.var = sign(a.var); end - function varargout = size(a, i) - if nargin == 1 - sz = a.sz; - else - sz = ones(1, max(i)); - sz(1:length(a.sz)) = a.sz; - sz = sz(i); - end - - if nargout <= 1 - varargout = {sz}; + + function d = size(a, dim) + % Sparse array dimensions. + % + % Usage + % ----- + % :code:`d = size(a)` + % returns the size of the array. + % + % :code:`d = size(a, dim)` + % returns the sizes of the dimensions specified by :code:`dim`, which is + % either a scalar or a vector of dimensions. + if nargin > 1 + d = a.sz(dim); else - varargout = num2cell(sz); + d = a.sz; end end @@ -812,25 +801,7 @@ function disp(a) function a = subsasgn(a, s, rhs) % Subscripted assignment for sparse array. - assert(strcmp(s(1).type, '()'), 'sparse:index', 'only () indexing allowed'); - - if length(s(1).subs) > 1 % non-linear indexing - assert(length(s(1).subs) == size(a.sz, 2), 'sparse:index', ... - 'number of indexing indices must match tensor size.'); - assert(all(a.sz >= cellfun(@max, s(1).subs)), 'sparse:bounds', ... - 'out of bounds assignment disallowed'); - s(1).subs = {sub2ind(a.sz, s(1).subs{:})}; - end - - [I, ~, V] = find(a.var); - [lia, locb] = ismember(s(1).subs{1}, I); - newI = vertcat(I, s(1).subs{1}(~lia)); - newJ = ones(size(newI)); - V(locb(lia)) = rhs(lia); - newV = vertcat(V, rhs(~lia)); - - a.var = sparse(newI, newJ, newV, ... - size(a.var, 1), size(a.var, 2)); + error('Not implemented.') end function a_sub = subsref(a, s) @@ -1115,9 +1086,7 @@ function disp(a) a = SparseArray(repmat(1:inddim, numinds, 1)', 1, repmat(inddim, 1, numinds)); end - function a = zeros(sz) - a = SparseArray([], [], sz); - end + function a = random(sz, density) % Create a random complex sparse array. diff --git a/test/TestAlgorithms.m b/test/TestAlgorithms.m index 2a5f279..31864a0 100644 --- a/test/TestAlgorithms.m +++ b/test/TestAlgorithms.m @@ -2,6 +2,7 @@ % Unit tests for algorithms properties (TestParameter) + unitcell = {1, 2, 3, 4} alg = {Vumps('which', 'smallestreal', 'maxiter', 5), ... ...IDmrg('which', 'smallestreal', 'maxiter', 5), ... Vumps2('which', 'smallestreal', 'maxiter', 6), ... @@ -10,59 +11,52 @@ symm = {'Z1', 'Z2'} end methods (Test) - function test2dIsing(tc, alg, symm) + function test2dIsing(tc, alg, unitcell, symm) alg.which = 'largestabs'; - for unitcell = 1:3 - if unitcell == 1 && (isa(alg, 'Vumps2') || isa(alg, 'IDmrg2')) - continue; - end - - E0 = 2.5337 ^ unitcell; - mpo1 = statmech2dIsing('Symmetry', symm); - - if strcmp(symm, 'Z1') - mps1 = initialize_mps(mpo1, CartesianSpace.new(12)); - else - mps1 = initialize_mps(mpo1, GradedSpace.new(Z2(0, 1), [6 6], false)); - end - - mpo = mpo1; - mps = mps1; - for i = 2:unitcell - mpo = [mpo mpo1]; - mps = [mps mps1]; - end - - [gs, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyEqual(expectation_value(gs, mpo, gs), E0, 'RelTol', 1e-2); + tc.assumeTrue(unitcell > 1 || isa(alg, 'IDmrg') || isa(alg, 'Vumps')) + + E0 = 2.5337 ^ unitcell; + if strcmp(symm, 'Z1') + mpo1 = InfMpo.Ising(); + mps1 = initialize_mps(mpo1, CartesianSpace.new(12)); + else + mpo1 = InfMpo.Ising('Symmetry', 'Z2'); + mps1 = initialize_mps(mpo1, GradedSpace.new(Z2(0, 1), [6 6], false)); end + + mpo = mpo1; + mps = mps1; + for i = 2:unitcell + mpo = [mpo mpo1]; + mps = [mps mps1]; + end + + [gs, lambda] = fixedpoint(alg, mpo, mps); + tc.verifyEqual(expectation_value(gs, mpo, gs), E0, 'RelTol', 1e-2); end - function test1dIsing(tc, alg, symm) + function test1dIsing(tc, unitcell, alg, symm) alg.which = 'smallestreal'; - for unitcell = 1:3 - if unitcell == 1 && (isa(alg, 'IDmrg2') || isa(alg, 'Vumps2')) - continue; - end - E0 = -1.273 * unitcell; - - mpo1 = quantum1dIsing('Symmetry', symm); - if strcmp(symm, 'Z1') - mps1 = initialize_mps(mpo1, CartesianSpace.new(12)); - else - mps1 = initialize_mps(mpo1, GradedSpace.new(Z2(0, 1), [6 6], false)); - end - - mpo = mpo1; - mps = mps1; - for i = 2:unitcell - mpo = [mpo mpo1]; - mps = [mps mps1]; - end - - [gs, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyEqual(expectation_value(gs, mpo), E0, 'RelTol', 1e-2); + tc.assumeTrue(unitcell > 1 || isa(alg, 'IDmrg') || isa(alg, 'Vumps')) + E0 = -1.273 * unitcell; + + if strcmp(symm, 'Z1') + mpo1 = InfJMpo.Ising(); + mps1 = initialize_mps(mpo1, CartesianSpace.new(12)); + else + mpo1 = InfJMpo.Ising('Symmetry', 'Z2'); + mps1 = initialize_mps(mpo1, GradedSpace.new(Z2(0, 1), [6 6], false)); + end + + mpo = mpo1; + mps = mps1; + for i = 2:unitcell + mpo = [mpo mpo1]; + mps = [mps mps1]; end + + [gs, lambda] = fixedpoint(alg, mpo, mps); + tc.verifyEqual(expectation_value(gs, mpo), E0, 'RelTol', 1e-2); end end end diff --git a/test/TestFiniteMpo.m b/test/TestFiniteMpo.m index ebb54ed..8fcdc92 100644 --- a/test/TestFiniteMpo.m +++ b/test/TestFiniteMpo.m @@ -8,7 +8,6 @@ FiniteMpo.randnc(ComplexSpace.new(2, false, 4, true, 2, true), ... ComplexSpace.new(2, true, 3, false)) ... ) - end methods (Test) @@ -41,40 +40,6 @@ function testTransposes(tc, mpo) tc.assertTrue(isequal(domain(mpo.'), codomain(mpo)')); tc.assertTrue(isequal(codomain(mpo.'), domain(mpo)')); end - - function test2dIsing(tc) - L = 8; - D = 64; - alg = Dmrg('maxiter', 10, 'which', 'largestabs'); - - mpo = statmech2dIsing('beta', 2, 'L', L); - vspace_max = CartesianSpace.new(D); - mps = initialize_mps(mpo, 'MaxVspace', vspace_max); - - [mps, envs, eta] = fixedpoint(alg, mpo, mps); - - mpo = statmech2dIsing('beta', 2, 'L', L, 'Symmetry', 'Z2'); - vspace_max = GradedSpace.new(Z2(0, 1), D ./ [2 2], false); - mps = initialize_mps(mpo, 'MaxVspace', vspace_max); - [mps, envs, eta] = fixedpoint(alg, mpo, mps); - end - - function test1dIsing(tc) - L = 8; - D = 64; - alg = Dmrg('miniter', 2, 'maxiter', 5, 'which', 'smallestreal'); - - mpo = quantum1dIsing('L', L); - vspace_max = CartesianSpace.new(D); - mps = initialize_mps(mpo, 'MaxVspace', vspace_max); - - [mps, envs, eta] = fixedpoint(alg, mpo, mps); - - mpo = quantum1dIsing('L', L, 'Symmetry', 'Z2'); - vspace_max = GradedSpace.new(Z2(0, 1), D ./ [2 2], false); - mps = initialize_mps(mpo, 'MaxVspace', vspace_max); - [mps, envs, eta] = fixedpoint(alg, mpo, mps); - end end end diff --git a/test/TestFiniteMps.m b/test/TestFiniteMps.m deleted file mode 100644 index de3eced..0000000 --- a/test/TestFiniteMps.m +++ /dev/null @@ -1,34 +0,0 @@ -classdef TestFiniteMps < matlab.unittest.TestCase - % Unit tests for finite matrix product states. - - properties - tol = 1e-10 - end - - methods (Test) - function testFullMps(tc) - L = 12; - P = ComplexSpace.new(2, false); - mps = FiniteMps.new([], P, 'L', L); - - % test conversion - psi = Tensor(mps); - psi2 = Tensor(FiniteMps(psi)); - tc.verifyTrue(isapprox(psi, psi2, 'RelTol', tc.tol)); - - % test norms - tc.verifyEqual(norm(psi), norm(mps), 'RelTol', tc.tol); - tc.verifyEqual(sqrt(abs(overlap(mps, mps))), norm(psi), 'RelTol', tc.tol); - mps_normed = normalize(mps); - tc.verifyEqual(norm(mps_normed), 1, 'RelTol', tc.tol); - tc.verifyEqual(norm(Tensor(mps_normed)), 1, 'RelTol', tc.tol); - - % test moving orthogonality center - for i = 1:length(mps) - tc.verifyEqual(overlap(movegaugecenter(mps_normed, i), mps_normed), 1, ... - 'RelTol', tc.tol); - end - end - end -end - diff --git a/test/TestInfJMpo.m b/test/TestInfJMpo.m index e6f812e..50e60ae 100644 --- a/test/TestInfJMpo.m +++ b/test/TestInfJMpo.m @@ -43,12 +43,12 @@ function testDerivatives(tc, mpo, mps) function test1dIsing(tc) alg = Vumps('which', 'smallestreal', 'maxiter', 5); D = 16; - mpo = quantum1dIsing('J', 1, 'h', 1, 'L', Inf); - mps = initialize_mps(mpo, CartesianSpace.new(D)); + mpo = InfJMpo.Ising(1, 1); + mps = UniformMps.randnc(CartesianSpace.new(2), CartesianSpace.new(D)); [mps2, lambda] = fixedpoint(alg, mpo, mps); tc.verifyTrue(isapprox(lambda, -1.27, 'RelTol', 1e-2)) - mpo = quantum1dIsing('J', 1, 'h', 1, 'L', Inf, 'Symmetry', 'Z2'); + mpo = InfJMpo.Ising(1, 1, 'Symmetry', 'Z2'); mps = initialize_mps(mpo, GradedSpace.new(Z2(0, 1), [D D] ./ 2, false)); [mps2, lambda2] = fixedpoint(alg, mpo, mps); tc.verifyTrue(isapprox(lambda, -1.27, 'RelTol', 1e-2)) diff --git a/test/TestInfMpo.m b/test/TestInfMpo.m index 45c26c5..a03bfaf 100644 --- a/test/TestInfMpo.m +++ b/test/TestInfMpo.m @@ -56,14 +56,14 @@ function test2dIsing(tc) D = 16; alg = Vumps('MaxIter', 10); - mpo = statmech2DIsing('beta', beta, 'Symmetry', 'Z1'); + mpo = InfMpo.Ising(beta); mps = UniformMps.randnc(CartesianSpace.new(2), CartesianSpace.new(D)); [mps2, lambda] = fixedpoint(alg, mpo, mps); tc.assertEqual(-log(lambda) / beta, freeEnergyExact, 'RelTol', 1e-5); mps = UniformMps.randnc(GradedSpace.new(Z2(0, 1), [1 1], false), ... GradedSpace.new(Z2(0, 1), [D D] ./ 2, false)); - mpo = statmech2DIsing('beta', beta, 'Symmetry', 'Z2'); + mpo = InfMpo.Ising(beta, 'Symmetry', 'Z2'); [mps2, lambda] = fixedpoint(alg, mpo, mps); tc.assertEqual(-log(lambda) / beta, freeEnergyExact, 'RelTol', 1e-5); @@ -71,13 +71,7 @@ function test2dIsing(tc) mpo = [mpo mpo]; [mps2, lambda] = fixedpoint(alg, mpo, mps); - tc.assertEqual(-log(lambda) / 2 / beta, freeEnergyExact, 'RelTol', 1e-5); - - mps = [mps; mps]; - mpo = [mpo; mpo]; - - [mps2, lambda] = fixedpoint(alg, mpo, mps); - tc.assertEqual(-log(lambda)/ 4 / beta, freeEnergyExact, 'RelTol', 1e-5); + tc.assertEqual(-log(sqrt(lambda)) / beta, freeEnergyExact, 'RelTol', 1e-5); end function test2dfDimer(tc) From c619699cbcc59e751046bdb86231a5e878ddd592 Mon Sep 17 00:00:00 2001 From: leburgel Date: Fri, 24 Feb 2023 10:25:43 +0100 Subject: [PATCH 154/245] Revert "Revert "Merge branch 'dev' of https://github.com/quantumghent/TensorTrack into dev"" This reverts commit 5ffdae99f7cc42f27f1287a9bd692bc7b7f32ba9. --- src/algorithms/Dmrg.m | 242 +++++ src/algorithms/Vumps.m | 74 +- src/environments/FiniteEnvironment.m | 61 ++ src/models/quantum1dHeisenberg.m | 49 + src/models/quantum1dIsing.m | 59 ++ src/models/statmech2dIsing.m | 60 ++ src/mps/FiniteMpo.m | 55 ++ src/mps/FiniteMps.m | 285 ++++++ src/mps/InfJMpo.m | 97 +- src/mps/InfMpo.m | 122 ++- src/mps/MpoTensor.m | 84 +- src/mps/MpsTensor.m | 95 +- src/mps/UniformMps.m | 75 +- src/sparse/SparseTensor.m | 22 +- src/sparse/sub2sub.m | 9 +- src/tensors/AbstractTensor.m | 122 ++- src/tensors/Tensor.m | 64 +- src/tensors/spaces/AbstractSpace.m | 4 + src/tensors/spaces/CartesianSpace.m | 4 - src/tensors/spaces/SumSpace.m | 21 + src/utility/diracdelta.m | 12 + src/utility/linalg/contract.m | 17 + src/utility/linalg/contractcost.m | 73 ++ src/utility/netcon.m | 1308 ++++++++++++++++++++++++++ src/utility/sparse/SparseArray.m | 65 +- test/TestAlgorithms.m | 88 +- test/TestFiniteMpo.m | 35 + test/TestFiniteMps.m | 34 + test/TestInfJMpo.m | 6 +- test/TestInfMpo.m | 12 +- 30 files changed, 2911 insertions(+), 343 deletions(-) create mode 100644 src/algorithms/Dmrg.m create mode 100644 src/environments/FiniteEnvironment.m create mode 100644 src/models/quantum1dHeisenberg.m create mode 100644 src/models/quantum1dIsing.m create mode 100644 src/models/statmech2dIsing.m create mode 100644 src/mps/FiniteMps.m create mode 100644 src/utility/diracdelta.m create mode 100644 src/utility/linalg/contractcost.m create mode 100644 src/utility/netcon.m create mode 100644 test/TestFiniteMps.m diff --git a/src/algorithms/Dmrg.m b/src/algorithms/Dmrg.m new file mode 100644 index 0000000..a10ffaf --- /dev/null +++ b/src/algorithms/Dmrg.m @@ -0,0 +1,242 @@ +classdef Dmrg + % Density Matrix Renormalisation Group algorithm for marix product states. + + properties + tol = 1e-10 + miniter = 5 + maxiter = 100 + verbosity = Verbosity.iter + doplot = false + which = 'largestabs' + + dynamical_tols = false + tol_min = 1e-12 + tol_max = 1e-10 + eigs_tolfactor = 1e-6 + + sweepstyle {mustBeMember(sweepstyle, {'f2f', 'b2b', 'm2m'})} = 'f2f' + + doSave = false + saveIterations = 1 + saveMethod = 'full' + name = 'DMRG' + end + + properties (Access = private) + alg_eigs = struct('MaxIter', 100, 'KrylovDim', 20); + progressfig + end + + methods + function alg = Dmrg(kwargs) + arguments + kwargs.?Dmrg + end + + fields = fieldnames(kwargs); + if ~isempty(fields) + for field = fields.' + alg.(field{1}) = kwargs.(field{1}); + end + end + + if ~isfield('alg_eigs', kwargs) + alg.alg_eigs.Tol = sqrt(alg.tol_min * alg.tol_max); + alg.alg_eigs.Verbosity = alg.verbosity - 2; + end + end + + function [mps, envs, eta] = fixedpoint(alg, mpo, mps, envs) + arguments + alg + mpo + mps + envs = initialize_envs(mpo) + end + + if length(mpo) ~= length(mps) + error('dmrg:argerror', ... + 'length of mpo (%d) should be equal to that of the mps (%d)', ... + length(mpo), length(mps)); + end + + t_total = tic; + disp_init(alg); + + for iter = 1:alg.maxiter + t_iter = tic; + + order = sweeporder(alg, length(mps)); + eta = zeros(1, length(mps)); + lambda = zeros(1, length(mps)); + + for pos = sweeporder(alg, length(mps)) + [mps, envs, lambda(pos), eta(pos)] = ... + localupdate(alg, mps, mpo, envs, order(pos)); + end + + eta = norm(eta, Inf); + lambda = sum(lambda) / length(lambda); + + if iter > alg.miniter && eta < alg.tol + disp_conv(alg, iter, lambda, eta, toc(t_total)); + return + end + + alg = updatetols(alg, iter, eta); + + plot(alg, iter, mps, eta); + disp_iter(alg, iter, lambda, eta, toc(t_iter)); + end + + disp_maxiter(alg, iter, lambda, eta, toc(t_total)); + end + + function [mps, envs, lambda, eta] = localupdate(alg, mps, mpo, envs, pos) + % update orthogonality center + mps = movegaugecenter(mps, pos); + envs = movegaugecenter(envs, mpo, mps, mps, pos); + + % compute update + H_AC = AC_hamiltonian(mpo, mps, envs, pos); + AC = mps.A(pos); + [AC.var, lambda] = eigsolve(H_AC, AC.var, 1, alg.which); + + % determine error + phase = dot(AC.var, mps.A(pos)); + eta = distance(AC.var ./ sign(phase), mps.A(pos)); + + % take step + mps.A(pos) = AC; + envs = invalidate(envs, pos); + end + end + + %% Option handling + methods + function alg = updatetols(alg, iter, eta) + if alg.dynamical_tols + alg.alg_eigs.Tol = between(alg.tol_min, eta * alg.eigs_tolfactor / iter, ... + alg.tol_max); + if alg.verbosity > Verbosity.iter + fprintf('Updated eigsolver tolerances: (%e)\n', alg.alg_eigs.Tol); + end + end + end + + function order = sweeporder(alg, L) + switch alg.sweepstyle + case 'f2f' + order = [1:L L-1:-1:2]; + case 'b2b' + order = [L:-1:1 2:L]; + case 'm2m' + order = circshift([2:L, L-1:-1:1], -floor(L / 2)); + otherwise + error('unknown sweepstyle %s', alg.sweepstyle); + end + end + end + + + %% Display + methods (Access = private) + function plot(alg, iter, mps, eta) + if ~alg.doplot, return; end + persistent axhistory axspectrum + + D = depth(mps); + W = period(mps); + + if isempty(alg.progressfig) || ~isvalid(alg.progressfig) || iter == 1 + alg.progressfig = figure('Name', 'Vumps'); + axhistory = subplot(D + 1, W, 1:W); + axspectrum = gobjects(D, W); + for d = 1:D, for w = 1:W + axspectrum(d, w) = subplot(D + 1, W, w + d * W); + end, end + linkaxes(axspectrum, 'y'); + end + + + if isempty(axhistory.Children) + semilogy(axhistory, iter, eta, '.', 'Color', colors(1), ... + 'DisplayName', 'Errors', 'MarkerSize', 10); + hold on + ylim(axhistory, [alg.tol / 5, max([1 eta])]); + yline(axhistory, alg.tol, '-', 'Convergence', ... + 'LineWidth', 2); + hold off + else + axhistory.Children(end).XData(end+1) = iter; + axhistory.Children(end).YData(end+1) = eta; + end + + plot_entanglementspectrum(mps, 1:period(mps), axspectrum); + drawnow + end + + function disp_init(alg) + if alg.verbosity < Verbosity.conv, return; end + fprintf('---- %s ----\n', alg.name); + end + + function disp_iter(alg, iter, lambda, eta, t) + if alg.verbosity < Verbosity.iter, return; end + + s = settings; + if abs(imag(lambda)) < eps(lambda)^(3/4) * abs(lambda) + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('%s %2d:\tE = %-0.4f\terror = %0.1e\t(%s)\n', ... + alg.name, iter, real(lambda), eta, time2str(t)); + otherwise + fprintf('%s %4d:\tE = %-0.15f\terror = %0.4e\t(%s)\n', ... + alg.name, iter, real(lambda), eta, time2str(t, 's')); + + end + else + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('%s %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... + alg.name, iter, real(lambda), imag(lambda), eta, time2str(t)); + otherwise + fprintf('%s %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... + alg.name, iter, real(lambda), imag(lambda), eta, time2str(t)); + + end + end + end + + function disp_conv(alg, iter, lambda, eta, t) + if alg.verbosity < Verbosity.conv, return; end + s = settings; + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('%s converged %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... + alg.name, iter, real(lambda), imag(lambda), eta, time2str(t)); + otherwise + fprintf('%s converged %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... + alg.name, iter, real(lambda), imag(lambda), eta, time2str(t)); + + end + fprintf('---------------\n'); + end + + function disp_maxiter(alg, iter, lambda, eta, t) + if alg.verbosity < Verbosity.warn, return; end + s = settings; + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('%s max iterations %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... + alg.name, iter, real(lambda), imag(lambda), eta, time2str(t)); + otherwise + fprintf('%s max iterations %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... + alg.name, iter, real(lambda), imag(lambda), eta, time2str(t)); + + end + fprintf('---------------\n'); + end + end +end + diff --git a/src/algorithms/Vumps.m b/src/algorithms/Vumps.m index 4526ea6..18d1752 100644 --- a/src/algorithms/Vumps.m +++ b/src/algorithms/Vumps.m @@ -25,7 +25,6 @@ saveIterations = 1 saveMethod = 'full' name = 'VUMPS' - end properties (Access = private) @@ -119,12 +118,16 @@ else sites = 1:period(mps); end - H_AC = AC_hamiltonian(mpo, mps, GL, GR, sites); - AC = mps.AC(sites); + ACs = arrayfun(@(x) x.AC(sites), mps, 'UniformOutput', false); + AC = vertcat(ACs{:}); for i = length(sites):-1:1 - [AC(i).var, ~] = eigsolve(H_AC{i}, AC(sites(i)).var, 1, alg.which, ... + [AC(1, i).var, ~] = eigsolve(H_AC{i}, AC(1, i).var, 1, alg.which, ... kwargs{:}); + + for d = 2:depth(mpo) + AC(d, i).var = H_AC{i}(d).apply(AC(d-1, i).var); + end end end @@ -137,9 +140,15 @@ end H_C = C_hamiltonian(mpo, mps, GL, GR, sites); + Cs = arrayfun(@(x) x.C(sites), mps, 'UniformOutput', false); + C = vertcat(Cs{:}); for i = length(sites):-1:1 - [C(i), ~] = eigsolve(H_C{i}, mps.C(sites(i)), 1, alg.which, ... + [C(1, i), ~] = eigsolve(H_C{i}, C(1, i), 1, alg.which, ... kwargs{:}); + + for d = 2:depth(mpo) + C(d, i) = H_C{i}(d).apply(C(d-1, i)); + end end end @@ -150,10 +159,12 @@ sites = 1:period(mps); end - for i = length(AC):-1:1 - [Q_AC, ~] = leftorth(AC(i), 'polar'); - [Q_C, ~] = leftorth(C(i), 1, 2, 'polar'); - mps.AL(sites(i)) = multiplyright(Q_AC, Q_C'); + for d = size(AC, 1):-1:1 + for i = size(AC, 2):-1:1 + [Q_AC, ~] = leftorth(AC(d, i), 'polar'); + [Q_C, ~] = leftorth(C(d, i), 1, 2, 'polar'); + mps(d).AL(sites(i)) = multiplyright(Q_AC, Q_C'); + end end kwargs = namedargs2cell(alg.alg_canonical); @@ -165,31 +176,40 @@ alg mpo mps - GL = cell(1, period(mps)) - GR = cell(1, period(mps)) + GL = cell(depth(mpo), period(mps)) + GR = cell(depth(mpo), period(mps)) end kwargs = namedargs2cell(alg.alg_environments); - [GL, GR, lambda] = environments(mpo, mps, mps, GL, GR, ... - kwargs{:}); + D = depth(mpo); + lambda = zeros(D, 1); + for d = 1:D + [GL(d, :), GR(d, :), lambda(d)] = environments(mpo.slice(d), mps(d), mps(next(d, D)), GL(d, :), GR(d, :), ... + kwargs{:}); + end + lambda = prod(lambda); end function eta = convergence(alg, mpo, mps, GL, GR) + % TODO: also implement galerkin error H_AC = AC_hamiltonian(mpo, mps, GL, GR); H_C = C_hamiltonian(mpo, mps, GL, GR); - eta = zeros(1, period(mps)); - for w = 1:period(mps) - AC_ = apply(H_AC{w}, mps.AC(w)); - lambda_AC = dot(AC_, mps.AC(w)); - AC_ = normalize(AC_ ./ lambda_AC); - - ww = prev(w, period(mps)); - C_ = apply(H_C{ww}, mps.C(ww)); - lambda_C = dot(C_, mps.C(ww)); - C_ = normalize(C_ ./ lambda_C); - - eta(w) = distance(AC_ , ... - repartition(multiplyleft(mps.AR(w), C_), rank(AC_))); + eta = zeros(depth(mpo), period(mps)); + for d = 1:depth(mpo) + dd = next(d, depth(mpo)); + for w = 1:period(mps) + AC_ = apply(H_AC{w}(d), mps(d).AC(w)); + lambda_AC = dot(AC_, mps(dd).AC(w)); + AC_ = normalize(AC_ ./ lambda_AC); + + ww = prev(w, period(mps)); + C_ = apply(H_C{ww}(d), mps(d).C(ww)); + lambda_C = dot(C_, mps(dd).C(ww)); + C_ = normalize(C_ ./ lambda_C); + + eta(dd, w) = distance(AC_ , ... + repartition(multiplyleft(mps(dd).AR(w), C_), rank(AC_))); + end end eta = max(eta, [], 'all'); end @@ -256,7 +276,7 @@ function plot(alg, iter, mps, eta) axhistory.Children(end).YData(end+1) = eta; end - plot_entanglementspectrum(mps, 1:period(mps), axspectrum); + plot_entanglementspectrum(mps, 1:D, 1:W, axspectrum); drawnow end diff --git a/src/environments/FiniteEnvironment.m b/src/environments/FiniteEnvironment.m new file mode 100644 index 0000000..9304f35 --- /dev/null +++ b/src/environments/FiniteEnvironment.m @@ -0,0 +1,61 @@ +classdef FiniteEnvironment + %FINITEENVIRONMENT Summary of this class goes here + % Detailed explanation goes here + + properties + GL + GR + end + + methods + function envs = FiniteEnvironment(varargin) + %FINITEENVIRONMENT Construct an instance of this class + % Detailed explanation goes here + if nargin == 0, return; end + if nargin == 2 + envs.GL = varargin{1}; + envs.GR = varargin{2}; + assert(isequal(size(envs.GL), size(envs.GR))); + return + end + error('undefined syntax'); + end + + function envs = movegaugecenter(envs, mpo, mps1, mps2, pos) + for i = 2:pos + if ~isempty(envs.GL{i}), continue; end + T = transfermatrix(mpo, mps1, mps2, i-1); + envs.GL{i} = apply(T, envs.GL{i-1}); + end + for i = length(mps1):-1:(pos+1) + if ~isempty(envs.GR{i}), continue; end + T = transfermatrix(mpo, mps1, mps2, i).'; + envs.GR{i} = apply(T, envs.GR{i+1}); + end + end + + function envs = invalidate(envs, pos) + envs.GL(pos+1:end) = cell(1, length(envs.GL) - pos); + envs.GR(1:pos) = cell(1, pos); + end + end + +% methods (Static) +% function envs = initialize(mpo, mps1, mps2) +% arguments +% mpo +% mps1 +% mps2 = mps1 +% end +% +% GL = cell(1, length(mpo) + 1); +% GR = cell(1, length(mpo) + 1); +% +% GL{1} = mpo.L; +% GR{end} = mpo.R; +% +% envs = FiniteEnvironment(GL, GR); +% end +% end +end + diff --git a/src/models/quantum1dHeisenberg.m b/src/models/quantum1dHeisenberg.m new file mode 100644 index 0000000..beff676 --- /dev/null +++ b/src/models/quantum1dHeisenberg.m @@ -0,0 +1,49 @@ +function mpo = quantum1dHeisenberg(kwargs) +arguments + kwargs.Spin = 1 + kwargs.J = 1 + kwargs.h = 0 + kwargs.L = Inf % size of system + kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'U1', 'SU2'})} = 'Z1' +end + +J = kwargs.J; +h = kwargs.h; + +Q = SU2(2 * kwargs.Spin + 1); + +switch kwargs.Symmetry + case 'SU2' + assert(isscalar(J) || all(J == J(1)), ... + 'Different spin couplings not invariant under SU2'); + assert(h == 0, 'Magnetic field not invariant under SU2'); + + pSpace = GradedSpace.new(Q, 1, false); + vSpace = GradedSpace.new(SU2(3), 1, false); + tSpace = one(vSpace); + + s = kwargs.Spin; + L = Tensor.ones([tSpace pSpace], [pSpace vSpace]); + L = L * (-J(1) * (s^2 + s)); + R = Tensor.ones([vSpace pSpace], [pSpace tSpace]); + + cod = SumSpace([one(vSpace) vSpace one(vSpace)], pSpace); + dom = SumSpace(pSpace, [one(vSpace), vSpace, one(vSpace)]); + O = MpoTensor.zeros(cod, dom); + O(1, 1, 1, 1) = 1; + O(3, 1, 3, 1) = 1; + O(1, 1, 2, 1) = L; + O(2, 1, 3, 1) = R; + + otherwise + error('TBA'); +end + +mpo = InfJMpo(O); + +if isfinite(kwargs.L) + mpo = open_boundary_conditions(mpo, L); +end + +end + diff --git a/src/models/quantum1dIsing.m b/src/models/quantum1dIsing.m new file mode 100644 index 0000000..449aec9 --- /dev/null +++ b/src/models/quantum1dIsing.m @@ -0,0 +1,59 @@ +function mpo = quantum1dIsing(kwargs) +arguments + kwargs.J = 1 + kwargs.h = 1 + kwargs.L = Inf % size of system + kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'Z2'})} = 'Z1' +end + +J = kwargs.J; +h = kwargs.h; + +sigma_x = [0 1; 1 0]; +sigma_z = [1 0; 0 -1]; + +if strcmp(kwargs.Symmetry, 'Z1') + pSpace = CartesianSpace.new(2); + vSpace = one(pSpace); + trivSpace = one(pSpace); + + S = Tensor([vSpace pSpace], [pSpace vSpace]); + Sx = fill_matrix(S, sigma_x); + Sz = fill_matrix(S, sigma_z); + + cod = SumSpace([vSpace vSpace vSpace], pSpace); + dom = SumSpace(pSpace, [vSpace vSpace vSpace]); + O = MpoTensor.zeros(cod, dom); + O(1, 1, 1, 1) = 1; + O(3, 1, 3, 1) = 1; + O(1, 1, 2, 1) = -J * Sx; + O(2, 1, 3, 1) = Sx; + O(1, 1, 3, 1) = (-J * h) * Sz; + +else + pSpace = GradedSpace.new(Z2(0, 1), [1 1], false); + vSpace = GradedSpace.new(Z2(1), 1, false); + trivSpace = one(pSpace); + + Sx_l = fill_matrix(Tensor([trivSpace pSpace], [pSpace vSpace]), {1 1}); + Sx_r = fill_matrix(Tensor([vSpace pSpace], [pSpace trivSpace]), {1 1}); + Sz = fill_matrix(Tensor([trivSpace pSpace], [pSpace trivSpace]), {1 -1}); + + cod = SumSpace([one(vSpace) vSpace one(vSpace)], pSpace); + dom = SumSpace(pSpace, [one(vSpace), vSpace, one(vSpace)]); + O = MpoTensor.zeros(cod, dom); + O(1, 1, 1, 1) = 1; + O(3, 1, 3, 1) = 1; + O(1, 1, 2, 1) = -J * Sx_l; + O(2, 1, 3, 1) = Sx_r; + O(1, 1, 3, 1) = (-J * h) * Sz; +end + +mpo = InfJMpo(O); + +if isfinite(kwargs.L) + mpo = open_boundary_conditions(InfJMpo(O), kwargs.L); +end + +end + diff --git a/src/models/statmech2dIsing.m b/src/models/statmech2dIsing.m new file mode 100644 index 0000000..81539ad --- /dev/null +++ b/src/models/statmech2dIsing.m @@ -0,0 +1,60 @@ +function O = statmech2dIsing(kwargs) + +arguments + kwargs.beta = log(1 + sqrt(2)) / 2; + kwargs.L = Inf % size of system + kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'Z2'})} = 'Z1' +end + +if ~isfinite(kwargs.L) + O = InfMpo({MpoTensor(bulk_mpo(kwargs.beta, kwargs.Symmetry))}); + +else + r = boundary_mpo(kwargs.beta, kwargs.Symmetry); + l = r'; + o = bulk_mpo(kwargs.beta, kwargs.Symmetry); + + O = FiniteMpo(MpsTensor(l), repmat({MpoTensor(o)}, 1, kwargs.L), MpsTensor(r)); +end + +end + +function O = bulk_mpo(beta, symmetry) + +if strcmp(symmetry, 'Z1') + sz = [2 2 2 2]; + t = sqrtm([exp(beta) exp(-beta); exp(-beta) exp(beta)]); + o = contract(diracdelta(sz), 1:4, t, [-1 1], t, [-2 2], t, [-3 3], t, [-4 4]); + O = fill_tensor(Tensor.zeros(sz), o); + +elseif strcmp(symmetry, 'Z2') + s = GradedSpace.new(Z2(0, 1), [1 1], false); + t = sqrtm(fill_matrix(Tensor(s, s), num2cell(2 * [cosh(beta) sinh(beta)]))); + o = Tensor.ones([s s], [s s]) / 2; + + O = contract(o, 1:4, t, [-1 1], t, [-2 2], t, [3 -3], t, [4 -4], 'Rank', [2 2]); +else + error('invalid symmetry'); +end + +end + +function O = boundary_mpo(beta, symmetry) + +if strcmp(symmetry, 'Z1') + sz = [2 2 2]; + t = sqrtm([exp(beta) exp(-beta); exp(-beta) exp(beta)]); + o = contract(diracdelta(sz), 1:3, t, [-1 1], t, [-2 2], t, [-3 3]); + O = fill_tensor(Tensor.zeros(sz), o); + +elseif strcmp(symmetry, 'Z2') + s = GradedSpace.new(Z2(0, 1), [1 1], false); + t = sqrtm(fill_matrix(Tensor(s, s), num2cell(2 * [cosh(beta) sinh(beta)]))); + o = Tensor.ones([s s], s) / sqrt(2); + + O = contract(o, 1:3, t, [-1 1], t, [-2 2], t, [3 -3], 'Rank', [2 1]); +else + error('invalid symmetry'); +end + +end \ No newline at end of file diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index 110dd99..015cc92 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -64,6 +64,61 @@ end end + function mps = initialize_mps(mpo, kwargs) + arguments + mpo + kwargs.MaxVspace + end + + pspaces = arrayfun(@(x) pspace(mpo, x), 1:length(mpo), 'UniformOutput', false); + + vspacefirst = rightvspace(mpo.L)'; + vspacelast = leftvspace(mpo.R); + + newkwargs = namedargs2cell(kwargs); + mps = FiniteMps.randnc(pspaces{:}, 'LeftVspace', vspacefirst, ... + 'RightVspace', vspacelast, newkwargs{:}); + mps = normalize(mps); + end + + function envs = initialize_envs(mpo) + arguments + mpo + end + + GL = cell(1, length(mpo) + 1); + GR = cell(1, length(mpo) + 1); + + GL{1} = mpo.L; + GR{end} = mpo.R; + + envs = FiniteEnvironment(GL, GR); + end + + function T = transfermatrix(mpo, mps1, mps2, sites) + arguments + mpo + mps1 + mps2 = mps1 + sites = 1:length(mps1) + end + + assert(all(diff(sites) == 1), 'sites must be neighbouring and increasing.'); + A1 = mps1.A(sites); + A2 = mps2.A(sites); + O = mpo.O(sites); %#ok + T = FiniteMpo.mps_channel_operator(A1, O, A2); %#ok + end + + function H = AC_hamiltonian(mpo, mps, envs, pos) + envs = movegaugecenter(envs, mpo, mps, mps, pos); + H = FiniteMpo(envs.GL{pos}, mpo.O(pos), envs.GR{pos + 1}); + end + + function s = pspace(mpo, x) + s = pspace(mpo.O{x}); + end + function s = domain(mpo) N = prod(cellfun(@(x) size(x, 4), mpo(1).O)); s = conj(... diff --git a/src/mps/FiniteMps.m b/src/mps/FiniteMps.m new file mode 100644 index 0000000..d0c6076 --- /dev/null +++ b/src/mps/FiniteMps.m @@ -0,0 +1,285 @@ +classdef FiniteMps + % Finite Matrix product states + + properties + A (1,:) MpsTensor + center + end + + %% Constructors + methods + function mps = FiniteMps(varargin) + if nargin == 0, return; end + if nargin == 1 + mps.A = varargin{1}; + elseif nargin == 2 + mps.A = varargin{1}; + mps.center = varargin{2}; + end + end + end + + methods (Static) + function mps = new(fun, pspaces, kwargs) + arguments + fun + end + arguments (Repeating) + pspaces + end + arguments + kwargs.L + kwargs.MaxVspace + kwargs.LeftVspace = one(pspaces{1}) + kwargs.RightVspace = one(pspaces{1}) + end + + if isempty(fun), fun = @randnc; end + + if isfield(kwargs, 'L') + pspaces = repmat(pspaces, 1, kwargs.L); + end + if isfield(kwargs, 'MaxVspace') && ~iscell(kwargs.MaxVspace) + kwargs.MaxVspace = {kwargs.MaxVspace}; + end + + L = length(pspaces); + + vspacesL = cell(1, L + 1); + vspacesL{1} = kwargs.LeftVspace; + for i = 1:L + vspacesL{i+1} = vspacesL{i} * pspaces{i}; + if isfield(kwargs, 'MaxVspace') + vspacesL{i + 1} = infimum(vspacesL{i + 1}, ... + kwargs.MaxVspace{mod1(i + 1, length(kwargs.MaxVspace))}); + end + end + + vspacesR = cell(1, L + 1); + vspacesR{end} = kwargs.RightVspace; + for i = L:-1:1 + vspacesR{i} = pspaces{i}' * vspacesR{i+1}; + if isfield(kwargs, 'MaxVspace') + vspacesR{i} = infimum(vspacesR{i}, ... + kwargs.MaxVspace{mod1(i, length(kwargs.MaxVspace))}); + end + end + + vspaces = cellfun(@infimum, vspacesL, vspacesR, 'UniformOutput', false); + + for w = length(pspaces):-1:1 + rankdeficient = vspaces{w} * pspaces{w} < vspaces{w + 1} || ... + vspaces{w} > pspaces{w}' * vspaces{w + 1}; + if rankdeficient + error('mps:rank', ... + 'Cannot create a full rank mps with given spaces.'); + end + + A{w} = Tensor.new(fun, [vspaces{w} pspaces{w}], vspaces{w + 1}); + end + mps = FiniteMps(A); + end + + function mps = new_from_spaces(fun, spaces, kwargs) + arguments + fun + end + arguments (Repeating) + spaces + end + arguments + kwargs.MaxVspace + end + + if isfield(kwargs, 'MaxVspace') && ~iscell(kwargs.MaxVspace) + kwargs.MaxVspace = {kwargs.MaxVspace}; + end + + assert(mod(length(spaces) - 1, 2)); + if isempty(fun), fun = @randnc; end + + L = (length(spaces) - 1) / 2; + + for w = L:-1:1 + Vleft = spaces{2 * w - 1}; + P = spaces{2 * w}; + Vright = spaces{2 * w + 1}; + rankdeficient = Vleft * P < Vright || Vleft > P' * Vright; + if rankdeficient + error('mps:rank', ... + 'Cannot create a full rank mps with given spaces.'); + end + A{w} = Tensor.new(fun, [Vleft P], Vright); + end + + mps = FiniteMps(A); + end + + function mps = randnc(varargin) + mps = FiniteMps.new(@randnc, varargin{:}); + end + end + + + %% Properties + methods + function p = length(mps) + p = length(mps.A); + end + + function [svals, charges] = schmidt_values(mps, w) + arguments + mps + w {mustBeInteger} = floor(length(mps) / 2) + end + + assert(0 <= w && w <= length(mps)); + if isempty(mps.center), mps = movegaugecenter(mps, w); end + + if w < mps.center + mps = movegaugecenter(mps, w + 1); + S = tsvd(mps.A(w + 1), 1, 2:nspaces(mps.A(w + 1))); + else + mps = movegaugecenter(mps, w); + S = tsvd(mps.A(w), 1:nspaces(mps.A(w))-1, nspaces(mps.A(w))); + end + [svals, charges] = matrixblocks(S); + svals = cellfun(@diag, svals, 'UniformOutput', false); + end + end + + + %% Derived operators + methods + function T = transfermatrix(mps1, mps2, sites) + arguments + mps1 + mps2 = mps1 + sites = 1:length(mps1) + end + + assert(all(diff(sites) == 1), 'sites must be neighbouring and increasing.'); + T = transfermatrix(mps1.A(sites), mps2.A(sites)); + end + end + + + %% Methods + methods + function mps = movegaugecenter(mps, newcenter, alg) + arguments + mps + newcenter {mustBeInteger} = floor(length(mps) / 2) + alg = 'polar' + end + + assert(newcenter >= 1 && newcenter <= length(mps), ... + 'mps:gauge', ... + sprintf('new center (%d) must be in 1:%d', newcenter, length(mps))); + + if isempty(mps.center) + low = 1; + high = length(mps); + else + low = mps.center; + high = mps.center; + end + + for i = low:(newcenter - 1) + [mps.A(i), L] = leftorth(mps.A(i), alg); + mps.A(i + 1) = multiplyleft(mps.A(i + 1), L); + end + for i = high:-1:(newcenter + 1) + [R, mps.A(i)] = rightorth(mps.A(i), alg); + [mps.A(i - 1)] = multiplyright(mps.A(i - 1), R); + end + mps.center = newcenter; + end + + function rho = fixedpoint(mps, type, w) + arguments + mps + type {mustBeMember(type, {'l', 'r'})} + w = floor(length(mps) / 2); + end + + if strcmp(type, 'l') + if mps.center > w + rho = mps.A.eye(leftvspace(mps, w), leftvspace(mps, w)); + else + T = transfermatrix(mps, mps, mps.center:w); + rho = apply(T, []); + end + else + if mps.center < w + rho = mps.A.eye(rightvspace(mps, w)', rightvspace(mps, w)'); + else + T = transfermatrix(mps, mps, w:mps.center).'; + rho = apply(T, []); + end + end + + end + + function o = overlap(mps1, mps2, rholeft, rhoright) + arguments + mps1 + mps2 + rholeft = [] + rhoright = [] + end + + assert(length(mps1) == length(mps2), 'mps should have equal length'); + assert(length(mps1) >= 2, 'edge case to be added'); + + n = floor(length(mps1) / 2); + Tleft = transfermatrix(mps1, mps2, 1:n); + rholeft = apply(Tleft, rholeft); + Tright = transfermatrix(mps1, mps2, n+1:length(mps1)).'; + rhoright = apply(Tright, rhoright); + + o = overlap(rholeft, rhoright); + end + + function n = norm(mps) + if isempty(mps.center) + n = sqrt(abs(overlap(mps, mps))); + return + end + + n = norm(mps.A(mps.center)); + end + + function [mps, n] = normalize(mps) + if isempty(mps.center), mps = movegaugecenter(mps); end + n = norm(mps); + mps.A(mps.center) = mps.A(mps.center) ./ n; + end + end + + + %% Converters + methods + function psi = Tensor(mps) + % Convert a finite mps to a dense tensor. + + tensors = num2cell(mps.A); + indices = cell(size(tensors)); + + nout = nspaces(mps.A(1)) - 1; + indices{1} = [-(1:nout), 1]; + + for i = 2:length(indices)-1 + plegs = mps.A(i).plegs; + indices{i} = [i-1 -(1:plegs)-nout i]; + nout = nout + plegs; + end + + plegs = mps.A(end).plegs; + indices{end} = [length(indices)-1 -(1:plegs+1)-nout]; + + args = [tensors; indices]; + psi = contract(args{:}); + end + end +end diff --git a/src/mps/InfJMpo.m b/src/mps/InfJMpo.m index 8817793..c05fff7 100644 --- a/src/mps/InfJMpo.m +++ b/src/mps/InfJMpo.m @@ -155,11 +155,6 @@ end end - function mpo = horzcat(varargin) - Os = cellfun(@(x) x.O, varargin, 'UniformOutput', false); - mpo = InfJMpo([Os{:}]); - end - function mpo = plus(a, b) if isa(a, 'InfJMpo') && isnumeric(b) if period(a) > 1 && isscalar(b) @@ -183,92 +178,20 @@ mpo = [mpo; b]; end - end - - methods (Static) - function mpo = Ising(J, h, kwargs) - arguments - J = 1 - h = 1 - kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'Z2'})} = 'Z1' - end + + function finitempo = open_boundary_conditions(mpo, L) + Os = repmat(mpo.O, 1, L); - sigma_x = [0 1; 1 0]; - sigma_z = [1 0; 0 -1]; + Os{1} = Os{1}(1, :, :, :); + Os{end} = Os{end}(:, :, end, :); - if strcmp(kwargs.Symmetry, 'Z1') - pSpace = CartesianSpace.new(2); - vSpace = one(pSpace); - S = Tensor([vSpace pSpace], [pSpace vSpace]); - Sx = fill_matrix(S, sigma_x); - Sz = fill_matrix(S, sigma_z); - - cod = SumSpace([vSpace vSpace vSpace], pSpace); - dom = SumSpace(pSpace, [vSpace vSpace vSpace]); - O = MpoTensor.zeros(cod, dom); - O(1, 1, 1, 1) = 1; - O(3, 1, 3, 1) = 1; - O(1, 1, 2, 1) = -J * Sx; - O(2, 1, 3, 1) = Sx; - O(1, 1, 3, 1) = (-J * h) * Sz; - - else - pSpace = GradedSpace.new(Z2(0, 1), [1 1], false); - vSpace = GradedSpace.new(Z2(1), 1, false); - trivSpace = one(pSpace); - - Sx_l = fill_matrix(Tensor([trivSpace pSpace], [pSpace vSpace]), {1 1}); - Sx_r = fill_matrix(Tensor([vSpace pSpace], [pSpace trivSpace]), {1 1}); - Sz = fill_matrix(Tensor([trivSpace pSpace], [pSpace trivSpace]), {1 -1}); - - cod = SumSpace([one(vSpace) vSpace one(vSpace)], pSpace); - dom = SumSpace(pSpace, [one(vSpace), vSpace, one(vSpace)]); - O = MpoTensor.zeros(cod, dom); - O(1, 1, 1, 1) = 1; - O(3, 1, 3, 1) = 1; - O(1, 1, 2, 1) = -J * Sx_l; - O(2, 1, 3, 1) = Sx_r; - O(1, 1, 3, 1) = (-J * h) * Sz; - end + rspace = subspaces(rightvspace(mpo, period(mpo)), size(Os{end}, 3)); + rightedge = MpsTensor(Tensor.eye([one(rspace) rspace'], one(rspace))); - mpo = InfJMpo(O); - end - - function mpo = Heisenberg(J, h, kwargs) - arguments - J = 1 - h = 0 - kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'U1', 'SU2'})} = 'Z1' - kwargs.Spin = SU2(3) - end + lspace = subspaces(leftvspace(mpo, 1), size(Os{1}, 1)); + leftedge = MpsTensor(Tensor.eye([one(lspace) lspace], one(lspace))'); - switch kwargs.Symmetry - case 'SU2' - assert(isscalar(J) || all(J == J(1)), ... - 'Different spin couplings not invariant under SU2'); - assert(h == 0, 'Magnetic field not invariant under SU2'); - - pSpace = GradedSpace.new(kwargs.Spin, 1, false); - vSpace = GradedSpace.new(SU2(3), 1, false); - tSpace = one(vSpace); - - s = spin(kwargs.Spin); - L = Tensor.ones([tSpace pSpace], [pSpace vSpace]); - L = L * (-J(1) * (s^2 + s)); - R = Tensor.ones([vSpace pSpace], [pSpace tSpace]); - - cod = SumSpace([one(vSpace) vSpace one(vSpace)], pSpace); - dom = SumSpace(pSpace, [one(vSpace), vSpace, one(vSpace)]); - O = MpoTensor.zeros(cod, dom); - O(1, 1, 1, 1) = 1; - O(3, 1, 3, 1) = 1; - O(1, 1, 2, 1) = L; - O(2, 1, 3, 1) = R; - - otherwise - error('TBA'); - end - mpo = InfJMpo(O); + finitempo = FiniteMpo(leftedge, Os, rightedge); end end end diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index 339d328..ef85d9d 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -13,18 +13,13 @@ O = varargin{1}; if isa(O, 'InfMpo') - for i = numel(O):1:1 - mpo(i, 1).O = O(i, 1).O; - end - mpo = reshape(mpo, size(O)); + mpo.O = O.O; elseif isa(O, 'MpoTensor') mpo.O = {O}; elseif isa(O, 'AbstractTensor') - for i = height(O):-1:1 - mpo(i, 1).O = arrayfun(@MpoTensor, O(i, :), 'UniformOutput', false); - end + mpo.O = arrayfun(@MpoTensor, O, 'UniformOutput', false); elseif iscell(O) mpo.O = O; @@ -36,44 +31,55 @@ methods function p = period(mpo) - p = length(mpo(1).O); + p = size(mpo.O, 2); end function d = depth(mpo) - d = length(mpo); + d = size(mpo.O, 1); end function mpo = block(mpo) if depth(mpo) == 1, return; end - O_ = mpo(1, 1).O; + O_ = mpo.O(1, :); for d = 2:depth(mpo) for w = period(mpo):-1:1 - vspaces = [rightvspace(O_{w})' rightvspace(mpo(d, 1).O{w})']; + vspaces = [rightvspace(O_{w})' rightvspace(mpo.O{d, w})']; fuser(w) = Tensor.eye(vspaces, prod(vspaces)); end for w = 1:period(mpo) - O_{w} = MpoTensor(contract(O_{w}, [1 2 5 -4], mpo(d, 1).O{w}, [3 -2 4 2], ... + O_{w} = MpoTensor(contract(O_{w}, [1 2 5 -4], mpo.O{d, w}, [3 -2 4 2], ... fuser(prev(w, period(mpo)))', [-1 3 1], fuser(w), [5 4 -3], ... 'Rank', [2 2])); end end - mpo = mpo(1, 1); mpo.O = O_; end - function s = pspace(mpo, x) - s = pspace(mpo.O{x}); + function s = pspace(mpo, w) + s = pspace(mpo.O{w}); + end + + function s = leftvspace(mpo, w) + s = leftvspace(mpo.O{w}); + end + + function s = rightvspace(mpo, w) + s = rightvspace(mpo.O{w}); end - function mpo = horzcat(varargin) + function mpo = horzcat(mpo, varargin) Os = cellfun(@(x) x.O, varargin, 'UniformOutput', false); - mpo = InfMpo([Os{:}]); + mpo.O = horzcat(mpo.O, Os{:}); end - function mpo = vertcat(varargin) + function mpo = vertcat(mpo, varargin) Os = cellfun(@(x) x.O, varargin, 'UniformOutput', false); - mpo = InfMpo(vertcat(Os{:})); + mpo.O = vertcat(mpo.O, Os{:}); + end + + function mpo = slice(mpo, d) + mpo.O = mpo.O(d, :); end function mps = initialize_mps(mpo, vspaces) @@ -213,10 +219,12 @@ H = cell(1, length(sites)); for i = 1:length(sites) - gl = twistdual(GL{sites(i)}, 1); - gr = GR{next(sites(i), period(mps))}; - gr = twistdual(gr, nspaces(gr)); - H{i} = FiniteMpo(gl, mpo.O(sites(i)), gr); + for d = depth(mpo):-1:1 + gl = twistdual(GL{d, sites(i)}, 1); + gr = GR{d, next(sites(i), period(mps))}; + gr = twistdual(gr, nspaces(gr)); + H{i}(d, 1) = FiniteMpo(gl, mpo.O(d, sites(i)), gr); + end end end @@ -231,17 +239,19 @@ H = cell(1, length(sites)); for i = 1:length(sites) - gl = GL{sites(i)}; - gl = twistdual(gl, 1); - gr = GR{mod1(sites(i) + 2, period(mps))}; - gr = twistdual(gr, nspaces(gr)); - H{i} = FiniteMpo(gl, mpo.O(mod1(sites(i) + [0 1], period(mps))), gr); + for d = depth(mpo):-1:1 + gl = GL{d, sites(i)}; + gl = twistdual(gl, 1); + gr = GR{d, mod1(sites(i) + 2, period(mps))}; + gr = twistdual(gr, nspaces(gr)); + H{i}(d, 1) = FiniteMpo(gl, mpo.O(d, mod1(sites(i) + [0 1], period(mps))), gr); + end end end - function H = C_hamiltonian(~, mps, GL, GR, sites) + function H = C_hamiltonian(mpo, mps, GL, GR, sites) arguments - ~ + mpo mps GL = fixedpoint(transfermatrix(mpo, mps, 'Type', 'LL')) GR = fixedpoint(transfermatrix(mpo, mps, 'Type', 'RR').') @@ -250,53 +260,27 @@ H = cell(1, length(sites)); for i = 1:length(sites) - gl = GL{next(sites(i), period(mps))}; - for j = 1:numel(gl) - if nnz(gl(j)) ~= 0 - gl(j) = twist(gl(j), find(isdual(space(gl(j), 1)))); + for d = depth(mpo):-1:1 + gl = GL{d, next(sites(i), period(mps))}; + for j = 1:numel(gl) + if nnz(gl(j)) ~= 0 + gl(j) = twist(gl(j), find(isdual(space(gl(j), 1)))); + end end - end - gr = GR{next(sites(i), period(mps))}; - for j = 1:numel(gr) - if nnz(gr(j)) ~= 0 - gr(j) = twist(gr(j), find(isdual(space(gr(j), nspaces(gr(j))))) + ... - nspaces(gr(j)) - 1); + gr = GR{d, next(sites(i), period(mps))}; + for j = 1:numel(gr) + if nnz(gr(j)) ~= 0 + gr(j) = twist(gr(j), find(isdual(space(gr(j), nspaces(gr(j))))) + ... + nspaces(gr(j)) - 1); + end end + H{i}(d, 1) = FiniteMpo(gl, {}, gr); end - H{i} = FiniteMpo(gl, {}, gr); end end end methods (Static) - function mpo = Ising(beta, kwargs) - arguments - beta = log(1 + sqrt(2)) / 2; - kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'Z2'})} = 'Z1' - end - - if strcmp(kwargs.Symmetry, 'Z1') - t = [exp(beta) exp(-beta); exp(-beta) exp(beta)]; - [v, d] = eig(t); - t = v * sqrt(d) * v; - - o = zeros(2, 2, 2, 2); - o(1, 1, 1, 1) = 1; - o(2, 2, 2, 2) = 1; - - o = contract(o, 1:4, t, [-1 1], t, [-2 2], t, [-3 3], t, [-4 4]); - - O = fill_tensor(Tensor.zeros([2 2 2 2]), o); - - else - s = GradedSpace.new(Z2(0, 1), [1 1], false); - O = fill_tensor(Tensor([s s], [s s]), ... - @(~, f) 2 * sqrt(prod(logical(f.uncoupled) .* sinh(beta) + ... - ~logical(f.uncoupled) .* cosh(beta)))); - end - - mpo = InfMpo(O); - end function mpo = fDimer() pspace = GradedSpace.new(fZ2(0, 1), [1 1], false); diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index bdc1088..394c3f2 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -13,13 +13,25 @@ properties tensors = [] - scalars = [] + scalars SparseArray = [] end methods function n = nspaces(~) n = 4; end + + function dom = domain(t) + dom = t.tensors.domain; + end + + function cod = codomain(t) + cod = t.tensors.codomain; + end + + function r = rank(t) + r = rank(t.tensors); + end end methods @@ -34,8 +46,8 @@ t.scalars = varargin{1}.scalars; t.tensors = varargin{1}.tensors; elseif isa(varargin{1}, 'Tensor') || isa(varargin{1}, 'SparseTensor') - t.tensors = sparse(varargin{1}); - t.scalars = zeros(size(t.tensors)); + t.tensors = varargin{1}; + t.scalars = SparseArray.zeros(size(t.tensors, 1:4)); end return end @@ -175,16 +187,22 @@ iB(1:2) = flip(iB(1:2)); end - A_ = reshape(permute(A.scalars, iA), ... - [prod(size(A, uncA)) prod(size(A, dimA))]); - B = reshape(tpermute(B, iB, rB), ... - [prod(size(B, dimB)) prod(size(B, uncB))]); + [Ia, Ja, Va] = find(spmatrix(reshape(permute(A.scalars, iA), ... + [prod(size(A, uncA)) prod(size(A, dimA))]))); + [Ib, Jb, Vb] = find(reshape(tpermute(B, iB, rB), ... + [prod(size(B, dimB)) prod(size(B, uncB))])); + sz2 = [prod(size(A, uncA)) prod(size(B, uncB))]; - C = C + reshape(sparse(A_) * B, size(C)); + for i = 1:length(Jb) + mask = find(Ja == Ib(i)); + if isempty(mask), continue; end + subs = [Ia(mask) repmat(Jb(i), length(mask), 1)]; + idx = sb2ind_(sz2, subs); + C(idx) = C(idx) + Va(mask) .* Vb(i); + end end else C = tensorprod(A, B.tensors, dimA, dimB, ca, cb); - szC = size(C); if nnz(B.scalars) > 0 assert(sum(dimB == 1 | dimB == 3, 'all') == 1, ... 'Cannot deduce output space unless leg 1 xor leg 3 is connected.'); @@ -204,24 +222,19 @@ iB(1:2) = flip(iB(1:2)); end - A_ = reshape(tpermute(A, iA, rA), ... - [prod(size(A, uncA)) prod(size(A, dimA))]); - B_ = reshape(permute(B.scalars, iB), ... - [prod(size(B, dimB)) prod(size(B, uncB))]); - + [Ia, Ja, Va] = find(reshape(tpermute(A, iA, rA), ... + [prod(size(A, uncA)) prod(size(A, dimA))])); + [Ib, Jb, Vb] = find(spmatrix(reshape(permute(B.scalars, iB), ... + [prod(size(B, dimB)) prod(size(B, uncB))]))); + sz2 = [prod(size(A, uncA)) prod(size(B, uncB))]; - subs = zeros(size(A_, 1), 2); - subs(:, 1) = (1:size(A_, 1)).'; - sz2 = [size(A_, 1) size(B_, 2)]; - [Brows, Bcols, Bvals] = find(B_); - C = reshape(C, sz2); - for i = 1:length(Bvals) - Atmp = A_(:, Brows(i)) .* Bvals(i); - subs(:, 2) = Bcols(i); + for i = 1:length(Ia) + mask = find(Ja(i) == Ib); + if isempty(mask), continue; end + subs = [repmat(Ia(i), length(mask), 1) Jb(mask)]; idx = sub2ind_(sz2, subs); - C(idx) = C(idx) + Atmp; + C(idx) = C(idx) + Va(i) .* Vb(mask); end - C = reshape(C, szC); end end end @@ -239,6 +252,9 @@ methods function s = space(O, i) + if nargin == 1 + i = 1:nspaces(O); + end s = space(O.tensors, i); end @@ -271,7 +287,12 @@ end function bool = iseye(O) - bool = nnz(O.tensors) == 0 && isequal(O.scalars, eye(size(O.scalars))); + bool = nnz(O.tensors) == 0; + if ~bool, return; end + + scal_mat = reshape(O.scalars, ... + prod(size(O.scalars, 1:2)), prod(size(O.scalars, 3:4))); + bool = isequal(spmatrix(scal_mat), speye(size(scal_mat))); end function n = nnz(O) @@ -314,7 +335,7 @@ i = prod(size(t)); return end - if n > ndims(t) + if k > ndims(t) i = 1; else i = size(t, k); @@ -341,12 +362,20 @@ [varargout{1:nargout}] = size(t.scalars); end end + + function n = ndims(t) + n = ndims(t.scalars); + end + + function disp(O) + builtin('disp', O); + end end methods (Static) function O = zeros(codomain, domain) tensors = SparseTensor.zeros(codomain, domain); - scalars = zeros(size(tensors)); + scalars = SparseArray.zeros(size(tensors)); O = MpoTensor(tensors, scalars); end @@ -399,4 +428,3 @@ end end end - diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index 94fa534..ba737af 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -115,19 +115,41 @@ function A = plus(varargin) for i = 1:2 if isa(varargin{i}, 'MpsTensor') + alegs = varargin{i}.alegs; varargin{i} = varargin{i}.var; end end - A = plus(varargin{:}); + A = MpsTensor(plus(varargin{:}), alegs); end function A = minus(varargin) for i = 1:2 if isa(varargin{i}, 'MpsTensor') + alegs = varargin{i}.alegs; varargin{i} = varargin{i}.var; end end - A = minus(varargin{:}); + A = MpsTensor(minus(varargin{:}), alegs); + end + + function A = rdivide(varargin) + for i = 1:2 + if isa(varargin{i}, 'MpsTensor') + alegs = varargin{i}.alegs; + varargin{i} = varargin{i}.var; + end + end + A = MpsTensor(rdivide(varargin{:}), alegs); + end + + function A = ldivide(varargin) + for i = 1:2 + if isa(varargin{i}, 'MpsTensor') + alegs = varargin{i}.alegs; + varargin{i} = varargin{i}.var; + end + end + A = MpsTensor(ldivide(varargin{:}), alegs); end function n = norm(A) @@ -198,7 +220,6 @@ C = tensorprod(varargin{:}); end - function [AL, CL, lambda, eta] = uniform_leftorth(A, CL, kwargs) arguments A @@ -293,7 +314,6 @@ end end - function [AR, CR, lambda, eta] = uniform_rightorth(A, CR, kwargs) arguments A @@ -339,7 +359,12 @@ arguments L MpsTensor R MpsTensor - v + v = [] + end + + if isempty(v) + v = tracetransfer(L, R); + return end auxlegs_v = nspaces(v) - 2; @@ -353,6 +378,24 @@ 'Rank', newrank); end + function v = tracetransfer(L, R) + arguments + L MpsTensor + R MpsTensor + end + + auxlegs_l = L.alegs; + auxlegs_r = R.alegs; + assert(R.plegs == L.plegs); + plegs = L.plegs; %#ok + newrank = [2 auxlegs_l + auxlegs_r]; + + v = contract(... + L, [-1 1:(plegs + 1) (-(1:auxlegs_l) - 2)], ... + R, [flip(1:(plegs + 1)) -2 (-(1:auxlegs_r) - 3 - auxlegs_l)], ... + 'Rank', newrank); %#ok + end + function rho = applyleft(T, B, rho) if nargin == 2 rho = []; @@ -507,6 +550,10 @@ function type = underlyingType(A) type = underlyingType(A.var); end + + function disp(t) + builtin('disp', t); + end end @@ -523,5 +570,43 @@ t = sparse(t); end end + + + %% + methods (Static) + function local_tensors = decompose_local_state(psi, kwargs) + % convert a tensor into a product of local operators. + % + % Usage + % ----- + % :code:`local_operators = MpoTensor.decompose_local_operator(H, kwargs)`. + % + % Arguments + % --------- + % H : :class:`AbstractTensor` + % tensor representing a local operator on N sites. + % + % Keyword Arguments + % ----------------- + % 'Trunc' : cell + % optional truncation method for the decomposition. See also + % :method:`Tensor.tsvd` + arguments + psi + kwargs.Trunc = {'TruncBelow', 1e-14} + end + + L = nspaces(psi); + assert(L >= 3, 'argerror', ... + sprintf('state must have at least 3 legs. (%d)', L)); + + local_tensors = cell(1, L - 2); + for i = 1:length(local_tensors)-1 + [u, s, psi] = tsvd(psi, [1 2], [3:nspaces(psi)], kwargs.Trunc{:}); + local_tensors{i} = multiplyright(MpsTensor(u), s); + end + local_tensors{end} = MpsTensor(repartition(psi, [2 1])); + end + end end diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index d8409ed..69ddd44 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -170,9 +170,7 @@ methods function p = period(mps) % period over which the mps is translation invariant. - for i = numel(mps):-1:1 - p(i) = length(mps(i).AL); - end + p = length(mps(1).AL); end function d = depth(mps) @@ -188,6 +186,14 @@ mps = UniformMps([ALs{:}], [ARs{:}], [Cs{:}], [ACs{:}]); end + function mps = vertcat(varargin) + for i = 2:length(varargin) + assert(period(varargin{1}) == period(varargin{i}), ... + 'Can only stack UniformMps with matching periods.') + end + mps = builtin('vertcat', varargin{:}); + end + function s = leftvspace(mps, w) % return the virtual space to the left of site w. if nargin == 1 || isempty(w), w = 1:period(mps); end @@ -601,43 +607,46 @@ svals = cellfun(@diag, svals, 'UniformOutput', false); end - function plot_entanglementspectrum(mps, w, ax) - if nargin == 1 - w = 1:period(mps); - figure; - ax = gobjects(depth(mps), width(mps)); - for ww = 1:length(w) - ax(1, ww) = subplot(1, length(w), ww); - end - elseif nargin == 2 + function plot_entanglementspectrum(mps, d, w, ax) + arguments + mps + d = 1:depth(mps) + w = 1:period(mps) + ax = [] + end + if isempty(ax) figure; ax = gobjects(depth(mps), width(mps)); - for ww = 1:length(w) - ax(1, ww) = subplot(1, length(w), ww); + for dd = 1:length(d) + for ww = 1:length(w) + ax(dd, ww) = subplot(length(d), length(w), ww + (dd-1)*length(w)); + end end end lim_y = 1; - for ww = 1:length(w) - [svals, charges] = schmidt_values(mps, w(ww)); - ctr = 0; - hold off; - lim_x = 1; - - ticks = zeros(size(svals)); - labels = arrayfun(@string, charges, 'UniformOutput', false); - lengths = cellfun(@length, svals); - ticks = cumsum(lengths); - try - semilogy(ax(1, ww), 1:sum(lengths), vertcat(svals{:}).', '.', 'MarkerSize', 10); - catch - bla + for dd = 1:length(d) + for ww = 1:length(w) + [svals, charges] = schmidt_values(mps(dd), w(ww)); + ctr = 0; + hold off; + lim_x = 1; + + ticks = zeros(size(svals)); + labels = arrayfun(@string, charges, 'UniformOutput', false); + lengths = cellfun(@length, svals); + ticks = cumsum(lengths); + try + semilogy(ax(dd, ww), 1:sum(lengths), vertcat(svals{:}).', '.', 'MarkerSize', 10); + catch + bla + end + set(ax(dd, ww), 'TickLabelInterpreter', 'latex'); + set(ax(dd, ww), 'Xtick', ticks, 'XTickLabel', labels, 'fontsize', 10, ... + 'XtickLabelRotation', 60, 'Xgrid', 'on'); + xlim(ax(dd, ww), [1 - 1e-8 ticks(end) + 1e-8]); + end - set(ax(1, ww), 'TickLabelInterpreter', 'latex'); - set(ax(1, ww), 'Xtick', ticks, 'XTickLabel', labels, 'fontsize', 10, ... - 'XtickLabelRotation', 60, 'Xgrid', 'on'); - xlim(ax(1, ww), [1 - 1e-8 ticks(end) + 1e-8]); - end % for ww = 1:length(w) diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index f6cf552..aa49de1 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -639,7 +639,7 @@ function disp(t) end Ccod = space(A, uncA); - Cdom = space(B, uncB); + Cdom = space(B, uncB)'; C = SparseTensor(Cind, Cvar, [size(A, 1) size(B, 2)], Ccod, Cdom); C = reshape(C, szC); @@ -819,7 +819,7 @@ function disp(t) return end - subs = sub2sub([t.sz(1) prod(t.sz(2:end))], t.sz, t.ind); + subs = sub2sub([t.sz(1) prod(t.sz(2:end))], t.sz, inds); I = subs(:,1); J = subs(:,2); @@ -878,8 +878,7 @@ function disp(t) t.var = t.var(p); end - function t = subsref(t, s) - + function varargout = subsref(t, s) switch s(1).type case '()' n = size(s(1).subs, 2); @@ -903,6 +902,12 @@ function disp(t) if nA ~= length(unique(A)) error("Repeated index in position %i",i); end + if i > length(t.codomain) + t.domain(end-(i-length(t.codomain))+1) = ... + SumSpace(subspaces(t.domain(end-(i-length(t.codomain))+1), A)); + else + t.codomain(i) = SumSpace(subspaces(t.codomain(i), A)); + end if ~isempty(t.ind) B = t.ind(:, i); P = false(max(max(A), max(B)) + 1, 1); @@ -922,13 +927,18 @@ function disp(t) assert(isscalar(t)) t = subsref(t.var, s(2:end)); end + varargout{1} = t; case '.' - t = builtin('subsref', t, s); + [varargout{1:nargout}] = builtin('subsref', t, s); otherwise error('sparse:index', '{} indexing not defined'); end end + function n = numArgumentsFromSubscript(t, ~, ~) + n = 1; + end + function t = subsasgn(t, s, v) assert(strcmp(s(1).type, '()'), 'sparse:index', 'only () indexing allowed'); @@ -940,7 +950,7 @@ function disp(t) assert(length(s(1).subs) == size(t.sz, 2), 'sparse:index', ... 'number of indexing indices must match tensor size.'); assert(all(t.sz >= cellfun(@max, s(1).subs)), 'sparse:bounds', ... - 'attempting to assign out of bounds'); + 'out of bounds assignment disallowed'); subsize = zeros(1, size(s(1).subs, 2)); for i = 1:length(subsize) diff --git a/src/sparse/sub2sub.m b/src/sparse/sub2sub.m index c5ed718..a1d0a2d 100644 --- a/src/sparse/sub2sub.m +++ b/src/sparse/sub2sub.m @@ -7,8 +7,8 @@ return; end sub2 = zeros(size(sub1, 1), numel(sz2)); % preallocate new subs -nto1 = sum(cumsum(flip(sz1-1), 2) == 0, 2), length(sz1); % extract number of trailing ones in both sizes -nto2 = sum(cumsum(flip(sz2-1), 2) == 0, 2), length(sz2); +nto1 = sum(cumsum(flip(sz1-1), 2) == 0, 2); % extract number of trailing ones in both sizes +nto2 = sum(cumsum(flip(sz2-1), 2) == 0, 2); pos1_prev = 0; pos2_prev = 0; flag = true; while flag @@ -19,7 +19,10 @@ error('Cannot map subscripts to new size as intermediate index exceeds MAXSIZE') end sub2(:, pos2_prev+1:pos2) = ind2sub_(sz2(pos2_prev+1:pos2), sub2ind_(sz1(pos1_prev+1:pos1), sub1(:, pos1_prev+1:pos1))); - if pos2 == numel(sz2) - nto2 || pos1 == numel(sz1) - nto1 + if (isempty(pos2) && numel(sz2) - nto2 == 0) || ... + (isempty(pos1) && numel(sz1) - nto1 == 0) || ... + pos2 == numel(sz2) - nto2 || ... + pos1 == numel(sz1) - nto1 flag = false; else pos1_prev = pos1; diff --git a/src/tensors/AbstractTensor.m b/src/tensors/AbstractTensor.m index 1c1a08f..2b69009 100644 --- a/src/tensors/AbstractTensor.m +++ b/src/tensors/AbstractTensor.m @@ -255,7 +255,8 @@ x0_vec = vectorize(x0); sz = size(x0_vec); - + assert(sz(1) >= howmany); + if isa(A, 'function_handle') A_fun = @(x) vectorize(A(devectorize(x, x0))); else @@ -264,11 +265,19 @@ options.KrylovDim = min(sz(1), options.KrylovDim); - [V, D, flag] = eigs(A_fun, sz(1), howmany, sigma, ... - 'Tolerance', options.Tol, 'MaxIterations', options.MaxIter, ... - 'SubspaceDimension', options.KrylovDim, 'IsFunctionSymmetric', ... - options.IsSymmetric, 'StartVector', x0_vec, ... - 'Display', options.Verbosity >= 3); + if howmany > sz(1) - 2 + [V, D, flag] = eigs(A_fun, sz(1), howmany, sigma, ... + 'Tolerance', options.Tol, 'MaxIterations', options.MaxIter, ... + 'IsFunctionSymmetric', ... + options.IsSymmetric, 'StartVector', x0_vec, ... + 'Display', options.Verbosity >= 3); + else + [V, D, flag] = eigs(A_fun, sz(1), howmany, sigma, ... + 'Tolerance', options.Tol, 'MaxIterations', options.MaxIter, ... + 'SubspaceDimension', options.KrylovDim, 'IsFunctionSymmetric', ... + options.IsSymmetric, 'StartVector', x0_vec, ... + 'Display', options.Verbosity >= 3); + end if nargout <= 1 varargout = {D}; @@ -302,9 +311,28 @@ kwargs.Conj (1, :) logical = false(size(tensors)) kwargs.Rank = [] kwargs.Debug = false + kwargs.CheckOptimal = false end + assert(length(kwargs.Conj) == length(tensors)); + if kwargs.CheckOptimal + legcosts = zeros(2, 0); + for i = 1:length(indices) + legcosts = [legcosts [indices{i}; dims(tensors{i})]]; + end + legcosts = unique(legcosts.', 'rows'); + + currentcost = contractcost(indices, legcosts); + [sequence, cost] = netcon(indices, 0, 1, currentcost, 1, legcosts); + + if cost < currentcost + warning('suboptimal contraction order.\n current (%d): %s\n optimal(%d): %s', ... + currentcost, num2str(1:max(legcosts(:,1))), ... + cost, num2str(sequence)); + end + end + for i = 1:length(tensors) [i1, i2] = traceinds(indices{i}); tensors{i} = tensortrace(tensors{i}, i1, i2); @@ -363,6 +391,48 @@ end end + function disp(t, details) + if nargin == 1 || isempty(details), details = false; end + if isscalar(t) + r = t.rank; + fprintf('Rank (%d, %d) %s:\n', r(1), r(2), class(t)); + s = space(t); + for i = 1:length(s) + fprintf('\t%d.\t', i); + disp(s(i)); + fprintf('\b'); + end + fprintf('\n'); + if details + [blocks, charges] = matrixblocks(t); + for i = 1:length(blocks) + if ~isempty(blocks) + fprintf('charge %s:\n', string(charges(i))); + end + disp(blocks{i}); + end + end + else + fprintf('%s of size %s:\n', class(t), ... + regexprep(mat2str(size(t)), {'\[', '\]', '\s+'}, {'', '', 'x'})); + subs = ind2sub_(size(t), 1:numel(t)); + spc = floor(log10(max(double(subs), [], 1))) + 1; + if numel(spc) == 1 + fmt = strcat("\t(%", num2str(spc(1)), "u)"); + else + fmt = strcat("\t(%", num2str(spc(1)), "u,"); + for i = 2:numel(spc) - 1 + fmt = strcat(fmt, "%", num2str(spc(i)), "u,"); + end + fmt = strcat(fmt, "%", num2str(spc(end)), "u)"); + end + for i = 1:numel(t) + fprintf('%s\t\t', compose(fmt, subs(i, :))); + disp(t(i), details); + end + end + end + function d = distance(A, B) % Compute the Euclidean distance between two tensors. % @@ -441,6 +511,44 @@ iE = [1:length(i1) length(i1) + (length(i2):-1:1)]; B = tensorprod(A, E, iA, iE); end + + function sz = dims(t, inds) + sz = dims(space(t)); + if nargin > 1 + sz = sz(inds); + end + end + + function o = overlap(t1, t2) + o = contract(t1, 1:nspaces(t1), t2, flip(1:nspaces(t1))); + end + end + + + %% Contractions + methods + function v = applytransfer(L, R, v) + arguments + L + R + v = [] + end + + if isempty(v) + v = tracetransfer(L, R); + return + end + + auxlegs_v = nspaces(v) - 2; + auxlegs_l = 0; + auxlegs_r = 0; + newrank = rank(v); newrank(2) = newrank(2) + auxlegs_l + auxlegs_r; + + v = contract(v, [1 3 (-(1:auxlegs_v) - 2 - auxlegs_l)], ... + L, [-1 2 1 (-(1:auxlegs_l) - 2)], ... + R, [3 2 -2 (-(1:auxlegs_r) - 3 - auxlegs_l - auxlegs_v)], ... + 'Rank', newrank); + end + end end - diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index f1c2a51..c515960 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -75,6 +75,8 @@ elseif isempty(domain) sz = nsubspaces(codomain); else + if ~isa(codomain, 'SumSpace'), codomain = SumSpace(codomain); end + if ~isa(domain, 'SumSpace'), domain = SumSpace(domain); end sz = [nsubspaces(codomain) flip(nsubspaces(domain))]; end subs = ind2sub_(sz, 1:prod(sz)); @@ -456,15 +458,13 @@ end end - function sz = dims(t, inds) - sz = dims([t.codomain, t.domain']); - if nargin > 1 - sz = sz(inds); - end - end - function sp = space(t, inds) - sp = [t.codomain t.domain']; + if isscalar(t) + sp = [t.codomain t.domain']; + else + [cod, dom] = deduce_spaces(t); + sp = [cod, dom']; + end if nargin > 1 sp = sp(inds); end @@ -835,9 +835,11 @@ end end - function t = normalize(t) + function [t, n] = normalize(t) + n = zeros(size(t)); for i = 1:numel(t) - t(i) = t(i) .* (1 / norm(t(i))); + n(i) = norm(t(i)); + t(i) = t(i) .* (1 / n(i)); end end @@ -2352,11 +2354,53 @@ if ~isempty(tsrc.codomain), tdst.codomain = ComplexSpace(tsrc.codomain); end end end + + function mps = FiniteMps(t, varargin) + A = MpsTensor.decompose_local_state(t, varargin{:}); + mps = FiniteMps(A); + end end %% Utility methods + function [I, J, V] = find(t, k, which) + arguments + t + k = [] + which = 'first' + end + assert(strcmp(which, 'first'), 'not implemented yet') + if ~isempty(k) + assert(k <= numel(t)); + else + k = numel(t); + end + + if isempty(t) + I = []; + J = []; + V = []; + return + end + + if ~isempty(k) + if strcmp(which, 'first') + I = 1:k; + else + I = numel(t):-1:numel(t)+1-k; + end + end + I = reshape(I, [], 1); + if nargout < 2, return; end + + sz = size(t); + subs = ind2sub_([sz(1) prod(sz(2:end))], I); + I = subs(:, 1); + J = subs(:, 2); + V = reshape(t, [], 1); + end + function s = GetMD5_helper(t) s = {t.codomain t.domain}; end diff --git a/src/tensors/spaces/AbstractSpace.m b/src/tensors/spaces/AbstractSpace.m index b1d2c8c..5f61b39 100644 --- a/src/tensors/spaces/AbstractSpace.m +++ b/src/tensors/spaces/AbstractSpace.m @@ -317,6 +317,10 @@ bool = ~le(space1, space2); end + function bool = ge(space1, space2) + bool = le(space2, space1); + end + function bool = isequal(spaces) % Check whether all input spaces are equal. Spaces are considered equal if they % are of same size, and are equal element-wise. For convenience, empty spaces diff --git a/src/tensors/spaces/CartesianSpace.m b/src/tensors/spaces/CartesianSpace.m index e3ef96f..c4e1131 100644 --- a/src/tensors/spaces/CartesianSpace.m +++ b/src/tensors/spaces/CartesianSpace.m @@ -274,10 +274,6 @@ bools = [spaces1.dimensions] == [spaces2.dimensions]; end - function bool = ge(space1, space2) - bool = le(space2, space1); - end - function bool = le(space1, space2) assert(isscalar(space1) && isscalar(space2)); bool = degeneracies(space1) <= degeneracies(space2); diff --git a/src/tensors/spaces/SumSpace.m b/src/tensors/spaces/SumSpace.m index 2f5f506..456551d 100644 --- a/src/tensors/spaces/SumSpace.m +++ b/src/tensors/spaces/SumSpace.m @@ -32,6 +32,10 @@ end function space = infimum(space1, space2) + arguments + space1 SumSpace + space2 SumSpace + end assert(isscalar(space1) && isscalar(space2)); assert(isdual(space1) == isdual(space2)); space = SumSpace(arrayfun(@infimum, space1.dimensions, space2.dimensions)); @@ -71,6 +75,12 @@ %% Utility methods + function d = dims(spaces) + for i = length(spaces):-1:1 + d(i) = sum(dims(subspaces(spaces(i)))); + end + end + function bools = eq(spaces1, spaces2) assert(isequal(size(spaces1), size(spaces2))); bools = false(size(spaces1)); @@ -79,6 +89,17 @@ end end + function bool = le(space1, space2) + arguments + space1 SumSpace + space2 SumSpace + end + + assert(isscalar(space1) && isscalar(space2)); + + bool = le(sum(subspaces(space1)), sum(subspaces(space2))); + end + function s = subspaces(space, i) assert(isscalar(space), ... 'space:argerror', 'method only defined for scalar inputs.'); diff --git a/src/utility/diracdelta.m b/src/utility/diracdelta.m new file mode 100644 index 0000000..2ed8d80 --- /dev/null +++ b/src/utility/diracdelta.m @@ -0,0 +1,12 @@ +function d = diracdelta(sz) + +assert(all(sz == sz(1))) +d = zeros(sz); + +subs = repmat(1:sz(1), length(sz), 1).'; +idx = sub2ind_(sz, subs); + +d(idx) = 1; + +end + diff --git a/src/utility/linalg/contract.m b/src/utility/linalg/contract.m index 024b575..7a3c1b3 100644 --- a/src/utility/linalg/contract.m +++ b/src/utility/linalg/contract.m @@ -41,10 +41,27 @@ kwargs.Conj (1, :) logical = false(size(tensors)) kwargs.Rank = [] kwargs.Debug = false + kwargs.CheckOptimal = false end assert(length(kwargs.Conj) == length(tensors)); +if kwargs.CheckOptimal + legcosts = zeros(2, 0); + for i = 1:length(indices) + legcosts = [legcosts [indices{i}; size(tensors{i}, 1:length(indices{i}))]]; + end + legcosts = unique(legcosts.', 'rows'); + + currentcost = contractcost(indices, legcosts); + [sequence, cost] = netcon(indices, 1, 1, currentcost, 1, legcosts); + + if cost < currentcost + warning('suboptimal contraction order.\n optimal: %s', ... + num2str(sequence)); + end +end + for i = 1:length(tensors) [i1, i2] = traceinds(indices{i}); tensors{i} = tensortrace(tensors{i}, i1, i2); diff --git a/src/utility/linalg/contractcost.m b/src/utility/linalg/contractcost.m new file mode 100644 index 0000000..1a7b0b7 --- /dev/null +++ b/src/utility/linalg/contractcost.m @@ -0,0 +1,73 @@ +function cost = contractcost(indices, legCosts) + +cost = 0; + +allInds = horzcat(indices{:}); +numCont = max(allInds); + +table = zeros(numCont, 2); +for ii = 1:length(indices) + for jj = indices{ii}(indices{ii} > 0) + if table(jj, 1) == 0 + table(jj, 1) = ii; + else + table(jj, 2) = ii; + end + end +end + + +%% Do contractions +ctr = 1; +contlist = 1:numCont; + +while ~isempty(contlist) + i1 = table(contlist(1), 1); + i2 = table(contlist(1), 2); + + if i1 + i2 == 0 + contlist(1) = []; + continue; + else + assert(i1 ~= i2, 'Tensor:isOptimalContract', 'Traces are not implemented.'); + end + + labels1 = indices{i1}; + labels2 = indices{i2}; + + [pos1, pos2] = contractinds(labels1, labels2); + unc1 = 1:length(labels1); unc1(pos1) = []; + unc2 = 1:length(labels2); unc2(pos2) = []; + contracting = labels1(pos1); + + % cost is dim(unc1) * dim(pos1) * dim(unc2) + dims1 = zeros(1, length(unc1)); + for ii = 1:length(unc1) + label = labels1(unc1(ii)); + dims1(ii) = legCosts(legCosts(:, 1) == label, 2); + end + dims2 = zeros(1, length(pos1)); + for ii = 1:length(pos1) + label = labels1(pos1(ii)); + dims2(ii) = legCosts(legCosts(:, 1) == label, 2); + end + dims3 = zeros(1, length(unc2)); + for ii = 1:length(unc2) + label = labels2(unc2(ii)); + dims3(ii) = legCosts(legCosts(:, 1) == label, 2); + end + + cost = cost + prod([dims1 dims2 dims3]); + + % update remaining contractions + indices{i1} = [indices{i1}(unc1) indices{i2}(unc2)]; + indices(i2) = []; + table(table == i2) = i1; + table(table > i2) = table(table > i2) - 1; + contlist = contlist(~ismember(contlist, contracting)); + + ctr = ctr + 1; +end + + +end diff --git a/src/utility/netcon.m b/src/utility/netcon.m new file mode 100644 index 0000000..4e18b31 --- /dev/null +++ b/src/utility/netcon.m @@ -0,0 +1,1308 @@ +function [sequence, cost] = netcon(legLinks,verbosity,costType,muCap,allowOPs,legCosts) +% function [sequence cost] = netcon(legLinks,verbosity,costType,muCap,allowOPs,legCosts) +% Finds most efficient way to contract a tensor network +% v2.00 by Robert N. C. Pfeifer 2014 +% Contact: rpfeifer.public@gmail.com, robert.pfeifer@mq.edu.au +% +% Parameters: (defaults) +% - legLinks: Labelling of tensor indices. (required) +% - verbosity: 0: Quiet. 1: State final result. 2: State intermediate and final results. 3: Also display object sizes during pairwise contractions. (2) +% - costType: 1: Absolute value. 2: Multiple and power of chi. (2) +% - muCap: Initially restrict search to sequences having a cost of muCap (if costType==1) or O(X^muCap) (if costType==2). This value will increment +% automatically if required, and it is recommended that it be left at the default value of 1. (1) +% - allowOPs: Allow contraction sequences including outer products: 0/false: No. 1/true: Yes. (true) +% - legCosts: For costType==1: nx2 table. A row reading [a b] assigns a dimension of b to index a. Default: 2 for all legs. +% For costType==2: nx3 table. A row reading [a b c] assigns a dimension of bX^c to index a, for unspecified X. Default: 1X^1 for all legs. +arguments + legLinks + verbosity = 2 % 0: No displayed output. 1: Display final result. 2: Display progress reports. + costType = 2 % 1: Absolute value. 2: Multiple and power of chi. + muCap = 1 + allowOPs = 1 + legCosts = [] +end + +% Benchmarking examples +% --------------------- +% 3:1 1D MERA: +% tic;netcon({[-1 1 2 3],[2 4 5 6],[1 5 7 -3],[3 8 4 9],[6 9 7 10],[-2 8 11 12],[10 11 12 -4]},0,2,1,1);toc +% All in MATLAB, no OPs: 0.041s +% All in MATLAB, with OPs: 0.054s +% Using C++, no OPs: 0.0019s +% Using C++, with OPs: 0.0019s +% +% 9:1 2D MERA: +% tic;netcon({[1 4 5 6 7 8],[2 9 10 11 12 13],[3 14 15 16 17 18],[-6 9 23 24 25 26],[-5 5 19 20 21 22],[8 14 27 28 29 30],[12 15 31 32 33 34],[22 25 28 31 35 36 37 38],[-4 20 23 35 39 40 41 42],[42 36 37 38 43 44 45 46],[41 24 44 26 47 48],[19 40 21 43 49 50],[27 45 29 30 51 52],[46 32 33 34 53 54],[-2 -3 39 49 47 55],[4 50 6 7 51 56],[48 10 11 53 13 57],[52 54 16 17 18 58],[55 56 57 58 -1 1 2 3]},0,2,1,1);toc +% All in MATLAB, no OPs: 5.4s +% All in MATLAB, with OPs: 6.1s +% Using C++, no OPs: 0.066s +% Using C++, with OPs: 0.069s +% +% 4:1 2D MERA: +% tic;netcon({[64 72 73 19 22],[65 74 75 23 76],[66 77 20 79 28],[67 21 24 29 34],[68 25 78 35 80],[69 81 30 83 84],[70 31 36 85 86],[71 37 82 87 88],[-5 19 20 21 1 2 4 5],[22 23 24 25 3 26 6 27],[28 29 30 31 7 8 32 33],[34 35 36 37 9 38 39 40],[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18],[10 11 13 14 41 42 43 44],[12 26 15 27 47 48 49 50],[16 17 32 33 53 54 55 56],[18 38 39 40 60 61 62 63],[-2 -3 -4 41 89],[72 73 42 47 90],[74 75 48 76 45],[77 43 79 53 46],[44 49 54 60 51],[50 78 61 80 52],[81 55 83 84 57],[56 62 85 86 58],[63 82 87 88 59],[89 90 45 46 51 52 57 58 59 -1 64 65 66 67 68 69 70 71]},0,2,1,1);toc +% All in MATLAB, no OPs: 2486s (41.4m) +% All in MATLAB, with OPs: 3919.6s (65.3m) +% Using C++, no OPs: 36.12s +% Using C++, with OPs: 36.49s + + +% Changes in v2.00: +% ----------------- +% Changed algorithm to breadth-first search instead of dynamic programming. +% Made handling of subnetworks iterative, not recursive. +% Made displayed output more user-friendly for networks composed of disjoint subnetworks. +% Added warning, not error, for tensors or subnetworks of trivial dimension (i.e. just a number). +% Added more vigorous exclusion of unnecessary outer products. + +% Changes in v1.01: +% ----------------- +% Improved structure of code. +% Fixed bug returning wrong cost when trivial indices present and non-consecutive numbering of positive indices. +% Removed keepTriv option. +% Fixed omission of trailing zeros in sequence when network is disjoint and all contractions are traces. +% Corrected helptext. + + +% Check input data (and strip trivial indices from legLinks) +% ================ +if ~isempty(legCosts) + [trivialindices legCosts legLinks] = checkInputData(legLinks,verbosity,costType,muCap,allowOPs,legCosts); +else + [trivialindices legCosts legLinks] = checkInputData(legLinks,verbosity,costType,muCap,allowOPs); +end + +% Divide network into disjoint subnets +% ==================================== +subnetlist = zeros(1,numel(legLinks)); +subnetcounter = 1; +while any(subnetlist==0) + flags = findSubnetIncluding(legLinks,find(subnetlist==0,1,'first')); + subnetlist(flags) = subnetcounter; + subnetcounter = subnetcounter + 1; +end +subnetcounter = subnetcounter-1; + +% Evaluate contraction sequences for disjoint subnets +% =================================================== +sequence = cell(1,subnetcounter); +cost = cell(1,subnetcounter); +freelegs = cell(1,subnetcounter); +donetrace = false(1,subnetcounter); +for a=1:subnetcounter + if verbosity > 0 + disp(' '); + if subnetcounter > 1 + disp(['Subnet ' num2str(a) ':']); + end + if verbosity > 1 + disp(['Tensors: ' unpaddednum2str(find(subnetlist==a))]); + end + end + [sequence{a} cost{a} freelegs{a} donetrace(a)] = netcon_getsubnetcost(legLinks(subnetlist==a),verbosity,costType,muCap,legCosts,allowOPs); +end +donetrace = any(donetrace); + +% Perform outer products of subnets, add costs, and merge sequences +% ================================================================= +[sequence cost] = performOPs(sequence,cost,freelegs,legCosts,costType,verbosity); +sequence = [sequence trivialindices]; % Append tracing over trivial indices on final object + +% Display result +% ============== +if verbosity>0 + if ~isempty(trivialindices) && verbosity > 1 + disp(' '); + disp(['Restore summed trivial indices: ' unpaddednum2str(trivialindices)]); + end + disp(' '); + if verbosity > 1 && subnetcounter > 1 + disp('Entire network:'); + end + netcon_displayresult(sequence,cost,donetrace,costType); +end +end + +function [sequence cost] = performOPs(sequence,cost,freelegs,legCosts,costType,verbosity) +% Renumber freelegs by position and trim index label list off legCosts +for a=1:numel(freelegs) + for b=1:numel(freelegs{a}) + freelegs{a}(b) = find(legCosts(:,1)==freelegs{a}(b)); + end +end +legCosts = legCosts(:,2:end); +% Perform outer products +if costType==2 + tensormulsize = ones(size(freelegs)); + tensorchisize = zeros(size(freelegs)); + for a=1:numel(freelegs) + for b=1:numel(freelegs{a}) + tensormulsize(a) = tensormulsize(a)*legCosts(freelegs{a}(b),1); + tensorchisize(a) = tensorchisize(a)+legCosts(freelegs{a}(b),2); + end + end + if any(tensormulsize==1 & tensorchisize==0) && numel(freelegs)>1 + if sum(tensormulsize==1 & tensorchisize==0)==1 + if verbosity > 0 + disp(' '); + end + warning('netcon:trivialsubnet',['Subnet ' unpaddednum2str(find(tensormulsize==1 & tensorchisize==0)) ' reduces to a single number. Since this subnet is not the entire network, contraction sequence may not be optimal.']); + else + if verbosity > 0 + disp(' '); + end + warning('netcon:trivialsubnet',['More than one subnet reduces to a single number: Contraction sequence may not be optimal. List of subnets reducing to a single number: ' unpaddednum2str(find(tensormulsize==1 & tensorchisize==0))]); + end + end + while numel(tensorchisize)>1 + % Sort tensors + ptr = 1; + while (ptrtensorchisize(ptr+1) || (tensorchisize(ptr)==tensorchisize(ptr+1) && tensormulsize(ptr)>tensormulsize(ptr+1)) + t = tensorchisize(ptr+1); tensorchisize(ptr+1) = tensorchisize(ptr); tensorchisize(ptr) = t; + t = tensormulsize(ptr+1); tensormulsize(ptr+1) = tensormulsize(ptr); tensormulsize(ptr) = t; + t = sequence{ptr}; sequence{ptr} = sequence{ptr+1}; sequence{ptr+1} = t; + t = cost{ptr}; cost{ptr} = cost{ptr+1}; cost{ptr+1} = t; + ptr = ptr - 1; + if ptr==0 + ptr = 2; + end + else + ptr = ptr + 1; + end + end + % Combine smallest two tensors + outerproductmul = tensormulsize(1) * tensormulsize(2); + outerproductpower = tensorchisize(1) + tensorchisize(2); + if numel(cost{1})1 + % Sort tensors + tensorsize = sort(tensorsize,'ascend'); + % Combine smallest two tensors + outerproduct = tensorsize(1) * tensorsize(2); + cost{1} = cost{1} + outerproduct; + tensorsize = [outerproduct tensorsize(3:end)]; + cost{1} = cost{1} + cost{2}; + sequence{1} = [sequence{1} sequence{2}]; + cost = cost([1 3:end]); + sequence = sequence([1 3:end]); + end +end +sequence = sequence{1}; +sequence = [sequence zeros(1,numel(freelegs)-1)]; % Add outer products between disjoint subnets to the contraction sequence +cost = cost{1}; +end + +function [sequence cost negindices donetrace] = netcon_getsubnetcost(legLinks,verbosity,costType,muCap,legCosts,allowOPs) +% Performs any traces on single tensors +% Checks if network is trivial +% If the network is not trivial, invokes netcon_nondisj to evaluate cost for the network + +% Get index lists for this subnet and trim legCosts +% ================================================= +[posindices negindices] = getIndexLists(cell2mat(legLinks)); + +% Any contraction to do? +% ====================== +if numel(legLinks)<2 + % List of tensors has only one entry + % ================================== + if ~isempty(posindices) + % One tensor, with traces - do them, then go to "Finished contracting" + if costType==2 + cost = []; + else + cost = 0; + end + sequence = posindices; + donetrace = true; + if verbosity > 0 + disp('Tracing costs only'); + end + % Go to "Finished contracting" + else + % Nothing to do + sequence = []; + if costType==2 + cost = []; + else + cost = 0; + end + donetrace = false; + if verbosity > 0 + disp('Network is trivial'); + end + % One tensor, no traces - go to "Finished contracting" + end +else + % >1 tensor, tracing and/or contraction to do + % =========================================== + % Make positive indices consecutive + % Make negative indices consecutive following on from positive indices + % Truncate cost table and remove indexing column + % (Note original labelling for legs may be recovered from posindices and negindices) + [legLinks legCosts] = reprocessLegLinks(legLinks,posindices,negindices,legCosts); + + % Determine any traces (legs with both ends on a single tensor) and eliminate + % =========================================================================== + [tracedindices donetrace legLinks legCosts linkposindices] = eliminateTraces(legLinks,posindices,legCosts); + + if isempty(linkposindices) + % No positive indices after tracing + sequence = posindices(tracedindices); + if costType==2 + cost = []; + else + cost = 0; + end + else + % Find best sequence + % ================== + % Invoke sequence finder for traceless non-disjoint network + for a=1:numel(legLinks) + legLinks{a} = int32(abs(legLinks{a})); + end + legCosts = double(legCosts); + verbosity = double(verbosity); + costType = double(costType); + muCap = double(muCap); + allowOPs = double(allowOPs); + posindices = double(posindices); + tracedindices = int32(tracedindices); + [sequence cost] = netcon_nondisj(legLinks,legCosts,verbosity,costType,muCap,allowOPs,posindices,tracedindices); +% try +% [sequence cost] = netcon_nondisj_cpp(legLinks,legCosts,verbosity,costType,muCap,allowOPs,posindices,tracedindices); +% catch ME +% if isequal(ME.identifier,'MATLAB:UndefinedFunction') +% [sequence cost] = netcon_nondisj(legLinks,legCosts,verbosity,costType,muCap,allowOPs,posindices,tracedindices); +% else +% rethrow(ME); +% end +% end + % Reinsert indices traced at the beginning of the contraction process + sequence = [tracedindices sequence]; + % Translate sequence back into user's original leg labels + sequence(sequence>0) = posindices(sequence(sequence>0)); + end +end +end + +function [tracedindices donetrace legLinks legCosts posindices] = eliminateTraces(legLinks,posindices,legCosts) +tracedindices = []; +donetrace = false; +% For each tensor: +for a=1:numel(legLinks) + % - Find all tracing legs on that tensor, note the indices, and delete them. + % - Tracing can be performed over all traced indices on a tensor simultaneously by combining them - no need to sort + b = 1; + while b + legLinks{a}(legLinks{a}==legLinks{a}(b)) = []; + else + b = b + 1; + end + end +end +% Then: +if ~isempty(tracedindices) + donetrace = true; + % - Delete removed rows from leg cost list + legCosts(tracedindices,:) = []; + % - Renumber all positive and negative legs consecutively again + renumindices = 1:numel(posindices); + renumindices(tracedindices) = []; + posindices(tracedindices) = []; + for a=1:numel(legLinks) + for b=1:numel(legLinks{a}) + if legLinks{a}(b)>0 + legLinks{a}(b) = find(renumindices==legLinks{a}(b)); + else + legLinks{a}(b) = legLinks{a}(b) + numel(tracedindices); + end + end + end +end +end + +function [legLinks legCosts] = reprocessLegLinks(legLinks,posindices,negindices,legCosts) +% Renumber all positive and negative indices consecutively +% ======================================================== +for a=1:numel(legLinks) + for b=find(legLinks{a}>0) + legLinks{a}(b) = find(posindices==legLinks{a}(b)); + end + for b=find(legLinks{a}<0) + legLinks{a}(b) = -find(negindices==legLinks{a}(b))-numel(posindices); + end +end + +% Assemble truncated cost table +% ============================= +for a=size(legCosts,1):-1:1 + if ~any([posindices negindices]==legCosts(a,1)) + legCosts(a,:) = []; + end +end +legCosts = legCosts(:,2:end); +end + +function cost = addCosts(cost1,cost2,costType) +if costType==1 + cost = cost1 + cost2; +else + cost = zeros(1,max([numel(cost1) numel(cost2)])); + cost(1:numel(cost1)) = reshape(cost1,1,numel(cost1)); + cost(1:numel(cost2)) = cost(1:numel(cost2)) + reshape(cost2,1,numel(cost2)); +end +end + +function flags = findSubnetIncluding(legLinks,pos) +new = true; +flags = zeros(1,numel(legLinks)); % 0: May not be in subnet +flags(pos) = 1; % 1: Follow connections off this tensor +while new + new = false; + fromlist = find(flags==1); + flags(flags==1) = 2; % 2: Have followed all connections from this tensor + for checkfrom = fromlist + for checkto = find(flags==0) + for x = 1:numel(legLinks{checkfrom}) + if any(legLinks{checkto}==legLinks{checkfrom}(x)) + flags(checkto) = true; + new = true; + end + end + end + end +end +flags = flags~=0; +end + +function [trivialindices legCosts legLinks] = checkInputData(legLinks,verbosity,costType,muCap,allowOPs,legCosts) +% Check data sizes +if ~iscell(legLinks) + error('legLinks must be a cell array') +end +if numel(legLinks)==0 + error('legLinks may not be empty') +end +if ~isnumeric(verbosity) || ~any([0 1 2 3]==verbosity) + error('verbosity must be 0, 1, 2 or 3') +end +if ~isnumeric(muCap) || numel(muCap)~=1 || ~isreal(muCap) || muCap<=0 + error('muCap must be a real positive number'); +end +if ~isnumeric(costType) || ~any([1 2]==costType) + error('costType must be either 1 or 2') +end +if (~isnumeric(allowOPs) && ~islogical(allowOPs)) || ~any([0 1]==allowOPs) + error('allowOPs must be either 0 or 1'); +end +for a=1:numel(legLinks) + if ~isnumeric(legLinks{a}) + error('Entries in legLinks must be numeric arrays') + end +end +if size(legLinks,1)~=1 || size(legLinks,2)~=numel(legLinks) + error('Array of index connections (legLinks) has incorrect dimension - should be 1xn') +end +for a=1:numel(legLinks) + if size(legLinks{a},1)~=1 || size(legLinks{a},2)~=numel(legLinks{a}) + error(['legLinks entry ' num2str(a) ' has wrong dimension - should be 1xn']); + end + if isempty(legLinks{a}) + error(['Empty list of indices on tensor ' num2str(a)]) + end +end +allindices = cell2mat(legLinks); +if any(allindices==0) + error('Zero entry in index list') +elseif any(imag(allindices)~=0) + error('Complex entry in index list') +elseif any(int32(allindices)~=allindices) + error('Non-integer entry in legLinks'); +end +posindices = sort(allindices(allindices>0),'ascend'); +negindices = sort(allindices(allindices<0),'descend'); +if ~isempty(posindices) + % Test all positive indices occur exactly twice + if mod(numel(posindices),2)~=0 + maxposindex = posindices(end); + posindices = posindices(1:end-1); + end + flags = (posindices(1:2:numel(posindices))-posindices(2:2:numel(posindices)))~=0; + if any(flags) + errorpos = 2*find(flags~=0,1,'first')-1; + if errorpos>1 && posindices(errorpos-1)==posindices(errorpos) + error(['Error in index list: Index ' num2str(posindices(errorpos)) ' appears more than twice']); + else + error(['Error in index list: Index ' num2str(posindices(errorpos)) ' only appears once']); + end + end + if exist('maxposindex','var') + if posindices(end)==maxposindex + error(['Error in index list: Index ' num2str(maxposindex) ' appears more than twice']); + else + error(['Error in index list: Index ' num2str(maxposindex) ' only appears once']); + end + end + posindices = posindices(1:2:numel(posindices)); % List of all positive indices, sorted ascending, each appearing once. + flags = posindices(1:end-1)==posindices(2:end); + if any(flags) + errorpos = find(flags,1,'first'); + error(['Error in index list: Index ' num2str(posindices(errorpos)) ' appears more than twice']); + end +else + posindices = []; +end +% Test all negative indices occur exactly once +flags = negindices(1:end-1)==negindices(2:end); +if any(flags) + errorpos = find(flags,1,'first'); + error(['Error in index list: Index ' num2str(negindices(errorpos)) ' appears more than once']); +end + +% Check leg size data +trivialindices = []; +if ~exist('legCosts','var') + if costType==1 + % Create basic leg costs list (all 2) + legCosts = [[posindices.';negindices.'] 2*ones(numel(posindices)+numel(negindices),1)]; + else + % Create basic leg costs list (all Chi^1) + legCosts = [[posindices.';negindices.'] ones(numel(posindices)+numel(negindices),2)]; + end +else + if ~isnumeric(legCosts) + error('legCosts must be numeric') + end + % Check valid leg costs list, & process + if ndims(legCosts)>2 || any(legCosts(:,1)==0) || size(legCosts,2)~=2+(costType==2) || any(legCosts(:,2)<=0) + error('Bad index dimensions list (error in legCosts: type ''help netcon'' for specifications)') + end + if costType==2 + if any(legCosts(:,3)<0) + error('For index dimensions of the form aX^b, values of b less than 0 are not supported') + end + if any(legCosts(:,2)<=0) + error('For index dimensions of the form aX^b, values of a less than or equal to 0 are not supported') + end + else + if any(legCosts(:,2)<=0) + error('Index dimensions less than or equal to 0 are not supported') + end + end + [t1 ix1] = sort(legCosts(legCosts(:,1)>0,1)); + [t2 ix2] = sort(legCosts(legCosts(:,1)<0,1),'descend'); + t1 = reshape(t1,1,[]); + t2 = reshape(t2,1,[]); + flag = t1(1:end-1)==t1(2:end); + if any(flag) + error(['Dimension of index ' num2str(t1(find(flag,1))) ' specified twice']) + end + if ~(isempty(t1) && isempty(posindices)) % Necessary as a 1x0 matrix is not the same as a 0x0 matrix + if ~isequal(t1,posindices) + error(['Dimension not specified for all positive indices. Supplied: ' num2str(t1) ' Required: ' num2str(posindices)]) + end + end + flag = t2(1:end-1)==t2(2:end); + if any(flag) + error(['Dimension of index ' num2str(t2(find(flag,1))) ' specified twice']) + end + if ~(isempty(t2) && isempty(negindices)) + if ~isequal(t2,negindices) + error(['Dimension not specified for all negative indices. Supplied: ' num2str(t2) ' Required: ' num2str(negindices)]) + end + end + + % Store list of any summed trivial indices to be stripped + if costType==1 + trivialindices = legCosts(legCosts(:,2)==1,1); + else + trivialindices = legCosts(legCosts(:,2)==1 & legCosts(:,3)==0,1); + end + trivialindices = reshape(sort(trivialindices(trivialindices>0),'descend'),1,[]); + + % Strip trivial indices + for a=numel(trivialindices):-1:1 + for b=1:numel(legLinks) + if sum(legLinks{b}==trivialindices(a))==2 + trivialindices(a) = []; % Do not strip trivial single-tensor traces (not that this actually matters either way) + else + legLinks{b}(legLinks{b}==trivialindices(a)) = []; + end + end + end + if ~isempty(trivialindices) && verbosity > 1 + disp(' '); + disp(['Ignore summed trivial indices: ' unpaddednum2str(trivialindices)]); + end + + % Order leg costs list + t1 = legCosts(legCosts(:,1)>0,2:end); + t1 = t1(ix1,:); + t2 = legCosts(legCosts(:,1)<0,2:end); + t2 = t2(ix2,:); + legCosts = [[posindices.';negindices.'] [t1;t2]]; +end +end + +function [posindices negindices] = getIndexLists(allindices) +posindices = sort(allindices(allindices>0),'ascend'); +negindices = sort(allindices(allindices<0),'descend'); +posindices = posindices(1:2:end); +end + +function netcon_displayresult(sequence,cost,donetrace,costType) +% Displays nicely-formatted final output +t = ['Best sequence: ' unpaddednum2str(sequence)]; +if isempty(sequence) + t = [t '']; +end +disp(t); +if isempty(cost) || isequal(cost,0) + if donetrace + disp('Cost: Tracing costs only'); + else + disp('Cost: 0'); + end +else + t = 'Cost: '; + if costType==2 + for a=numel(cost):-1:2 + t = [t num2str(cost(a)) 'X^' num2str(a-1) ' + ']; %#ok + end + t = [t num2str(cost(1)) 'X^0']; + else + t = [t num2str(cost)]; + end + if donetrace + t = [t ' + tracing costs']; + end + disp(t); +end +end + +function str = unpaddednum2str(row) +str = []; +for a=row(1:end-1) + str = [str num2str(a) ' ']; %#ok +end +if ~isempty(row) + str = [str num2str(row(end))]; +end +end + +% Functions used in the pure-MATLAB version only: + +function [sequence cost] = netcon_nondisj(legLinks,legCosts,verbosity,costType,muCap,allowOPs,posindices,tracedindices) +% Set up initial data +% =================== +numtensors = numel(legLinks); +if costType==1 + zerocost = 0; +else + zerocost = []; +end +allowOPs = (allowOPs==1); + +% This code uses the nomenclature of Appendix G, in which a tensor which may be contracted with the result of an outer product [e.g. C in Fig.4(a)] is denoted X. + +% Structure of "objects": objects{numElements}{positionInList}{legFlags,tensorFlags,sequenceToBuild,costToBuild,isOP,OPmaxdim,allIn} +% legFlags: Legs present on object +% tensorFlags: Tensor constituents of object +% sequenceToBuild: Cheapest identified contraction sequence for constructing this object +% costToBuild: Minimum identified cost of constructing this object +% isOP: Indicates whether the contraction which yielded this object was an outer product +% OPmaxdim: If isOP==true, then OPmaxdim gives the dimension of the largest constituent tensor +% allIn: If this tensor is an object X which may be contracted with an outer product, allIn is the dimension of the tensor which contributed no external legs to X, i.e. xi_C in Fig.5(c). + +objects = cell(1,numtensors); +objects{1} = cell(1,numtensors); +tensorflags = false(1,numtensors); +numleglabels = size(legCosts,1); +legflags = false(1,numleglabels); + +% ### Create lists used in enforcing Sec. II.B.2.c (structure of tensor X contractable with an outer product) +tensorXlegs = {}; +tensorXflags = []; +tensorXdims = {}; +% ### End of creating lists + +for a=1:numtensors + tensorflags(a) = true; + legflags(abs(legLinks{a})) = true; + objects{1}{a} = {legflags,tensorflags,[],zerocost,false,[],zeros(1,costType)}; + + % ### Set up initial list data used in enforcing Sec. II.B.2.c + if allowOPs + [tensorXlegs tensorXdims tensorXflags] = addToTensorXlist(tensorXlegs,tensorXdims,tensorXflags,legflags,Inf*ones(1,costType),costType,true); + end + % ### End of setting up linked list data + + tensorflags(a) = false; + legflags(abs(legLinks{a})) = false; +end + +% ### Set up initial list data used in enforcing Sec. II.B.2.c (continued) +tensorXflags = zeros(1,numel(tensorXflags)); +% ### End of setting up initial linked list data (continued) + +for a=2:numtensors + objects{a} = {}; +end + +newobjectflags = cell(1,numtensors); +newobjectflags{1} = true(1,numtensors); + +oldmuCap = 0; +newmuCap = Inf; + +done = false; +while ~done + if verbosity>0 + if oldmuCap~=muCap + if costType==1 + disp(['Looking for solutions with maximum cost of ' num2str(muCap)]); + else + disp(['Looking for solutions of cost O(X^' num2str(muCap) ')']); + end + end + end + for numInObjects = 2:numtensors + if verbosity > 2 + disp(['Pairwise contractions (AB) involving ' num2str(numInObjects) ' fundamental tensors:']); + end + for numInPieceOne = 1:floor(numInObjects/2) + numInPieceTwo = numInObjects - numInPieceOne; + if verbosity > 2 + disp(['A contains ' num2str(numInPieceOne) ', B contains ' num2str(numInPieceTwo)'.']); + pause(0.01); + end + if numel(objects{numInPieceOne})>0 && numel(objects{numInPieceTwo})>0 + + % Iterate over pairings: Iterate over object 1 + for a = 1:numel(objects{numInPieceOne}) + + % Get data for object 1 + obj1data = objects{numInPieceOne}{a}; + % obj1data: legs, tensors, seqToBuild, costToBuild, isOP, maxdimOP, allIn + legs1 = obj1data{1}; + tensorsIn1 = obj1data{2}; + seq1 = obj1data{3}; + costToBuild1 = obj1data{4}; + isOP1 = obj1data{5}; + OPmaxdim1 = obj1data{6}; + allIn1 = obj1data{7}; + isnew1 = newobjectflags{numInPieceOne}(a); + + % Iterate over pairings: Iterate over object 2 + if numInPieceOne == numInPieceTwo + obj2list = a+1:numel(objects{numInPieceTwo}); + else + obj2list = 1:numel(objects{numInPieceTwo}); + end + for b = obj2list + + % Check object 1 and object 2 don't share any common tensors (which would then appear twice in the resulting network) + if ~any(objects{numInPieceOne}{a}{2} & objects{numInPieceTwo}{b}{2}) + + % Get data for object 2 + obj2data = objects{numInPieceTwo}{b}; + % obj2data: legs, tensors, seqToBuild, costToBuild, isOP, maxdimOP, allIn + legs2 = obj2data{1}; + tensorsIn2 = obj2data{2}; + seq2 = obj2data{3}; + costToBuild2 = obj2data{4}; + isOP2 = obj2data{5}; + OPmaxdim2 = obj2data{6}; + allIn2 = obj2data{7}; + isnew2 = newobjectflags{numInPieceTwo}(b); + + commonlegs = legs1 & legs2; % Common legs + freelegs = xor(legs1,legs2); + freelegs1 = legs1 & freelegs; + freelegs2 = legs2 & freelegs; + commonlegsflag = any(commonlegs); + + isOK = allowOPs || commonlegsflag; % Exclude outer products if allowOPs is not set + + thisTensorXflag = -1; + % ### Enforce Sec. II.B.2.b,c,d (only perform outer product if there is a known tensor X with appropriate structure; only contract resulting object in another outer product or with an appropriate tensor X; enforce index dimension constraints) + if isOK && ~commonlegsflag + % It's an outer product. Check if a suitable tensor X exists yet to contract with this outer product [Fig.5(c) & Eq.(25)]. + if oldmuCap == muCap + % Pass for new X's + if isnew1 || isnew2 + % Using a new object - allowed to contract with old and new X's + % Note - the while/end construction is faster than using "find" + Xstart = 1; + Xend = 1; + while Xend<=numel(tensorXflags) && tensorXflags(Xend)~=2; + Xend = Xend + 1; + end + else + % Made from old objects - only allowed to contract with new X's. Already had a chance to contract with old X's + % on the previous pass so repeating these is unnecessary. + Xstart = 1; + while Xstart<=numel(tensorXflags) && tensorXflags(Xstart)~=1; + Xstart = Xstart + 1; + end + Xend = Xstart; + while Xend<=numel(tensorXflags) && tensorXflags(Xend)==1; + Xend = Xend + 1; + end + end + else + % Old X's only on this pass + Xstart = 1; + Xend = 1; + while Xend<=numel(tensorXflags) && tensorXflags(Xend)==0; + Xend = Xend + 1; + end + end + for x = Xstart:Xend-1 + if all(tensorXlegs{x}(freelegs)) + % IIB2c: xi_C > xi_A (25) + if isGreaterThan_sd(tensorXdims{a},getProdLegDims(freelegs,legCosts,costType),costType) + % IIB2b: xi_C > xi_D && xi_C > xi_E: (16) + if isGreaterThan_sd(getProdLegDims(tensorXlegs{x}&~freelegs,legCosts,costType),getProdLegDims(freelegs1,legCosts,costType),costType) + if isGreaterThan_sd(getProdLegDims(tensorXlegs{x}&~freelegs,legCosts,costType),getProdLegDims(freelegs2,legCosts,costType),costType) + thisTensorXflag = tensorXflags(x); + break; + end + end + end + end + end + isOK = thisTensorXflag~=-1; + end + + % If either constituent is the result of an outer product, check that it is being contracted with an appropriate tensor + % [either this is a contraction over all indices, or this is an outer product with a tensor of larger total dimension than + % either constituent of the previous outer product, and Eqs. (16), (25), and (26) are satisfied]. + if isOK && (isOP1 || isOP2) + % Post-OP. This contraction only allowed if it is also an outer product, or if only one object is an outer product, it involves all indices on that tensor, and the other object satisfies the relevant conditions. + isOK = xor(isOP1,isOP2) || ~commonlegsflag; % If contracting over common indices, only one object may be an outer product + if isOK + if commonlegsflag + % This contraction is not itself an outer product + % Conditions on outer product object: + if isOP1 + % Check all-indices condition: + if isequal(commonlegs,legs1) + % Check free legs on contracting tensor are larger than summing legs going to each component of outer product [Eq. (16)] + isOK = isGreaterThan_sd(getProdLegDims(freelegs,legCosts,costType),OPmaxdim1,costType); % IIB2b: xi_C > xi_D, xi_C > xi_E (16) + else + isOK = false; + end + else + % Check all-indices condition: + if isequal(commonlegs,legs2) + % Check free legs on contracting tensor are larger than summing legs going to each component of outer + % product [Eq. (16)] + isOK = isGreaterThan_sd(getProdLegDims(freelegs,legCosts,costType),OPmaxdim2,costType); % IIB2b: xi_C > xi_D, xi_C > xi_E (16) + else + isOK = false; + end + end + % Conditions on X: Ensure X is fundamental or acceptably-constructed (note: structure is checked by requiring + % non-zero value of allIn) + if isOK + if isOP1 + % Tensor 2 is X + if numInPieceTwo > 1 + % Tensor 2 is not fundamental + % Check tensor 2 is constructed in an acceptable fashion [Fig. 5(c) and Eqs. (25) and (26)] + isOK = isGreaterThan_sd(allIn2,getProdLegDims(freelegs,legCosts,costType),costType); % IIB2c: xi_C > xi_D (26) + if isOK + isOK = isGreaterThan_sd(allIn2,getProdLegDims(freelegs1,legCosts,costType),costType); % IIB2c: xi_C > xi_A (25) + end + end + else + % Tensor 1 is X + if numInPieceOne > 1 + % Tensor 1 is not fundamental + % Check tensor 1 is constructed in an acceptable fashion [Fig. 5(c) and Eqs. (25) and (26)] + isOK = isGreaterThan_sd(allIn1,getProdLegDims(freelegs,legCosts,costType),costType); % IIB2c: xi_C > xi_D (26) + if isOK + isOK = isGreaterThan_sd(allIn1,getProdLegDims(freelegs2,legCosts,costType),costType); % IIB2c: xi_C > xi_A (25) + end + end + end + end + else + % This contraction is an outer product. If either constituent is an outer product, check that both tensors + % within that object are not larger than the third tensor with which they are now being contracted. + if isOP1 + isOK = ~isGreaterThan_sd(OPmaxdim1,getProdLegDims(freelegs2,legCosts,costType),costType); % IIB2b: xi_C >= xi_A, xi_C >= xi_B (20) + end + if isOK && isOP2 + isOK = ~isGreaterThan_sd(OPmaxdim2,getProdLegDims(freelegs1,legCosts,costType),costType); % IIB2b: xi_C >= xi_A, xi_C >= xi_B (20) + end + end + end + end + % ### End of enforcing Sec. II.B.2.b,c,d (only perform outer product if there is a known tensor X with appropriate structure; only contract resulting object in another outer product or with an appropriate tensor X; enforce index dimension constraints) + + % If contraction is not prohibited, check cost is acceptable (<=muCap and, if not involving new objects, >oldmuCap) + if isOK + % If constructing an outer product which may contract with a new X, do not exclude on basis of low cost: Hence + % isnew1||isnew2||thisTensorXflag>0 + [newCost isOK] = getBuildCost(freelegs,commonlegs,legCosts,costType,oldmuCap,muCap,isnew1||isnew2||thisTensorXflag>0,costToBuild1,costToBuild2); + if ~isOK + newmuCap = min([newmuCap newCost]); + end + end + + % If cost is OK, compare with previous best known cost for constructing this object + if isOK + % Get involved tensors + tensorsInNew = tensorsIn1 | tensorsIn2; + % Find if previously constructed + objectPtr = 0; + for x=1:numel(objects{numInObjects}) + if isequal(objects{numInObjects}{x}{2},tensorsInNew) + objectPtr = x; + break; + end + end + isnew = objectPtr==0; + if isnew + % Is a new construction + objectPtr = numel(objects{numInObjects})+1; + else + % Compare new cost with best-so-far cost for construction of this object + isOK = isLessThan(newCost,objects{numInObjects}{objectPtr}{4},costType); + end + + % ### If appropriate, update tensorXlist (list of tensors which can be contracted with objects created by outer product) + if allowOPs + if isOK + % New tensor or new best cost + E_is_2 = ~any(freelegs2) && any(freelegs1); + if (~any(freelegs1) && any(freelegs2)) || E_is_2 + % New best sequence consistent with Fig.5(c). + % Determine the value of allIn, which corresponds to xi_C. (This is used in determining valid tensors X to contract with outer products). + if E_is_2 + allIn = getProdLegDims(legs2,legCosts,costType); + else + allIn = getProdLegDims(legs1,legCosts,costType); + end + % Add to tensor X list for outer products (or if already there, update the value of allIn): + if isnew + if isGreaterThan_sd(dimSquared(allIn,costType),getProdLegDims(freelegs,legCosts,costType),costType) % Enforce Eq.(27) + [tensorXlegs tensorXdims tensorXflags] = addToTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs,allIn,costType,isnew); + end + else + if isGreaterThan_sd(dimSquared(allIn,costType),getProdLegDims(freelegs,legCosts,costType),costType) % Enforce Eq.(27) + [tensorXlegs tensorXdims tensorXflags] = addToTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs,allIn,costType,isnew,~isequal(objects{numInObjects}{objectPtr}{7},zeros(1,costType))); + elseif ~isequal(objects{numInObjects}{objectPtr}{7},zeros(1,costType)) % Only need to invoke removeFromTensorXList if there might actually be an entry + [tensorXlegs tensorXdims tensorXflags] = removeFromTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs); + end + end + else + % This tensor is not an eligible tensor X for an outer product: Store a dummy value in allIn to indicate this + allIn = zeros(1,costType); + % Best cost and not consistent with Fig.5(c): Ensure does not appear in tensorXlist. Active removal only + % required if object is not new, and previous best sequence was consistent with Fig.5(c), so allIn is not a + % dummy on the old entry. + if ~isnew && ~isequal(objects{numInObjects}{objectPtr}{7},zeros(1,costType)) + [tensorXlegs tensorXdims tensorXflags] = removeFromTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs); + end + end + elseif isequal(newCost,objects{numInObjects}{objectPtr}{4}) + % Equal-best cost to a known sequence for the same tensor + if ~isequal(objects{numInObjects}{objectPtr}{7},zeros(1,costType)) + % Previous best sequence was consistent with Fig.5(c) so tensor may appear in the provisional environments list + E_is_2 = ~any(freelegs2) && any(freelegs1); + if (~any(freelegs1) && any(freelegs2)) || E_is_2 + % Determine the value of allIn, which corresponds to xi_C in Fig.5(c). + if E_is_2 + allIn = getProdLegDims(legs2,legCosts,costType); + else + allIn = getProdLegDims(legs1,legCosts,costType); + end + % If smaller than previous value, update the value of allIn: + if isGreaterThan_sd(objects{numInObjects}{objectPtr}{7},allIn,costType) + if isGreaterThan_sd(dimSquared(allIn,costType),getProdLegDims(freelegs,legCosts,costType),costType) % Enforce Eq.(27) + [tensorXlegs tensorXdims tensorXflags] = updateTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs,allIn); + else + [tensorXlegs tensorXdims tensorXflags] = removeFromTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs); + end + end + else + % Found best-equal sequence not consistent with Fig.5(c) + [tensorXlegs tensorXdims tensorXflags] = removeFromTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs); + end + %else: There already exists a best-known-cost sequence for the tensor which is not consistent with Fig.5(c), and isOK=false. Tensor does not appear in tensorXlist. No need to assign allIn. Now returning to start of main loop. + end + % else: Sequence is not capable of updating tensorXlist (not better cost, not equal cost). Also, isOK=false. No need to assign allIn. Now returning to start of main loop. + end + else + % Not doing outer products. Store a dummy value in allIn, which is never used. + allIn = []; + end + % ### Done updating tensorXlist (list of tensors which can be contracted with objects created by outer product) + end + + if isOK + % Either no previous construction, or this one is better + if ~any(commonlegs) + % ### This construction is an outer product. Note dimension of larger of the two participating tensors in newmaxdim. (This is used in enforcing index-dimension-related constraints.) + newmaxdim = getProdLegDims(legs1,legCosts,costType); + newmaxdim2 = getProdLegDims(legs2,legCosts,costType); + if isGreaterThan_sd(newmaxdim2,newmaxdim,costType) + newmaxdim = newmaxdim2; + end + % ### End recording dimension of larger of the two participating tensors in newmaxdim. + thisIsOP = true; + newseq = [seq1 seq2 0]; + else + % This construction is not an outer product. + if isOP1 + newseq = [seq2 seq1 find(commonlegs)]; + else + newseq = [seq1 seq2 find(commonlegs)]; + end + thisIsOP = false; + % ### This construction is not an outer product. Therefore store a dummy value in maxdim. (For outer products, maxdim records the dimension of the larger participating tensor, to assist in enforcing index-dimension-related constraints.) + newmaxdim = []; + % ### End storing dummy value in maxdim + end + + % Update objects{} with this construction + objects{numInObjects}{objectPtr} = {freelegs,tensorsInNew,newseq,newCost,thisIsOP,newmaxdim,allIn}; + % ### Note 1: If this tensor has the structure of Fig.5(c) and so is capable of being contracted with an outer product object, |E| is recorded in allIn (otherwise this is a dummy value). + % ### Note 2: If this tensor is constructed by outer product, the dimension of the largest participating tensor is recorded in newmaxdim. (This is used in enforcing index-dimension-related constraints.) Otherwise, this is a dummy value. + + % Flag as a new construction + newobjectflags{numInObjects}(objectPtr) = true; + + % If top level, display result + if numInObjects == numtensors + % ### If a valid contraction sequence has been found, there is no need to perform any contraction sequence more expensive than this. Set muCap accordingly. + if costType==1 + muCap = newCost; + else + muCap = numel(newCost)-1; + end + % ### Done setting muCap accordingly. + if verbosity > 1 + displayInterimCostAndSequence(newCost,newseq,costType,posindices,tracedindices); + end + end + end + end + end + end + end + end + end + + % ### Finished searching if an object has been constructed which contains all tensors, and no new outer products have been enabled on the last pass (all(tensorXflags<2)==true, indicating that there are no new entries in the list of tensors which can be contracted with outer products). + done = numel(objects{numtensors})~=0 && (all(tensorXflags<2) || ~allowOPs); % Final object has been constructed, and if outer products are allowed, also no new X's have recently been constructed + + if ~done + if all(tensorXflags<2) + % ### All X tensors have been present for an entire pass, so all permitted outer products at this cost have already been constructed. + % Increment muCap, update oldmuCap + if costType==1 + if newmuCap < muCap * max([min(legCosts) 2]) + newmuCap = muCap * max([min(legCosts) 2]); + end + end + oldmuCap = muCap; + muCap = newmuCap; + newmuCap = Inf; + else + % ### New X tensors generated this pass (some tensor X flags==2). Do another pass with same cost limit, to construct newly-allowed objects (i.e. sequences of affordable cost including at least one new outer product). + % ### This is achieved by updating oldmuCap only. Now only outer products and contractions involving newly-created tensors will satisfy mu_0 < mu <= muCap. + oldmuCap = muCap; + end + % Clear all new object flags + for a=1:numel(newobjectflags) + newobjectflags{a} = false(1,numel(newobjectflags{a})); + end + % ### Update tensor X flags (2 -> 1 -> 0): + % ### 2: Newly created this pass becomes + % ### 1: Created last pass; allow construction of cheap objects which contract with this, as they may have previously been excluded due to lack of a valid tensor X. 1 becomes... + % ### 0: Old tensor X. Standard costing rules apply. + % ### Delete redundant entries in tensorXlist (e.g. if A has a subset of the legs on B, and an equal or lower value of allIn (i.e. |E| in Fig.5(c))) + [tensorXlegs tensorXdims tensorXflags] = mergeTensorXlist(tensorXlegs,tensorXdims,tensorXflags,costType); + % ### Done updating tensor X flags + else + if numel(objects{numtensors})~=1 + error('Inappropriate number of objects containing all tensors!') + end + end +end + +% Extract final result +sequence = int32(objects{numtensors}{1}{3}); +cost = objects{numtensors}{1}{4}; +end + +function [newCost isOK] = getBuildCost(freelegs,commonlegs,legCosts,costType,oldmuCap,muCap,isnew,costToBuild1,costToBuild2) +% Get fusion cost +allLegs = freelegs | commonlegs; +isOK = true; +if costType==1 + % Is cost too high (>muCap)? + newCost = prod(legCosts(allLegs)) + costToBuild1 + costToBuild2; + if newCost > muCap + isOK = false; + end + + % Is cost too low (not made from new objects, and <=oldmuCap: This construction has been done before) + if isOK && ~isnew + if newCost <= oldmuCap + isOK = false; + newCost = Inf; + end + end +else + % Is cost too high (>muCap)? + fusionpower = sum(legCosts(allLegs,2)); + if fusionpower > muCap + isOK = false; + newCost = fusionpower; + end + + % Is cost too low (not made from new objects, and <=oldmuCap: This construction has been done before) + if isOK && ~isnew + if fusionpower <= oldmuCap + isOK = false; + newCost = Inf; + end + end + + % If cost OK, determine total cost of construction + if isOK + newCostLen = max([numel(costToBuild1) numel(costToBuild2) fusionpower+1]); + newCost = zeros(1,newCostLen); + newCost(1:numel(costToBuild1)) = costToBuild1; + if ~isempty(costToBuild2) + newCost(1:numel(costToBuild2)) = newCost(1:numel(costToBuild2)) + costToBuild2; + end + newCost(fusionpower+1) = newCost(fusionpower+1) + prod(legCosts(allLegs,1)); + end +end +end + +function flag = isLessThan(cost1,cost2,costType) +% Compares two full network costs +if costType==1 + flag = cost1 < cost2; +else + if numel(cost1) < numel(cost2) + flag = true; + elseif numel(cost1) > numel(cost2) + flag = false; + else + flag = false; + for a = numel(cost2):-1:1 + if cost1(a) < cost2(a) + flag = true; + break; + elseif cost1(a) > cost2(a) + break; + end + end + end +end +end + +function flag = isGreaterThan_sd(cost1,cost2,costType) +% Compares two single-index costs +if costType==1 + flag = cost1 > cost2; +else + flag = false; + if cost1(2) > cost2(2) + flag = true; + elseif cost1(2) == cost2(2) + if cost1(1) > cost2(1) + flag = true; + end + end +end +end + +function dim = getProdLegDims(freelegs,legCosts,costType) +if costType==1 + dim = prod(legCosts(freelegs)); +else + dim = [prod(legCosts(freelegs,1)) sum(legCosts(freelegs,2))]; +end +end + +function dim = dimSquared(dim,costType) +if costType==1 + dim = dim*dim; +else + dim = [dim(1)*dim(1) dim(2)*2]; +end +end + +function displayInterimCostAndSequence(cost,seq,costType,posindices,tracedindices) +% Displays cost and sequence +dispseq = [tracedindices seq]; +dispseq(dispseq>0) = posindices(dispseq(dispseq>0)); +disp(['Sequence: ' unpaddednum2str(dispseq)]); +if costType==1 + t = ['Cost: ' num2str(cost)]; +else + t = 'Cost: '; + for a = numel(cost):-1:2 + t = [t num2str(cost(a)) 'X^' num2str(a-1) ' + ']; %#ok + end + t = [t num2str(cost(1)) 'X^0']; +end +if ~isempty(tracedindices) + t = [t ' + tracing costs']; +end +disp(t); +end + +function [tensorXlegs tensorXdims tensorXflags] = addToTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs,allIn,costType,isnew,oldMayHaveEntry) +% Constructed a new tensor for tensorXlist, or a known tensor at same or better cost. +% If legs exactly match a known non-provisional entry, consider as a possible tighter bound on allIn. +% Otherwise, consider for provisional list if not made redundant by any non-provisional entries. +% Add to provisional list if not made redundant by any non-provisional entries. +consider = true; +ptr = find(tensorXflags==1,1,'last'); +for a=ptr:-1:1 + if isequal(tensorXlegs{a},freelegs) + % If legs exactly match a non-provisional entry, update value of allIn. + % If allIn for this entry just got increased, associated constraints have been relaxed. Flag this updated entry as provisional to trigger another pass. + if isGreaterThan_sd(allIn,tensorXdims{a},costType) + tensorXdims{a} = allIn; + tensorXflags(a) = 2; + % Flags are always in ascending order: Move re-flagged entry to end of list + tensorXdims = tensorXdims([1:a-1 a+1:end a]); + tensorXlegs = tensorXlegs([1:a-1 a+1:end a]); + tensorXflags = tensorXflags([1:a-1 a+1:end a]); + else + tensorXdims{a} = allIn; + end + consider = false; + break; + else + % Check to see if made redundant by existing non-provisional entry + if all(tensorXlegs{a}(freelegs)) && ~isGreaterThan_sd(allIn,tensorXdims{a},costType) + % All legs in freelegs are in overlap with tensorXlegs{a}, and dimension of absorbed tensor is not greater: Proposed new entry is redundant. + % (Greater dimension is allowed as it would mean more permissive bounds for a subset of legs) + consider = false; + if ~isnew + if oldMayHaveEntry + % This tensor: Excluded from list. + % Previous, higer-cost contraction sequence may have successfully made an entry. Remove it. + [tensorXlegs tensorXdims tensorXflags] = removeFromTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs); + end + end + break; + end + end +end +if consider + % Tensor not excluded after comparison with non-provisional entries: + % If legs exactly match another provisional entry, new submission is a cheaper sequence so keep only the new value of allIn. + % (This is the same subnetwork but with a better cost, and possibly a different value of \xi_C.) + if ~isnew + if oldMayHaveEntry + for a=ptr+1:numel(tensorXlegs) + if isequal(tensorXlegs{a},freelegs) + tensorXdims{a} = allIn; + consider = false; + break; + end + end + end + end + % Otherwise: This is a new tensorXlist entry + if consider + tensorXlegs{end+1} = freelegs; + tensorXflags(end+1) = 2; + tensorXdims{end+1} = allIn; + end +end +end + +function [tensorXlegs tensorXdims tensorXflags] = updateTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs,allIn) +% Found new contraction sequence for an existing entry, but with a smaller value of allIn. Update the stored value to this value. +% (This is the same subnetwork, but the \xi_C constraint has been tightened.) +for a=numel(tensorXlegs):-1:1 + if isequal(freelegs,tensorXlegs{a}) + tensorXdims{a} = allIn; + break; + end +end +% If the original entry was provisional and redundant, this one is too. No match will be found (as the redundant tensor was never recorded), and that's OK. +% If the original entry was not provisional and redundant, it has now been updated. +end + +function [tensorXlegs tensorXdims tensorXflags] = removeFromTensorXlist(tensorXlegs,tensorXdims,tensorXflags,freelegs) +% Remove this tensor from tensorXlist. +% Cannot restrict checking just to provisional portion, because might need to remove a confirmed tensor's data from the list in the scenario described in footnote 2. +for a=numel(tensorXlegs):-1:1 + if isequal(freelegs,tensorXlegs{a}) + tensorXlegs(a) = []; + tensorXflags(a) = []; + tensorXdims(a) = []; + break; + end +end +end + +function [tensorXlegs tensorXdims tensorXflags] = mergeTensorXlist(tensorXlegs,tensorXdims,tensorXflags,costType) +% Merge provisional tensors into main list and decrease all nonzero flags by one. +% For each provisional entry in turn, check if it makes redundant or is made redundant by any other provisional entries, and if it makes redundant any +% non-provisional entries. If so, delete the redundant entries. + +% Decrease non-zero tensorXflags +tensorXflags(tensorXflags>0) = tensorXflags(tensorXflags>0)-1; + +ptr = find(tensorXflags==1,1,'first'); +for a=numel(tensorXlegs):-1:ptr + % Permit provisional tensors to be eliminated by other provisional tensors (elimination by non-provisional tensors was done earlier) + for b=[ptr:a-1 a+1:numel(tensorXlegs)] + if all(tensorXlegs{b}(tensorXlegs{a})) && ~isGreaterThan_sd(tensorXdims{a},tensorXdims{b},costType) % All legs in tensorXlegs{a} overlap with tensorXlegs{b}, and dimension of absorbed tensor is not greater: tensorXlegs{a} is redundant. + tensorXlegs(a) = []; + tensorXflags(a) = []; + tensorXdims(a) = []; + break; + end + end +end + +for a=ptr-1:-1:1 + % Now permit non-provisional tensors to be eliminated by provisional tensors, as these are now confirmed + for b=ptr:numel(tensorXlegs) + if all(tensorXlegs{b}(tensorXlegs{a})) && ~isGreaterThan_sd(tensorXdims{a},tensorXdims{b},costType) % All legs in tensorXlegs{a} overlap with tensorXlegs{b}, and dimension of absorbed tensor is not greater: tensorXlegs{a} is redundant. + tensorXlegs(a) = []; + tensorXflags(a) = []; + tensorXdims(a) = []; + break; + end + end +end +end diff --git a/src/utility/sparse/SparseArray.m b/src/utility/sparse/SparseArray.m index cbb8518..88ebd5b 100644 --- a/src/utility/sparse/SparseArray.m +++ b/src/utility/sparse/SparseArray.m @@ -284,6 +284,12 @@ function disp(a) function [subs, idx, vals] = find(a) % Find subscripts of nonzero elements in a sparse array. % + % Usage + % ----- + % :code:`idx = find(a)` + % + % :code:`[subs, idx, vals] = find(a)` + % % Arguments % --------- % a : :class:`SparseArray` @@ -300,6 +306,10 @@ function disp(a) % var : (:, 1) :class:`double` % values of nonzero array entries. [idx, ~, vals] = find(a.var); + if nargout == 1 + subs = idx; + return + end subs = ind2sub_(a.sz, idx); end @@ -418,6 +428,10 @@ function disp(a) bool = false; end + function bool = istriu(a) + bool = istriu(spmatrix(a)); + end + function c = ldivide(a, b) % Elementwise left division for sparse arrays. % @@ -741,22 +755,19 @@ function disp(a) a.var = sign(a.var); end - - function d = size(a, dim) - % Sparse array dimensions. - % - % Usage - % ----- - % :code:`d = size(a)` - % returns the size of the array. - % - % :code:`d = size(a, dim)` - % returns the sizes of the dimensions specified by :code:`dim`, which is - % either a scalar or a vector of dimensions. - if nargin > 1 - d = a.sz(dim); + function varargout = size(a, i) + if nargin == 1 + sz = a.sz; + else + sz = ones(1, max(i)); + sz(1:length(a.sz)) = a.sz; + sz = sz(i); + end + + if nargout <= 1 + varargout = {sz}; else - d = a.sz; + varargout = num2cell(sz); end end @@ -801,7 +812,25 @@ function disp(a) function a = subsasgn(a, s, rhs) % Subscripted assignment for sparse array. - error('Not implemented.') + assert(strcmp(s(1).type, '()'), 'sparse:index', 'only () indexing allowed'); + + if length(s(1).subs) > 1 % non-linear indexing + assert(length(s(1).subs) == size(a.sz, 2), 'sparse:index', ... + 'number of indexing indices must match tensor size.'); + assert(all(a.sz >= cellfun(@max, s(1).subs)), 'sparse:bounds', ... + 'out of bounds assignment disallowed'); + s(1).subs = {sub2ind(a.sz, s(1).subs{:})}; + end + + [I, ~, V] = find(a.var); + [lia, locb] = ismember(s(1).subs{1}, I); + newI = vertcat(I, s(1).subs{1}(~lia)); + newJ = ones(size(newI)); + V(locb(lia)) = rhs(lia); + newV = vertcat(V, rhs(~lia)); + + a.var = sparse(newI, newJ, newV, ... + size(a.var, 1), size(a.var, 2)); end function a_sub = subsref(a, s) @@ -1086,7 +1115,9 @@ function disp(a) a = SparseArray(repmat(1:inddim, numinds, 1)', 1, repmat(inddim, 1, numinds)); end - + function a = zeros(sz) + a = SparseArray([], [], sz); + end function a = random(sz, density) % Create a random complex sparse array. diff --git a/test/TestAlgorithms.m b/test/TestAlgorithms.m index 31864a0..2a5f279 100644 --- a/test/TestAlgorithms.m +++ b/test/TestAlgorithms.m @@ -2,7 +2,6 @@ % Unit tests for algorithms properties (TestParameter) - unitcell = {1, 2, 3, 4} alg = {Vumps('which', 'smallestreal', 'maxiter', 5), ... ...IDmrg('which', 'smallestreal', 'maxiter', 5), ... Vumps2('which', 'smallestreal', 'maxiter', 6), ... @@ -11,52 +10,59 @@ symm = {'Z1', 'Z2'} end methods (Test) - function test2dIsing(tc, alg, unitcell, symm) + function test2dIsing(tc, alg, symm) alg.which = 'largestabs'; - tc.assumeTrue(unitcell > 1 || isa(alg, 'IDmrg') || isa(alg, 'Vumps')) - - E0 = 2.5337 ^ unitcell; - if strcmp(symm, 'Z1') - mpo1 = InfMpo.Ising(); - mps1 = initialize_mps(mpo1, CartesianSpace.new(12)); - else - mpo1 = InfMpo.Ising('Symmetry', 'Z2'); - mps1 = initialize_mps(mpo1, GradedSpace.new(Z2(0, 1), [6 6], false)); + for unitcell = 1:3 + if unitcell == 1 && (isa(alg, 'Vumps2') || isa(alg, 'IDmrg2')) + continue; + end + + E0 = 2.5337 ^ unitcell; + mpo1 = statmech2dIsing('Symmetry', symm); + + if strcmp(symm, 'Z1') + mps1 = initialize_mps(mpo1, CartesianSpace.new(12)); + else + mps1 = initialize_mps(mpo1, GradedSpace.new(Z2(0, 1), [6 6], false)); + end + + mpo = mpo1; + mps = mps1; + for i = 2:unitcell + mpo = [mpo mpo1]; + mps = [mps mps1]; + end + + [gs, lambda] = fixedpoint(alg, mpo, mps); + tc.verifyEqual(expectation_value(gs, mpo, gs), E0, 'RelTol', 1e-2); end - - mpo = mpo1; - mps = mps1; - for i = 2:unitcell - mpo = [mpo mpo1]; - mps = [mps mps1]; - end - - [gs, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyEqual(expectation_value(gs, mpo, gs), E0, 'RelTol', 1e-2); end - function test1dIsing(tc, unitcell, alg, symm) + function test1dIsing(tc, alg, symm) alg.which = 'smallestreal'; - tc.assumeTrue(unitcell > 1 || isa(alg, 'IDmrg') || isa(alg, 'Vumps')) - E0 = -1.273 * unitcell; - - if strcmp(symm, 'Z1') - mpo1 = InfJMpo.Ising(); - mps1 = initialize_mps(mpo1, CartesianSpace.new(12)); - else - mpo1 = InfJMpo.Ising('Symmetry', 'Z2'); - mps1 = initialize_mps(mpo1, GradedSpace.new(Z2(0, 1), [6 6], false)); - end - - mpo = mpo1; - mps = mps1; - for i = 2:unitcell - mpo = [mpo mpo1]; - mps = [mps mps1]; + for unitcell = 1:3 + if unitcell == 1 && (isa(alg, 'IDmrg2') || isa(alg, 'Vumps2')) + continue; + end + E0 = -1.273 * unitcell; + + mpo1 = quantum1dIsing('Symmetry', symm); + if strcmp(symm, 'Z1') + mps1 = initialize_mps(mpo1, CartesianSpace.new(12)); + else + mps1 = initialize_mps(mpo1, GradedSpace.new(Z2(0, 1), [6 6], false)); + end + + mpo = mpo1; + mps = mps1; + for i = 2:unitcell + mpo = [mpo mpo1]; + mps = [mps mps1]; + end + + [gs, lambda] = fixedpoint(alg, mpo, mps); + tc.verifyEqual(expectation_value(gs, mpo), E0, 'RelTol', 1e-2); end - - [gs, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyEqual(expectation_value(gs, mpo), E0, 'RelTol', 1e-2); end end end diff --git a/test/TestFiniteMpo.m b/test/TestFiniteMpo.m index 8fcdc92..ebb54ed 100644 --- a/test/TestFiniteMpo.m +++ b/test/TestFiniteMpo.m @@ -8,6 +8,7 @@ FiniteMpo.randnc(ComplexSpace.new(2, false, 4, true, 2, true), ... ComplexSpace.new(2, true, 3, false)) ... ) + end methods (Test) @@ -40,6 +41,40 @@ function testTransposes(tc, mpo) tc.assertTrue(isequal(domain(mpo.'), codomain(mpo)')); tc.assertTrue(isequal(codomain(mpo.'), domain(mpo)')); end + + function test2dIsing(tc) + L = 8; + D = 64; + alg = Dmrg('maxiter', 10, 'which', 'largestabs'); + + mpo = statmech2dIsing('beta', 2, 'L', L); + vspace_max = CartesianSpace.new(D); + mps = initialize_mps(mpo, 'MaxVspace', vspace_max); + + [mps, envs, eta] = fixedpoint(alg, mpo, mps); + + mpo = statmech2dIsing('beta', 2, 'L', L, 'Symmetry', 'Z2'); + vspace_max = GradedSpace.new(Z2(0, 1), D ./ [2 2], false); + mps = initialize_mps(mpo, 'MaxVspace', vspace_max); + [mps, envs, eta] = fixedpoint(alg, mpo, mps); + end + + function test1dIsing(tc) + L = 8; + D = 64; + alg = Dmrg('miniter', 2, 'maxiter', 5, 'which', 'smallestreal'); + + mpo = quantum1dIsing('L', L); + vspace_max = CartesianSpace.new(D); + mps = initialize_mps(mpo, 'MaxVspace', vspace_max); + + [mps, envs, eta] = fixedpoint(alg, mpo, mps); + + mpo = quantum1dIsing('L', L, 'Symmetry', 'Z2'); + vspace_max = GradedSpace.new(Z2(0, 1), D ./ [2 2], false); + mps = initialize_mps(mpo, 'MaxVspace', vspace_max); + [mps, envs, eta] = fixedpoint(alg, mpo, mps); + end end end diff --git a/test/TestFiniteMps.m b/test/TestFiniteMps.m new file mode 100644 index 0000000..de3eced --- /dev/null +++ b/test/TestFiniteMps.m @@ -0,0 +1,34 @@ +classdef TestFiniteMps < matlab.unittest.TestCase + % Unit tests for finite matrix product states. + + properties + tol = 1e-10 + end + + methods (Test) + function testFullMps(tc) + L = 12; + P = ComplexSpace.new(2, false); + mps = FiniteMps.new([], P, 'L', L); + + % test conversion + psi = Tensor(mps); + psi2 = Tensor(FiniteMps(psi)); + tc.verifyTrue(isapprox(psi, psi2, 'RelTol', tc.tol)); + + % test norms + tc.verifyEqual(norm(psi), norm(mps), 'RelTol', tc.tol); + tc.verifyEqual(sqrt(abs(overlap(mps, mps))), norm(psi), 'RelTol', tc.tol); + mps_normed = normalize(mps); + tc.verifyEqual(norm(mps_normed), 1, 'RelTol', tc.tol); + tc.verifyEqual(norm(Tensor(mps_normed)), 1, 'RelTol', tc.tol); + + % test moving orthogonality center + for i = 1:length(mps) + tc.verifyEqual(overlap(movegaugecenter(mps_normed, i), mps_normed), 1, ... + 'RelTol', tc.tol); + end + end + end +end + diff --git a/test/TestInfJMpo.m b/test/TestInfJMpo.m index 50e60ae..e6f812e 100644 --- a/test/TestInfJMpo.m +++ b/test/TestInfJMpo.m @@ -43,12 +43,12 @@ function testDerivatives(tc, mpo, mps) function test1dIsing(tc) alg = Vumps('which', 'smallestreal', 'maxiter', 5); D = 16; - mpo = InfJMpo.Ising(1, 1); - mps = UniformMps.randnc(CartesianSpace.new(2), CartesianSpace.new(D)); + mpo = quantum1dIsing('J', 1, 'h', 1, 'L', Inf); + mps = initialize_mps(mpo, CartesianSpace.new(D)); [mps2, lambda] = fixedpoint(alg, mpo, mps); tc.verifyTrue(isapprox(lambda, -1.27, 'RelTol', 1e-2)) - mpo = InfJMpo.Ising(1, 1, 'Symmetry', 'Z2'); + mpo = quantum1dIsing('J', 1, 'h', 1, 'L', Inf, 'Symmetry', 'Z2'); mps = initialize_mps(mpo, GradedSpace.new(Z2(0, 1), [D D] ./ 2, false)); [mps2, lambda2] = fixedpoint(alg, mpo, mps); tc.verifyTrue(isapprox(lambda, -1.27, 'RelTol', 1e-2)) diff --git a/test/TestInfMpo.m b/test/TestInfMpo.m index a03bfaf..45c26c5 100644 --- a/test/TestInfMpo.m +++ b/test/TestInfMpo.m @@ -56,14 +56,14 @@ function test2dIsing(tc) D = 16; alg = Vumps('MaxIter', 10); - mpo = InfMpo.Ising(beta); + mpo = statmech2DIsing('beta', beta, 'Symmetry', 'Z1'); mps = UniformMps.randnc(CartesianSpace.new(2), CartesianSpace.new(D)); [mps2, lambda] = fixedpoint(alg, mpo, mps); tc.assertEqual(-log(lambda) / beta, freeEnergyExact, 'RelTol', 1e-5); mps = UniformMps.randnc(GradedSpace.new(Z2(0, 1), [1 1], false), ... GradedSpace.new(Z2(0, 1), [D D] ./ 2, false)); - mpo = InfMpo.Ising(beta, 'Symmetry', 'Z2'); + mpo = statmech2DIsing('beta', beta, 'Symmetry', 'Z2'); [mps2, lambda] = fixedpoint(alg, mpo, mps); tc.assertEqual(-log(lambda) / beta, freeEnergyExact, 'RelTol', 1e-5); @@ -71,7 +71,13 @@ function test2dIsing(tc) mpo = [mpo mpo]; [mps2, lambda] = fixedpoint(alg, mpo, mps); - tc.assertEqual(-log(sqrt(lambda)) / beta, freeEnergyExact, 'RelTol', 1e-5); + tc.assertEqual(-log(lambda) / 2 / beta, freeEnergyExact, 'RelTol', 1e-5); + + mps = [mps; mps]; + mpo = [mpo; mpo]; + + [mps2, lambda] = fixedpoint(alg, mpo, mps); + tc.assertEqual(-log(lambda)/ 4 / beta, freeEnergyExact, 'RelTol', 1e-5); end function test2dfDimer(tc) From 1c041fcf89f93fc8d1b3335448a1eed372c37b94 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Fri, 24 Feb 2023 16:40:04 +0100 Subject: [PATCH 155/245] quasiparticles states infmpo quasienvironments --- src/mps/FiniteMpo.m | 4 +- src/mps/InfMpo.m | 167 +++++++++++++++++++++++++++---- src/mps/InfQP.m | 116 +++++++++++++++++++++ src/mps/MpsTensor.m | 167 ++++++++++++++++++++++++------- src/tensors/spaces/GradedSpace.m | 2 +- test/TestInfMpo.m | 85 +++++++++++++++- 6 files changed, 483 insertions(+), 58 deletions(-) create mode 100644 src/mps/InfQP.m diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index 015cc92..34d3f71 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -29,9 +29,9 @@ end end - function v = apply_regularised(mpo, fp1, fp2, v) + function v = apply_regularized(mpo, fp1, fp2, v) v = apply(mpo, v); - v = v - contract(fp1, 1:nspaces(fp1), v, nspaces(fp1):-1:1) * fp2; + v = v - overlap(v, fp2) * fp1; end function [V, D, flag] = eigsolve(mpo, v0, howmany, sigma, options) diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index ef85d9d..fec6130 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -78,6 +78,16 @@ mpo.O = vertcat(mpo.O, Os{:}); end + function mpo = mrdivide(mpo, scalar) + assert(depth(mpo) == 1); + if isscalar(scalar) + scalar = scalar .^ (ones(size(mpo.O)) ./ length(mpo)); + end + for i = 1:length(mpo.O) + mpo.O{i} = mpo.O{i} / scalar(i); + end + end + function mpo = slice(mpo, d) mpo.O = mpo.O(d, :); end @@ -147,6 +157,113 @@ end end + function GBL = leftquasienvironment(mpo, qp, GL, GR, GBL, linopts) + arguments + mpo + qp + GL + GR + GBL = cell(1, period(mpo)) + linopts.Algorithm = 'bicgstab' + linopts.MaxIter = 500 + linopts.Verbosity = Verbosity.warn + linopts.Tol = eps(underlyingType(qp))^(3/4) + end + + assert(period(qp) == period(mpo), 'quasiparticles have different period'); + + linkwargs = namedargs2cell(linopts); + + expP = exp(-1i * qp.p); + + rho = GL{1}; + for pos = 1:period(mpo) + T_B = transfermatrix(mpo, qp, qp, pos, 'Type', 'BL'); + if pos == 1 + rho = apply(T_B, GL{pos}); + else + T_R = transfermatrix(mpo, qp, qp, pos, 'Type', 'RL'); + rho = apply(T_B, GL{pos}) + apply(T_R, rho); + end + end + + T_R = transfermatrix(mpo, qp, qp, 1:period(mpo), 'Type', 'RL'); + if istrivial(qp) + C = qp.mpsleft.C(1); + FL = insert_onespace(multiplyright(MpsTensor(GL{1}), C), ... + nspaces(GL{1}) + 1, isdual(auxspace(qp, 1))); + FR = insert_onespace(multiplyright(MpsTensor(GR{1}), C'), ... + 1, ~isdual(auxspace(qp, 1))); + + rho = rho - overlap(rho, FR) * FL; + GBL{1} = linsolve(@(x) x - expP * apply_regularized(T_R, FL, FR, x), ... + expP * rho, [], linkwargs{:}); + else + GBL{1} = expP * linsolve(@(x) x - expP * apply(T_R, x), ... + rho, [], linkwargs{:}); + end + + for i = 2:period(mpo) + T_R = transfermatrix(mpo, qp, qp, i-1, 'Type', 'RL'); + T_B = transfermatrix(mpo, qp, qp, i-1, 'Type', 'BL'); + GBL{i} = apply(T_R, GBL{i-1}) + apply(T_B, GL{w-1}); + end + end + + function GBR = rightquasienvironment(mpo, qp, GL, GR, GBR, linopts) + arguments + mpo + qp + GL + GR + GBR = cell(1, period(mpo)) + linopts.Algorithm = 'bicgstab' + linopts.MaxIter = 500 + linopts.Verbosity = Verbosity.warn + linopts.Tol = eps(underlyingType(qp))^(3/4) + end + + assert(period(qp) == period(mpo), 'quasiparticles have different period'); + + linkwargs = namedargs2cell(linopts); + + expP = exp(1i * qp.p); + + rho = GR{1}; + for pos = period(mpo):-1:1 + T_B = transfermatrix(mpo, qp, qp, pos, 'Type', 'BR').'; + if pos == period(mpo) + rho = apply(T_B, GR{pos}); + else + T_L = transfermatrix(mpo, qp, qp, pos, 'Type', 'LR').'; + rho = apply(T_B, GR{pos}) + apply(T_L, rho); + end + end + + T_L = transfermatrix(mpo, qp, qp, 1:period(mpo), 'Type', 'LR').'; + if istrivial(qp) + C = qp.mpsright.C(1); + FL = insert_onespace(multiplyright(MpsTensor(GL{1}), C), ... + nspaces(GL{1}) + 1, isdual(auxspace(qp, 1))); + FR = insert_onespace(multiplyright(MpsTensor(GR{1}), C'), ... + nspaces(GR{1}) + 1, ~isdual(auxspace(qp, 1))); + + rho = rho - overlap(rho, FL) * FR; + GBR{1} = linsolve(@(x) x - expP * apply_regularized(T_L, FR, FL, x), ... + expP * rho, [], linkwargs{:}); + else + GBR{1} = linsolve(@(x) x - expP * apply(T_L, x), ... + expP * rho, [], linkwargs{:}); + end + + N = period(mpo); + for i = N:-1:2 + T_L = transfermatrix(mpo, qp, qp, i, 'Type', 'LR').'; + T_B = transfermatrix(mpo, qp, qp, i, 'Type', 'BR').'; + GBR{i} = apply(T_L, GBR{next(i, N)}) + apply(T_B, GR{next(i, N)}); + end + end + function [GL, GR, lambda] = environments(mpo, mps1, mps2, GL, GR, eigopts) arguments mpo @@ -183,29 +300,46 @@ %% Derived operators methods - function T = transfermatrix(mpo, mps1, mps2, sites, kwargs) + function T = transfermatrix(operator, state1, state2, sites, kwargs) arguments - mpo - mps1 - mps2 = mps1 - sites = 1:period(mps1) - kwargs.Type {mustBeMember(kwargs.Type, {'LL' 'LR' 'RL' 'RR'})} = 'RR' + operator + state1 + state2 = state1 + sites = 1:period(state1) + kwargs.Type (1,2) char = 'RR' end assert(all(diff(sites) == 1), 'sites must be neighbouring and increasing.'); + assert(length(kwargs.Type) == 2); - if kwargs.Type(1) == 'L' - A1 = mps1.AL(sites); - else - A1 = mps1.AR(sites); + % unpack tensors top state + switch kwargs.Type(1) + case 'L' + top = state1.AL(sites); + case 'R' + top = state1.AR(sites); + case 'B' + top = state1.B(sites); + otherwise + error('invalid transfermatrix top type (%s)', kwargs.Type(1)); end - if kwargs.Type(2) == 'L' - A2 = mps2.AL(sites); - else - A2 = mps2.AR(sites); + + % unpack tensors bottom state + switch kwargs.Type(2) + case 'L' + bot = state2.AL(sites); + case 'R' + bot = state2.AR(sites); + case 'B' + bot = state2.B(sites); + otherwise + error('invalid transfermatrix bot type (%s)', kwargs.Type(2)); end - O = mpo.O(sites); %#ok - T = FiniteMpo.mps_channel_operator(A1, O, A2); %#ok + + % unpack tensors operator + mid = operator.O(sites); + + T = FiniteMpo.mps_channel_operator(top, mid, bot); end function H = AC_hamiltonian(mpo, mps, GL, GR, sites) @@ -281,7 +415,6 @@ end methods (Static) - function mpo = fDimer() pspace = GradedSpace.new(fZ2(0, 1), [1 1], false); O = Tensor([pspace pspace], [pspace pspace]); diff --git a/src/mps/InfQP.m b/src/mps/InfQP.m new file mode 100644 index 0000000..c7d7b93 --- /dev/null +++ b/src/mps/InfQP.m @@ -0,0 +1,116 @@ +classdef InfQP + % Infinite Quasi-Particle states + + + %% Properties + properties + mpsleft UniformMps + mpsright UniformMps + X + VL + B + p + end + + + %% Constructors + methods + function qp = InfQP(varargin) + if nargin == 0, return; end + + qp.mpsleft = varargin{1}; + qp.mpsright = varargin{2}; + qp.X = varargin{3}; + qp.VL = varargin{4}; + qp.B = varargin{5}; + qp.p = varargin{6}; + end + end + + methods (Static) + function qp = new(fun, mpsleft, mpsright, p, charge) + arguments + fun + mpsleft + mpsright = [] + p = 0 + charge = [] + end + + if isempty(mpsright), mpsright = mpsleft; end + assert(period(mpsleft) == period(mpsright)); + + dims = struct; + dims.charges = charge; + dims.degeneracies = ones(size(charge)); + + AL = mpsleft.AL; + for i = period(mpsleft):-1:1 + VL(i) = leftnull(AL(i)); + rVspace = rightvspace(mpsleft, i); + lVspace = leftvspace(mpsright, i); + if isempty(charge) + aspace = one(rVspace); + else + aspace = rVspace.new(dims, false); + end + X(i) = Tensor.new(fun, rVspace', [aspace lVspace]); + end + + qp = InfQP(mpsleft, mpsright, X, VL, [], p); + qp.B = computeB(qp); + end + + function qp = randnc(varargin) + qp = InfQP.new(@randnc, varargin{:}); + end + end + + + %% Derived Properties + methods + function s = auxspace(qp, i) + s = space(qp.X(i), 3); + end + + function al = AL(qp, sites) + if nargin > 1 + al = qp.mpsleft.AL(sites); + else + al = qp.mpsleft.AL; + end + end + + function ar = AR(qp, sites) + if nargin > 1 + ar = qp.mpsright.AR(sites); + else + ar = qp.mpsright.AR; + end + end + + function B = computeB(qp) + if ~isempty(qp.B), B = qp.B; return; end + for w = period(qp):-1:1 + B(w) = multiplyright(qp.VL(w), qp.X(w)); + end + end + + function bool = istrivial(qp) + bool = qp.p == 0 && ... + space(qp.X(1), nspaces(qp.X(1))) == one(space(qp.X(1), nspaces(qp.X(1)))) || ... + space(qp.X(1), nspaces(qp.X(1))) == one(space(qp.X(1), nspaces(qp.X(1))))'; + end + end + + methods + function p = period(qp) + p = length(qp.X); + end + + function type = underlyingType(qp) + type = underlyingType(qp.X); + end + end +end + diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index ba737af..bc157ec 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -65,6 +65,11 @@ function r = rank(A) r = rank([A.var]); end + + function tdst = insert_onespace(tsrc, varargin) + % insert a trivial space at position i. + tdst = MpsTensor(insert_onespace(tsrc.var, varargin{:}), tsrc.alegs + 1); + end end @@ -78,7 +83,7 @@ if numel(A) > 1 for i = numel(A):-1:1 - [A(i), L(i)] = leftorth(A, alg); + [A(i), L(i)] = leftorth(A(i), alg); end return end @@ -96,6 +101,46 @@ end end + function [R, A] = rightorth(A, alg) + arguments + A + alg = 'rqpos' + end + + if numel(A) > 1 + for i = numel(A):-1:1 + [R(i), A(i)] = rightorth(A, alg); + end + return + end + + [R, A.var] = rightorth(A.var, 1, 2:nspaces(A), alg); + if isdual(space(R, 1)) == isdual(space(R, 2)) + R.domain = conj(R.domain); + R = twist(R, 2); + A.var.codomain = conj(A.var.codomain); + end + end + + function A = leftnull(A, alg) + arguments + A + alg = 'svd' + end + + if numel(A) > 1 + for i = numel(A):-1:1 + A(i) = leftorth(A(i), alg); + end + return + end + + p = 1:nspaces(A); + p1 = p(1:(end - A.alegs - 1)); + p2 = p((end - A.alegs):end); + A.var = leftnull(A.var, p1, p2, alg); + end + function d = dot(A, B) if isa(A, 'MpsTensor') A = A.var; @@ -106,6 +151,22 @@ d = dot(A, B); end + function C = mtimes(A, B) + if isnumeric(A) + C = B; + C.var = A * C.var; + return + end + + if isnumeric(B) + C = A; + C.var = C.var * B; + return + end + + error('not implemented when both inputs not numeric.'); + end + function A = repartition(A, varargin) for i = 1:numel(A) A(i).var = repartition(A(i).var, varargin{:}); @@ -164,27 +225,6 @@ [A.var] = twist([A.var], varargin{:}); end - function [R, A] = rightorth(A, alg) - arguments - A - alg = 'rqpos' - end - - if numel(A) > 1 - for i = numel(A):-1:1 - [R(i), A(i)] = rightorth(A, alg); - end - return - end - - [R, A.var] = rightorth(A.var, 1, 2:nspaces(A), alg); - if isdual(space(R, 1)) == isdual(space(R, 2)) - R.domain = conj(R.domain); - R = twist(R, 2); - A.var.codomain = conj(A.var.codomain); - end - end - function t = ctranspose(t) % Compute the adjoint of a tensor. This is defined as swapping the codomain and % domain, while computing the adjoint of the matrix blocks. @@ -511,21 +551,21 @@ end function A = multiplyright(A, C) -% if A.alegs == 0 -% A = MpsTensor(repartition(... -% repartition(A, [nspaces(A)-1 1]) * repartition(C, [1 1]), ... -% rank(A)), 0); -% return -% end -% if isdual(space(C, 1)), C = twist(C, 1); end + Clegs = nspaces(C); Alegs = nspaces(A); - if A.alegs == 0 - A = contract(A, [-(1:Alegs-1) 1], C, [1 -Alegs], 'Rank', rank(A)); - else - A = contract(A, [-(1:Alegs-1-A.alegs) 1 -(Alegs-A.legs+1:Alegs)], ... + + if A.alegs == 0 && Clegs == 2 + A.var = contract(A, [-(1:Alegs-1) 1], C, [1 -Alegs], 'Rank', rank(A)); + elseif Clegs == 2 + A.var = contract(A, [-(1:Alegs-1-A.alegs) 1 -(Alegs-A.legs+1:Alegs)], ... C, [1 Alegs - A.alegs], 'Rank', rank(A)); + elseif A.alegs == 0 + A.var = contract(A, [-(1:Alegs-1) 1], C, [1 -Alegs-(0:Clegs-2)], ... + 'Rank', rank(A) + [0 Clegs - 2]); + A.alegs = A.alegs + Clegs - 2; + else + error('contraction with auxiliary leg on A and C not implemented.'); end - A = MpsTensor(A); end function C = initializeC(AL, AR) @@ -572,6 +612,65 @@ function disp(t) end + %% Solvers + methods + function v = vectorize(t, type) + % Collect all parameters in a vector, weighted to reproduce the correct + % inproduct. + % + % Arguments + % --------- + % t : :class:`Tensor` + % input tensor. + % + % type : 'real' or 'complex' + % optionally specify if complex entries should be seen as 1 or 2 parameters. + % Defaults to 'complex', with complex parameters. + % + % Returns + % ------- + % v : numeric + % real or complex vector containing the parameters of the tensor. + + arguments + t + type = 'complex' + end + + v = vectorize(t.var, type); + end + + function t = devectorize(v, t, type) + % Collect all parameters from a vector, and insert into a tensor. + % + % Arguments + % --------- + % v : numeric + % real or complex vector containing the parameters of the tensor. + % + % t : :class:`Tensor` + % input tensor. + % + % type : 'real' or 'complex' + % optionally specify if complex entries should be seen as 1 or 2 parameters. + % Defaults to 'complex', with complex parameters. + % + % Returns + % ------- + % t : :class:`Tensor` + % output tensor, filled with the parameters. + + arguments + v + t + type = 'complex' + end + + t.var = devectorize(v, t.var, type); + end + end + + %% methods (Static) function local_tensors = decompose_local_state(psi, kwargs) diff --git a/src/tensors/spaces/GradedSpace.m b/src/tensors/spaces/GradedSpace.m index 8eb717e..2639ea4 100644 --- a/src/tensors/spaces/GradedSpace.m +++ b/src/tensors/spaces/GradedSpace.m @@ -108,7 +108,7 @@ % array of spaces. if isstruct(varargin{1}) % default - assert(mod(nargin, 2) == 0); + assert(mod(nargin, 2) == 0, 'input arguments should come in pairs'); spaces = GradedSpace(varargin{:}); return end diff --git a/test/TestInfMpo.m b/test/TestInfMpo.m index 45c26c5..a689aaf 100644 --- a/test/TestInfMpo.m +++ b/test/TestInfMpo.m @@ -3,8 +3,8 @@ properties (TestParameter) mpo = struct(... - 'trivial', InfMpo.Ising(), ... - 'Z2', InfMpo.Ising('Symmetry', 'Z2'), ... + 'trivial', statmech2dIsing(), ... + 'Z2', statmech2dIsing('Symmetry', 'Z2'), ... 'fermion', block(InfMpo.fDimer()) ... ) mps = struct(... @@ -29,6 +29,76 @@ function testEnvironments(tc, mpo, mps) tc.assertTrue(isapprox(lambdaL, lambdaR)); end + + + function testQuasiEnvironments(tc) + + beta = 0.5; + D = 16; + + %% testset 1: environments + mpo = statmech2dIsing('beta', beta, 'Symmetry', 'Z2'); + mps = initialize_mps(mpo, GradedSpace.new(Z2(0, 1), D ./ [2 2], false)); + + [GL, GR, lambda] = environments(mpo, mps); + + % left environment +% [GL, lambdaL] = leftenvironment(mpo, mps); + TL = transfermatrix(mpo, mps, mps, 'Type', 'LL'); + tc.assertTrue(isapprox(apply(TL, GL{1}), lambda * GL{1}), ... + 'left environment fixed point equation unfulfilled.'); + for i = 1:length(GL) + tc.assertTrue(... + isapprox(apply(TL(i), GL{i}), lambda^(1/length(GL)) * GL{next(i, length(GL))}), ... + 'left environment partial fixed point equation unfulfilled.'); + end + + % right environment +% [GR, lambda] = rightenvironment(mpo, mps); + TR = transfermatrix(mpo, mps, mps, 'Type', 'RR').'; + tc.assertTrue(isapprox(apply(TR, GR{1}), lambda * GR{1}), ... + 'right environment fixed point equation unfulfilled.'); + for i = length(GR):-1:1 + tc.assertTrue(... + isapprox(apply(TR(i), GR{i}), lambda^(1/length(GR)) * GR{prev(i, length(GR))}), ... + 'right environment partial fixed point equation unfulfilled.'); + end + + % normalization + for i = 1:length(GL) + gl = multiplyright(MpsTensor(GL{i}), mps.C(i)); + gr = multiplyright(MpsTensor(GR{i}), mps.C(i)'); + tc.assertEqual(overlap(gl, gr), 1, 'AbsTol', 1e-10, ... + 'environment normalization incorrect.'); + end + + %% testset 2: quasiparticle environments + mpo = mpo / lambda; + [GL, GR, lambda] = environments(mpo, mps, mps, GL, GR); + tc.assertEqual(lambda, 1, 'AbsTol', 1e-10); + + for p = [0 pi 0.5] + charge = Z2(1); + qp = InfQP.randnc(mps, mps, p, charge); + + % left environments + GBL = leftquasienvironment(mpo, qp, GL, GR); + T_R = transfermatrix(mpo, qp, qp, 'Type', 'RL'); + T_B = transfermatrix(mpo, qp, qp, 1, 'Type', 'BL'); + tc.assertTrue(isapprox(exp(-1i*p) * apply(T_B, GL{1}) + exp(-1i*p) * apply(T_R, GBL{1}), ... + GBL{1}), sprintf(... + 'left quasi environment fixed point equation unfulfilled for p=%e.', p)); + + % right environments + GBR = rightquasienvironment(mpo, qp, GL, GR); + T_L = transfermatrix(mpo, qp, qp, 'Type', 'LR').'; + T_B = transfermatrix(mpo, qp, qp, 'Type', 'BR').'; + tc.assertTrue(isapprox(exp(1i*p) * apply(T_B, GR{1}) + exp(1i*p) * apply(T_L, GBR{1}), ... + GBR{1}), 'right quasi environment fixed point equation unfulfilled.'); + end + + end + function testDerivatives(tc, mpo, mps) [GL, GR] = environments(mpo, mps, mps); @@ -48,22 +118,29 @@ function testDerivatives(tc, mpo, mps) end function test2dIsing(tc) + % compute exact solution beta = 0.9 * log(1 + sqrt(2)) / 2; theta = 0:1e-6:pi/2; x = 2 * sinh(2 * beta) / cosh(2 * beta)^2; freeEnergyExact = -1 / beta * (log(2 * cosh(2 * beta)) + 1 / pi * ... trapz(theta, log(1/2 * (1 + sqrt(1 - x^2 * sin(theta).^2))))); + % compute fixedpoint D = 16; alg = Vumps('MaxIter', 10); - mpo = statmech2DIsing('beta', beta, 'Symmetry', 'Z1'); + mpo = statmech2dIsing('beta', beta, 'Symmetry', 'Z1'); mps = UniformMps.randnc(CartesianSpace.new(2), CartesianSpace.new(D)); [mps2, lambda] = fixedpoint(alg, mpo, mps); tc.assertEqual(-log(lambda) / beta, freeEnergyExact, 'RelTol', 1e-5); + % compute excitations + qp = InfQP.new([], mps2, mps2); + GBL = leftquasienvironment(mpo, qp); + + mps = UniformMps.randnc(GradedSpace.new(Z2(0, 1), [1 1], false), ... GradedSpace.new(Z2(0, 1), [D D] ./ 2, false)); - mpo = statmech2DIsing('beta', beta, 'Symmetry', 'Z2'); + mpo = statmech2dIsing('beta', beta, 'Symmetry', 'Z2'); [mps2, lambda] = fixedpoint(alg, mpo, mps); tc.assertEqual(-log(lambda) / beta, freeEnergyExact, 'RelTol', 1e-5); From 9895541023627cf7c37a381c3fd2f31391e11d26 Mon Sep 17 00:00:00 2001 From: leburgel Date: Fri, 24 Feb 2023 17:11:35 +0100 Subject: [PATCH 156/245] Bugfix in Vumps2 plot for multiline MPS --- src/algorithms/Vumps2.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/algorithms/Vumps2.m b/src/algorithms/Vumps2.m index 86d87ed..ab2feb0 100644 --- a/src/algorithms/Vumps2.m +++ b/src/algorithms/Vumps2.m @@ -277,7 +277,7 @@ function plot(alg, iter, mps, eta) axhistory.Children(end).YData(end+1) = eta; end - plot_entanglementspectrum(mps, 1:period(mps), axspectrum); + plot_entanglementspectrum(mps, 1:D, 1:W, axspectrum); drawnow end From eaeff707ae1381f917b75ec6e4eb491d4a9f4fd7 Mon Sep 17 00:00:00 2001 From: leburgel Date: Fri, 24 Feb 2023 17:42:39 +0100 Subject: [PATCH 157/245] Scale down peps tests --- test/TestPeps.m | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/TestPeps.m b/test/TestPeps.m index 0661746..c89b379 100644 --- a/test/TestPeps.m +++ b/test/TestPeps.m @@ -39,8 +39,7 @@ function testFiniteMpo(tc, spaces, depth, width, dualdepth, dualwidth, samebot) % test tensor behavior - tc.assumeTrue(width < 3 || (fusionstyle(spaces) <= FusionStyle.Unique || width < 2), ... - 'FiniteMpo -> Tensor contraction not reasonable.') + tc.assumeTrue(width < 2, 'FiniteMpo -> Tensor contraction not reasonable.') % test domain and codomain mpo_tensor = tc.mpo_to_tensor(mpo); From 31e5829480770046db5776b81b57d6c1bce4d8d6 Mon Sep 17 00:00:00 2001 From: leburgel Date: Fri, 24 Feb 2023 18:36:32 +0100 Subject: [PATCH 158/245] Fix bug in eigsolve due to new FailureTreatment setting --- src/tensors/AbstractTensor.m | 6 +++--- test/TestSolvers.m | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tensors/AbstractTensor.m b/src/tensors/AbstractTensor.m index ecb85d7..28a2054 100644 --- a/src/tensors/AbstractTensor.m +++ b/src/tensors/AbstractTensor.m @@ -294,9 +294,9 @@ varargout{3} = flag; end - if flag - warning('eigsolve did not converge.'); - elseif ~flag && options.Verbosity > 1 + if any(flag) + warning('eigsolve did not converge on eigenvalues %s.', num2str(find(flag))); + elseif ~any(flag) && options.Verbosity > 1 fprintf('eigsolve converged.\n'); end end diff --git a/test/TestSolvers.m b/test/TestSolvers.m index 1afb8b1..df9fe5e 100644 --- a/test/TestSolvers.m +++ b/test/TestSolvers.m @@ -52,11 +52,11 @@ function eigsolve(tc, spaces) d1 = eigsolve(A, x0, 1, 'IsSymmetric', true); [v, d, flag] = eigsolve(A, x0, 1, 'IsSymmetric', true); tc.verifyTrue(isapprox(d, d1)); - tc.verifyTrue(flag == 0); + tc.verifyTrue(all(flag == 0)); tc.verifyTrue(isapprox(A * v, v * d)); [v, d, flag] = eigsolve(A, x0, 3, 'IsSymmetric', true); - tc.verifyTrue(flag == 0); + tc.verifyTrue(all(flag == 0)); tc.verifyTrue(isapprox(A * v, v * d)); end end From a61e2803f5b07a5b84eb577c8197610225aba3a5 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sat, 25 Feb 2023 10:04:04 +0100 Subject: [PATCH 159/245] consistency charges of spaces `charges(ComplexSpace)` now returns `Z1` instead of `[]`, in line with `CartesianSpace` --- src/tensors/spaces/ComplexSpace.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tensors/spaces/ComplexSpace.m b/src/tensors/spaces/ComplexSpace.m index 1498cd1..e29b657 100644 --- a/src/tensors/spaces/ComplexSpace.m +++ b/src/tensors/spaces/ComplexSpace.m @@ -88,7 +88,7 @@ % c : [] % empty result. - c = []; + c = Z1; end function d = degeneracies(spaces) From 4cdf2a3678fe11174e39710c183c4d3cc3c4781a Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sat, 25 Feb 2023 11:26:07 +0100 Subject: [PATCH 160/245] fix non-converging FiniteMpo test L=8 D=64 is a full rank mps, error criterion fails --- src/models/statmech2dIsing.m | 3 ++- src/models/statmech2dIsing_free_energy.m | 15 +++++++++++++++ src/mps/FiniteMps.m | 24 +++++++++++++++++++++++- src/mps/MpoTensor.m | 1 + src/tensors/AbstractTensor.m | 2 +- test/TestFiniteMpo.m | 18 +++++++++++++++--- test/TestInfMpo.m | 14 ++++++-------- 7 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 src/models/statmech2dIsing_free_energy.m diff --git a/src/models/statmech2dIsing.m b/src/models/statmech2dIsing.m index 81539ad..58470ca 100644 --- a/src/models/statmech2dIsing.m +++ b/src/models/statmech2dIsing.m @@ -10,11 +10,12 @@ O = InfMpo({MpoTensor(bulk_mpo(kwargs.beta, kwargs.Symmetry))}); else + assert(kwargs.L > 3, 'needs to be implemented'); r = boundary_mpo(kwargs.beta, kwargs.Symmetry); l = r'; o = bulk_mpo(kwargs.beta, kwargs.Symmetry); - O = FiniteMpo(MpsTensor(l), repmat({MpoTensor(o)}, 1, kwargs.L), MpsTensor(r)); + O = FiniteMpo(MpsTensor(l), repmat({MpoTensor(o)}, 1, kwargs.L-2), MpsTensor(r)); end end diff --git a/src/models/statmech2dIsing_free_energy.m b/src/models/statmech2dIsing_free_energy.m new file mode 100644 index 0000000..44c564a --- /dev/null +++ b/src/models/statmech2dIsing_free_energy.m @@ -0,0 +1,15 @@ +function f = statmech2dIsing_free_energy(beta) +% Compute the free energy of the classical 2d Ising model using Onsagers solution. + +INTEGRAL_STEP_SIZE = 1e-6; + +theta = 0:INTEGRAL_STEP_SIZE:(pi / 2); + +sh = sinh(2 * beta); +ch = cosh(2 * beta); +x = 2 * sh / ch^2; + +f = -1 / beta * (log(2 * cosh(2 * beta)) + ... + 1 / pi * trapz(theta, log(1/2 * (1 + sqrt(1 - x^2 * sin(theta).^2))))); + +end diff --git a/src/mps/FiniteMps.m b/src/mps/FiniteMps.m index d0c6076..a5cea39 100644 --- a/src/mps/FiniteMps.m +++ b/src/mps/FiniteMps.m @@ -225,7 +225,7 @@ arguments mps1 mps2 - rholeft = [] + rholeft = [] rhoright = [] end @@ -241,6 +241,28 @@ o = overlap(rholeft, rhoright); end + function E = expectation_value(mps1, O, mps2) + arguments + mps1 + O + mps2 = mps1 + end + + if isa(O, 'FiniteMpo') + n = floor(length(mps1) / 2); + Tleft = transfermatrix(O, mps1, mps2, 1:n); + rholeft = apply(Tleft, O.L); + Tright = transfermatrix(O, mps1, mps2, n+1:length(mps1)).'; + rhoright = apply(Tright, O.R); + + E = overlap(rholeft, rhoright); + + else + error('unknown operator'); + end + + end + function n = norm(mps) if isempty(mps.center) n = sqrt(abs(overlap(mps, mps))); diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index 394c3f2..51a2412 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -105,6 +105,7 @@ R MpsTensor v end + auxlegs_v = nspaces(v) - 3; auxlegs_l = nspaces(L) - 3; auxlegs_r = nspaces(R) - 3; diff --git a/src/tensors/AbstractTensor.m b/src/tensors/AbstractTensor.m index 2b69009..380b46b 100644 --- a/src/tensors/AbstractTensor.m +++ b/src/tensors/AbstractTensor.m @@ -303,7 +303,7 @@ function C = contract(tensors, indices, kwargs) arguments (Repeating) - tensors {mustBeA(tensors, 'AbstractTensor')} + tensors indices (1, :) {mustBeInteger} end diff --git a/test/TestFiniteMpo.m b/test/TestFiniteMpo.m index ebb54ed..856e86b 100644 --- a/test/TestFiniteMpo.m +++ b/test/TestFiniteMpo.m @@ -43,20 +43,32 @@ function testTransposes(tc, mpo) end function test2dIsing(tc) - L = 8; + beta = 0.8 * log(1 + sqrt(2)) / 2; + free_energy_exact = statmech2dIsing_free_energy(beta); + + L = 16; D = 64; alg = Dmrg('maxiter', 10, 'which', 'largestabs'); - mpo = statmech2dIsing('beta', 2, 'L', L); + mpo = statmech2dIsing('beta', beta, 'L', L); vspace_max = CartesianSpace.new(D); mps = initialize_mps(mpo, 'MaxVspace', vspace_max); [mps, envs, eta] = fixedpoint(alg, mpo, mps); - mpo = statmech2dIsing('beta', 2, 'L', L, 'Symmetry', 'Z2'); + free_energy = - 1 / (beta * (L)) * log(expectation_value(mps, mpo, mps)); + + tc.assertEqual(free_energy, free_energy_exact, 'RelTol', 1e-2); + + mpo = statmech2dIsing('beta', beta, 'L', L, 'Symmetry', 'Z2'); vspace_max = GradedSpace.new(Z2(0, 1), D ./ [2 2], false); mps = initialize_mps(mpo, 'MaxVspace', vspace_max); + [mps, envs, eta] = fixedpoint(alg, mpo, mps); + + free_energy = - 1 / (beta * (L)) * log(expectation_value(mps, mpo, mps)); + + tc.assertEqual(free_energy, free_energy_exact, 'RelTol', 1e-2); end function test1dIsing(tc) diff --git a/test/TestInfMpo.m b/test/TestInfMpo.m index 45c26c5..9096e8c 100644 --- a/test/TestInfMpo.m +++ b/test/TestInfMpo.m @@ -49,35 +49,33 @@ function testDerivatives(tc, mpo, mps) function test2dIsing(tc) beta = 0.9 * log(1 + sqrt(2)) / 2; - theta = 0:1e-6:pi/2; - x = 2 * sinh(2 * beta) / cosh(2 * beta)^2; - freeEnergyExact = -1 / beta * (log(2 * cosh(2 * beta)) + 1 / pi * ... - trapz(theta, log(1/2 * (1 + sqrt(1 - x^2 * sin(theta).^2))))); + + f = statmech2dIsing_free_energy(beta); D = 16; alg = Vumps('MaxIter', 10); mpo = statmech2DIsing('beta', beta, 'Symmetry', 'Z1'); mps = UniformMps.randnc(CartesianSpace.new(2), CartesianSpace.new(D)); [mps2, lambda] = fixedpoint(alg, mpo, mps); - tc.assertEqual(-log(lambda) / beta, freeEnergyExact, 'RelTol', 1e-5); + tc.assertEqual(-log(lambda) / beta, f, 'RelTol', 1e-5); mps = UniformMps.randnc(GradedSpace.new(Z2(0, 1), [1 1], false), ... GradedSpace.new(Z2(0, 1), [D D] ./ 2, false)); mpo = statmech2DIsing('beta', beta, 'Symmetry', 'Z2'); [mps2, lambda] = fixedpoint(alg, mpo, mps); - tc.assertEqual(-log(lambda) / beta, freeEnergyExact, 'RelTol', 1e-5); + tc.assertEqual(-log(lambda) / beta, f, 'RelTol', 1e-5); mps = [mps mps]; mpo = [mpo mpo]; [mps2, lambda] = fixedpoint(alg, mpo, mps); - tc.assertEqual(-log(lambda) / 2 / beta, freeEnergyExact, 'RelTol', 1e-5); + tc.assertEqual(-log(lambda) / 2 / beta, f, 'RelTol', 1e-5); mps = [mps; mps]; mpo = [mpo; mpo]; [mps2, lambda] = fixedpoint(alg, mpo, mps); - tc.assertEqual(-log(lambda)/ 4 / beta, freeEnergyExact, 'RelTol', 1e-5); + tc.assertEqual(-log(lambda)/ 4 / beta, f, 'RelTol', 1e-5); end function test2dfDimer(tc) From 5bb20daffc5f8a3134534f93466f8d4f743459e3 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sat, 25 Feb 2023 11:28:57 +0100 Subject: [PATCH 161/245] update tests to use models/ --- test/TestInfMpo.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/TestInfMpo.m b/test/TestInfMpo.m index 9096e8c..8e77db4 100644 --- a/test/TestInfMpo.m +++ b/test/TestInfMpo.m @@ -3,8 +3,8 @@ properties (TestParameter) mpo = struct(... - 'trivial', InfMpo.Ising(), ... - 'Z2', InfMpo.Ising('Symmetry', 'Z2'), ... + 'trivial', statmech2dIsing('Symmetry', 'Z1'), ... + 'Z2', statmech2dIsing('Symmetry', 'Z2'), ... 'fermion', block(InfMpo.fDimer()) ... ) mps = struct(... @@ -54,14 +54,14 @@ function test2dIsing(tc) D = 16; alg = Vumps('MaxIter', 10); - mpo = statmech2DIsing('beta', beta, 'Symmetry', 'Z1'); + mpo = statmech2dIsing('beta', beta, 'Symmetry', 'Z1'); mps = UniformMps.randnc(CartesianSpace.new(2), CartesianSpace.new(D)); [mps2, lambda] = fixedpoint(alg, mpo, mps); tc.assertEqual(-log(lambda) / beta, f, 'RelTol', 1e-5); mps = UniformMps.randnc(GradedSpace.new(Z2(0, 1), [1 1], false), ... GradedSpace.new(Z2(0, 1), [D D] ./ 2, false)); - mpo = statmech2DIsing('beta', beta, 'Symmetry', 'Z2'); + mpo = statmech2dIsing('beta', beta, 'Symmetry', 'Z2'); [mps2, lambda] = fixedpoint(alg, mpo, mps); tc.assertEqual(-log(lambda) / beta, f, 'RelTol', 1e-5); From a76bcc6170238ac1b565b24f133362ed4e08148d Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sun, 26 Feb 2023 11:38:40 +0100 Subject: [PATCH 162/245] infmpo quasienvironments + tests --- src/mps/InfMpo.m | 8 +-- src/mps/InfQP.m | 4 +- src/tensors/spaces/AbstractSpace.m | 7 +++ test/TestInfMpo.m | 86 +++++++++++++++++------------- 4 files changed, 61 insertions(+), 44 deletions(-) diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index fec6130..ee3daba 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -243,10 +243,10 @@ T_L = transfermatrix(mpo, qp, qp, 1:period(mpo), 'Type', 'LR').'; if istrivial(qp) C = qp.mpsright.C(1); - FL = insert_onespace(multiplyright(MpsTensor(GL{1}), C), ... - nspaces(GL{1}) + 1, isdual(auxspace(qp, 1))); - FR = insert_onespace(multiplyright(MpsTensor(GR{1}), C'), ... - nspaces(GR{1}) + 1, ~isdual(auxspace(qp, 1))); + FL = insert_onespace(multiplyleft(MpsTensor(GL{1}), C'), ... + 1, ~isdual(auxspace(qp, 1))); + FR = insert_onespace(multiplyleft(MpsTensor(GR{1}), C), ... + nspaces(GR{1}) + 1, isdual(auxspace(qp, 1))); rho = rho - overlap(rho, FL) * FR; GBR{1} = linsolve(@(x) x - expP * apply_regularized(T_L, FR, FL, x), ... diff --git a/src/mps/InfQP.m b/src/mps/InfQP.m index c7d7b93..4015666 100644 --- a/src/mps/InfQP.m +++ b/src/mps/InfQP.m @@ -97,9 +97,7 @@ end function bool = istrivial(qp) - bool = qp.p == 0 && ... - space(qp.X(1), nspaces(qp.X(1))) == one(space(qp.X(1), nspaces(qp.X(1)))) || ... - space(qp.X(1), nspaces(qp.X(1))) == one(space(qp.X(1), nspaces(qp.X(1))))'; + bool = qp.p == 0 && istrivial(auxspace(qp, 1)); end end diff --git a/src/tensors/spaces/AbstractSpace.m b/src/tensors/spaces/AbstractSpace.m index 5f61b39..0c69de3 100644 --- a/src/tensors/spaces/AbstractSpace.m +++ b/src/tensors/spaces/AbstractSpace.m @@ -321,6 +321,13 @@ bool = le(space2, space1); end + function bool = istrivial(space) + % Check whether a space is isomorphic to a trivial space. + + E = one(space); + bool = space == E || space == conj(E); + end + function bool = isequal(spaces) % Check whether all input spaces are equal. Spaces are considered equal if they % are of same size, and are equal element-wise. For convenience, empty spaces diff --git a/test/TestInfMpo.m b/test/TestInfMpo.m index a689aaf..cf0a67a 100644 --- a/test/TestInfMpo.m +++ b/test/TestInfMpo.m @@ -17,22 +17,7 @@ end methods (Test, ParameterCombination='sequential') - function testEnvironments(tc, mpo, mps) - [GL, lambdaL] = leftenvironment(mpo, mps, mps); - T = transfermatrix(mpo, mps, mps, 'Type', 'LL'); - tc.assertTrue(isapprox(apply(T, GL{1}), lambdaL * GL{1})); - - [GR, lambdaR] = rightenvironment(mpo, mps, mps); - T = transfermatrix(mpo, mps, mps, 'Type', 'RR'); - tc.assertTrue(isapprox(apply(T.', GR{1}), lambdaR * GR{1})); - - tc.assertTrue(isapprox(lambdaL, lambdaR)); - end - - - - function testQuasiEnvironments(tc) - + function testEnvironments(tc) beta = 0.5; D = 16; @@ -43,7 +28,6 @@ function testQuasiEnvironments(tc) [GL, GR, lambda] = environments(mpo, mps); % left environment -% [GL, lambdaL] = leftenvironment(mpo, mps); TL = transfermatrix(mpo, mps, mps, 'Type', 'LL'); tc.assertTrue(isapprox(apply(TL, GL{1}), lambda * GL{1}), ... 'left environment fixed point equation unfulfilled.'); @@ -54,7 +38,6 @@ function testQuasiEnvironments(tc) end % right environment -% [GR, lambda] = rightenvironment(mpo, mps); TR = transfermatrix(mpo, mps, mps, 'Type', 'RR').'; tc.assertTrue(isapprox(apply(TR, GR{1}), lambda * GR{1}), ... 'right environment fixed point equation unfulfilled.'); @@ -77,26 +60,55 @@ function testQuasiEnvironments(tc) [GL, GR, lambda] = environments(mpo, mps, mps, GL, GR); tc.assertEqual(lambda, 1, 'AbsTol', 1e-10); - for p = [0 pi 0.5] - charge = Z2(1); - qp = InfQP.randnc(mps, mps, p, charge); - - % left environments - GBL = leftquasienvironment(mpo, qp, GL, GR); - T_R = transfermatrix(mpo, qp, qp, 'Type', 'RL'); - T_B = transfermatrix(mpo, qp, qp, 1, 'Type', 'BL'); - tc.assertTrue(isapprox(exp(-1i*p) * apply(T_B, GL{1}) + exp(-1i*p) * apply(T_R, GBL{1}), ... - GBL{1}), sprintf(... - 'left quasi environment fixed point equation unfulfilled for p=%e.', p)); - - % right environments - GBR = rightquasienvironment(mpo, qp, GL, GR); - T_L = transfermatrix(mpo, qp, qp, 'Type', 'LR').'; - T_B = transfermatrix(mpo, qp, qp, 'Type', 'BR').'; - tc.assertTrue(isapprox(exp(1i*p) * apply(T_B, GR{1}) + exp(1i*p) * apply(T_L, GBR{1}), ... - GBR{1}), 'right quasi environment fixed point equation unfulfilled.'); - end + + for charge = Z2([0 1]) + for p = [0 pi 0.5] + qp = InfQP.randnc(mps, mps, p, charge); + + % left environments + GBL = leftquasienvironment(mpo, qp, GL, GR); + T_R = transfermatrix(mpo, qp, qp, 'Type', 'RL'); + T_B = transfermatrix(mpo, qp, qp, 1, 'Type', 'BL'); + if istrivial(qp) + C = qp.mpsleft.C(1); + FL_L = insert_onespace(multiplyright(MpsTensor(GL{1}), C), ... + nspaces(GL{1}) + 1, isdual(auxspace(qp, 1))); + FR_L = insert_onespace(multiplyright(MpsTensor(GR{1}), C'), ... + 1, ~isdual(auxspace(qp, 1))); + + tc.verifyTrue(isapprox(... + apply_regularized(T_B, FL_L, FR_L, GL{1}) + apply_regularized(T_R, FL_L, FR_L, GBL{1}), ... + exp(1i*p) * GBL{1}), ... + sprintf('left quasi environment fixed point equation unfulfilled for p=%e, c=%d', p, charge)); + else + tc.verifyTrue(isapprox(apply(T_B, GL{1}) + apply(T_R, GBL{1}), ... + exp(1i*p) * GBL{1}), ... + sprintf('left quasi environment fixed point equation unfulfilled for p=%e, c=%d', p, charge)); + end + + % right environments + GBR = rightquasienvironment(mpo, qp, GL, GR); + T_L = transfermatrix(mpo, qp, qp, 'Type', 'LR').'; + T_B = transfermatrix(mpo, qp, qp, 'Type', 'BR').'; + if istrivial(qp) + C = qp.mpsright.C(1); + FL_R = insert_onespace(multiplyleft(MpsTensor(GL{1}), C'), ... + 1, ~isdual(auxspace(qp, 1))); + FR_R = insert_onespace(multiplyleft(MpsTensor(GR{1}), C), ... + nspaces(GR{1}) + 1, isdual(auxspace(qp, 1))); + + tc.verifyTrue(isapprox(... + apply_regularized(T_B, FR_R, FL_R, GR{1}) + apply_regularized(T_L, FR_R, FL_R, GBR{1}), ... + exp(-1i*p) * GBR{1}), ... + sprintf('right quasi environment fixed point equation unfulfilled for p=%e, c=%d.', p, charge)); + else + tc.verifyTrue(isapprox(apply(T_B, GR{1}) + apply(T_L, GBR{1}), ... + exp(-1i*p) * GBR{1}), ... + sprintf('right quasi environment fixed point equation unfulfilled for p=%e, c=%d.', p, charge)); + end + end + end end function testDerivatives(tc, mpo, mps) From 7387c3eaca8a772ac0e496310fed3f87c32423f2 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sun, 26 Feb 2023 11:41:57 +0100 Subject: [PATCH 163/245] fix parameters --- test/TestInfJMpo.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/TestInfJMpo.m b/test/TestInfJMpo.m index e6f812e..5e99f06 100644 --- a/test/TestInfJMpo.m +++ b/test/TestInfJMpo.m @@ -3,7 +3,7 @@ properties (TestParameter) mpo = struct(... - 'trivial', InfJMpo.Ising() ... + 'trivial', quantum1dIsing() ... ) mps = struct(... 'trivial', UniformMps.randnc(CartesianSpace.new(2), CartesianSpace.new(4)) ... @@ -62,7 +62,7 @@ function test1dIsing(tc) function test1dHeisenberg(tc) alg = Vumps('which', 'smallestreal', 'maxiter', 5); - mpo = InfJMpo.Heisenberg('Spin', SU2(3), 'Symmetry', 'SU2'); + mpo = quantum1dHeisenberg('Spin', 1, 'Symmetry', 'SU2'); mpo = [mpo mpo]; vspace1 = GradedSpace.new(SU2(1:2:5), [5 5 1], false); From 5cc1509efbea6a33daf1c033c9541c773c1e7ccb Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sun, 26 Feb 2023 14:21:09 +0100 Subject: [PATCH 164/245] remove newline --- test/TestInfJMpo.m | 1 - 1 file changed, 1 deletion(-) diff --git a/test/TestInfJMpo.m b/test/TestInfJMpo.m index 5e99f06..9bb454e 100644 --- a/test/TestInfJMpo.m +++ b/test/TestInfJMpo.m @@ -72,7 +72,6 @@ function test1dHeisenberg(tc) [gs_mps] = fixedpoint(alg, mpo, mps); lambda = expectation_value(gs_mps, mpo); tc.verifyEqual(lambda / period(mps), -1.40, 'RelTol', 1e-2); - end end end From efe9bfb9daa4a6fd960b78ff0e8be8af5c29b4f6 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sun, 26 Feb 2023 23:00:26 +0100 Subject: [PATCH 165/245] leftquasienvironment for InfJMpo --- src/mps/InfJMpo.m | 73 +++++++++++++++++++++++++-- src/mps/InfQP.m | 38 ++++++++++++++ src/mps/MpoTensor.m | 13 ++++- src/sparse/SparseTensor.m | 5 +- src/tensors/Tensor.m | 3 +- src/utility/sparse/SparseArray.m | 2 + test/TestInfJMpo.m | 87 +++++++++++++++++++++++++++++--- test/TestInfMpo.m | 8 +-- 8 files changed, 208 insertions(+), 21 deletions(-) diff --git a/src/mps/InfJMpo.m b/src/mps/InfJMpo.m index c05fff7..9f508d3 100644 --- a/src/mps/InfJMpo.m +++ b/src/mps/InfJMpo.m @@ -50,13 +50,12 @@ 2, isdual(space(rhs, 2))), rank(rhs)); fp_right = insert_onespace(fixedpoint(mps1, 'r_LL'), ... 2, ~isdual(space(rhs, 2))); - lambda = contract(rhs, 1:3, fp_right, 3:-1:1); + lambda = overlap(rhs, fp_right); rhs = rhs - lambda * fp_left; [GL{1}(i), ~] = linsolve(@(x) x - apply(Tdiag, x), rhs, GL{1}(i), ... linkwargs{:}); - GL{1}(i) = GL{1}(i) - ... - contract(GL{1}(i), 1:3, fp_right, 3:-1:1) * fp_left; + GL{1}(i) = GL{1}(i) - overlap(GL{1}(i), fp_right) * fp_left; else [GL{1}(i), ~] = linsolve(@(x) x - apply(Tdiag, x), rhs, GL{1}(i), ... linkwargs{:}); @@ -133,6 +132,65 @@ end end + function GBL = leftquasienvironment(mpo, qp, GL, GR, linopts) + arguments + mpo + qp + GL + GR + linopts.Algorithm = 'bicgstab' + linopts.MaxIter = 500 + linopts.Verbosity = Verbosity.warn + linopts.Tol = eps(underlyingType(qp))^(3/4) + end + + linkwargs = namedargs2cell(linopts); + expP = exp(-1i*qp.p); + + T = transfermatrix(mpo, qp, qp, 'Type', 'RL'); + TB = transfermatrix(mpo, qp, qp, 'Type', 'BL'); + + GBL = cell(size(GL)); + GBL{1} = SparseTensor.zeros(domain(T), auxspace(qp, 1)); + + for i = 2:size(GBL{1}, 2) + rhs = apply(slice(T, i, 1:i-1), GBL{1}(1, 1:i-1, 1, 1)) + ... + apply(slice(TB, i, 1:i), GL{1}(1, 1:i, 1)); + + Tdiag = slice(T, i, i); + if iszero(Tdiag) + GBL{1}(i) = expP * rhs; + + elseif iseye(T, i) && istrivial(qp) + fp_left = insert_onespace(insert_onespace(... + fixedpoint(qp, 'l_RL'), ... + 2, ~isdual(leftvspace(mpo, 1))), ... + 4, isdual(auxspace(qp, 1))); + fp_left = repartition(fp_left, rank(rhs)); + fp_right = insert_onespace(insert_onespace(... + fixedpoint(qp, 'r_RL'), ... + 2, ~isdual(rightvspace(mpo, 1))), ... + 1, ~isdual(auxspace(qp, 1))); + rhs = rhs - overlap(rhs, fp_right) * fp_left; + [GBL{1}(i), ~] = linsolve(@(x) x - expP * apply_regularized(Tdiag, fp_left, fp_right, x), ... + expP * rhs, [], linkwargs{:}); + else + [GBL{1}(i), ~] = linsolve(@(x) x - expP * apply(Tdiag, x), expP * rhs, [], ... + linkwargs{:}); + end + end + + + if nnz(GL{1}) == numel(GL{1}) + GL{1} = full(GL{1}); + end + + for w = 1:period(qp)-1 + T = transfermatrix(mpo, mps1, mps2, w, 'Type', 'LL'); + GL{next(w, period(mps1))} = apply(T, GL{w}); + end + end + function [GL, GR, lambda] = environments(mpo, mps1, mps2, GL, GR, linopts) arguments mpo @@ -150,8 +208,8 @@ [GL, lambdaL] = leftenvironment(mpo, mps1, mps2, GL, kwargs{:}); [GR, lambdaR] = rightenvironment(mpo, mps1, mps2, GR, kwargs{:}); lambda = (lambdaL + lambdaR) / 2; - if abs(lambdaL - lambdaR)/abs(lambda) > eps(lambda)^(1/3) - warning('lambdas disagree'); + if ~isapprox(lambdaL, lambdaR, 'AbsTol', eps^(1/3), 'RelTol', eps(lambda)^(1/3)) + warning('lambdas disagree (%e, %e)', lambdaL, lambdaR); end end @@ -164,12 +222,17 @@ for i = 1:period(a) a.O{i}(1, 1, end, 1) = a.O{i}(1, 1, end, 1) + b(i); end + mpo = a; elseif isnumeric(a) && isa(b, 'InfJMpo') mpo = b + a; end end + function mpo = minus(a, b) + mpo = a + (-b); + end + function mpo = mtimes(mpo, b) if isnumeric(mpo) || isnumeric(b) mpo = mpo .* b; diff --git a/src/mps/InfQP.m b/src/mps/InfQP.m index 4015666..b2b1fa7 100644 --- a/src/mps/InfQP.m +++ b/src/mps/InfQP.m @@ -99,6 +99,44 @@ function bool = istrivial(qp) bool = qp.p == 0 && istrivial(auxspace(qp, 1)); end + + function rho = fixedpoint(qp, type, w) + % compute the fixed point of the transfer matrix of an mps. + % + % Usage + % ----- + % :code:`rho = fixedpoint(mps, type, w)` + % + % Arguments + % --------- + % mps : :class:`UniformMps` + % input state. + % + % type : char + % specification of the type of transfer matrix: + % general format: sprintf(%c_%c%c, side, top, bot) where side is 'l' or 'r' to + % determine which fixedpoint, and top and bot are 'L' or 'R' to specify + % whether to use AL or AR in the transfer matrix. + % + % w : integer + % position within the mps unitcell of the fixed point. + + arguments + qp + type {mustBeMember(type, ... + {'l_LL' 'l_LR' 'l_RL' 'l_RR' 'r_LL' 'r_LR' 'r_RL' 'r_RR'})} + w = strcmp(type(1), 'l') * 1 + strcmp(type(1), 'r') * period(qp) + end + + switch type(1) + case 'l' + rho = fixedpoint(qp.mpsleft, type, w); + case 'r' + rho = fixedpoint(qp.mpsright, type, w); + otherwise + error('invalid type'); + end + end end methods diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index 51a2412..a88f14c 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -70,6 +70,14 @@ end function t = plus(t, t2) + if isnumeric(t2) + t.scalars = t.scalars + t2; + return + end + if isnumeric(t) + t = t2 + t; + return + end t.tensors = t.tensors + t2.tensors; t.scalars = t.scalars + t2.scalars; end @@ -234,7 +242,10 @@ if isempty(mask), continue; end subs = [repmat(Ia(i), length(mask), 1) Jb(mask)]; idx = sub2ind_(sz2, subs); - C(idx) = C(idx) + Va(i) .* Vb(mask); + % TODO this should probably work vectorized? + for j = 1:length(idx) + C(idx(j)) = C(idx(j)) + Va(i) .* Vb(mask(j)); + end end end end diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index aa49de1..d34e770 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -654,7 +654,8 @@ function disp(t) t2 = repmat(t2, size(t1)); end - assert(isequal(size(t1), size(t2))); + nd = max(ndims(t1), ndims(t2)); + assert(isequal(size(t1, 1:nd), size(t2, 1:nd))); if isnumeric(t1) t = t2; @@ -662,7 +663,7 @@ function disp(t) t1 = t1(idx); idx2 = find(t1); if ~isempty(idx2) - t.var = t.var(idx2) .* full(t1(idx2)); + t.var = t.var(idx2) .* reshape(full(t1(idx2)), [],1); t.ind = t.ind(idx2, :); else t.var = t.var(idx2); diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index c515960..89647cc 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -1236,7 +1236,8 @@ B = repmat(B, size(A)); end - assert(isequal(size(A), size(B)), ... + nd = max(ndims(A), ndims(B)); + assert(isequal(size(A, 1:nd), size(B, 1:nd)), ... 'times:dimagree', 'incompatible dimensions.'); if isnumeric(A) diff --git a/src/utility/sparse/SparseArray.m b/src/utility/sparse/SparseArray.m index 88ebd5b..69953f8 100644 --- a/src/utility/sparse/SparseArray.m +++ b/src/utility/sparse/SparseArray.m @@ -822,6 +822,8 @@ function disp(a) s(1).subs = {sub2ind(a.sz, s(1).subs{:})}; end + rhs = full(rhs); + [I, ~, V] = find(a.var); [lia, locb] = ismember(s(1).subs{1}, I); newI = vertcat(I, s(1).subs{1}(~lia)); diff --git a/test/TestInfJMpo.m b/test/TestInfJMpo.m index 9bb454e..248da1b 100644 --- a/test/TestInfJMpo.m +++ b/test/TestInfJMpo.m @@ -11,16 +11,87 @@ end methods (Test, ParameterCombination='sequential') - function testEnvironments(tc, mpo, mps) - [GL, lambdaL] = leftenvironment(mpo, mps, mps); - tc.verifyTrue(isapprox(abs(lambdaL), abs(real(lambdaL)), 'RelTol', 1e-6), ... - sprintf('lambda should be real. (%-g i)', imag(lambdaL))); + function testEnvironments(tc) + D = 16; + mpo = quantum1dIsing('Symmetry', 'Z2'); + mps = initialize_mps(mpo, GradedSpace.new(Z2(0, 1), D ./ [2 2], false)); + + [GL, GR, lambda] = environments(mpo, mps); + + % left environment + fp_left = repartition(insert_onespace(fixedpoint(mps, 'l_LL'), ... + 2, ~isdual(leftvspace(mpo, 1))), rank(GL{1})); + fp_right = insert_onespace(fixedpoint(mps, 'r_LL'), ... + 2, ~isdual(rightvspace(mpo, 1))); - [GR, lambdaR] = rightenvironment(mpo, mps, mps); - tc.verifyTrue(isapprox(abs(lambdaR), abs(real(lambdaR)), 'RelTol', 1e-6), ... - sprintf('lambda should be real. (%gi)', imag(lambdaR))); + TL = transfermatrix(mpo, mps, mps, 'Type', 'LL'); + GL_2 = apply(TL, GL{1}); + lambda2 = overlap(GL_2(end), fp_right); + GL_2(end) = GL_2(end) - lambda2 * fp_left; + tc.assertTrue(isapprox(GL_2, GL{1}), ... + 'left environment fixed point equation unfulfilled.'); + tc.assertEqual(lambda, lambda2, 'RelTol', 1e-10); + + % right environment + fp_left = insert_onespace(fixedpoint(mps, 'l_RR'), ... + 2, ~isdual(leftvspace(mpo, 1))); + fp_right = repartition(insert_onespace(fixedpoint(mps, 'r_RR'), ... + 2, ~isdual(rightvspace(mpo, 1))), rank(GR{1})); + + TR = transfermatrix(mpo, mps, mps, 'Type', 'RR').'; + GR_2 = apply(TR, GR{1}); + lambda3 = overlap(GR_2(1), fp_left); + GR_2(1) = GR_2(1) - lambda3 * fp_right; + tc.assertTrue(isapprox(GR_2, GR{1}), ... + 'right environment fixed point equation unfulfilled.'); + tc.assertEqual(lambda, lambda3, 'RelTol', 1e-10); + end + + function testQuasiEnvironments(tc) + D = 16; + mpo = quantum1dIsing('Symmetry', 'Z2'); + mps = initialize_mps(mpo, GradedSpace.new(Z2(0, 1), D ./ [2 2], false)); + [~, ~, lambda] = environments(mpo, mps); + + mpo = mpo - lambda; + [GL, GR, lambda] = environments(mpo, mps); + tc.assertEqual(lambda, 0, 'AbsTol', 1e-10); + + for charge = Z2([1 0]) + for p = [0 pi 0.5] + qp = InfQP.randnc(mps, mps, p, charge); + +% [GBL, GBR] = quasienvironments(mpo, qp, GL, GR); + + % left environments + GBL = leftquasienvironment(mpo, qp, GL, GR); + tc.assertEqual(norm(GBL{1}(1)), 0, 'AbsTol', 1e-10); + + T_R = transfermatrix(mpo, qp, qp, 'Type', 'RL'); + T_B = transfermatrix(mpo, qp, qp, 'Type', 'BL'); + + GBL2 = apply(T_R, GBL{1}) + apply(T_B, GL{1}); + if istrivial(qp) + fp_left = insert_onespace(insert_onespace(... + fixedpoint(qp, 'l_RL'), ... + 2, ~isdual(leftvspace(mpo, 1))), ... + 4, isdual(auxspace(qp, 1))); + fp_left = repartition(fp_left, rank(GBL2(end))); + fp_right = insert_onespace(insert_onespace(... + fixedpoint(qp, 'r_RL'), ... + 2, ~isdual(rightvspace(mpo, 1))), ... + 1, ~isdual(auxspace(qp, 1))); + GBL2(end) = GBL2(end) - overlap(GBL2(end), fp_right) * fp_left; + end + tc.assertTrue(isapprox(GBL2, GBL{1} * exp(1i*p)), ... + sprintf('left environment fixed point equation unfulfilled for p=%e, c=%d', p, charge)); + + + + % right environments + end + end - tc.verifyTrue(isapprox(lambdaL, lambdaR), 'lambdas should be equal.'); end function testDerivatives(tc, mpo, mps) diff --git a/test/TestInfMpo.m b/test/TestInfMpo.m index d181e8d..d9e6869 100644 --- a/test/TestInfMpo.m +++ b/test/TestInfMpo.m @@ -60,8 +60,6 @@ function testEnvironments(tc) [GL, GR, lambda] = environments(mpo, mps, mps, GL, GR); tc.assertEqual(lambda, 1, 'AbsTol', 1e-10); - - for charge = Z2([0 1]) for p = [0 pi 0.5] qp = InfQP.randnc(mps, mps, p, charge); @@ -78,7 +76,8 @@ function testEnvironments(tc) 1, ~isdual(auxspace(qp, 1))); tc.verifyTrue(isapprox(... - apply_regularized(T_B, FL_L, FR_L, GL{1}) + apply_regularized(T_R, FL_L, FR_L, GBL{1}), ... + apply_regularized(T_B, FL_L, FR_L, GL{1}) + ... + apply_regularized(T_R, FL_L, FR_L, GBL{1}), ... exp(1i*p) * GBL{1}), ... sprintf('left quasi environment fixed point equation unfulfilled for p=%e, c=%d', p, charge)); else @@ -99,7 +98,8 @@ function testEnvironments(tc) nspaces(GR{1}) + 1, isdual(auxspace(qp, 1))); tc.verifyTrue(isapprox(... - apply_regularized(T_B, FR_R, FL_R, GR{1}) + apply_regularized(T_L, FR_R, FL_R, GBR{1}), ... + apply_regularized(T_B, FR_R, FL_R, GR{1}) + ... + apply_regularized(T_L, FR_R, FL_R, GBR{1}), ... exp(-1i*p) * GBR{1}), ... sprintf('right quasi environment fixed point equation unfulfilled for p=%e, c=%d.', p, charge)); else From b130b9585568d5dd8b2eb0585f3009835aadccfa Mon Sep 17 00:00:00 2001 From: leburgel Date: Sun, 26 Feb 2023 23:23:51 +0100 Subject: [PATCH 166/245] Address comments --- src/mps/FiniteMpo.m | 41 ++++++++++++--- src/mps/PepsSandwich.m | 11 +--- src/mps/PepsTensor.m | 115 +++++++++++++++++++++++------------------ test/TestPeps.m | 95 ++++++++++++---------------------- 4 files changed, 134 insertions(+), 128 deletions(-) diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index 6129dc2..765ef7e 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -239,13 +239,42 @@ % function t = Tensor(mpo) assert(depth(mpo) == 1, 'not implemented for 1 < depth'); - N = length(mpo); - inds = arrayfun(@(x) [x -(x+1) (x+1) -(2*N+4-x)], 1:N, ... - 'UniformOutput', false); - args = [mpo.O; inds]; - t = contract(mpo.L, [-1 1 -(2*N+4)], args{:}, mpo.R, [-(N+3) N+1 -(N+2)], ... - 'Rank', [N+2 N+2]); + W = length(mpo); + switch class(mpo.O{1}) + case 'MpoTensor' + inds = arrayfun(@(x) [x -(x+1) (x+1) -(2*W+4-x)], 1:W, ... + 'UniformOutput', false); + args = [mpo.O; inds]; + t = contract(... + mpo.L, [-1 1 -(2*W+4)], ... + args{:}, ... + mpo.R, [-(W+3) W+1 -(W+2)], ... + 'Rank', [W+2 W+2]); + + case 'PepsSandwich' + inds_top = arrayfun(@(x) [ 3 + 3*(x-1), ... + 1 + 3*(x-1), ... + -(2 + 2*(x-1)), ... + 1 + 3*x, ... + -(4*W + 5 - 2*x)], ... + 1:W, 'UniformOutput', false); + inds_bot = arrayfun(@(x) [ 3 + 3*(x-1), ... + 2 + 3*(x-1), ... + -(3 + 2*(x-1)), ... + 2 + 3*x, ... + -(4*W + 4 - 2*x)], ... + 1:W, 'UniformOutput', false); + tops = cellfun(@(x) x.top.var, mpo.O, 'UniformOutput', false); + bots = cellfun(@(x) x.bot.var, mpo.O, 'UniformOutput', false); + args = [tops; inds_top; bots; inds_bot]; + t = contract(... + mpo.L, [-1, 2, 1, -(4*W + 4)], ... + args{:}, ... + mpo.R, [-(2*W + 3), 1 + 3*W, 2 + 3*W, -(2 + 2*W)], ... + 'Rank', [2*W+2, 2*W+2]); + end end + end methods (Static) diff --git a/src/mps/PepsSandwich.m b/src/mps/PepsSandwich.m index ff170a1..56aa464 100644 --- a/src/mps/PepsSandwich.m +++ b/src/mps/PepsSandwich.m @@ -14,14 +14,7 @@ top bot = conj(top) end - - if ~isa(top, 'PepsTensor') - top = PepsTensor(top); - end - if ~isa(bot, 'PepsTensor') - bot = PepsTensor(bot); - end - + T.top = top; T.bot = bot; end @@ -44,7 +37,6 @@ end function T = ctranspose(T) - % TODO: test if this is correct when inserted into FiniteMpo T.top = tpermute(conj(T.top), [1, 2, 5, 4, 3], rank(T.top)); T.bot = tpermute(conj(T.bot), [1, 2, 5, 4, 3], rank(T.bot)); end @@ -93,7 +85,6 @@ end function v = applympo(varargin) - % lolz assert(nargin >= 3) v = varargin{end}; R = varargin{end-1}; diff --git a/src/mps/PepsTensor.m b/src/mps/PepsTensor.m index 234d4af..4ce27f6 100644 --- a/src/mps/PepsTensor.m +++ b/src/mps/PepsTensor.m @@ -1,6 +1,18 @@ classdef PepsTensor - % Generic PEPS tensor objects that have a notion of virtual and physical legs. - + % Generic PEPS tensor object that hos a notion of virtual and physical legs. + % This object represents the PEPS tensor at a single site as a rank (1, 4) tensor, + % where the physical index lies in the codomain and the virtual indices lie in the + % domain. + % + % 5 1 + % | / + % v ^ + % |/ + % 2 ->-- O --<- 4 + % | + % ^ + % | + % 3 properties var end @@ -12,14 +24,7 @@ arguments tensor = [] end - - if iscell(tensor) - for i = length(tensor):-1:1 - A(i) = PepsTensor(tensor{i}); - end - return - end - + if ~isempty(tensor) A.var = tensor; end @@ -29,14 +34,14 @@ %% Properties methods - function s = space(A, varargin) - s = space(A.var, varargin{:}); - end - function n = nspaces(A) - n = nspaces(A.var); + n = 5; end + function s = space(A, varargin) + s = space(A.var, varargin{:}); + end + function s = pspace(A) s = space(A, 1); end @@ -76,11 +81,7 @@ %% Linear Algebra - methods - function d = dot(A, B) - % TODO? - end - + methods function A = repartition(A, varargin) A.var = repartition(A.var, varargin{:}); end @@ -89,22 +90,20 @@ A.var = tpermute(A.var, varargin{:}); end - function A = plus(varargin) - for i = 1:2 - if isa(varargin{i}, 'PepsTensor') - varargin{i} = varargin{i}.var; - end + function A = plus(A, B) + arguments + A PepsTensor + B PepsTensor end - A = plus(varargin{:}); + A.var = plus(A.var, B.var); end - function A = minus(varargin) - for i = 1:2 - if isa(varargin{i}, 'PepsTensor') - varargin{i} = varargin{i}.var; - end + function A = minus(A, B) + arguments + A PepsTensor + B PepsTensor end - A = minus(varargin{:}); + A.var = minus(A.var, B.var); end function n = norm(A) @@ -120,26 +119,7 @@ end function t = ctranspose(t) - % Compute the adjoint of a tensor. This is defined as swapping the codomain and - % domain, while computing the adjoint of the matrix blocks. - % - % Usage - % ----- - % :code:`t = ctranspose(t)` - % :code:`t = t'` - % - % Arguments - % --------- - % t : :class:`Tensor` - % input tensor. - % - % Returns - % ------- - % t : :class:`Tensor` - % adjoint tensor. - t.var = t.var'; - t = permute(t, ndims(t):-1:1); end @@ -169,5 +149,38 @@ t = sparse(t); end end + + + %% Static constructors + methods (Static) + function t = new(fun, pspace, westvspace, southvspace, eastvspace, northvspace) + arguments + fun + pspace + westvspace + southvspace + eastvspace = westvspace' + northvspace = southvspace' + end + + t = PepsTensor(Tensor.new(fun, pspace, [westvspace, southvspace, eastvspace, northvspace]')); + end + + function t = rand(varargin) + t = PepsTensor.new(@rand, varargin{:}); + end + + function t = randn(varargin) + t = PepsTensor.new(@randn, varargin{:}); + end + + function t = randc(varargin) + t = PepsTensor.new(@randc, varargin{:}); + end + + function t = randnc(varargin) + t = PepsTensor.new(@randnc, varargin{:}); + end + end end diff --git a/test/TestPeps.m b/test/TestPeps.m index c89b379..905a0eb 100644 --- a/test/TestPeps.m +++ b/test/TestPeps.m @@ -24,7 +24,7 @@ methods (Test, ParameterCombination='exhaustive') function testFiniteMpo(tc, spaces, depth, width, dualdepth, dualwidth, samebot) - tc.assumeTrue(braidingstyle(spaces) ~= BraidingStyle.Fermionic, 'fZ2 test broken') + tc.assumeTrue(istwistless(braidingstyle(spaces)), 'Fermionic tests broken.') tc.assumeTrue(depth == 1, 'Test ill-defined for multiline PEPS.') mpo = tc.random_mpo(spaces, depth, width, dualdepth, dualwidth, samebot); @@ -42,7 +42,7 @@ function testFiniteMpo(tc, spaces, depth, width, dualdepth, dualwidth, samebot) tc.assumeTrue(width < 2, 'FiniteMpo -> Tensor contraction not reasonable.') % test domain and codomain - mpo_tensor = tc.mpo_to_tensor(mpo); + mpo_tensor = Tensor(mpo); tc.assertTrue(isequal(mpo.domain, mpo_tensor.domain), ... 'domain should remain fixed after conversion.'); tc.assertTrue(isequal(mpo.codomain, mpo_tensor.codomain), ... @@ -53,16 +53,16 @@ function testFiniteMpo(tc, spaces, depth, width, dualdepth, dualwidth, samebot) tc.assertTrue(isapprox(mpo.apply(v), mpo_tensor * v)); % test transpose - tc.assertTrue(isapprox(tc.mpo_to_tensor(mpo).', tc.mpo_to_tensor(mpo.')), ... + tc.assertTrue(isapprox(Tensor(mpo).', Tensor(mpo.')), ... 'transpose should not change mpo'); % test ctranspose - tc.assertTrue(isapprox(tc.mpo_to_tensor(mpo)', tc.mpo_to_tensor(mpo')), ... + tc.assertTrue(isapprox(Tensor(mpo)', Tensor(mpo')), ... 'ctranspose should not change mpo'); end function testFixedpoints(tc, spaces, depth, width, dualdepth, dualwidth, samebot) - tc.assumeTrue(braidingstyle(spaces) ~= BraidingStyle.Fermionic, 'fZ2 test broken') + tc.assumeTrue(istwistless(braidingstyle(spaces)), 'Fermionic tests broken.') mpo = tc.random_mpo(spaces, depth, width, dualdepth, dualwidth, samebot); @@ -74,7 +74,7 @@ function testFixedpoints(tc, spaces, depth, width, dualdepth, dualwidth, samebot end function testDerivatives(tc, spaces, depth, width, dualdepth, dualwidth, samebot) - tc.assumeTrue(braidingstyle(spaces) ~= BraidingStyle.Fermionic, 'fZ2 test broken') + tc.assumeTrue(istwistless(braidingstyle(spaces)), 'Fermionic tests broken.') mpo = tc.random_inf_mpo(spaces, depth, width, dualdepth, dualwidth, samebot); vspaces = repmat({spaces(end)}, 1, width); @@ -98,11 +98,11 @@ function testDerivatives(tc, spaces, depth, width, dualdepth, dualwidth, samebot end function testMpoTensor(tc, spaces, dualdepth, dualwidth) - tc.assumeTrue(braidingstyle(spaces) ~= BraidingStyle.Fermionic, 'fZ2 test broken') + tc.assumeTrue(istwistless(braidingstyle(spaces)), 'Fermionic tests broken.') pspace = spaces(1); horzspace = spaces(2); vertspace = spaces(3); - top = TestPeps.random_peps(pspace, horzspace, vertspace, 1, 1, dualdepth, dualwidth); - bot = TestPeps.random_peps(pspace', vertspace, horzspace, 1, 1, dualdepth, dualwidth); + top = TestPeps.random_peps_unitcell(pspace, horzspace, vertspace, 1, 1, dualdepth, dualwidth); + bot = TestPeps.random_peps_unitcell(pspace', vertspace, horzspace, 1, 1, dualdepth, dualwidth); O = PepsSandwich(top{1}, bot{1}); t = MpoTensor(O); end @@ -110,44 +110,42 @@ function testMpoTensor(tc, spaces, dualdepth, dualwidth) end methods (Static) - function A = random_peps(pspace, horzspace, vertspace, depth, width, dualdepth, dualwidth) - % spit out random peps unit cell - A = cell(depth, width); + function A = random_peps_unitcell(pspace, horzspace, vertspace, depth, width, dualdepth, dualwidth) + % Spit out random peps unit cell with all kinds of arrows for testing + if dualwidth, horzspace = horzspace'; end if dualdepth, vertspace = vertspace'; end - - if mod(depth, 2) - vertspaces = [vertspace vertspace']; + pspaces = repmat(pspace, depth, width); pspaces(2:2:depth*width) = conj(pspaces(2:2:depth*width)); + westspaces = repmat(horzspace, depth, width); + southspaces = repmat(vertspace, depth, width); + + % staggering + if ~mod(width, 2) + westspaces(2:2:depth*width) = conj(westspaces(2:2:depth*width)); + eastspaces = westspaces; else - vertspaces = [vertspace vertspace]; + eastspaces = conj(westspaces); end - - if mod(width, 2) - horzspaces = [horzspace horzspace']; + if ~mod(depth, 2) + southspaces(2:2:depth*width) = conj(southspaces(2:2:depth*width)); + northspaces = southspaces; else - horzspaces = [horzspace horzspace]; - end - - codomainspaces = reshape([vertspaces; horzspaces], 1, []); - - for d = 1:depth - for w = 1:width - dmsp = pspace; - cdmsp = codomainspaces; - if ~mod(depth, 2) && ~mod(d, 2), cdmsp = conj(cdmsp); dmsp = conj(dmsp); end - if ~mod(width, 2) && ~mod(w, 2), cdmsp = conj(cdmsp); dmsp = conj(dmsp); end - A{d, w} = Tensor.randnc(dmsp, cdmsp); - A{d, w} = A{d, w} / norm(A{d, w}); - end + northspaces = conj(southspaces); end + + % fill up cell + for d = depth:-1:1, for w = width:-1:1 + A{d, w} = PepsTensor.randnc(pspaces(d, w), ... + westspaces(d, w), southspaces(d, w), eastspaces(d, w), northspaces(d, w)); + end, end end function T = random_sandwich(pspace, horzspace, vertspace, depth, width, dualdepth, dualwidth, samebot) - top = TestPeps.random_peps(pspace, horzspace, vertspace, depth, width, dualdepth, dualwidth); + top = TestPeps.random_peps_unitcell(pspace, horzspace, vertspace, depth, width, dualdepth, dualwidth); if samebot bot = cellfun(@conj, top, 'UniformOutput', false); else - bot = TestPeps.random_peps(pspace', horzspace, vertspace, depth, width, dualdepth, dualwidth); + bot = TestPeps.random_peps_unitcell(pspace', horzspace, vertspace, depth, width, dualdepth, dualwidth); end T = cellfun(@(t, b) PepsSandwich(t, b), top, bot, 'UniformOutput', false); end @@ -166,32 +164,7 @@ function testMpoTensor(tc, spaces, dualdepth, dualwidth) pspace = spaces(1); horzspace = spaces(2); vertspace = spaces(3); O = TestPeps.random_sandwich(pspace, horzspace, vertspace, depth, width, dualdepth, dualwidth, samebot); mpo = InfMpo(O); - end - - function t = mpo_to_tensor(mpo) - % contract FiniteMpo{PepsSandwich} to tensor... - assert(depth(mpo) == 1, 'not implemented for 1 < depth'); - W = length(mpo); - inds_top = arrayfun(@(x) [ 3 + 3*(x-1), ... - 1 + 3*(x-1), ... - -(2 + 2*(x-1)), ... - 1 + 3*x, ... - -(4*W + 5 - 2*x)], ... - 1:W, 'UniformOutput', false); - inds_bot = arrayfun(@(x) [ 3 + 3*(x-1), ... - 2 + 3*(x-1), ... - -(3 + 2*(x-1)), ... - 2 + 3*x, ... - -(4*W + 4 - 2*x)], ... - 1:W, 'UniformOutput', false); - tops = cellfun(@(x) x.top.var, mpo.O, 'UniformOutput', false); - bots = cellfun(@(x) x.bot.var, mpo.O, 'UniformOutput', false); - args = [tops; inds_top; bots; inds_bot]; - t = contract(mpo.L, [-1, 2, 1, -(4*W + 4)], args{:}, mpo.R, [-(2*W + 3), 1 + 3*W, 2 + 3*W, -(2 + 2*W)], ... - 'Rank', [2*W+2, 2*W+2]); - - end - + end end end From d6a812eb6e0005307ec282dbfb67cf291e4f5041 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 27 Feb 2023 12:27:44 +0100 Subject: [PATCH 167/245] right quasienvironment infjmpo --- src/mps/InfJMpo.m | 67 ++++++++++++++++++++++++++++++++++++++++++++++ test/TestInfJMpo.m | 25 ++++++++++++++--- 2 files changed, 89 insertions(+), 3 deletions(-) diff --git a/src/mps/InfJMpo.m b/src/mps/InfJMpo.m index 9f508d3..68a3762 100644 --- a/src/mps/InfJMpo.m +++ b/src/mps/InfJMpo.m @@ -153,6 +153,8 @@ GBL = cell(size(GL)); GBL{1} = SparseTensor.zeros(domain(T), auxspace(qp, 1)); + % GBL{1}(1) = 0 because of quasiparticle gauge + for i = 2:size(GBL{1}, 2) rhs = apply(slice(T, i, 1:i-1), GBL{1}(1, 1:i-1, 1, 1)) + ... apply(slice(TB, i, 1:i), GL{1}(1, 1:i, 1)); @@ -191,6 +193,71 @@ end end + function GBR = rightquasienvironment(mpo, qp, GL, GR, linopts) + arguments + mpo + qp + GL + GR + linopts.Algorithm = 'bicgstab' + linopts.MaxIter = 500 + linopts.Verbosity = Verbosity.warn + linopts.Tol = eps(underlyingType(qp))^(3/4) + end + + linkwargs = namedargs2cell(linopts); + expP = exp(+1i*qp.p); + + T = transfermatrix(mpo, qp, qp, 'Type', 'LR').'; + TB = transfermatrix(mpo, qp, qp, 'Type', 'BR').'; + + GBR = cell(size(GR)); + GBR{1} = SparseTensor.zeros(domain(T), auxspace(qp, 1)); + + N = size(GBR{1}, 2); + + for i = N:-1:1 + if i == N + rhs = apply(slice(TB, i, i:N), GR{1}(1, i:N, 1)); + else + rhs = apply(slice(T, i, i+1:N), GBR{1}(1, i+1:N, 1, 1)) + ... + apply(slice(TB, i, i:N), GR{1}(1, i:N, 1)); + end + + Tdiag = slice(T, i, i); + if iszero(Tdiag) + GBR{1}(i) = expP * rhs; + + elseif iseye(T, i) && istrivial(qp) + fp_left = insert_onespace(insert_onespace(... + fixedpoint(qp, 'l_LR'), ... + 2, ~isdual(leftvspace(mpo, 1))), ... + 1, ~isdual(auxspace(qp, 1))); + fp_right = insert_onespace(insert_onespace(... + fixedpoint(qp, 'r_LR'), ... + 2, ~isdual(rightvspace(mpo, 1))), ... + 4, isdual(auxspace(qp, 1))); + fp_right = repartition(fp_right, rank(rhs)); + rhs = rhs - overlap(rhs, fp_left) * fp_right; + [GBR{1}(i), ~] = linsolve(@(x) x - expP * apply_regularized(Tdiag, fp_right, fp_left, x), ... + expP * rhs, [], linkwargs{:}); + else + [GBR{1}(i), ~] = linsolve(@(x) x - expP * apply(Tdiag, x), expP * rhs, [], ... + linkwargs{:}); + end + end + + + if nnz(GL{1}) == numel(GL{1}) + GL{1} = full(GL{1}); + end + + for w = 1:period(qp)-1 + T = transfermatrix(mpo, mps1, mps2, w, 'Type', 'LL'); + GL{next(w, period(mps1))} = apply(T, GL{w}); + end + end + function [GL, GR, lambda] = environments(mpo, mps1, mps2, GL, GR, linopts) arguments mpo diff --git a/test/TestInfJMpo.m b/test/TestInfJMpo.m index 248da1b..376c458 100644 --- a/test/TestInfJMpo.m +++ b/test/TestInfJMpo.m @@ -65,7 +65,8 @@ function testQuasiEnvironments(tc) % left environments GBL = leftquasienvironment(mpo, qp, GL, GR); - tc.assertEqual(norm(GBL{1}(1)), 0, 'AbsTol', 1e-10); + tc.assertEqual(norm(GBL{1}(1)), 0, 'AbsTol', 1e-10, ... + 'left gauge quasiparticle violation.'); T_R = transfermatrix(mpo, qp, qp, 'Type', 'RL'); T_B = transfermatrix(mpo, qp, qp, 'Type', 'BL'); @@ -83,12 +84,30 @@ function testQuasiEnvironments(tc) 1, ~isdual(auxspace(qp, 1))); GBL2(end) = GBL2(end) - overlap(GBL2(end), fp_right) * fp_left; end - tc.assertTrue(isapprox(GBL2, GBL{1} * exp(1i*p)), ... + tc.assertTrue(isapprox(GBL2, GBL{1} * exp(+1i*p)), ... sprintf('left environment fixed point equation unfulfilled for p=%e, c=%d', p, charge)); + % right environments + GBR = rightquasienvironment(mpo, qp, GL, GR); + T_L = transfermatrix(mpo, qp, qp, 'Type', 'LR').'; + T_B = transfermatrix(mpo, qp, qp, 'Type', 'BR').'; - % right environments + GBR2 = apply(T_L, GBR{1}) + apply(T_B, GR{1}); + if istrivial(qp) + fp_left = insert_onespace(insert_onespace(... + fixedpoint(qp, 'l_LR'), ... + 2, ~isdual(leftvspace(mpo, 1))), ... + 1, ~isdual(auxspace(qp, 1))); + fp_right = insert_onespace(insert_onespace(... + fixedpoint(qp, 'r_LR'), ... + 2, ~isdual(rightvspace(mpo, 1))), ... + 4, isdual(auxspace(qp, 1))); + fp_right = repartition(fp_right, rank(GBR2(1))); + GBR2(1) = GBR2(1) - overlap(GBR2(1), fp_left) * fp_right; + end + tc.assertTrue(isapprox(GBR2, GBR{1} * exp(-1i*p)), ... + sprintf('right environment fixed point equation unfulfilled for p=%e, c=%d', p, charge)); end end From 9cfa4fed2f54d78d1cfcd420504db7d9662bb891 Mon Sep 17 00:00:00 2001 From: leburgel Date: Mon, 27 Feb 2023 15:46:04 +0100 Subject: [PATCH 168/245] Update `FiniteMpo` -> `Tensor` handling --- src/mps/FiniteMpo.m | 36 ++++-------------------------------- src/mps/MpoTensor.m | 30 +++++++++++++++++++++++++++++- src/mps/MpsTensor.m | 19 ++++++++++++++++++- src/mps/PepsSandwich.m | 40 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 34 deletions(-) diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index 765ef7e..9681d87 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -240,38 +240,10 @@ function t = Tensor(mpo) assert(depth(mpo) == 1, 'not implemented for 1 < depth'); W = length(mpo); - switch class(mpo.O{1}) - case 'MpoTensor' - inds = arrayfun(@(x) [x -(x+1) (x+1) -(2*W+4-x)], 1:W, ... - 'UniformOutput', false); - args = [mpo.O; inds]; - t = contract(... - mpo.L, [-1 1 -(2*W+4)], ... - args{:}, ... - mpo.R, [-(W+3) W+1 -(W+2)], ... - 'Rank', [W+2 W+2]); - - case 'PepsSandwich' - inds_top = arrayfun(@(x) [ 3 + 3*(x-1), ... - 1 + 3*(x-1), ... - -(2 + 2*(x-1)), ... - 1 + 3*x, ... - -(4*W + 5 - 2*x)], ... - 1:W, 'UniformOutput', false); - inds_bot = arrayfun(@(x) [ 3 + 3*(x-1), ... - 2 + 3*(x-1), ... - -(3 + 2*(x-1)), ... - 2 + 3*x, ... - -(4*W + 4 - 2*x)], ... - 1:W, 'UniformOutput', false); - tops = cellfun(@(x) x.top.var, mpo.O, 'UniformOutput', false); - bots = cellfun(@(x) x.bot.var, mpo.O, 'UniformOutput', false); - args = [tops; inds_top; bots; inds_bot]; - t = contract(... - mpo.L, [-1, 2, 1, -(4*W + 4)], ... - args{:}, ... - mpo.R, [-(2*W + 3), 1 + 3*W, 2 + 3*W, -(2 + 2*W)], ... - 'Rank', [2*W+2, 2*W+2]); + if W == 0 + t = contracttransfer(mpo.L, mpo.R); + else + t = contractmpo(mpo.O{:}, mpo.L, mpo.R); end end diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index f5a4683..6e3108a 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -312,9 +312,37 @@ function n = nnz(O) n = nnz(O.tensors) + nnz(O.scalars); end + + function t = contractmpo(varargin) + assert(nargin >= 3) + R = varargin{end}; + L = varargin{end-1}; + O = varargin(1:end-2); + W = length(O); + + if isa(L, 'MpsTensor') + auxlegs_l = L.alegs; + else + auxlegs_l = 0; + end + if isa(R, 'MpsTensor') + auxlegs_r = R.alegs; + else + auxlegs_r = 0; + end + + inds = arrayfun(@(x) [x -(x+1) (x+1) -(2*W+4-x)], 1:W, ... + 'UniformOutput', false); + args = [O; inds]; + t = contract(... + L, [-1, 1, -(2*W+4), -(1:auxlegs_l) - 2*W+4], ... + args{:}, ... + R, [-(W+3), W+1, -(W+2), -(1:auxlegs_r) - 2*W+4 - auxlegs_l], ... + 'Rank', [W+2 W+2] + [0, auxlegs_l + auxlegs_r]); + end end - methods + methods function t = subsref(t, s) assert(length(s) == 1, 'mpotensor:index', ... 'only a single level of indexing allowed.'); diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index 3fb330e..f7e8a32 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -383,6 +383,23 @@ 'Rank', newrank); end + function v = contracttransfer(L, R) + arguments + L MpsTensor + R MpsTensor + end + + auxlegs_l = L.alegs; + auxlegs_r = R.alegs; + assert(R.plegs == L.plegs); + plegs = L.plegs; %#ok + + v = contract(... + L, [-1, 1:plegs, -4, (-(1:auxlegs_l) - 4)], ... + R, [-3, flip(1:plegs), -2 (-(1:auxlegs_r) - 4 - auxlegs_l)], ... + 'Rank', [2, 2] + [0, auxlegs_l + auxlegs_r]); %#ok + end + function v = tracetransfer(L, R) arguments L MpsTensor @@ -397,7 +414,7 @@ v = contract(... L, [-1 1:(plegs + 1) (-(1:auxlegs_l) - 2)], ... - R, [flip(1:(plegs + 1)) -2 (-(1:auxlegs_r) - 3 - auxlegs_l)], ... + R, [flip(1:(plegs + 1)) -2 (-(1:auxlegs_r) - 2 - auxlegs_l)], ... 'Rank', newrank); %#ok end diff --git a/src/mps/PepsSandwich.m b/src/mps/PepsSandwich.m index 56aa464..28d15aa 100644 --- a/src/mps/PepsSandwich.m +++ b/src/mps/PepsSandwich.m @@ -127,6 +127,46 @@ 'Rank', rank(v) + [0 auxlegs_extra]); end + function t = contractmpo(varargin) + assert(nargin >= 3) + R = varargin{end}; + L = varargin{end-1}; + O = varargin(1:end-2); + W = length(O); + + if isa(L, 'MpsTensor') + auxlegs_l = L.alegs; + else + auxlegs_l = 0; + end + if isa(R, 'MpsTensor') + auxlegs_r = R.alegs; + else + auxlegs_r = 0; + end + + inds_top = arrayfun(@(x) [ 3 + 3*(x-1), ... + 1 + 3*(x-1), ... + -(2 + 2*(x-1)), ... + 1 + 3*x, ... + -(4*W + 5 - 2*x)], ... + 1:W, 'UniformOutput', false); + inds_bot = arrayfun(@(x) [ 3 + 3*(x-1), ... + 2 + 3*(x-1), ... + -(3 + 2*(x-1)), ... + 2 + 3*x, ... + -(4*W + 4 - 2*x)], ... + 1:W, 'UniformOutput', false); + tops = cellfun(@(x) x.top.var, O, 'UniformOutput', false); + bots = cellfun(@(x) x.bot.var, O, 'UniformOutput', false); + args = [tops; inds_top; bots; inds_bot]; + t = contract(... + L, [-1, 2, 1, -(4*W + 4), -(1:auxlegs_l) - (4*W + 4)], ... + args{:}, ... + R, [-(2*W + 3), 1 + 3*W, 2 + 3*W, -(2 + 2*W), -(1:auxlegs_r) - (4*W + 4) - auxlegs_r], ... + 'Rank', [2*W+2, 2*W+2] + [0, auxlegs_l + auxlegs_r]); + end + function t = MpoTensor(T) fuse_east = Tensor.eye(prod(leftvspace(T)), leftvspace(T)); fuse_south = Tensor.eye(prod(codomainspace(T)), codomainspace(T)); From 0373eacad4ba1ebd2e9de649137e1cead247e532 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 28 Feb 2023 21:54:00 +0100 Subject: [PATCH 169/245] excitations + multisite work --- src/algorithms/QPAnsatz.m | 83 +++++++++++++++++++ src/models/quantum1dIsing.m | 4 +- src/mps/FiniteMpo.m | 2 +- src/mps/InfJMpo.m | 156 +++++++++++++++++++++++------------- src/mps/InfMpo.m | 27 ++++++- src/mps/InfQP.m | 13 ++- src/mps/MpsTensor.m | 15 ++++ src/tensors/Tensor.m | 10 ++- test/TestInfJMpo.m | 121 +++++++++++++++++++++------- test/TestInfMpo.m | 3 +- 10 files changed, 337 insertions(+), 97 deletions(-) create mode 100644 src/algorithms/QPAnsatz.m diff --git a/src/algorithms/QPAnsatz.m b/src/algorithms/QPAnsatz.m new file mode 100644 index 0000000..4e5a9c3 --- /dev/null +++ b/src/algorithms/QPAnsatz.m @@ -0,0 +1,83 @@ +classdef QPAnsatz + % Quasi-Particle excitation ansatz + + properties + alg_eigs = struct('MaxIter', 100, 'KrylovDim', 30, 'Tol', 1e-8, ... + 'Verbosity', Verbosity.diagnostics) + alg_environments = struct + howmany = 1 + which = 'smallestreal' + end + + methods + function alg = QPAnsatz(kwargs) + arguments + kwargs.?QPAnsatz + end + + fields = fieldnames(kwargs); + if ~isempty(fields) + for field = fields.' + alg.(field{1}) = kwargs.(field{1}); + end + end + end + + function [qp, mu] = excitations(alg, mpo, qp) + if period(mpo) ~= period(qp) + error('QPAnsatz:argerror', ... + 'periodicity of mpo (%d) should be equal to that of the mps (%d)', ... + period(mpo), period(qp)); + end + + % Renormalization + [GL, GR, lambda] = environments(mpo, qp.mpsleft, qp.mpsleft); + + for i = 1:period(mpo) + T = AC_hamiltonian(mpo, qp.mpsleft, GL, GR, i); + offset(i) = dot(qp.mpsleft.AC(i), apply(T{1}, qp.mpsleft.AC(i))); + end + +% mpo = renormalize(mpo, lambda); +% [GL, GR, lambda] = environments(mpo, qp.mpsleft, qp.mpsleft); + + % Algorithm + eigkwargs = namedargs2cell(alg.alg_eigs); + H_effective = @(x) updateX(alg, mpo, qp, GL, GR, x, offset); + [X, mu] = eigsolve(H_effective, qp.X, alg.howmany, alg.which, ... + eigkwargs{:}); + + qp = repmat(qp, [1 alg.howmany]); + for i = alg.howmany:-1:1 + qp(i).X = X(i); + qp(i).B = computeB(qp(i)); + end + end + + function y = updateX(alg, mpo, qp, GL, GR, x, offset) + qp.X = x; + qp.B = computeB(qp); + + H_c = B_hamiltonian(mpo, qp, GL, GR, 'Type', 'center'); + for i = period(qp):-1:1 + B(i) = MpsTensor(apply(H_c{i}, qp.B(i)), 1); + end + + H_l = B_hamiltonian(mpo, qp, GL, GR, 'Type', 'left'); + for i = 1:period(qp) + B(i) = B(i) + repartition(apply(H_l{i}, qp.AR(i)), rank(B(i))); + end + + H_r = B_hamiltonian(mpo, qp, GL, GR, 'Type', 'right'); + for i = 1:period(qp) + B(i) = B(i) + repartition(apply(H_r{i}, qp.AL(i)), rank(B(i))); + end + + for i = 1:period(qp) + qp.B(i) = B(i) - qp.B(i) * offset(i); + end + y = computeX(qp); + end + end +end + diff --git a/src/models/quantum1dIsing.m b/src/models/quantum1dIsing.m index 449aec9..3f743d7 100644 --- a/src/models/quantum1dIsing.m +++ b/src/models/quantum1dIsing.m @@ -9,8 +9,8 @@ J = kwargs.J; h = kwargs.h; -sigma_x = [0 1; 1 0]; -sigma_z = [1 0; 0 -1]; +sigma_x = [0 1; 1 0] / 2; +sigma_z = [1 0; 0 -1] / 2; if strcmp(kwargs.Symmetry, 'Z1') pSpace = CartesianSpace.new(2); diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index 34d3f71..3fadc6e 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -31,7 +31,7 @@ function v = apply_regularized(mpo, fp1, fp2, v) v = apply(mpo, v); - v = v - overlap(v, fp2) * fp1; + v = v - overlap(v, fp2) * repartition(fp1, rank(v)); end function [V, D, flag] = eigsolve(mpo, v0, howmany, sigma, options) diff --git a/src/mps/InfJMpo.m b/src/mps/InfJMpo.m index 68a3762..5953715 100644 --- a/src/mps/InfJMpo.m +++ b/src/mps/InfJMpo.m @@ -146,50 +146,54 @@ linkwargs = namedargs2cell(linopts); expP = exp(-1i*qp.p); + L = period(mpo); + + needsRegularization = istrivial(qp); + if needsRegularization || true + fp_left = fixedpoint(mpo, qp, 'l_RL_0', 1); + fp_right = fixedpoint(mpo, qp, 'r_RL_1', L); + end T = transfermatrix(mpo, qp, qp, 'Type', 'RL'); TB = transfermatrix(mpo, qp, qp, 'Type', 'BL'); + % initialize and precompute GL * TB GBL = cell(size(GL)); GBL{1} = SparseTensor.zeros(domain(T), auxspace(qp, 1)); + for w = 1:L + GBL{next(w, L)} = ... + (apply(TB(w), GL{w}) + apply(T(w), GBL{w})) * (expP^(1/L)); + end - % GBL{1}(1) = 0 because of quasiparticle gauge - - for i = 2:size(GBL{1}, 2) - rhs = apply(slice(T, i, 1:i-1), GBL{1}(1, 1:i-1, 1, 1)) + ... - apply(slice(TB, i, 1:i), GL{1}(1, 1:i, 1)); + N = size(GBL{1}, 2); + for i = 2:N % GBL{1}(1) = 0 because of quasiparticle gauge + rhs = apply(slice(T, i, 1:i-1), GBL{1}(1, 1:i-1, 1, 1)) * expP; + rhs = rhs + GBL{1}(i); Tdiag = slice(T, i, i); if iszero(Tdiag) - GBL{1}(i) = expP * rhs; - - elseif iseye(T, i) && istrivial(qp) - fp_left = insert_onespace(insert_onespace(... - fixedpoint(qp, 'l_RL'), ... - 2, ~isdual(leftvspace(mpo, 1))), ... - 4, isdual(auxspace(qp, 1))); - fp_left = repartition(fp_left, rank(rhs)); - fp_right = insert_onespace(insert_onespace(... - fixedpoint(qp, 'r_RL'), ... - 2, ~isdual(rightvspace(mpo, 1))), ... - 1, ~isdual(auxspace(qp, 1))); - rhs = rhs - overlap(rhs, fp_right) * fp_left; - [GBL{1}(i), ~] = linsolve(@(x) x - expP * apply_regularized(Tdiag, fp_left, fp_right, x), ... - expP * rhs, [], linkwargs{:}); + GBL{1}(i) = rhs; else - [GBL{1}(i), ~] = linsolve(@(x) x - expP * apply(Tdiag, x), expP * rhs, [], ... - linkwargs{:}); + if needsRegularization && iseye(T, i) + fp_left = repartition(fp_left, rank(rhs)); + rhs = rhs - overlap(rhs, fp_right) * fp_left; + H_effective = @(x) x - expP * ... + apply_regularized(Tdiag, fp_left, fp_right, x); + else + H_effective = @(x) x - expP * apply(Tdiag, x); + end + + [GBL{1}(i), ~] = linsolve(H_effective, rhs, [], linkwargs{:}); end end - - if nnz(GL{1}) == numel(GL{1}) - GL{1} = full(GL{1}); + if nnz(GBL{1}) == numel(GBL{1}) + GBL{1} = full(GBL{1}); end - for w = 1:period(qp)-1 - T = transfermatrix(mpo, mps1, mps2, w, 'Type', 'LL'); - GL{next(w, period(mps1))} = apply(T, GL{w}); + for w = 1:L-1 + GBL{next(w, L)} = expP^(1 / L) * ... + (apply(TB(w), GL{w}) + apply(T(w), GBL{w})); end end @@ -207,54 +211,57 @@ linkwargs = namedargs2cell(linopts); expP = exp(+1i*qp.p); + L = period(mpo); + + needsRegularization = istrivial(qp); + if needsRegularization || true + fp_left = fixedpoint(mpo, qp, 'l_LR_1', 1); + fp_right = fixedpoint(mpo, qp, 'r_LR_0', L); + end T = transfermatrix(mpo, qp, qp, 'Type', 'LR').'; TB = transfermatrix(mpo, qp, qp, 'Type', 'BR').'; GBR = cell(size(GR)); GBR{1} = SparseTensor.zeros(domain(T), auxspace(qp, 1)); + for w = L:-1:1 + ww = next(w, L); + GBR{w} = expP^(1/L) * (apply(TB(ww), GR{ww}) + apply(T(ww), GBR{ww})); + end N = size(GBR{1}, 2); - for i = N:-1:1 if i == N - rhs = apply(slice(TB, i, i:N), GR{1}(1, i:N, 1)); + rhs = GBR{1}(1, i, 1, 1); else - rhs = apply(slice(T, i, i+1:N), GBR{1}(1, i+1:N, 1, 1)) + ... - apply(slice(TB, i, i:N), GR{1}(1, i:N, 1)); + rhs = apply(slice(T, i, i+1:N), GBR{1}(1, i+1:N, 1, 1)) * expP; + rhs = rhs + GBR{1}(1, i, 1, 1); end Tdiag = slice(T, i, i); if iszero(Tdiag) - GBR{1}(i) = expP * rhs; - - elseif iseye(T, i) && istrivial(qp) - fp_left = insert_onespace(insert_onespace(... - fixedpoint(qp, 'l_LR'), ... - 2, ~isdual(leftvspace(mpo, 1))), ... - 1, ~isdual(auxspace(qp, 1))); - fp_right = insert_onespace(insert_onespace(... - fixedpoint(qp, 'r_LR'), ... - 2, ~isdual(rightvspace(mpo, 1))), ... - 4, isdual(auxspace(qp, 1))); - fp_right = repartition(fp_right, rank(rhs)); - rhs = rhs - overlap(rhs, fp_left) * fp_right; - [GBR{1}(i), ~] = linsolve(@(x) x - expP * apply_regularized(Tdiag, fp_right, fp_left, x), ... - expP * rhs, [], linkwargs{:}); + GBR{1}(i) = rhs; else - [GBR{1}(i), ~] = linsolve(@(x) x - expP * apply(Tdiag, x), expP * rhs, [], ... - linkwargs{:}); + if needsRegularization && iseye(T, i) + fp_right = repartition(fp_right, rank(rhs)); + rhs = rhs - overlap(rhs, fp_left) * fp_right; + H_effective = @(x) x - expP * ... + apply_regularized(Tdiag, fp_right, fp_left, x); + else + H_effective = @(x) x - expP * apply(Tdiag, x); + end + + [GBR{1}(i), ~] = linsolve(H_effective, rhs, [], linkwargs{:}); end end - - if nnz(GL{1}) == numel(GL{1}) - GL{1} = full(GL{1}); + if nnz(GBR{1}) == numel(GBR{1}) + GBR{1} = full(GBR{1}); end - for w = 1:period(qp)-1 - T = transfermatrix(mpo, mps1, mps2, w, 'Type', 'LL'); - GL{next(w, period(mps1))} = apply(T, GL{w}); + for w = L:-1:2 + ww = next(w, L); + GBR{w} = expP^(1/L) * (apply(TB(ww), GR{ww}) + apply(T(ww), GBR{ww})); end end @@ -280,6 +287,45 @@ end end + function fp = fixedpoint(operator, state, type, w) + arguments + operator + state + type + w = 1 + end + + fp = fixedpoint(state, type(1:4), w); + + % add leg to fit operator + switch type(1) + case 'l' + fp = insert_onespace(fp, 2, ~isdual(leftvspace(operator, w))); + case 'r' + fp = insert_onespace(fp, 2, ~isdual(rightvspace(operator, w))); + otherwise + error('invalid fixedpoint type (%s)', type); + end + + % add leg to fit quasiparticle auxiliary leg + if isa(state, 'InfQP') + switch type(6) + case '0' + dual = isdual(auxspace(state, w)); + case '1' + dual = ~isdual(auxspace(state, w)); + otherwise + error('invalid type (%s)', type); + end + fp = MpsTensor(insert_onespace(fp, nspaces(fp) + 1, dual), 1); + end + end + + + function mpo = renormalize(mpo, lambda) + mpo = mpo - lambda; + end + function mpo = plus(a, b) if isa(a, 'InfJMpo') && isnumeric(b) if period(a) > 1 && isscalar(b) diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index ee3daba..dc9beae 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -355,7 +355,7 @@ for i = 1:length(sites) for d = depth(mpo):-1:1 gl = twistdual(GL{d, sites(i)}, 1); - gr = GR{d, next(sites(i), period(mps))}; + gr = GR{d, next(sites(i), period(mpo))}; gr = twistdual(gr, nspaces(gr)); H{i}(d, 1) = FiniteMpo(gl, mpo.O(d, sites(i)), gr); end @@ -412,6 +412,31 @@ end end end + + function H = B_hamiltonian(mpo, qp, GL, GR, sites, envopts, kwargs) + arguments + mpo + qp + GL + GR + sites = 1:period(mpo) + envopts = {} + kwargs.Type + end + + switch kwargs.Type + case {'l', 'left'} + GBL = leftquasienvironment(mpo, qp, GL, GR, envopts{:}); + H = AC_hamiltonian(mpo, qp, GBL, GR, sites); + case {'c', 'center'} + H = AC_hamiltonian(mpo, qp, GL, GR, sites); + case {'r', 'right'} + GBR = rightquasienvironment(mpo, qp, GL, GR, envopts{:}); + H = AC_hamiltonian(mpo, qp, GL, GBR, sites); + otherwise + error('unknown type %s', kwargs.Type) + end + end end methods (Static) diff --git a/src/mps/InfQP.m b/src/mps/InfQP.m index b2b1fa7..2f01c4a 100644 --- a/src/mps/InfQP.m +++ b/src/mps/InfQP.m @@ -7,8 +7,8 @@ mpsleft UniformMps mpsright UniformMps X - VL B + VL p end @@ -47,8 +47,8 @@ AL = mpsleft.AL; for i = period(mpsleft):-1:1 VL(i) = leftnull(AL(i)); - rVspace = rightvspace(mpsleft, i); - lVspace = leftvspace(mpsright, i); + rVspace = rightvspace(VL(i)); + lVspace = leftvspace(mpsright, next(i, period(mpsleft))); if isempty(charge) aspace = one(rVspace); else @@ -90,12 +90,17 @@ end function B = computeB(qp) - if ~isempty(qp.B), B = qp.B; return; end for w = period(qp):-1:1 B(w) = multiplyright(qp.VL(w), qp.X(w)); end end + function X = computeX(qp) + for w = period(qp):-1:1 + X(w) = tracetransfer(qp.VL(w)', qp.B(w)); + end + end + function bool = istrivial(qp) bool = qp.p == 0 && istrivial(auxspace(qp, 1)); end diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index bc157ec..cd72afa 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -151,6 +151,17 @@ d = dot(A, B); end + function d = overlap(A, B) + arguments + A + B MpsTensor + end + + inds = 1:nspaces(A); + + d = contract(A, inds, B, [fliplr(inds(1:end-B.alegs)) inds(end-B.alegs+1:end)]); + end + function C = mtimes(A, B) if isnumeric(A) C = B; @@ -594,6 +605,10 @@ function disp(t) builtin('disp', t); end + + function n = nnz(t) + n = nnz(t.var); + end end diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 89647cc..6b451dd 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -864,7 +864,12 @@ else if ~isequal(t1(i).domain, t2(i).domain) || ... ~isequal(t1(i).codomain, t2(i).codomain) - error('plus:spacemismatch', 'Incompatible spaces.'); + t1_str = sprintf('%s\t<-\t%s', join(string(t1(i).codomain)), ... + join(string(t1(i).domain))); + t2_str = sprintf('%s\t<-\t%s', join(string(t2(i).codomain)), ... + join(string(t2(i).domain))); + error('tensors:spacemismatch', ... + 'Added spaces incompatible.\n%s\n%s', t1_str, t2_str); end t1(i).var = t1(i).var + t2(i).var; end @@ -1721,7 +1726,8 @@ dims.degeneracies = dims.degeneracies(mask); Ns = Ns(mask); - N = t.eye(t.codomain, t.codomain.new(dims, false)); + W = t.codomain.new(dims, false); + N = t.eye(t.codomain, W); N.var = fill_matrix_data(N.var, Ns, dims.charges); end diff --git a/test/TestInfJMpo.m b/test/TestInfJMpo.m index 376c458..32409b1 100644 --- a/test/TestInfJMpo.m +++ b/test/TestInfJMpo.m @@ -48,21 +48,16 @@ function testEnvironments(tc) end function testQuasiEnvironments(tc) + %% Ising D = 16; mpo = quantum1dIsing('Symmetry', 'Z2'); mps = initialize_mps(mpo, GradedSpace.new(Z2(0, 1), D ./ [2 2], false)); - [~, ~, lambda] = environments(mpo, mps); - - mpo = mpo - lambda; - [GL, GR, lambda] = environments(mpo, mps); - tc.assertEqual(lambda, 0, 'AbsTol', 1e-10); + [GL, GR] = environments(mpo, mps); for charge = Z2([1 0]) for p = [0 pi 0.5] qp = InfQP.randnc(mps, mps, p, charge); -% [GBL, GBR] = quasienvironments(mpo, qp, GL, GR); - % left environments GBL = leftquasienvironment(mpo, qp, GL, GR); tc.assertEqual(norm(GBL{1}(1)), 0, 'AbsTol', 1e-10, ... @@ -72,21 +67,17 @@ function testQuasiEnvironments(tc) T_B = transfermatrix(mpo, qp, qp, 'Type', 'BL'); GBL2 = apply(T_R, GBL{1}) + apply(T_B, GL{1}); + if istrivial(qp) - fp_left = insert_onespace(insert_onespace(... - fixedpoint(qp, 'l_RL'), ... - 2, ~isdual(leftvspace(mpo, 1))), ... - 4, isdual(auxspace(qp, 1))); - fp_left = repartition(fp_left, rank(GBL2(end))); - fp_right = insert_onespace(insert_onespace(... - fixedpoint(qp, 'r_RL'), ... - 2, ~isdual(rightvspace(mpo, 1))), ... - 1, ~isdual(auxspace(qp, 1))); + r = rank(GBL2(end)); + fp_left = repartition(fixedpoint(mpo, qp, 'l_RL_0'), r); + fp_right = fixedpoint(mpo, qp, 'r_RL_1'); GBL2(end) = GBL2(end) - overlap(GBL2(end), fp_right) * fp_left; end - tc.assertTrue(isapprox(GBL2, GBL{1} * exp(+1i*p)), ... - sprintf('left environment fixed point equation unfulfilled for p=%e, c=%d', p, charge)); + tc.verifyTrue(isapprox(GBL2, GBL{1} * exp(+1i*p)), ... + sprintf('left environment fixed point equation unfulfilled for p=%e, c=%d', p, charge)); + % right environments GBR = rightquasienvironment(mpo, qp, GL, GR); @@ -95,21 +86,75 @@ function testQuasiEnvironments(tc) GBR2 = apply(T_L, GBR{1}) + apply(T_B, GR{1}); if istrivial(qp) - fp_left = insert_onespace(insert_onespace(... - fixedpoint(qp, 'l_LR'), ... - 2, ~isdual(leftvspace(mpo, 1))), ... - 1, ~isdual(auxspace(qp, 1))); - fp_right = insert_onespace(insert_onespace(... - fixedpoint(qp, 'r_LR'), ... - 2, ~isdual(rightvspace(mpo, 1))), ... - 4, isdual(auxspace(qp, 1))); - fp_right = repartition(fp_right, rank(GBR2(1))); + r = rank(GBR2(1)); + fp_left = fixedpoint(mpo, qp, 'l_LR_1'); + fp_right = repartition(fixedpoint(mpo, qp, 'r_LR_0'), r); GBR2(1) = GBR2(1) - overlap(GBR2(1), fp_left) * fp_right; end tc.assertTrue(isapprox(GBR2, GBR{1} * exp(-1i*p)), ... sprintf('right environment fixed point equation unfulfilled for p=%e, c=%d', p, charge)); end end + end + + function testQuasiEnvironments2(tc) + D = 16; + %% Heisenberg + mpo = quantum1dHeisenberg('Spin', 0.5, 'Symmetry', 'SU2'); + mpo = [mpo mpo]; + vspace = GradedSpace.new(... + SU2(1:2:5), [2 2 1] * (D / 4), false, ... + SU2(2:2:6), [2 2 1] * (D / 4), false); + mps = initialize_mps(mpo, vspace(1), vspace(2)); + alg = Vumps('maxiter', 3, 'which', 'smallestreal'); + mps = fixedpoint(alg, mpo, mps); + [GL, GR] = environments(mpo, mps); + + for charge = SU2(1, 3) + for p = [0 pi 0.5] + qp = InfQP.randnc(mps, mps, p, charge); + + % left environments + GBL = leftquasienvironment(mpo, qp, GL, GR); + T_R = transfermatrix(mpo, qp, qp, 'Type', 'RL'); + T_B = transfermatrix(mpo, qp, qp, 'Type', 'BL'); + + for w = 1:period(mpo) + tc.assertEqual(norm(GBL{w}(1)), 0, 'AbsTol', 1e-10, ... + 'left gauge quasiparticle violation.'); + GBL2 = apply(T_R(w), GBL{w}) + apply(T_B(w), GL{w}); + + if istrivial(qp) && w == prev(1, period(mpo)) + r = rank(GBL2(end)); + fp_left = repartition(fixedpoint(mpo, qp, 'l_RL_0', next(w, period(mpo))), r); + fp_right = fixedpoint(mpo, qp, 'r_RL_1', w); + GBL2(end) = GBL2(end) - overlap(GBL2(end), fp_right) * fp_left; + end + + tc.verifyTrue(isapprox(GBL2, GBL{next(w, period(mpo))} * exp(+1i*p/period(mpo))), ... + sprintf('left environment fixed point equation unfulfilled for p=%e, c=%d', p, charge)); + end + + % right environments + GBR = rightquasienvironment(mpo, qp, GL, GR); + T_L = transfermatrix(mpo, qp, qp, 'Type', 'LR').'; + T_B = transfermatrix(mpo, qp, qp, 'Type', 'BR').'; + + for w = 1:period(mpo) + GBR2 = apply(T_L(w), GBR{w}) + apply(T_B(w), GR{w}); + + if istrivial(qp) && w == next(1, period(mpo)) + r = rank(GBR2(1)); + fp_left = fixedpoint(mpo, qp, 'l_LR_1', prev(w, period(mpo))); + fp_right = repartition(fixedpoint(mpo, qp, 'r_LR_0', w), r); + GBR2(1) = GBR2(1) - overlap(GBR2(1), fp_left) * fp_right; + end + + tc.verifyTrue(isapprox(GBR2, GBR{prev(w, period(mpo))} * exp(-1i*p/period(mpo))), ... + sprintf('right environment fixed point equation unfulfilled for p=%e, c=%d', p, charge)); + end + end + end end @@ -130,14 +175,24 @@ function testDerivatives(tc, mpo, mps) end end - function test1dIsing(tc) - alg = Vumps('which', 'smallestreal', 'maxiter', 5); + function test1dIsing(tc) + alg = Vumps('which', 'smallestreal', 'maxiter', 10); D = 16; mpo = quantum1dIsing('J', 1, 'h', 1, 'L', Inf); mps = initialize_mps(mpo, CartesianSpace.new(D)); - [mps2, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyTrue(isapprox(lambda, -1.27, 'RelTol', 1e-2)) + [gs, lambda] = fixedpoint(alg, mpo, mps); +% tc.verifyTrue(isapprox(lambda, -1.27, 'RelTol', 1e-2)) + + p = 0; + qp = InfQP.randnc(gs, gs); + momenta = 0:0.2:pi; + for i = 1:length(momenta) + qp.p = momenta(i); + [qp, mu(i)] = excitations(QPAnsatz(), mpo, qp); + mu + end + mu mpo = quantum1dIsing('J', 1, 'h', 1, 'L', Inf, 'Symmetry', 'Z2'); mps = initialize_mps(mpo, GradedSpace.new(Z2(0, 1), [D D] ./ 2, false)); [mps2, lambda2] = fixedpoint(alg, mpo, mps); @@ -162,6 +217,10 @@ function test1dHeisenberg(tc) [gs_mps] = fixedpoint(alg, mpo, mps); lambda = expectation_value(gs_mps, mpo); tc.verifyEqual(lambda / period(mps), -1.40, 'RelTol', 1e-2); + + p = 0; + charge = SU2(3); + qp = InfQP.randnc(gs_mps, gs_mps, p, charge); end end end diff --git a/test/TestInfMpo.m b/test/TestInfMpo.m index d9e6869..37f8f9f 100644 --- a/test/TestInfMpo.m +++ b/test/TestInfMpo.m @@ -144,7 +144,8 @@ function test2dIsing(tc) tc.assertEqual(-log(lambda) / beta, f, 'RelTol', 1e-5); % compute excitations - qp = InfQP.new([], mps2, mps2); + p = 0; + qp = InfQP.randnc(mps2, mps2, p); GBL = leftquasienvironment(mpo, qp); From 395441d5cc5e08e5bb85164746f9e7f753e1e36b Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 28 Feb 2023 23:19:14 +0100 Subject: [PATCH 170/245] multisite works -> fails for symmetry? --- src/algorithms/QPAnsatz.m | 5 ++- src/models/quantum1dIsing.m | 6 ++-- src/tensors/Tensor.m | 10 +++++- src/tensors/kernels/MatrixBlock.m | 48 +++++++++++++++----------- src/tensors/kernels/TrivialBlock.m | 30 +++++++++++----- test/TestInfJMpo.m | 55 +++++++++++++++++++----------- 6 files changed, 100 insertions(+), 54 deletions(-) diff --git a/src/algorithms/QPAnsatz.m b/src/algorithms/QPAnsatz.m index 4e5a9c3..a95823c 100644 --- a/src/algorithms/QPAnsatz.m +++ b/src/algorithms/QPAnsatz.m @@ -4,7 +4,7 @@ properties alg_eigs = struct('MaxIter', 100, 'KrylovDim', 30, 'Tol', 1e-8, ... 'Verbosity', Verbosity.diagnostics) - alg_environments = struct + alg_environments = struct('Tol', 1e-10); howmany = 1 which = 'smallestreal' end @@ -47,8 +47,7 @@ [X, mu] = eigsolve(H_effective, qp.X, alg.howmany, alg.which, ... eigkwargs{:}); - qp = repmat(qp, [1 alg.howmany]); - for i = alg.howmany:-1:1 + for i = alg.howmany:-1:2 qp(i).X = X(i); qp(i).B = computeB(qp(i)); end diff --git a/src/models/quantum1dIsing.m b/src/models/quantum1dIsing.m index 3f743d7..3480ab8 100644 --- a/src/models/quantum1dIsing.m +++ b/src/models/quantum1dIsing.m @@ -35,9 +35,9 @@ vSpace = GradedSpace.new(Z2(1), 1, false); trivSpace = one(pSpace); - Sx_l = fill_matrix(Tensor([trivSpace pSpace], [pSpace vSpace]), {1 1}); - Sx_r = fill_matrix(Tensor([vSpace pSpace], [pSpace trivSpace]), {1 1}); - Sz = fill_matrix(Tensor([trivSpace pSpace], [pSpace trivSpace]), {1 -1}); + Sx_l = fill_matrix(Tensor([trivSpace pSpace], [pSpace vSpace]), {1 1}) / 2; + Sx_r = fill_matrix(Tensor([vSpace pSpace], [pSpace trivSpace]), {1 1}) / 2; + Sz = fill_matrix(Tensor([trivSpace pSpace], [pSpace trivSpace]), {1 -1}) / 2; cod = SumSpace([one(vSpace) vSpace one(vSpace)], pSpace); dom = SumSpace(pSpace, [one(vSpace), vSpace, one(vSpace)]); diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 6b451dd..1fb99a4 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -2260,7 +2260,7 @@ type = 'complex' end - v = vectorize(t.var, type); + v = vectorize([t.var], type); end function t = devectorize(v, t, type) @@ -2289,6 +2289,14 @@ type = 'complex' end + if numel(t) > 1 + X = devectorize(v, [t.var], type); + for i = 1:numel(t) + t(i).var = X(i); + end + return + end + t.var = devectorize(v, t.var, type); end end diff --git a/src/tensors/kernels/MatrixBlock.m b/src/tensors/kernels/MatrixBlock.m index b0ff81a..97655ab 100644 --- a/src/tensors/kernels/MatrixBlock.m +++ b/src/tensors/kernels/MatrixBlock.m @@ -642,6 +642,15 @@ end function v = vectorize(X, type) + if numel(X) > 1 + vs = cell(size(X)); + for i = 1:numel(vs) + vs{i} = vectorize(X(i), type); + end + v = vertcat(vs{:}); + return + end + qdims = sqrt(qdim(X.charge)); switch type case 'complex' @@ -663,25 +672,26 @@ end function X = devectorize(v, X, type) - qdims = sqrt(qdim(X.charge)); - switch type - case 'complex' - ctr = 0; - for i = 1:length(X.var) - n = numel(X.var{i}); - X.var{i} = reshape(v(ctr + (1:n)), size(X.var{i})) ./ qdims(i); - ctr = ctr + n; - end - - case 'real' - ctr = 0; - for i = 1:length(X.var) - n = numel(X.var{i}); - sz = size(X.var{i}); - X.var{i} = complex(reshape(v(ctr + (1:n)), sz), ... - reshape(v(ctr + (n + 1:2 * n)), sz)) ./ qdims(i); - ctr = ctr + 2 * n; - end + ctr = 0; + for i = 1:numel(X) + qdims = sqrt(qdim(X(i).charge)); + switch type + case 'complex' + for j = 1:length(X(i).var) + n = numel(X(i).var{j}); + X(i).var{j} = reshape(v(ctr + (1:n)), size(X(i).var{j})) ./ qdims(j); + ctr = ctr + n; + end + + case 'real' + for j = 1:length(X(i).var) + n = numel(X(i).var{j}); + sz = size(X(i).var{j}); + X(i).var{j} = complex(reshape(v(ctr + (1:n)), sz), ... + reshape(v(ctr + (n + 1:2 * n)), sz)) ./ qdims(j); + ctr = ctr + 2 * n; + end + end end end end diff --git a/src/tensors/kernels/TrivialBlock.m b/src/tensors/kernels/TrivialBlock.m index 5959f7c..76c3dcb 100644 --- a/src/tensors/kernels/TrivialBlock.m +++ b/src/tensors/kernels/TrivialBlock.m @@ -193,6 +193,15 @@ end function v = vectorize(X, type) + if numel(X) > 1 + vs = cell(size(X)); + for i = 1:numel(X) + vs{i} = vectorize(X(i), type); + end + v = vertcat(vs{:}); + return + end + switch type case 'complex' v = X.var(:); @@ -203,14 +212,19 @@ end function X = devectorize(v, X, type) - switch type - case 'complex' - X.var = reshape(v, size(X.var)); - - case 'real' - m = size(v, 1) / 2; - sz = size(X.var); - X.var = complex(reshape(v(1:m), sz), reshape(v(m + 1:2 * m), sz)); + ctr = 0; + for i = 1:numel(X) + switch type + case 'complex' + X(i).var = reshape(v(ctr + (1:numel(X(i).var))), size(X(i).var)); + ctr = ctr + numel(X(i).var); + case 'real' + v_ = v(ctr + 1:2*numel(X(i).var)); + m = size(v_, 1) / 2; + sz = size(X(i).var); + X(i).var = complex(reshape(v_(1:m), sz), reshape(v_(m + 1:2 * m), sz)); + ctr = ctr + numel(X(i).var) * 2; + end end end diff --git a/test/TestInfJMpo.m b/test/TestInfJMpo.m index 32409b1..9e654d2 100644 --- a/test/TestInfJMpo.m +++ b/test/TestInfJMpo.m @@ -181,46 +181,61 @@ function test1dIsing(tc) mpo = quantum1dIsing('J', 1, 'h', 1, 'L', Inf); mps = initialize_mps(mpo, CartesianSpace.new(D)); [gs, lambda] = fixedpoint(alg, mpo, mps); -% tc.verifyTrue(isapprox(lambda, -1.27, 'RelTol', 1e-2)) + tc.verifyTrue(isapprox(lambda, -0.53, 'RelTol', 1e-2)) p = 0; - qp = InfQP.randnc(gs, gs); - momenta = 0:0.2:pi; - for i = 1:length(momenta) - qp.p = momenta(i); - [qp, mu(i)] = excitations(QPAnsatz(), mpo, qp); - mu - end + qp = InfQP.randnc(gs, gs, p); + [qp, mu] = excitations(QPAnsatz(), mpo, qp); + tc.verifyEqual(mu, 0.5, 'AbsTol', 1e-8); + + gs2 = [gs gs]; + mpo2 = [mpo mpo]; + gs2 = fixedpoint(alg, mpo2, gs2); + + qp2 = InfQP.randnc(gs2, gs2); + [qp2, mu2] = excitations(QPAnsatz(), mpo2, qp2); + + tc.verifyEqual(mu, mu2, 'AbsTol', 1e-8); - mu mpo = quantum1dIsing('J', 1, 'h', 1, 'L', Inf, 'Symmetry', 'Z2'); mps = initialize_mps(mpo, GradedSpace.new(Z2(0, 1), [D D] ./ 2, false)); [mps2, lambda2] = fixedpoint(alg, mpo, mps); - tc.verifyTrue(isapprox(lambda, -1.27, 'RelTol', 1e-2)) + tc.verifyTrue(isapprox(lambda2, -0.53, 'RelTol', 1e-2)) + + p = 0; + qp = InfQP.randnc(mps2, mps2, p, Z2(2)); + [qp, mu] = excitations(QPAnsatz(), mpo, qp); + tc.verifyEqual(mu, 0.5, 'AbsTol', 1e-8); mpo = [mpo mpo]; - mps = [mps mps]; - [mps2, lambda2] = fixedpoint(alg, mpo, mps); - tc.verifyTrue(isapprox(lambda2/2, -1.27, 'RelTol', 5e-2)) + mps2 = [mps2 mps2]; + [mps2, lambda2] = fixedpoint(alg, mpo, mps2); + tc.verifyTrue(isapprox(lambda2/2, -0.53, 'RelTol', 1e-2)) + + p = 0; + qp = InfQP.randnc(mps2, mps2, p, Z2(2)); + [qp, mu] = excitations(QPAnsatz(), mpo, qp); + tc.verifyEqual(mu, 0.5, 'AbsTol', 1e-8); end function test1dHeisenberg(tc) - alg = Vumps('which', 'smallestreal', 'maxiter', 5); + alg = Vumps('which', 'smallestreal', 'maxiter', 100); mpo = quantum1dHeisenberg('Spin', 1, 'Symmetry', 'SU2'); - mpo = [mpo mpo]; - - vspace1 = GradedSpace.new(SU2(1:2:5), [5 5 1], false); - vspace2 = GradedSpace.new(SU2(1:2:5), [5 5 1], false); - mps = initialize_mps(mpo, vspace1, vspace2); + vspace1 = GradedSpace.new(SU2(2:2:6), [5 5 1], false); + mps = initialize_mps(mpo, vspace1); [gs_mps] = fixedpoint(alg, mpo, mps); lambda = expectation_value(gs_mps, mpo); - tc.verifyEqual(lambda / period(mps), -1.40, 'RelTol', 1e-2); + tc.verifyEqual(lambda, -1.4, 'RelTol', 1e-2); p = 0; charge = SU2(3); qp = InfQP.randnc(gs_mps, gs_mps, p, charge); + + [qp, mu] = excitations(QPAnsatz(), mpo, qp); + mu + end end end From 1cacd4c1d70f5e830751977d292dc3c9beda0721 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sun, 5 Mar 2023 12:40:46 +0100 Subject: [PATCH 171/245] Fix fermionic trace + add test --- src/tensors/AbstractTensor.m | 9 ++++++++- test/TestTensor.m | 7 +++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/tensors/AbstractTensor.m b/src/tensors/AbstractTensor.m index 547d460..1287ff9 100644 --- a/src/tensors/AbstractTensor.m +++ b/src/tensors/AbstractTensor.m @@ -506,7 +506,14 @@ function disp(t, details) if isempty(i1) && isempty(i2), B = A; return; end assert(length(i1) == length(i2), 'invalid indices'); - E = A.eye(conj(space(A, i1)), space(A, i2)); + firstspaces = conj(space(A, i1)); + secondspaces = space(A, i2); + assert(isequal(firstspaces, secondspaces), 'Tensor:SpaceMismatch', ... + 'Cannot trace spaces %s and %s', string(firstspaces), string(secondspaces)); + + E = A.eye(firstspaces, secondspaces); + E = twistdual(E, 1:length(firstspaces)); + iA = [i1 i2]; iE = [1:length(i1) length(i1) + (length(i2):-1:1)]; B = tensorprod(A, E, iA, iE); diff --git a/test/TestTensor.m b/test/TestTensor.m index 08ce982..d6ab972 100644 --- a/test/TestTensor.m +++ b/test/TestTensor.m @@ -218,6 +218,13 @@ function tensortrace(tc, spaces) t4 = contract(t1, [-1 1 2 2 1 -2], 'Rank', [1 1]); tc.assertTrue(isapprox(t3, t4, 'AbsTol', tc.tol, 'RelTol', tc.tol)); + % issue with fermionic traces: + Nl = Tensor.randnc(spaces(1), spaces(1:2)); + Nr = Tensor.randnc(spaces(1:2), spaces(1)); + result1 = contract(Nl, [-1 1 2], Nr, [2 1 -2]); + result2 = contract(contract(Nl, [-1 1 -4], Nr, [-2 1 -3]), [-1 1 -2 1]); + tc.assertTrue(isapprox(result1, result2, 'AbsTol', tc.tol, 'RelTol', tc.tol)); + t5 = contract(t1, [1 2 3 3 2 1]); t6 = contract(t4, [1 1]); tc.assertTrue(isapprox(t5, t6, 'AbsTol', tc.tol, 'RelTol', tc.tol)); From 23c299f277985e574968eb0d2732f2860ec4782d Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sun, 5 Mar 2023 12:41:10 +0100 Subject: [PATCH 172/245] remove obsolete code --- src/tensors/kernels/AbstractBlock.m | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/tensors/kernels/AbstractBlock.m b/src/tensors/kernels/AbstractBlock.m index 6e6a5fd..884e5ee 100644 --- a/src/tensors/kernels/AbstractBlock.m +++ b/src/tensors/kernels/AbstractBlock.m @@ -60,9 +60,6 @@ med = MatrixBlock(codomain, domain); end end -% if ~strcmp(class(charges(codomain)), class(med(1).charge)) -% bla -% end X = med; end end From ad37a1332f0f68d7455f7d04d8c094cb7eb62c8e Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sun, 5 Mar 2023 13:14:06 +0100 Subject: [PATCH 173/245] Add netcon tests --- test/TestTensor.m | 5 +++++ test/TestUtility.m | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 test/TestUtility.m diff --git a/test/TestTensor.m b/test/TestTensor.m index d6ab972..70b0af3 100644 --- a/test/TestTensor.m +++ b/test/TestTensor.m @@ -44,6 +44,7 @@ function classSetup(tc) end methods (Test) + %% General properties function basic_linear_algebra(tc, spaces) t1 = Tensor.rand(spaces(1:3), spaces(4:5)); @@ -115,6 +116,8 @@ function matrix_functions(tc, spaces) end end + + %% Contractions function permute_via_inner(tc, spaces) rng(213); t1 = Tensor.rand(spaces, []); @@ -249,6 +252,8 @@ function contract_order(tc, spaces) end end + + %% Factorizations function orthogonalize(tc, spaces) t = Tensor.randnc(spaces, []); diff --git a/test/TestUtility.m b/test/TestUtility.m new file mode 100644 index 0000000..b44b52b --- /dev/null +++ b/test/TestUtility.m @@ -0,0 +1,44 @@ +classdef TestUtility < matlab.unittest.TestCase + % Unit tests for utility functions. + + properties + Property1 + end + + methods (Test) + function testNetcon(tc) + network1 = {[-1, 1, 2, 3] [2, 4, 5, 6] [1, 5, 7, -3] [3, 8, 4, 9] ... + [6, 9, 7, 10] [-2, 8, 11, 12] [10, 11, 12, -4]}; + [~, cost] = netcon(network1, 0, 2, 1, 1); + tc.verifyEqual(cost, [0, 0, 0, 0, 0, 0, 2, 2, 2]); + + network2 = {[1, 4, 5, 6, 7, 8] [2, 9, 10, 11, 12, 13] ... + [3, 14, 15, 16, 17, 18] [-6, 9, 23, 24, 25, 26] [-5, 5, 19, 20, 21, 22] ... + [8, 14, 27, 28, 29, 30] [12, 15, 31, 32, 33, 34] ... + [22, 25, 28, 31, 35, 36, 37, 38] [-4, 20, 23, 35, 39, 40, 41, 42] ... + [42, 36, 37, 38, 43, 44, 45, 46] [41, 24, 44, 26, 47, 48] ... + [19, 40, 21, 43, 49, 50] [27, 45, 29, 30, 51, 52] ... + [46, 32, 33, 34, 53, 54] [-2, -3, 39, 49, 47, 55] [4, 50, 6, 7, 51, 56] ... + [48, 10, 11, 53, 13, 57] [52, 54, 16, 17, 18, 58] ... + [55, 56, 57, 58, -1, 1, 2, 3]}; + [~, cost] = netcon(network2, 0, 2, 1, 1); + tc.verifyEqual(cost, [0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 0, 1, 1, 3, 0, 3]); + + % passes but fairly expensive: +% network3 = {[64, 72, 73, 19, 22] [65, 74, 75, 23, 76] [66, 77, 20, 79, 28] ... +% [67, 21, 24, 29, 34] [68, 25, 78, 35, 80] [69, 81, 30, 83, 84] ... +% [70, 31, 36, 85, 86] [71, 37, 82, 87, 88] [-5, 19, 20, 21, 1, 2, 4, 5] ... +% [22, 23, 24, 25, 3, 26, 6, 27] [28, 29, 30, 31, 7, 8, 32, 33] ... +% [34, 35, 36, 37, 9, 38, 39, 40] 1:18 ... +% [10, 11, 13, 14, 41, 42, 43, 44] [12, 26, 15, 27, 47, 48, 49, 50] ... +% [16, 17, 32, 33, 53, 54, 55, 56] [18, 38, 39, 40, 60, 61, 62, 63] ... +% [-2, -3, -4, 41, 89] [72, 73, 42, 47, 90] [74, 75, 48, 76, 45] ... +% [77, 43, 79, 53, 46] [44, 49, 54, 60, 51] [50, 78, 61, 80, 52] ... +% [81, 55, 83, 84, 57] [56, 62, 85, 86, 58] [63, 82, 87, 88, 59] ... +% [89, 90, 45, 46, 51, 52, 57, 58, 59, -1, 64, 65, 66, 67, 68, 69, 70, 71]}; +% [~, cost] = netcon(network3, 0, 2, 1, 1); +% tc.verifyEqual(cost, [zeros(1, 7) 4 4 0 0 0 1 1 1 0 1 0 0 0 3 0 3 2 0 2 4]); + end + end +end + From f8975c0324a2bd9b9133afdb033ce0ac340dea06 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 7 Mar 2023 11:36:53 +0100 Subject: [PATCH 174/245] refactor models --- src/models/quantum1dHeisenberg.m | 44 +++++++++++++++-------- src/models/spinoperators/pauliterm.m | 6 ++++ src/models/spinoperators/sigma_exchange.m | 24 +++++++++++++ src/models/spinoperators/sigma_min.m | 29 +++++++++++++++ src/models/spinoperators/sigma_plus.m | 29 +++++++++++++++ src/models/spinoperators/sigma_z.m | 25 +++++++++++++ src/mps/InfJMpo.m | 40 ++++++++++++++++++++- src/mps/MpoTensor.m | 4 +-- 8 files changed, 183 insertions(+), 18 deletions(-) create mode 100644 src/models/spinoperators/pauliterm.m create mode 100644 src/models/spinoperators/sigma_exchange.m create mode 100644 src/models/spinoperators/sigma_min.m create mode 100644 src/models/spinoperators/sigma_plus.m create mode 100644 src/models/spinoperators/sigma_z.m diff --git a/src/models/quantum1dHeisenberg.m b/src/models/quantum1dHeisenberg.m index beff676..e869bb1 100644 --- a/src/models/quantum1dHeisenberg.m +++ b/src/models/quantum1dHeisenberg.m @@ -16,30 +16,44 @@ case 'SU2' assert(isscalar(J) || all(J == J(1)), ... 'Different spin couplings not invariant under SU2'); + J = J(1); assert(h == 0, 'Magnetic field not invariant under SU2'); - pSpace = GradedSpace.new(Q, 1, false); - vSpace = GradedSpace.new(SU2(3), 1, false); - tSpace = one(vSpace); + H2 = sigma_exchange(kwargs.Spin, 'SU2'); + H1 = []; - s = kwargs.Spin; - L = Tensor.ones([tSpace pSpace], [pSpace vSpace]); - L = L * (-J(1) * (s^2 + s)); - R = Tensor.ones([vSpace pSpace], [pSpace tSpace]); + case 'U1' + if isscalar(J) + Jxy = J; + Jz = J; + else + assert(length(J) == 2) + Jxy = J(1); + Jz = J(2); + end + assert(h == 0, 'TBA'); - cod = SumSpace([one(vSpace) vSpace one(vSpace)], pSpace); - dom = SumSpace(pSpace, [one(vSpace), vSpace, one(vSpace)]); - O = MpoTensor.zeros(cod, dom); - O(1, 1, 1, 1) = 1; - O(3, 1, 3, 1) = 1; - O(1, 1, 2, 1) = L; - O(2, 1, 3, 1) = R; + Splus = sigma_plus(kwargs.Spin, 'U1'); + Smin = sigma_min(kwargs.Spin, 'U1'); + Sz = sigma_z(kwargs.Spin, 'U1'); + + H2 = Jxy/2 * (contract(Splus, [-1 1 -3], conj(Splus), [-4 1 -2], 'Rank', [2 2]) + ... + contract(Smin, [-1 1 -3], conj(Smin), [-4 1 -2], 'Rank', [2 2])); + if Jz ~= 0 + H2 = H2 + Jz * contract(Sz, [-1 -3], Sz, [-2 -4], 'Rank', [2 2]); + end + + if h == 0 + H1 = []; + else + H1 = h * Sz; + end otherwise error('TBA'); end -mpo = InfJMpo(O); +mpo = InfJMpo.twosite(H2, H1); if isfinite(kwargs.L) mpo = open_boundary_conditions(mpo, L); diff --git a/src/models/spinoperators/pauliterm.m b/src/models/spinoperators/pauliterm.m new file mode 100644 index 0000000..01e7b0c --- /dev/null +++ b/src/models/spinoperators/pauliterm.m @@ -0,0 +1,6 @@ +function p = pauliterm(spin, i, j) + +p = sqrt((spin + 1) * (i + j - 1) - i * j) / 2; + +end + diff --git a/src/models/spinoperators/sigma_exchange.m b/src/models/spinoperators/sigma_exchange.m new file mode 100644 index 0000000..24400b7 --- /dev/null +++ b/src/models/spinoperators/sigma_exchange.m @@ -0,0 +1,24 @@ +function S = sigma_exchange(spin, symmetry) +arguments + spin = 1/2 + symmetry = 'Z1' +end + +switch symmetry +% case 'Z1' + + case 'SU2' + pspace = GradedSpace.new(SU2(2 * spin + 1), 1, false); + aspace = GradedSpace.new(SU2(3), 1, false); + + Sleft = Tensor.ones([pspace aspace], pspace); + Sright = Tensor.ones(pspace, [aspace pspace]); + + S = (spin^2 + spin) * contract(Sleft, [-1 1 -3], Sright, [-2 1 -4], 'Rank', [2 2]); + + otherwise + error('models:TBA', 'not implemented'); +end + +end + diff --git a/src/models/spinoperators/sigma_min.m b/src/models/spinoperators/sigma_min.m new file mode 100644 index 0000000..eb2a59a --- /dev/null +++ b/src/models/spinoperators/sigma_min.m @@ -0,0 +1,29 @@ +function S = sigma_min(spin, symmetry) +arguments + spin = 1/2 + symmetry = 'Z1' +end + +switch symmetry + case 'Z1' + + case 'U1' + charges = U1((-2 * spin):2:(2 * spin)); + degeneracies = ones(size(charges)); + pspace = GradedSpace.new(charges, degeneracies, false); + aspace = GradedSpace.new(U1(2), 1, false); + + S = Tensor.zeros([pspace aspace], pspace); + [mblocks, bcharges] = matrixblocks(S); + for i = 1:length(charges) + if charges(i) == U1(-2 * spin), continue; end + mblocks{bcharges == charges(i)} = 2 * pauliterm(spin, i - 1, i); + end + S = S.fill_matrix(mblocks, bcharges); + + otherwise + error('models:TBA', 'not implemented'); +end + +end + diff --git a/src/models/spinoperators/sigma_plus.m b/src/models/spinoperators/sigma_plus.m new file mode 100644 index 0000000..e7d907c --- /dev/null +++ b/src/models/spinoperators/sigma_plus.m @@ -0,0 +1,29 @@ +function S = sigma_plus(spin, symmetry) +arguments + spin = 1/2 + symmetry = 'Z1' +end + +switch symmetry +% case 'Z1' + + case 'U1' + charges = U1((-2 * spin):2:(2 * spin)); + degeneracies = ones(size(charges)); + pspace = GradedSpace.new(charges, degeneracies, false); + aspace = GradedSpace.new(U1(-2), 1, false); + + S = Tensor.zeros([pspace aspace], pspace); + [mblocks, bcharges] = matrixblocks(S); + for i = 1:length(charges) + if charges(i) == U1(2 * spin), continue; end + mblocks{charges(i) == bcharges} = 2 * pauliterm(spin, i, i + 1); + end + S = S.fill_matrix(mblocks, bcharges); + + otherwise + error('models:TBA', 'not implemented'); +end + +end + diff --git a/src/models/spinoperators/sigma_z.m b/src/models/spinoperators/sigma_z.m new file mode 100644 index 0000000..43972bc --- /dev/null +++ b/src/models/spinoperators/sigma_z.m @@ -0,0 +1,25 @@ +function S = sigma_z(spin, symmetry) +arguments + spin = 1/2 + symmetry = 'Z1' +end + +switch symmetry + case 'U1' + charges = U1((-2 * spin):2:(2 * spin)); + degeneracies = ones(size(charges)); + pspace = GradedSpace.new(charges, degeneracies, false); + + S = Tensor.zeros(pspace, pspace); + [mblocks, bcharges] = matrixblocks(S); + for i = 1:length(charges) + mblocks{charges(i) == bcharges} = spin + 1 - i; + end + S = S.fill_matrix(mblocks, bcharges); + + otherwise + error('models:TBA', 'not implemented'); +end + +end + diff --git a/src/mps/InfJMpo.m b/src/mps/InfJMpo.m index 5953715..b5bf867 100644 --- a/src/mps/InfJMpo.m +++ b/src/mps/InfJMpo.m @@ -302,7 +302,7 @@ case 'l' fp = insert_onespace(fp, 2, ~isdual(leftvspace(operator, w))); case 'r' - fp = insert_onespace(fp, 2, ~isdual(rightvspace(operator, w))); + fp = insert_onespace(fp, 2, isdual(rightvspace(operator, w))); otherwise error('invalid fixedpoint type (%s)', type); end @@ -370,4 +370,42 @@ finitempo = FiniteMpo(leftedge, Os, rightedge); end end + + methods (Static) + function mpo = twosite(Htwosite, Honesite, kwargs) + arguments + Htwosite + Honesite = [] + kwargs.Trunc + end + newkwargs = namedargs2cell(kwargs); + local_ops = MpoTensor.decompose_local_operator(Htwosite, newkwargs{:}); + L = local_ops{1}; + R = local_ops{2}; + + assert(pspace(L) == pspace(R), 'operators:spacemismatch', ... + sprintf('incompatible physical spaces %s and %s', pspace(L), pspace(R))); + + cod = SumSpace([leftvspace(L), leftvspace(R), rightvspace(R)'], pspace(L)); + dom = SumSpace(pspace(L), [leftvspace(L)', rightvspace(L), rightvspace(R)]); + + O = MpoTensor.zeros(cod, dom); + O(1, 1, 1, 1) = 1; + O(3, 1, 3, 1) = 1; + O(1, 1, 2, 1) = L; + O(2, 1, 3, 1) = R; + + if ~isempty(Honesite) + local_op = MpoTensor.decompose_local_operator(Honesite, newkwargs{:}); + assert(leftvspace(local_op{1}) == subspaces(leftvspace(O), 1) && ... + rightvspace(local_op{1}) == subspaces(rightvspace(O), 3) && ... + pspace(local_op) == subspaces(pspace(O), 1), ... + 'operators:spacemismatch', ... + 'onesite operator incompatible with twosite operator.'); + O(1, 1, 3, 1) = local_op{1}; + end + + mpo = InfJMpo(O); + end + end end diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index a88f14c..e25148e 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -422,7 +422,7 @@ function disp(O) N = indin(H); local_operators = cell(1, N); if N == 1 - local_operators{1} = insert_onespace(insert_onespace(H, 1), 3); + local_operators{1} = insert_onespace(insert_onespace(H, 1), 3, true); else [u, s, v] = tsvd(H, [1 N+1], [2:N N+2:2*N], kwargs.Trunc{:}); local_operators{1} = insert_onespace(tpermute(u * s, [1 3 2], [1 2]), 1); @@ -433,7 +433,7 @@ function disp(O) local_operators{i} = tpermute(u * s, [1 2 4 3], [2 2]); end - local_operators{N} = insert_onespace(repartition(v, [2 1]), 3); + local_operators{N} = insert_onespace(repartition(v, [2 1]), 3, true); end local_operators = cellfun(@MpoTensor, local_operators, 'UniformOutput', false); From 7c7b3a622b532e82fa819b4bc160fee3d23089fe Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 7 Mar 2023 11:37:23 +0100 Subject: [PATCH 175/245] nullspace thingies --- src/mps/MpsTensor.m | 18 ++++++++++++++- src/tensors/Tensor.m | 55 ++++++++++++++++++++++++++++++++++++-------- test/TestTensor.m | 9 +++++++- 3 files changed, 70 insertions(+), 12 deletions(-) diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index cd72afa..7fbf394 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -130,7 +130,7 @@ if numel(A) > 1 for i = numel(A):-1:1 - A(i) = leftorth(A(i), alg); + A(i) = leftnull(A(i), alg); end return end @@ -141,6 +141,22 @@ A.var = leftnull(A.var, p1, p2, alg); end + function A = rightnull(A, alg) + arguments + A + alg = 'svd' + end + + if numel(A) > 1 + for i = numel(A):-1:1 + A(i) = rightnull(A(i), alg); + end + return + end + + A.var = rightnull(A.var, 1, 2:nspaces(A), alg); + end + function d = dot(A, B) if isa(A, 'MpsTensor') A = A.var; diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 1fb99a4..bc9adef 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -1709,26 +1709,61 @@ if isempty(p1), p1 = 1:rank(t, 1); end if isempty(p2), p2 = rank(t, 1) + (1:rank(t, 2)); end - t = tpermute(t, [p1 p2], [length(p1) length(p2)]); + [U, S] = tsvd(t, p1, p2); dims = struct; - [mblocks, dims.charges] = matrixblocks(t); - Ns = cell(size(mblocks)); - dims.degeneracies = zeros(size(mblocks)); + [Sblocks, c1] = matrixblocks(S); + [Ublocks, c2] = matrixblocks(U); - for i = 1:length(mblocks) - Ns{i} = leftnull(mblocks{i}, alg, atol); - dims.degeneracies(i) = size(Ns{i}, 2); + lia = ismember_sorted(c2, c1); + ctr = 0; + for i = 1:length(Ublocks) + if ~lia(i), continue; end + ctr = ctr + 1; + u = Ublocks{i}; + s = Sblocks{ctr}; + if size(s, 2) == 1 + diags = s(1); + else + diags = diag(s); + end + r = sum(diags > atol); + Ublocks{i} = u(:, (r + 1):size(u, 1)); + end + dims.degeneracies = cellfun(@(x) size(x, 2), Ublocks); + dims.charges = c2; + mask = dims.degeneracies > 0; - dims.charges = dims.charges(mask); + dims.charges = c2(mask); dims.degeneracies = dims.degeneracies(mask); - Ns = Ns(mask); + Ns = Ublocks(mask); W = t.codomain.new(dims, false); - N = t.eye(t.codomain, W); + N = t.eye(U.codomain, W); N.var = fill_matrix_data(N.var, Ns, dims.charges); + +% t = tpermute(t, [p1 p2], [length(p1) length(p2)]); +% +% dims = struct; +% [mblocks, dims.charges] = matrixblocks(t); +% Ns = cell(size(mblocks)); +% dims.degeneracies = zeros(size(mblocks)); +% +% for i = 1:length(mblocks) +% Ns{i} = leftnull(mblocks{i}, alg, atol); +% dims.degeneracies(i) = size(Ns{i}, 2); +% end +% +% mask = dims.degeneracies > 0; +% dims.charges = dims.charges(mask); +% dims.degeneracies = dims.degeneracies(mask); +% Ns = Ns(mask); +% +% W = t.codomain.new(dims, false); +% N = t.eye(t.codomain, W); +% N.var = fill_matrix_data(N.var, Ns, dims.charges); end function N = rightnull(t, p1, p2, alg, atol) diff --git a/test/TestTensor.m b/test/TestTensor.m index 7fe3917..b3f690c 100644 --- a/test/TestTensor.m +++ b/test/TestTensor.m @@ -298,7 +298,10 @@ function nullspace(tc, spaces) %% Left nullspace for alg = ["qr", "svd"] N = leftnull(t, [3 4 2], [1 5], alg); - + dimN = dims(N, nspaces(N)); + dimW = prod(dims(t, [3 4 2])); + dimV = prod(dims(t, [1 5])); + tc.assertTrue(dimW == dimN + dimV); assertTrue(tc, norm(N' * tpermute(t, [3 4 2 1 5], [3 2])) < ... 100 * eps(norm(t)), ... 'N should be a left nullspace.'); @@ -311,6 +314,10 @@ function nullspace(tc, spaces) %% Right nullspace for alg = ["lq", "svd"] N = rightnull(t, [3 4], [2 1 5], alg); + dimN = dims(N, 1); + dimW = prod(dims(t, [3 4])); + dimV = prod(dims(t, [2 1 5])); + tc.assertTrue(dimV == dimW + dimN); assertTrue(tc, norm(tpermute(t, [3 4 2 1 5], [2 3]) * N') < ... 100 * eps(norm(t)), ... 'N should be a right nullspace.'); From abcdd01fcd46d3d1d42ffd8ebada486c3726a12d Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 7 Mar 2023 11:37:48 +0100 Subject: [PATCH 176/245] add todo for spacechecks --- src/sparse/SparseTensor.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index d34e770..5c57ba7 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -936,13 +936,15 @@ function disp(t) end end - function n = numArgumentsFromSubscript(t, ~, ~) + function n = numArgumentsFromSubscript(~, ~, ~) n = 1; end function t = subsasgn(t, s, v) assert(strcmp(s(1).type, '()'), 'sparse:index', 'only () indexing allowed'); + % Todo: check spaces when assigning + if length(s(1).subs) == 1 I = ind2sub_(t.sz, s(1).subs{1}); s(1).subs = arrayfun(@(x) I(:,x), 1:width(I), 'UniformOutput',false); From c294646438ba43cae6bf2c5e13b322588954080f Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 7 Mar 2023 11:38:16 +0100 Subject: [PATCH 177/245] update tests --- test/TestInfJMpo.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/TestInfJMpo.m b/test/TestInfJMpo.m index 9e654d2..a6f193c 100644 --- a/test/TestInfJMpo.m +++ b/test/TestInfJMpo.m @@ -203,7 +203,7 @@ function test1dIsing(tc) tc.verifyTrue(isapprox(lambda2, -0.53, 'RelTol', 1e-2)) p = 0; - qp = InfQP.randnc(mps2, mps2, p, Z2(2)); + qp = InfQP.randnc(mps2, mps2, p, Z2(0)); [qp, mu] = excitations(QPAnsatz(), mpo, qp); tc.verifyEqual(mu, 0.5, 'AbsTol', 1e-8); @@ -213,7 +213,7 @@ function test1dIsing(tc) tc.verifyTrue(isapprox(lambda2/2, -0.53, 'RelTol', 1e-2)) p = 0; - qp = InfQP.randnc(mps2, mps2, p, Z2(2)); + qp = InfQP.randnc(mps2, mps2, p, Z2(0)); [qp, mu] = excitations(QPAnsatz(), mpo, qp); tc.verifyEqual(mu, 0.5, 'AbsTol', 1e-8); end @@ -227,9 +227,9 @@ function test1dHeisenberg(tc) [gs_mps] = fixedpoint(alg, mpo, mps); lambda = expectation_value(gs_mps, mpo); - tc.verifyEqual(lambda, -1.4, 'RelTol', 1e-2); + tc.verifyEqual(lambda, -1.401, 'RelTol', 1e-2); - p = 0; + p = pi; charge = SU2(3); qp = InfQP.randnc(gs_mps, gs_mps, p, charge); From 6d8ce0956e334f318d01fd401eb4b8a4c66bfbdc Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 7 Mar 2023 11:38:31 +0100 Subject: [PATCH 178/245] script for testing qp --- test/testExcitations.m | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 test/testExcitations.m diff --git a/test/testExcitations.m b/test/testExcitations.m new file mode 100644 index 0000000..2d4fd7c --- /dev/null +++ b/test/testExcitations.m @@ -0,0 +1,33 @@ +%% Groundstate search +alg = Vumps('which', 'smallestreal', 'maxiter', 100); + +symmetry = 'U1'; + +mpo = quantum1dHeisenberg('Spin', 1, 'Symmetry', symmetry); + +switch symmetry + case 'SU2' + vspace = GradedSpace.new(SU2(2:2:6), [5 5 1], false); + case 'U1' + vspace = GradedSpace.new(U1(-3:2:3), [2 5 5 2], false); + otherwise + error('invalid symmetry'); +end + +mps = initialize_mps(mpo, vspace); + +if ~exist('gs_mps', 'var') + [gs_mps] = fixedpoint(alg, mpo, mps); +end + +lambda = expectation_value(gs_mps, mpo); +assert(isapprox(lambda, -1.401, 'RelTol', 1e-2)); + +%% Excitations +p = pi; +charge = U1(0); +qp = InfQP.randnc(gs_mps, gs_mps, p, charge); +tic; +[qp, mu] = excitations(QPAnsatz(), mpo, qp); +toc; +mu From 0092d60b17bf73547e26af40cbffb01fef50b887 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 7 Mar 2023 12:54:15 +0100 Subject: [PATCH 179/245] correlation length, marek gap, mps utilities --- src/mps/UniformMps.m | 117 ++++++++++++++++++++++++++++++++++- src/tensors/AbstractTensor.m | 5 +- test/TestUniformMps.m | 3 + 3 files changed, 121 insertions(+), 4 deletions(-) diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index 1f568ab..ce98f62 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -524,7 +524,7 @@ [V, D] = eigsolve(T, v0, howmany, which, eigkwargs{:}); if kwargs.Type(1) == 'r', V = V'; end - if nargout < 2, V = D; end + if nargout < 2, V = diag(D); end end function f = fidelity(mps1, mps2, kwargs) @@ -665,8 +665,119 @@ function plot_entanglementspectrum(mps, d, w, ax) mps.AC = desymmetrize(mps.AC); end - xi = CorrelationLength(mps, charge); - [epsilon, delta, spectrum] = MarekGap(mps, charge, angle, num) + function [xi, theta] = correlation_length(mps, charge) + % Compute the correlation length of an MPS in a given charge sector. + % + % Usage + % ----- + % :code:`[xi, theta] = correlation_length(mps, charge)` + % + % Arguments + % --------- + % mps : :class:`UniformMps` + % input mps. + % + % charge : :class:`AbstractCharge` + % charge sector for correlation length to target. + % + % Returns + % ------- + % xi : numeric + % correlation length in the given charge sector. + % + % theta : numeric + % angle of the corresponding oscillation period. + + arguments + mps + charge = [] + end + + if isempty(charge) || charge == one(charge) + f = transfereigs(mps, mps, 5, 'KrylovDim', 30); + if abs(f(1) - 1) > 1e-12 + warning('mps:noninjective', ... + 'mps might be non-injective:\n%s', num2str(f)); + end + epsilon = -log(abs(f(2))); + theta = angle(f(2)); + else + f = transfereigs(mps, mps, 1, 'KrylovDim', 20, 'Charge', charge); + epsilon = -log(abs(f)); + theta = angle(f); + end + + xi = 1 / epsilon; + end + + function [epsilon, delta, spectrum] = marek_gap(mps, charge, kwargs) + % Compute the Marek gap of an MPS in a given charge sector. + % + % Usage + % ----- + % :code:`[epsilon, delta, spectrum] = marek_gap(mps, charge, kwargs)` + % + % Arguments + % --------- + % mps : :class:`UniformMps` + % input mps. + % + % charge : :class:`AbstractCharge` + % charge sector for correlation length to target. + % + % Keyword Arguments + % ----------------- + % HowMany : int + % amount of transfer matrix eigenvalues to compute. + % + % Angle : numeric + % angle in radians around which the gap should be computed. + % + % AngleTol : numeric + % tolerance in radians for angles to be considered equal. + % + % Returns + % ------- + % epsilon : numeric + % inverse correlation length in the given charge sector. + % + % delta : numeric + % refinement parameter. + % + % spectrum : numeric + % computed partial transfer matrix spectrum. + arguments + mps + charge = [] + kwargs.Angle + kwargs.AngleTol = 1e-1 + kwargs.HowMany = 20 + end + + spectrum = transfereigs(mps, mps, kwargs.HowMany, 'largestabs', 'Charge', charge); + [d, p] = sort(abs(spectrum), 'descend'); + inds = d > 1 - 1e-12; + + if ((isempty(charge) || charge == one(charge)) && sum(inds) > 1) || ... + sum(inds) > 0 + warning('mps:noninjective', ... + 'mps might be non-injective:\n%s', num2str(spectrum)); + end + + d(inds) = []; + p(inds) = []; + + if abs(diff(unwrap(angle(spectrum(p(1:2)))))) > 1e-1 + warning('comparing values with different angles:\n%s', num2str(spectrum)); + end + if isfield('Angle', kwargs) + error('tba'); + end + + epsilon = -log(d(1)); + delta = log(d(1) / d(2)); + end + S = EntanglementEntropy(mps, loc); S = RenyiEntropy(mps,n, loc); E = ExpectationValue(mps, W, GL, GR) diff --git a/src/tensors/AbstractTensor.m b/src/tensors/AbstractTensor.m index 1287ff9..4ca5d49 100644 --- a/src/tensors/AbstractTensor.m +++ b/src/tensors/AbstractTensor.m @@ -255,7 +255,10 @@ x0_vec = vectorize(x0); sz = size(x0_vec); - assert(sz(1) >= howmany); + if sz(1) < howmany + warning('requested %d out of %d eigenvalues.', howmany, sz(1)); + howmany = min(howmany, sz(1)); + end if isa(A, 'function_handle') A_fun = @(x) vectorize(A(devectorize(x, x0))); diff --git a/test/TestUniformMps.m b/test/TestUniformMps.m index 92ae323..7e96f4e 100644 --- a/test/TestUniformMps.m +++ b/test/TestUniformMps.m @@ -85,6 +85,9 @@ function testTransferEigs(tc, A) [~, charges] = matrixblocks(V); [V2, D2] = transfereigs(mps, mps, 1, 'largestabs', 'Charge', one(charges)); + xi = correlation_length(mps); + [epsilon, delta] = marek_gap(mps); + tc.verifyEqual(xi, 1/epsilon, 'AbsTol', 1e-12); end end end From 6525b36ac6c38f46779b5331cef2c99ef9ece36d Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 7 Mar 2023 14:08:58 +0100 Subject: [PATCH 180/245] add entanglement/renyi entropies --- src/mps/UniformMps.m | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index ce98f62..1d6b1bd 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -778,6 +778,36 @@ function plot_entanglementspectrum(mps, d, w, ax) delta = log(d(1) / d(2)); end + function S = entanglement_entropy(mps, w) + arguments + mps + w = 1 + end + + [svals, charges] = schmidt_values(mps, w); + + S = 0; + for i = 1:length(svals) + S = S - qdim(charges(i)) * sum(svals{i}.^2 .* log(svals{i}.^2)); + end + end + + function S = renyi_entropy(mps, n, w) + arguments + mps + n + w = 1 + end + + [svals, charges] = schmidt_values(mps, w); + + S = 0; + for i = 1:length(svals) + S = S + qdim(charges(i)) * sum(svals{i}.^(2 * n)); + end + S = 1 / (1 - n) * log(S); + end + S = EntanglementEntropy(mps, loc); S = RenyiEntropy(mps,n, loc); E = ExpectationValue(mps, W, GL, GR) From 4e504d7f181b102b807c5180dab544b9ef7536d9 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Fri, 10 Mar 2023 10:30:37 +0100 Subject: [PATCH 181/245] time in milli and microseconds --- src/utility/time2str.m | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/utility/time2str.m b/src/utility/time2str.m index 1634e02..9de752f 100644 --- a/src/utility/time2str.m +++ b/src/utility/time2str.m @@ -22,10 +22,18 @@ case 'm' time = time / 60; + case 's' + + case 'ms' + time = time * 1000; + + case 'mus' + time = time * 1000000; + end -timeStr = sprintf('%0.*f%c', precision, time, unit); +timeStr = sprintf('%0.*f%s', precision, time, unit); end From 69e10b48a63cbde5e64ea5543414afdf255d8500 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Fri, 10 Mar 2023 12:59:19 +0100 Subject: [PATCH 182/245] update entanglement spectra plotting --- src/mps/UniformMps.m | 47 +++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index 1f568ab..1560996 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -607,12 +607,14 @@ svals = cellfun(@diag, svals, 'UniformOutput', false); end - function plot_entanglementspectrum(mps, d, w, ax) + function plot_entanglementspectrum(mps, d, w, ax, kwargs) arguments mps d = 1:depth(mps) w = 1:period(mps) ax = [] + kwargs.SymmetrySort = true + kwargs.ExpandQdim = false end if isempty(ax) figure; @@ -623,35 +625,44 @@ function plot_entanglementspectrum(mps, d, w, ax) end end end - - lim_y = 1; + hold off for dd = 1:length(d) for ww = 1:length(w) [svals, charges] = schmidt_values(mps(dd), w(ww)); + if kwargs.ExpandQdim + for i = 1:length(svals) + svals{i} = reshape(repmat(svals{i}, 1, qdim(charges(i))), [], 1); + end + end ctr = 0; - hold off; - lim_x = 1; - - ticks = zeros(size(svals)); labels = arrayfun(@string, charges, 'UniformOutput', false); lengths = cellfun(@length, svals); ticks = cumsum(lengths); - try - semilogy(ax(dd, ww), 1:sum(lengths), vertcat(svals{:}).', '.', 'MarkerSize', 10); - catch - bla + if kwargs.SymmetrySort + for i = 1:length(svals) + semilogy(ax(dd, ww), ctr+(1:lengths(i)), svals{i}, '.', 'MarkerSize', 10, 'Color', colors(i)); + if i == 1, hold(ax(dd,ww), 'on'); end + ctr = ctr + lengths(i); + end + set(ax(dd, ww), 'Xtick', ticks, 'fontsize', 10, ... + 'XtickLabelRotation', 60, 'Xgrid', 'on'); + else + [~, p] = sort(vertcat(svals{:}), 'descend'); + p = invperm(p); + for i = 1:length(svals) + semilogy(ax(dd, ww), p(ctr+(1:lengths(i))), svals{i}, '.', 'MarkerSize', 10, 'Color', colors(i)); + if i == 1, hold(ax(dd,ww), 'on'); end + ctr = ctr + lengths(i); + end + end + legend(ax(dd, ww), labels) set(ax(dd, ww), 'TickLabelInterpreter', 'latex'); - set(ax(dd, ww), 'Xtick', ticks, 'XTickLabel', labels, 'fontsize', 10, ... - 'XtickLabelRotation', 60, 'Xgrid', 'on'); xlim(ax(dd, ww), [1 - 1e-8 ticks(end) + 1e-8]); - end end - -% for ww = 1:length(w) -% ylim(ax(1, ww), [10^(floor(log10(lim_y))) 1]); -% end + hold off + linkaxes(ax, 'y'); end function mps = desymmetrize(mps) From d41c760150f3eadd1e060b7a53b78ca861533b34 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Fri, 10 Mar 2023 13:02:25 +0100 Subject: [PATCH 183/245] convenience constructors for common spacetypes --- src/tensors/spaces/SU2Space.m | 11 +++++++++++ src/tensors/spaces/U1Space.m | 11 +++++++++++ src/tensors/spaces/Z2Space.m | 11 +++++++++++ 3 files changed, 33 insertions(+) create mode 100644 src/tensors/spaces/SU2Space.m create mode 100644 src/tensors/spaces/U1Space.m create mode 100644 src/tensors/spaces/Z2Space.m diff --git a/src/tensors/spaces/SU2Space.m b/src/tensors/spaces/SU2Space.m new file mode 100644 index 0000000..53fd3fb --- /dev/null +++ b/src/tensors/spaces/SU2Space.m @@ -0,0 +1,11 @@ +function V = SU2Space(charges, bonds, isdual) +% Convenience constructor for SU2-graded spaces. +arguments + charges + bonds + isdual = false +end + +V = GradedSpace.new(SU2(charges), bonds, isdual); + +end diff --git a/src/tensors/spaces/U1Space.m b/src/tensors/spaces/U1Space.m new file mode 100644 index 0000000..48dbfb8 --- /dev/null +++ b/src/tensors/spaces/U1Space.m @@ -0,0 +1,11 @@ +function V = U1Space(charges, bonds, isdual) +% Convenience constructor for U1-graded spaces. +arguments + charges + bonds + isdual = false +end + +V = GradedSpace.new(U1(charges), bonds, isdual); + +end diff --git a/src/tensors/spaces/Z2Space.m b/src/tensors/spaces/Z2Space.m new file mode 100644 index 0000000..4a6feee --- /dev/null +++ b/src/tensors/spaces/Z2Space.m @@ -0,0 +1,11 @@ +function V = Z2Space(charges, bonds, isdual) +% Convenience constructor for Z2-graded spaces. +arguments + charges + bonds + isdual = false +end + +V = GradedSpace.new(Z2(charges), bonds, isdual); + +end From 35124e56d1b44f40bf83a6c06157c0ec0591180c Mon Sep 17 00:00:00 2001 From: leburgel Date: Sun, 12 Mar 2023 13:25:29 +0100 Subject: [PATCH 184/245] Hold fix in entanglement spectrum plots --- src/mps/UniformMps.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index 3510418..2352de3 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -625,9 +625,9 @@ function plot_entanglementspectrum(mps, d, w, ax, kwargs) end end end - hold off for dd = 1:length(d) for ww = 1:length(w) + hold(ax(dd,ww), 'off'); [svals, charges] = schmidt_values(mps(dd), w(ww)); if kwargs.ExpandQdim for i = 1:length(svals) From df4d4b155e99d67f89088070d419d2b4f9a72544 Mon Sep 17 00:00:00 2001 From: leburgel Date: Mon, 27 Mar 2023 12:23:57 +0200 Subject: [PATCH 185/245] First pass at naive bond expansion --- src/algorithms/Expand.m | 345 ++++++++++++++++++++++++++++++++++++++++ src/mps/InfMpo.m | 4 +- src/mps/UniformMps.m | 2 +- 3 files changed, 348 insertions(+), 3 deletions(-) create mode 100644 src/algorithms/Expand.m diff --git a/src/algorithms/Expand.m b/src/algorithms/Expand.m new file mode 100644 index 0000000..c3af194 --- /dev/null +++ b/src/algorithms/Expand.m @@ -0,0 +1,345 @@ +classdef Expand + % Bond expansion algorithm for uniform matrix product states. + + %% Options + properties + bondsmethod {mustBeMember(bondsmethod, ... + {'off', 'factor', 'extrapolate', 'twosite'})} = 'factor' + + chargesmethod {mustBeMember(chargesmethod, ... + {'off', 'fusionproduct', 'twosite'})} = 'off' + + % general expansion options + schmidtcut = 1e-5 + notrunc = false + + noisefactor = 1e-3 + which = 'largestabs' % needed for twosite update; needs to be passed from fixedpoint algorithm! + + % bond expansion options + minbond = 1 + maxbond = 1e9 + tolbond = 0.2 + bondfactor = 1.2 + cutfactor = 1 + + % charge expansion options + mincharges = 2 + + % optional finalization + finalize {mustBeMember(finalize, {'nothing', 'vomps'})} = 'nothing' + end + + + %% + methods + function v = Expand(kwargs) + arguments + kwargs.?Expand + end + + fields = fieldnames(kwargs); + if ~isempty(fields) + for field = fields.' + v.(field{1}) = kwargs.(field{1}); + end + end + end + + function [mps, flag] = changebonds(alg, mpo, mps) + % Change sectors and bond dimensions of mps virtual spaces. + + % determine new charges and bonds + [addbond, addcharge, flag] = determine_new_bonds(alg, mpo, mps); + + % expand mps tensors + mps = expand_mps(alg, mps, addbond, addcharge); + + % finalize + if strcmp(alg.finalize, 'vomps') + error('tbd') + end + end + + function [addbond, addcharge, flag] = determine_new_bonds(alg, mpo, mps) + % Determine which charges and bond dimensions to add/remvove in mps virtual + % spaces. + + addbond = cell(depth(mps), period(mps)); + addcharge = cell(depth(mps), period(mps)); + + for d = 1:depth(mps) + for w = 1:period(mps) + if strcmp(alg.bondsmethod, 'twosite') || strcmp(alg.chargesmethod, 'twosite') + % twosite expansion takes care of both bonds and charges at the same time + [addbond{d, w}, addcharge{d, w}] = expand_twosite(alg, mpo, mps, d, w); + else + % heuristic charge and bond expansion based on current spectrum + [svals, charges] = schmidt_values(mps(d), w); + addbond{d, w} = expand_bonds(alg, svals); + addcharge{d, w} = expand_charges(alg, mps, d, w, svals, charges); + end + if alg.notrunc + addbond{d,w} = max(0, addbond{d,w}); + end + flag = addcharge{d, w}.flag ~= 0 || nnz(addbond{d, w}) > 0; + end + end + end + + + function [addbond, addcharge] = expand_twosite(alg, mpo, mps, d, w) + % Does a twosite update and adds/removes charges that are above/under the cut + % in the new spectrum. + % For larger unit cells, cut is chosen to the left of site w, so + % couple sites w-1 and w. + + [svals, charges] = schmidt_values(mps(d), w); + dd = prev(d, depth(mps)); + ww = prev(w, period(mps)); + + % perform twosite update, take SVD and normalize + [GL, GR] = environments(Vumps, mpo, mps); % should be able to input this... + H_AC2 = AC2_hamiltonian(mpo, mps, GL, GR, ww); + AC2 = MpsTensor(contract(mps(dd).AC(ww), [-(1:mps(dd).AC(ww).plegs+1), 1], ... + mps(dd).AR(w), [1, -(1:mps(dd).AR(w).plegs+1) - 1 - mps(dd).AC(ww).plegs], ... + 'Rank', [1 + mps(dd).AC(ww).plegs, 1 + mps(dd).AR(w).plegs])); + [AC2.var, ~] = eigsolve(H_AC2{1}, AC2.var, 1, alg.which); + [~, C2, ~] = tsvd(AC2.var, ... + 1:mps(dd).AC(ww).plegs+1, ... + (1:mps(dd).AR(w).plegs+1) + 1 + mps(dd).AC(ww).plegs, ... + 'TruncBelow', alg.schmidtcut, ... + 'TruncDim', alg.maxbond); + [svals2, charges2] = matrixblocks(C2); + + added_charges = setdiff(charges2, charges); + removed_charges = setdiff(charges, charges2); + if ~isempty(removed_charges) && ~alg.notrunc + % need to remove sectors + addcharge.flag = -1; + addcharge.charges = removed_charges; + elseif ~isempty(added_charges) + % need to add sectors + addcharge.flag = +1; + addcharge.charges = added_charges; + addcharge.bonds = ones(size(added_charges)); + for ii = 1:size(added_charges) + addcharge.bonds(ii) = length(svals2{added_charges(ii) == charges2}); % TODO: will this work for product charges? + end + else + % do nothing + addcharge.flag = 0; + end + + % determine new bonds + addbond = zeros(size(svals)); + for ii = 1:length(svals) + if ismember(charges(ii), charges2) + chi_new = length(svals2{charges(ii) == charges2}); + chi_new = between(alg.minbond, chi_new, alg.maxbond); + addbond(ii) = chi_new - length(svals{ii}); + else % charge was removed + addbond(ii) = 0; + end + end + + % enforce alg.notrunc option + if alg.notrunc + if addcharge.flag == -1 + addcharge = struct('flag', 0, 'bonds', [], 'charges', []); + end + addbond = max(addbond, 0); + end + + end + + function addcharge = expand_charges(alg, mps, d, w, svals, charges) + % Determine whether to add or substract sectors. + % addcharge.flag = {+1 [0] -1} for addition/subtraction of sectors. + % addcharge.charges = [new_charges] + % addcharge.bonds = [new_bonds] + + addcharge = struct('flag', false, 'bonds', [], 'charges', []); + + % determine cut if alg.maxbond is reached: + cut = alg.schmidtcut; + for ii = 1:length(svals) + if length(svals{ii}) > alg.maxbond + cut = max(cut, svals{ii}(alg.maxbond)); + end + end + + highest_schmidt = cellfun(@(x) x(1), svals); + + switch alg.chargesmethod + + case {'off', []} + + addcharge.flag = 0; + + case 'fusionproduct' + % Standard case, adds all remaining charges in fusion product of virtual + % and physical space if none of current charges has a highest schmidt + % value above the specified cut. + % Removes all but one charge whose highest schmidt value falls below + % the specified cut. + + if all(highest_schmidt > cut) + % need to add sectors + addcharge.flag = +1; + ww = prev(w, period(mps)); + new_sector = leftvspace(mps(d), ww) * prod(pspace(mps, ww)); + new_charges = new_sector.dimensions.charges; + addcharge.charges = setdiff(new_charges, charges); + addcharge.bonds = ones(size(addcharge.charges)); + elseif length(svals) > alg.mincharges && sum(highest_schmidt < cut) >= 2 + % need to remove sectors + addcharge.flag = -1; + below = find(highest_schmidt < cut); + [~, keep] = max(highest_schmidt(below)); + below(keep) = []; + addcharge.charges = charges(below); + else + % do nothing + addcharge.flag = 0; + end + + end + + % enforce alg.notrunc option + if addcharge.flag == -1 && alg.notrunc + addcharge = struct('flag', 0, 'bonds', [], 'charges', []); + end + + end + + function addbond = expand_bonds(alg, svals) + % Determine whether to add or substract bond dimension. Returns an 1 x n array + % :code:`addbond` of integers, where :code:`addbond(i)` is the bond dimension to + % be added to/subtracted from sector :code:`i`. + + % recursive + if iscell(svals) + addbond = zeros(size(svals)); + for ii = 1:length(svals) + addbond(ii) = expand_bonds(alg, svals{ii}); + end + return + end + + cut = alg.schmidtcut; + if length(svals) > alg.maxbond + cut = max(cut, svals(alg.maxbond)); + end + chi = length(svals); + chi_tol = ceil(alg.tolbond * chi); + + switch alg.bondsmethod + case 'factor' + % new bond is factor * old bond + + if svals(end) > cut + % need to add bond + chi_new = ceil(alg.bondfactor * chi); + elseif chi > alg.minbond && svals(max(1, chi-chi_tol)) < cut && ~alg.notrunc + % need to subtract bond + chi_new = min(nnz(svals > cut) + floor(chi_tol / 2), chi); + else + % do nothing + chi_new = chi; + end + + case 'extrapolate' + % do an extrapolation in order to determine new bond + + if svals(end) > cut + % need to add bond + chi_new = extrapolate_schmidt(svals, cut) + floor(chi_tol / 2); + elseif chi > alg.minond && svals(max(1, chi-chi_tol)) < cut && ~alg.notrunc + % need to subtract bond + chi_new = min(nnz(svals > cut) + 1 + ceil(chi_tol / 2), chi); + else + % do nothing + chi_new = chi; + end + + otherwise + % no method specified + chi_new = chi; + + end + + % enforce min/max bonds. + chi_new = between(alg.minbond, chi_new, alg.maxbond); + addbond = chi_new - chi; + + % enforce alg.notrunc option + if addbond < 0 && alg.notrunc + addbond = 0; + end + + % auxiliary function + function chi = extrapolate_schmidt(svals, cut) + if length(svals) < 5 + chi = max(alg.minbond, ceil(alg.bondfactor * length(svals))); + return + end + y = reshape(log10(svals), [length(svals), 1]); + x = reshape(1:length(y), [length(svals), 1]); + range = floor(length(y) * 1/3):length(y); + ab = [x(range), 1 + 0 * x(range)] \ y(range); + chi = ceil((log10(cut / alg.cutfactor) -ab(2)) / ab(1)); + end + end + + function mps = expand_mps(alg, mps, addbond, addcharge) + % Update mps virtual spaces according to given charge and bond expansion. + + for d = 1:depth(mps) + % make canonical + mps(d) = canonicalize(mps(d), 'DiagC', true); % TODO: why DiagC? + % expand tensors + newAR = mps(d).AR; + for w = 1:period(mps) + ww = next(w, period(mps)); + addBnd = struct('left', addbond{d, w}, 'right', addbond{d, ww}); + addCh = struct('left', addcharge{d, w}, 'right', addcharge{d, ww}); + newAR(w) = expand_tensor(mps(d).AR(w), addBnd, addCh); + end + % convert to UniformMps after expansion + mps(d) = UniformMps(newAR); + end + + function newAR = expand_tensor(AR, addbond, addcharge) + % fuck + vspaces.left = leftvspace(AR); + vspaces.right = rightvspace(AR); + + for side = ["left", "right"] + + % add/remove bonds + vspaces.(side).dimensions.degeneracies = vspaces.(side).dimensions.degeneracies ... + + addbond.(side); + + if addcharge.(side).flag == +1 % add charges + vspaces.(side).dimensions.charges = [vspaces.(side).dimensions.charges, addcharge.(side).charges]; + vspaces.(side).dimensions.degeneracies = [vspaces.(side).dimensions.degeneracies, addcharge.(side).bonds]; + + elseif addcharge.(side).flag == -1 % remove charges + keep = ~ismember(vspaces.(side).dimensions.charges, addcharge.(side).charges); + vspaces.(side).dimensions.charges = vspaces.(side).dimensions.charges(keep); + vspaces.(side).dimensions.degeneracies = vspaces.(side).dimensions.degeneracies(keep); + + end + end + + newAR = MpsTensor(Tensor.randnc([vspaces.left], [space(AR, 2:AR.plegs+1), vspaces.right]', 'Rank', rank(AR))); + + newAR.var = embed(AR.var, newAR.var .* alg.noisefactor); + + end + + + end + end +end diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index 224540b..580a7a9 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -234,8 +234,8 @@ arguments mpo mps - GL = fixedpoint(transfermatrix(mpo, mps, 'Type', 'LL')) - GR = fixedpoint(transfermatrix(mpo, mps, 'Type', 'RR').') + GL = fixedpoint(transfermatrix(mpo, mps, 'Type', 'LL')) % BROKEN + GR = fixedpoint(transfermatrix(mpo, mps, 'Type', 'RR').') % BROKEN sites = 1:period(mps) end diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index 2352de3..6578990 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -63,7 +63,7 @@ end mps = reshape(mps, size(mps)); - elseif isa(varargin{1}, 'Tensor') + elseif isa(varargin{1}, 'Tensor') || isa(varargin{1}, 'MpsTensor') % by default we take the input as AR, as canonicalize right % orthogonalizes before left orthogonalization for i = height(varargin{1}):-1:1 From 33c99a3eed9e74b9a01146f47a6132bbb986338a Mon Sep 17 00:00:00 2001 From: leburgel Date: Mon, 27 Mar 2023 16:20:05 +0200 Subject: [PATCH 186/245] Fix off by one; actually use `MpsTensor.expand` --- src/algorithms/Expand.m | 114 +++++++++++++++++----------------------- src/mps/MpsTensor.m | 19 ++++--- 2 files changed, 60 insertions(+), 73 deletions(-) diff --git a/src/algorithms/Expand.m b/src/algorithms/Expand.m index c3af194..d2b3ad6 100644 --- a/src/algorithms/Expand.m +++ b/src/algorithms/Expand.m @@ -49,11 +49,16 @@ function [mps, flag] = changebonds(alg, mpo, mps) % Change sectors and bond dimensions of mps virtual spaces. + % canonicalize before starting + for d = 1:depth(mps) + mps(d) = canonicalize(mps(d)); + end + % determine new charges and bonds - [addbond, addcharge, flag] = determine_new_bonds(alg, mpo, mps); + [new_spaces, flag] = determine_new_spaces(alg, mpo, mps); % expand mps tensors - mps = expand_mps(alg, mps, addbond, addcharge); + mps = expand_mps(alg, mps, new_spaces); % finalize if strcmp(alg.finalize, 'vomps') @@ -61,28 +66,45 @@ end end - function [addbond, addcharge, flag] = determine_new_bonds(alg, mpo, mps) + function [new_spaces, flag] = determine_new_spaces(alg, mpo, mps) % Determine which charges and bond dimensions to add/remvove in mps virtual % spaces. - addbond = cell(depth(mps), period(mps)); - addcharge = cell(depth(mps), period(mps)); - - for d = 1:depth(mps) - for w = 1:period(mps) + for d = depth(mps):-1:1 + for w = period(mps):-1:1 if strcmp(alg.bondsmethod, 'twosite') || strcmp(alg.chargesmethod, 'twosite') % twosite expansion takes care of both bonds and charges at the same time - [addbond{d, w}, addcharge{d, w}] = expand_twosite(alg, mpo, mps, d, w); + [addbond, addcharge] = expand_twosite(alg, mpo, mps, d, w); else % heuristic charge and bond expansion based on current spectrum [svals, charges] = schmidt_values(mps(d), w); - addbond{d, w} = expand_bonds(alg, svals); - addcharge{d, w} = expand_charges(alg, mps, d, w, svals, charges); + addbond = expand_bonds(alg, svals); + addcharge = expand_charges(alg, mps, d, w, svals, charges); end if alg.notrunc - addbond{d,w} = max(0, addbond{d,w}); + addbond = max(0, addbond); + end + flag = addcharge.flag ~= 0 || nnz(addbond) > 0; + + new_space = rightvspace(mps(d).AR(w)); + + % add/remove bonds + new_space.dimensions.degeneracies = new_space.dimensions.degeneracies ... + + addbond; + + if addcharge.flag == +1 % add charges + new_space.dimensions.charges = [new_space.dimensions.charges, addcharge.charges]; + new_space.dimensions.degeneracies = [new_space.dimensions.degeneracies, addcharge.bonds]; + + elseif addcharge.flag == -1 % remove charges + keep = ~ismember(new_space.dimensions.charges, addcharge.charges); + new_space.dimensions.charges = new_space.dimensions.charges(keep); + new_space.dimensions.degeneracies = new_space.dimensions.degeneracies(keep); + end - flag = addcharge{d, w}.flag ~= 0 || nnz(addbond{d, w}) > 0; + + new_spaces(d, w) = new_space; + end end end @@ -91,23 +113,23 @@ function [addbond, addcharge] = expand_twosite(alg, mpo, mps, d, w) % Does a twosite update and adds/removes charges that are above/under the cut % in the new spectrum. - % For larger unit cells, cut is chosen to the left of site w, so - % couple sites w-1 and w. + % For larger unit cells, cut is chosen to the right of site w, so + % couple sites w and w+1. [svals, charges] = schmidt_values(mps(d), w); dd = prev(d, depth(mps)); - ww = prev(w, period(mps)); + ww = next(w, period(mps)); % perform twosite update, take SVD and normalize [GL, GR] = environments(Vumps, mpo, mps); % should be able to input this... - H_AC2 = AC2_hamiltonian(mpo, mps, GL, GR, ww); - AC2 = MpsTensor(contract(mps(dd).AC(ww), [-(1:mps(dd).AC(ww).plegs+1), 1], ... - mps(dd).AR(w), [1, -(1:mps(dd).AR(w).plegs+1) - 1 - mps(dd).AC(ww).plegs], ... - 'Rank', [1 + mps(dd).AC(ww).plegs, 1 + mps(dd).AR(w).plegs])); + H_AC2 = AC2_hamiltonian(mpo, mps, GL, GR, w); + AC2 = MpsTensor(contract(mps(dd).AC(w), [-(1:mps(dd).AC(w).plegs+1), 1], ... + mps(dd).AR(ww), [1, -(1:mps(dd).AR(ww).plegs+1) - 1 - mps(dd).AC(ww).plegs], ... + 'Rank', [1 + mps(dd).AC(w).plegs, 1 + mps(dd).AR(ww).plegs])); [AC2.var, ~] = eigsolve(H_AC2{1}, AC2.var, 1, alg.which); [~, C2, ~] = tsvd(AC2.var, ... - 1:mps(dd).AC(ww).plegs+1, ... - (1:mps(dd).AR(w).plegs+1) + 1 + mps(dd).AC(ww).plegs, ... + 1:mps(dd).AC(w).plegs+1, ... + (1:mps(dd).AR(ww).plegs+1) + 1 + mps(dd).AC(w).plegs, ... 'TruncBelow', alg.schmidtcut, ... 'TruncDim', alg.maxbond); [svals2, charges2] = matrixblocks(C2); @@ -292,54 +314,14 @@ end end - function mps = expand_mps(alg, mps, addbond, addcharge) + function mps = expand_mps(alg, mps, new_spaces) % Update mps virtual spaces according to given charge and bond expansion. for d = 1:depth(mps) - % make canonical - mps(d) = canonicalize(mps(d), 'DiagC', true); % TODO: why DiagC? - % expand tensors - newAR = mps(d).AR; - for w = 1:period(mps) - ww = next(w, period(mps)); - addBnd = struct('left', addbond{d, w}, 'right', addbond{d, ww}); - addCh = struct('left', addcharge{d, w}, 'right', addcharge{d, ww}); - newAR(w) = expand_tensor(mps(d).AR(w), addBnd, addCh); - end - % convert to UniformMps after expansion - mps(d) = UniformMps(newAR); + % expand tensors and convert to UniformMps + mps(d) = UniformMps(expand(mps(d).AR, new_spaces(d, :), alg.noisefactor)); end - - function newAR = expand_tensor(AR, addbond, addcharge) - % fuck - vspaces.left = leftvspace(AR); - vspaces.right = rightvspace(AR); - - for side = ["left", "right"] - - % add/remove bonds - vspaces.(side).dimensions.degeneracies = vspaces.(side).dimensions.degeneracies ... - + addbond.(side); - - if addcharge.(side).flag == +1 % add charges - vspaces.(side).dimensions.charges = [vspaces.(side).dimensions.charges, addcharge.(side).charges]; - vspaces.(side).dimensions.degeneracies = [vspaces.(side).dimensions.degeneracies, addcharge.(side).bonds]; - - elseif addcharge.(side).flag == -1 % remove charges - keep = ~ismember(vspaces.(side).dimensions.charges, addcharge.(side).charges); - vspaces.(side).dimensions.charges = vspaces.(side).dimensions.charges(keep); - vspaces.(side).dimensions.degeneracies = vspaces.(side).dimensions.degeneracies(keep); - - end - end - - newAR = MpsTensor(Tensor.randnc([vspaces.left], [space(AR, 2:AR.plegs+1), vspaces.right]', 'Rank', rank(AR))); - - newAR.var = embed(AR.var, newAR.var .* alg.noisefactor); - - end - - end + end end diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index f7e8a32..6abc475 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -533,16 +533,21 @@ end end - function A = expand(A, addspace) - NOISE_FACTOR = 1e-3; + function A = expand(A, addspace, noisefactor) + arguments + A + addspace + noisefactor = 1e-3 + end + for i = length(A):-1:1 spaces = space(A(i)); - spaces(1) = addspace(i); - spaces(nspaces(A(i)) - A(i).alegs) = conj(addspace(next(i, length(A)))); + spaces(nspaces(A(i)) - A(i).alegs) = addspace(i); + spaces(1) = conj(addspace(prev(i, length(A)))); r = rank(A(i)); - A(i) = embed(A(i), ... - NOISE_FACTOR * ... - normalize(A.randnc(spaces(1:r(1)), spaces(r(1)+1:end)'))); + A(i).var = embed(A(i).var, ... + noisefactor * ... + normalize(A(i).var.randnc(spaces(1:r(1)), spaces(r(1)+1:end)'))); end end From 27e142adfbe0c2691e38175e567d103b32662b0e Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 28 Mar 2023 10:08:05 +0200 Subject: [PATCH 187/245] fusiontrees(space) consistency --- src/tensors/spaces/AbstractSpace.m | 19 ++++++++++++++----- src/tensors/spaces/CartesianSpace.m | 17 ----------------- src/tensors/spaces/ComplexSpace.m | 17 ----------------- src/tensors/spaces/GradedSpace.m | 27 --------------------------- 4 files changed, 14 insertions(+), 66 deletions(-) diff --git a/src/tensors/spaces/AbstractSpace.m b/src/tensors/spaces/AbstractSpace.m index 5f61b39..a970898 100644 --- a/src/tensors/spaces/AbstractSpace.m +++ b/src/tensors/spaces/AbstractSpace.m @@ -120,20 +120,29 @@ end function trees = fusiontrees(codomain, domain) - % Compute all allowed fusiontrees that connect domain and codomain. If the - % spaces have no internal structure than this returns `[]`. + % Compute all allowed fusiontrees that connect domain and codomain. Only the + % trivial fusion tree is allowed, so this returns empty. % % Arguments % --------- - % codomain, domain : :class:`AbstractSpace` + % codomain, domain : :class:`GradedSpace` % input spaces. % % Returns % ------- % trees : :class:`FusionTree` - % list of fusiontrees that are allowed. + % list of all allowed fusion trees. - error('tensors:AbstractMethod', 'This method should be overloaded.'); + rank = [length(codomain) length(domain)]; + spaces = [codomain flip(domain)]; + + args = cell(2, sum(rank)); + for i = 1:size(args, 2) + args{1, i} = charges(spaces(i)); + args{2, i} = isdual(spaces(i)); + end + + trees = FusionTree.new(rank, args{:}); end function style = braidingstyle(codomain, domain) diff --git a/src/tensors/spaces/CartesianSpace.m b/src/tensors/spaces/CartesianSpace.m index c4e1131..9d7ed2f 100644 --- a/src/tensors/spaces/CartesianSpace.m +++ b/src/tensors/spaces/CartesianSpace.m @@ -132,23 +132,6 @@ d = [spaces.dimensions]; end - function trees = fusiontrees(~, ~) - % Compute all allowed fusiontrees that connect domain and codomain. Only the - % trivial fusion tree is allowed, so this returns empty. - % - % Arguments - % --------- - % codomain, domain : :class:`CartesianSpace` - % input spaces. - % - % Returns - % ------- - % trees : :class:`FusionTree` - % only the trivial tree is allowed. - - trees = FusionTree(); - end - function style = braidingstyle(~, ~) % Determine the braiding style of the internal structure of a space. % diff --git a/src/tensors/spaces/ComplexSpace.m b/src/tensors/spaces/ComplexSpace.m index e29b657..a0c90a9 100644 --- a/src/tensors/spaces/ComplexSpace.m +++ b/src/tensors/spaces/ComplexSpace.m @@ -122,23 +122,6 @@ d = [spaces.dimensions]; end - - function trees = fusiontrees(~, ~) - % Compute all allowed fusiontrees that connect domain and codomain. Only the - % trivial fusion tree is allowed, so this returns empty. - % - % Arguments - % --------- - % codomain, domain : :class:`ComplexSpace` - % input spaces. - % - % Returns - % ------- - % trees : :class:`FusionTree` - % only the trivial tree is allowed. - - trees = FusionTree(); - end function style = braidingstyle(~, ~) % Determine the braiding style of the internal structure of a space. diff --git a/src/tensors/spaces/GradedSpace.m b/src/tensors/spaces/GradedSpace.m index 8eb717e..ef1cc4b 100644 --- a/src/tensors/spaces/GradedSpace.m +++ b/src/tensors/spaces/GradedSpace.m @@ -199,33 +199,6 @@ end end - function trees = fusiontrees(codomain, domain) - % Compute all allowed fusiontrees that connect domain and codomain. Only the - % trivial fusion tree is allowed, so this returns empty. - % - % Arguments - % --------- - % codomain, domain : :class:`GradedSpace` - % input spaces. - % - % Returns - % ------- - % trees : :class:`FusionTree` - % list of all allowed fusion trees. - - rank = [length(codomain) length(domain)]; - spaces = [codomain flip(domain)]; - - args = cell(2, sum(rank)); - for i = 1:size(args, 2) - args{1, i} = charges(spaces(i)); - args{2, i} = spaces(i).dual; - end - - trees = FusionTree.new(rank, args{:}); - end - - %% Space manipulations function space = mtimes(space1, space2) % Fuse two spaces to a single space. From 47c4a79450e503b73ecee1fe1bb70379a24ce51c Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 28 Mar 2023 10:42:07 +0200 Subject: [PATCH 188/245] Add VOMPS to expand --- src/algorithms/Expand.m | 47 ++++++++++++-------------- src/algorithms/Vomps.m | 75 ++++++++++++++++++++++++++--------------- src/tensors/Tensor.m | 10 +++--- 3 files changed, 75 insertions(+), 57 deletions(-) diff --git a/src/algorithms/Expand.m b/src/algorithms/Expand.m index d2b3ad6..3d051ff 100644 --- a/src/algorithms/Expand.m +++ b/src/algorithms/Expand.m @@ -27,7 +27,7 @@ mincharges = 2 % optional finalization - finalize {mustBeMember(finalize, {'nothing', 'vomps'})} = 'nothing' + finalize = [] end @@ -46,23 +46,21 @@ end end - function [mps, flag] = changebonds(alg, mpo, mps) + function [mps2, flag] = changebonds(alg, mpo, mps1) % Change sectors and bond dimensions of mps virtual spaces. % canonicalize before starting - for d = 1:depth(mps) - mps(d) = canonicalize(mps(d)); + for d = 1:depth(mps1) + mps1(d) = canonicalize(mps1(d)); end - % determine new charges and bonds - [new_spaces, flag] = determine_new_spaces(alg, mpo, mps); + [new_spaces, flag] = determine_new_spaces(alg, mpo, mps1); - % expand mps tensors - mps = expand_mps(alg, mps, new_spaces); + mps2 = expand_mps(alg, mps1, new_spaces); % finalize - if strcmp(alg.finalize, 'vomps') - error('tbd') + if ~isempty(alg.finalize) + mps2 = approximate(alg.finalize, mpo, mps1, mps2); end end @@ -77,34 +75,31 @@ [addbond, addcharge] = expand_twosite(alg, mpo, mps, d, w); else % heuristic charge and bond expansion based on current spectrum - [svals, charges] = schmidt_values(mps(d), w); - addbond = expand_bonds(alg, svals); - addcharge = expand_charges(alg, mps, d, w, svals, charges); + [S, C] = schmidt_values(mps(d), w); + addbond = expand_bonds(alg, S); + addcharge = expand_charges(alg, mps, d, w, S, C); end if alg.notrunc addbond = max(0, addbond); end flag = addcharge.flag ~= 0 || nnz(addbond) > 0; - new_space = rightvspace(mps(d).AR(w)); + old_space = rightvspace(mps(d).AR(w)); - % add/remove bonds - new_space.dimensions.degeneracies = new_space.dimensions.degeneracies ... - + addbond; + new_space = struct; + new_space.charges = charges(old_space); + new_space.degeneracies = degeneracies(old_space) + addbond; if addcharge.flag == +1 % add charges - new_space.dimensions.charges = [new_space.dimensions.charges, addcharge.charges]; - new_space.dimensions.degeneracies = [new_space.dimensions.degeneracies, addcharge.bonds]; - + new_space.charges = [new_space.charges, addcharge.charges]; + new_space.degeneracies = [new_space.degeneracies, addcharge.bonds]; elseif addcharge.flag == -1 % remove charges - keep = ~ismember(new_space.dimensions.charges, addcharge.charges); - new_space.dimensions.charges = new_space.dimensions.charges(keep); - new_space.dimensions.degeneracies = new_space.dimensions.degeneracies(keep); - + keep = ~ismember(new_space.charges, addcharge.charges); + new_space.charges = new_space.charges(keep); + new_space.degeneracies = new_space.degeneracies(keep); end - new_spaces(d, w) = new_space; - + new_spaces(d, w) = old_space.new(new_space, isdual(old_space)); end end end diff --git a/src/algorithms/Vomps.m b/src/algorithms/Vomps.m index 52ab432..1e9196c 100644 --- a/src/algorithms/Vomps.m +++ b/src/algorithms/Vomps.m @@ -3,9 +3,9 @@ %% Options properties - tol = 1e-10 + tol = 1e-5 miniter = 2 - maxiter = 100 + maxiter = 10 verbosity = Verbosity.iter which = 'largestabs' @@ -70,18 +70,18 @@ C = updateC (alg, iter, mpo, mps1, GL, GR); mps2 = updatemps(alg, iter, mps2, AC, C); - [GL, GR] = environments(alg, mpo, mps1, mps2, GL, GR); + [GL, GR, lambda] = environments(alg, mpo, mps1, mps2, GL, GR); eta = convergence(alg, mpo, mps1, mps2, GL, GR); if iter > alg.miniter && eta < alg.tol - disp_conv(alg, iter, eta, toc(t_total)); + disp_conv(alg, iter, lambda, eta, toc(t_total)); return end alg = updatetols(alg, iter, eta); - disp_iter(alg, iter, eta, toc(t_iter)); + disp_iter(alg, iter, lambda, eta, toc(t_iter)); end - disp_maxiter(alg, iter, eta, toc(t_total)); + disp_maxiter(alg, iter, lambda, eta, toc(t_total)); end end @@ -89,42 +89,51 @@ methods function AC = updateAC(alg, iter, mpo, mps, GL, GR) if strcmp(alg.multiAC, 'sequential') - sites = mod(iter, period(mps)) + 1; + sites = mod1(iter, period(mps)); else sites = 1:period(mps); end H_AC = AC_hamiltonian(mpo, mps, GL, GR, sites); + ACs = arrayfun(@(x) x.AC(sites), mps, 'UniformOutput', false); + AC = vertcat(ACs{:}); for i = length(sites):-1:1 - AC(i) = MpsTensor(apply(H_AC{sites(i)}, mps.AC(sites(i))), ... - mps.AC(sites(i)).alegs); + for d = 1:depth(mpo) + AC(d, i).var = H_AC{i}(d).apply(AC(prev(d, depth(mpo)), i).var); + end end end function C = updateC(alg, iter, mpo, mps, GL, GR) if strcmp(alg.multiAC, 'sequential') - sites = mod(iter, period(mps)) + 1; + sites = mod1(iter, period(mps)); else sites = 1:period(mps); end H_C = C_hamiltonian(mpo, mps, GL, GR, sites); + Cs = arrayfun(@(x) x.C(sites), mps, 'UniformOutput', false); + C = vertcat(Cs{:}); for i = length(sites):-1:1 - C(i) = apply(H_C{sites(i)}, mps.C(sites(i))); + for d = 1:depth(mpo) + C(d, i) = H_C{i}(d).apply(C(prev(d, depth(mpo)), i)); + end end end function mps = updatemps(alg, iter, mps, AC, C) if strcmp(alg.multiAC, 'sequential') - sites = mod(iter, period(mps)) + 1; + sites = mod1(iter, period(mps)); else sites = 1:period(mps); end - for i = length(AC):-1:1 - [Q_AC, ~] = leftorth(AC(i)); - [Q_C, ~] = leftorth(C(i), 1, 2); - mps.AL(sites(i)) = multiplyright(Q_AC, Q_C'); + for d = size(AC, 1):-1:1 + for i = length(AC):-1:1 + [Q_AC, ~] = leftorth(AC(d, i), 'polar'); + [Q_C, ~] = leftorth(C(d, i), 1, 2, 'polar'); + mps(d).AL(sites(i)) = multiplyright(Q_AC, Q_C'); + end end kwargs = namedargs2cell(alg.alg_canonical); @@ -199,22 +208,34 @@ function disp_init(alg) fprintf('---- VOMPS ----\n'); end - function disp_iter(alg, iter, eta, t) + function disp_iter(alg, iter, lambda, eta, t) if alg.verbosity < Verbosity.iter, return; end s = settings; - switch s.matlab.commandwindow.NumericFormat.ActiveValue - case 'short' - fprintf('Vomps %2d:\terror = %0.1e\t(%s)\n', ... - iter, eta, time2str(t)); - otherwise - fprintf('Vomps %4d:\terror = %0.4e\t(%s)\n', ... - iter, eta, time2str(t)); - + if abs(imag(lambda)) < eps(lambda)^(3/4) * abs(lambda) + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('Vomps %2d:\tE = %-0.4f\terror = %0.1e\t(%s)\n', ... + iter, real(lambda), eta, time2str(t)); + otherwise + fprintf('Vomps %4d:\tE = %-0.15f\terror = %0.4e\t(%s)\n', ... + iter, real(lambda), eta, time2str(t, 's')); + + end + else + switch s.matlab.commandwindow.NumericFormat.ActiveValue + case 'short' + fprintf('Vomps %2d:\tE = %-0.4f %+0.4fi\terror = %0.1e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + otherwise + fprintf('Vomps %4d:\tE = %-0.15f %+0.15fi\terror = %0.4e\t(%s)\n', ... + iter, real(lambda), imag(lambda), eta, time2str(t)); + + end end end - function disp_conv(alg, iter, eta, t) + function disp_conv(alg, iter, lambda, eta, t) if alg.verbosity < Verbosity.conv, return; end s = settings; switch s.matlab.commandwindow.NumericFormat.ActiveValue @@ -229,7 +250,7 @@ function disp_conv(alg, iter, eta, t) fprintf('---------------\n'); end - function disp_maxiter(alg, iter, eta, t) + function disp_maxiter(alg, iter, lambda, eta, t) if alg.verbosity < Verbosity.warn, return; end s = settings; switch s.matlab.commandwindow.NumericFormat.ActiveValue diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index af921d4..e41d24e 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -517,11 +517,13 @@ end function tdst = embed(tsrc, tdst) + % embed a tensor in a different tensor. - bsrc = tensorblocks(tsrc); - fsrc = fusiontrees(tsrc); - bdst = tensorblocks(tdst); - fdst = fusiontrees(tdst); + assert(isequal(rank(tsrc), rank(tdst)), 'tensors:argerror', ... + 'tensors must have the same rank'); + + [bsrc, fsrc] = tensorblocks(tsrc); + [bdst, fdst] = tensorblocks(tdst); [lia, locb] = ismember(fsrc, fdst); nsp = nspaces(tdst); From eece74af74318f4bc256b1474698b3dc8cef71f2 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 28 Mar 2023 10:42:27 +0200 Subject: [PATCH 189/245] add Expand to mpo tests --- test/TestAlgorithms.m | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/TestAlgorithms.m b/test/TestAlgorithms.m index 2a5f279..789d8dc 100644 --- a/test/TestAlgorithms.m +++ b/test/TestAlgorithms.m @@ -3,7 +3,7 @@ properties (TestParameter) alg = {Vumps('which', 'smallestreal', 'maxiter', 5), ... - ...IDmrg('which', 'smallestreal', 'maxiter', 5), ... + IDmrg('which', 'smallestreal', 'maxiter', 5), ... Vumps2('which', 'smallestreal', 'maxiter', 6), ... IDmrg2('which', 'smallestreal', 'maxiter', 5) ... } @@ -35,6 +35,10 @@ function test2dIsing(tc, alg, symm) [gs, lambda] = fixedpoint(alg, mpo, mps); tc.verifyEqual(expectation_value(gs, mpo, gs), E0, 'RelTol', 1e-2); + + alg_expand = Expand('which', alg.which, 'schmidtcut', 1e-7, 'finalize', Vomps('maxiter', 5)); + gs2 = changebonds(alg_expand, mpo, mps); + tc.verifyGreaterThan(abs(fidelity(gs, gs2)), 0.9^unitcell) end end From d2b115c9b990cbb00caf2b8f43bf2a2cc1fc3718 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 29 Mar 2023 15:39:42 +0200 Subject: [PATCH 190/245] remove IDMRG tests (IDMRG currently broken) --- test/TestAlgorithms.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/TestAlgorithms.m b/test/TestAlgorithms.m index 789d8dc..0c0406a 100644 --- a/test/TestAlgorithms.m +++ b/test/TestAlgorithms.m @@ -3,7 +3,7 @@ properties (TestParameter) alg = {Vumps('which', 'smallestreal', 'maxiter', 5), ... - IDmrg('which', 'smallestreal', 'maxiter', 5), ... + ...IDmrg('which', 'smallestreal', 'maxiter', 5), ... Vumps2('which', 'smallestreal', 'maxiter', 6), ... IDmrg2('which', 'smallestreal', 'maxiter', 5) ... } From 6528f7d4b648d5bf956880bfee09e2baaa50898c Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 29 Mar 2023 15:48:47 +0200 Subject: [PATCH 191/245] fix bug --- src/tensors/Tensor.m | 7 ++++--- src/tensors/spaces/CartesianSpace.m | 4 ++-- src/tensors/spaces/ComplexSpace.m | 6 +++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index e41d24e..4f561ba 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -2304,13 +2304,14 @@ % ------- % a : double - trees = fusiontrees(t); - blocks = tensorblocks(t); - if isempty(trees) + if isa(t.var, 'TrivialBlock') + blocks = tensorblocks(t); a = blocks{1}; return end + [blocks, trees] = tensorblocks(t); + % Initialize output a = zeros(dims(t)); s = [t.codomain flip(t.domain)]; diff --git a/src/tensors/spaces/CartesianSpace.m b/src/tensors/spaces/CartesianSpace.m index 9d7ed2f..380f64b 100644 --- a/src/tensors/spaces/CartesianSpace.m +++ b/src/tensors/spaces/CartesianSpace.m @@ -83,7 +83,7 @@ %% Structure methods - function c = charges(~) + function c = charges(spaces) % Compute all charge combinations of a space. No internal structure is present, % this yields an empty result. % @@ -97,7 +97,7 @@ % c : [] % empty result. - c = Z1; + c = repmat(Z1, length(spaces), 1); end function d = degeneracies(spaces) diff --git a/src/tensors/spaces/ComplexSpace.m b/src/tensors/spaces/ComplexSpace.m index a0c90a9..b851950 100644 --- a/src/tensors/spaces/ComplexSpace.m +++ b/src/tensors/spaces/ComplexSpace.m @@ -74,13 +74,13 @@ %% Structure methods - function c = charges(~) + function c = charges(spaces) % Compute all charge combinations of a space. No internal structure is present, % this yields an empty result. % % Arguments % --------- - % spaces : (1, :) :class:`ComplexSpace` + % spaces : (1, :) :class:`CartesianSpace` % input spaces. % % Returns @@ -88,7 +88,7 @@ % c : [] % empty result. - c = Z1; + c = repmat(Z1, length(spaces), 1); end function d = degeneracies(spaces) From 5b8f649dac19851eebbcb732dcff324c26da85cd Mon Sep 17 00:00:00 2001 From: lkdvos Date: Fri, 31 Mar 2023 11:42:51 +0200 Subject: [PATCH 192/245] Fix rank issue (thanks @lavdrstr) --- src/mps/MpsTensor.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index 7fbf394..00be8cc 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -455,7 +455,7 @@ auxlegs_r = R.alegs; assert(R.plegs == L.plegs); plegs = L.plegs; %#ok - newrank = [2 auxlegs_l + auxlegs_r]; + newrank = [1 1 + auxlegs_l + auxlegs_r]; v = contract(... L, [-1 1:(plegs + 1) (-(1:auxlegs_l) - 2)], ... From d105ad5ee6832263ee0ab5202a74f25444cf4fe1 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Fri, 31 Mar 2023 17:32:28 +0200 Subject: [PATCH 193/245] quasiparticle updates --- src/models/quantum1dIsing.m | 2 +- src/mps/InfJMpo.m | 2 +- src/mps/InfMpo.m | 9 ++++++ test/TestAlgorithms.m | 62 +++++++++++++++++++++++-------------- 4 files changed, 50 insertions(+), 25 deletions(-) diff --git a/src/models/quantum1dIsing.m b/src/models/quantum1dIsing.m index 3480ab8..1e100c2 100644 --- a/src/models/quantum1dIsing.m +++ b/src/models/quantum1dIsing.m @@ -1,7 +1,7 @@ function mpo = quantum1dIsing(kwargs) arguments kwargs.J = 1 - kwargs.h = 1 + kwargs.h = 0.5 kwargs.L = Inf % size of system kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'Z2'})} = 'Z1' end diff --git a/src/mps/InfJMpo.m b/src/mps/InfJMpo.m index b5bf867..b1373cd 100644 --- a/src/mps/InfJMpo.m +++ b/src/mps/InfJMpo.m @@ -302,7 +302,7 @@ case 'l' fp = insert_onespace(fp, 2, ~isdual(leftvspace(operator, w))); case 'r' - fp = insert_onespace(fp, 2, isdual(rightvspace(operator, w))); + fp = insert_onespace(fp, 2, ~isdual(rightvspace(operator, w))); otherwise error('invalid fixedpoint type (%s)', type); end diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index dc9beae..a46ba5f 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -68,6 +68,10 @@ s = rightvspace(mpo.O{w}); end + function mpo = repmat(mpo, varargin) + mpo.O = repmat(mpo.O, varargin{:}); + end + function mpo = horzcat(mpo, varargin) Os = cellfun(@(x) x.O, varargin, 'UniformOutput', false); mpo.O = horzcat(mpo.O, Os{:}); @@ -101,6 +105,11 @@ end pspaces = arrayfun(@(x) subspaces(pspace(mpo, x)), 1:period(mpo), 'UniformOutput', false); + if length(vspaces) ~= length(pspaces) + assert(length(vspaces) == 1, 'ArgError', ... + 'Invalid length of input spaces') + vspaces = repmat(vspaces, size(pspaces)); + end args = [pspaces; vspaces]; mps = UniformMps.randnc(args{:}); end diff --git a/test/TestAlgorithms.m b/test/TestAlgorithms.m index 2a5f279..b63b0c2 100644 --- a/test/TestAlgorithms.m +++ b/test/TestAlgorithms.m @@ -38,30 +38,46 @@ function test2dIsing(tc, alg, symm) end end - function test1dIsing(tc, alg, symm) - alg.which = 'smallestreal'; - for unitcell = 1:3 - if unitcell == 1 && (isa(alg, 'IDmrg2') || isa(alg, 'Vumps2')) - continue; + function test1dIsing(tc) + E0 = -0.2525; + momenta = [0 0.5 pi]; + Delta0 = [0.805833 0.813325 1.00029]; + + for L = 1:3 + for symm = ["Z1", "Z2"] + H = quantum1dIsing('Symmetry', symm, 'h', 0.1); + H = repmat(H, 1, L); + if strcmp(symm, 'Z1') + vspace = CartesianSpace.new(12); + else + vspace = GradedSpace.new(Z2(0, 1), [6 6], false); + end + gs = initialize_mps(H, vspace); + + %% Groundstate algorithms + gs = fixedpoint(Vumps('which', 'smallestreal', 'maxiter', 5), ... + H, gs); + tc.assertEqual(expectation_value(gs, H), E0 * L, 'RelTol', 1e-2); +% gs = fixedpoint(IDmrg('which', 'smallestreal', 'maxiter', 5), ... +% H, gs); +% tc.verifyEqual(expectation_value(gs, H), E0 * L, 'RelTol', 1e-2); + if L > 1 + gs2 = fixedpoint(IDmrg2('which', 'smallestreal', 'maxiter', 5), ... + H, gs); + tc.assertEqual(expectation_value(gs2, H), E0 * L, 'RelTol', 1e-2); + gs2 = fixedpoint(Vumps2('which', 'smallestreal', 'maxiter', 6), ... + H, gs); + tc.assertEqual(expectation_value(gs2, H), E0 * L, 'RelTol', 1e-2); + end + + %% Excitations + for i = 1:length(momenta) + qp = InfQP.randnc(gs, gs, momenta(i)); + [qp, mu] = excitations(QPAnsatz(), H, qp); + tc.verifyEqual(mu, Delta0(i), 'RelTol', 1e-3); + end + end - E0 = -1.273 * unitcell; - - mpo1 = quantum1dIsing('Symmetry', symm); - if strcmp(symm, 'Z1') - mps1 = initialize_mps(mpo1, CartesianSpace.new(12)); - else - mps1 = initialize_mps(mpo1, GradedSpace.new(Z2(0, 1), [6 6], false)); - end - - mpo = mpo1; - mps = mps1; - for i = 2:unitcell - mpo = [mpo mpo1]; - mps = [mps mps1]; - end - - [gs, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyEqual(expectation_value(gs, mpo), E0, 'RelTol', 1e-2); end end end From da648fab2fe2229e85948e31b264d9dda8b4d3ae Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sun, 9 Apr 2023 12:05:57 +0200 Subject: [PATCH 194/245] update progress --- src/algorithms/QPAnsatz.m | 3 +-- test/TestAlgorithms.m | 6 +++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/algorithms/QPAnsatz.m b/src/algorithms/QPAnsatz.m index a95823c..2d02ce8 100644 --- a/src/algorithms/QPAnsatz.m +++ b/src/algorithms/QPAnsatz.m @@ -2,8 +2,7 @@ % Quasi-Particle excitation ansatz properties - alg_eigs = struct('MaxIter', 100, 'KrylovDim', 30, 'Tol', 1e-8, ... - 'Verbosity', Verbosity.diagnostics) + alg_eigs = struct('MaxIter', 100, 'KrylovDim', 30, 'Tol', 1e-8) alg_environments = struct('Tol', 1e-10); howmany = 1 which = 'smallestreal' diff --git a/test/TestAlgorithms.m b/test/TestAlgorithms.m index b63b0c2..547b476 100644 --- a/test/TestAlgorithms.m +++ b/test/TestAlgorithms.m @@ -72,7 +72,11 @@ function test1dIsing(tc) %% Excitations for i = 1:length(momenta) - qp = InfQP.randnc(gs, gs, momenta(i)); + if strcmp(symm, 'Z1') + qp = InfQP.randnc(gs, gs, momenta(i)); + else + qp = InfQP.randnc(gs, gs, momenta(i), Z2(1)); + end [qp, mu] = excitations(QPAnsatz(), H, qp); tc.verifyEqual(mu, Delta0(i), 'RelTol', 1e-3); end From 145e2e6fe82a472cf8b9fbf7c110a09a321f020f Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 10 Apr 2023 15:44:03 +0200 Subject: [PATCH 195/245] bugfix expansion --- src/algorithms/Expand.m | 29 +++++++++++++++++++++++++++-- src/tensors/Tensor.m | 5 +++-- src/tensors/charges/ProductCharge.m | 13 ++++++++----- src/tensors/kernels/AbstractBlock.m | 4 ++++ src/tensors/spaces/CartesianSpace.m | 2 +- src/tensors/spaces/ComplexSpace.m | 9 ++++++--- src/tensors/spaces/GradedSpace.m | 14 ++++++++++---- 7 files changed, 59 insertions(+), 17 deletions(-) diff --git a/src/algorithms/Expand.m b/src/algorithms/Expand.m index 3d051ff..2fcb927 100644 --- a/src/algorithms/Expand.m +++ b/src/algorithms/Expand.m @@ -55,7 +55,7 @@ end [new_spaces, flag] = determine_new_spaces(alg, mpo, mps1); - + new_spaces = fix_injectivity(alg, mps1, new_spaces); mps2 = expand_mps(alg, mps1, new_spaces); % finalize @@ -90,6 +90,10 @@ new_space.charges = charges(old_space); new_space.degeneracies = degeneracies(old_space) + addbond; + if addcharge.flag && isdual(old_space) + addcharge.charges = conj(addcharge.charges); + end + if addcharge.flag == +1 % add charges new_space.charges = [new_space.charges, addcharge.charges]; new_space.degeneracies = [new_space.degeneracies, addcharge.bonds]; @@ -104,6 +108,27 @@ end end + function spaces = fix_injectivity(alg, mps, spaces) + % attempt to ensure injectivity + for d = 1:depth(mps) + flag = false(1, period(mps)); + while ~all(flag) + for w = period(mps):-1:1 + ww = next(w, period(mps)); + www = prev(w, period(mps)); + rhs1 = prod([spaces(d, www) pspace(mps, w)'], ... + isdual(spaces(d, w))); + rhs2 = prod([spaces(d, ww) pspace(mps, ww)], ... + isdual(spaces(d, w))); + tmp = spaces(d, w); + spaces(d, w) = infimum(spaces(d, w), rhs1); + spaces(d, w) = infimum(spaces(d, w), rhs2); + assert(dims(spaces(d, w)) > 0, 'no fusion channels left'); + flag(w) = tmp == spaces(d, w); + end + end + end + end function [addbond, addcharge] = expand_twosite(alg, mpo, mps, d, w) % Does a twosite update and adds/removes charges that are above/under the cut @@ -141,7 +166,7 @@ addcharge.charges = added_charges; addcharge.bonds = ones(size(added_charges)); for ii = 1:size(added_charges) - addcharge.bonds(ii) = length(svals2{added_charges(ii) == charges2}); % TODO: will this work for product charges? + addcharge.bonds(ii) = length(svals2{added_charges(ii) == charges2}); end else % do nothing diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 4f561ba..998eff3 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -1574,7 +1574,8 @@ V = t.codomain.new(dims, false); if strcmp(alg, 'polar') - assert(isequal(V, prod(t.domain))); + assert(isequal(V, prod(t.domain)), ... + 'linalg:polar', 'polar decomposition should lead to square R.'); W = V; elseif length(p1) == 1 && V == t.codomain W = t.codomain; @@ -2200,7 +2201,7 @@ end function bool = iszero(t) - bool = isempty(t.var); + bool = isempty(t.var) || iszero(t.var); end function r = cond(t, p) diff --git a/src/tensors/charges/ProductCharge.m b/src/tensors/charges/ProductCharge.m index f35319a..28ffd07 100644 --- a/src/tensors/charges/ProductCharge.m +++ b/src/tensors/charges/ProductCharge.m @@ -35,14 +35,17 @@ prodcharge.charges = charges; end - function a = cat(dim, a, varargin) + function a = cat(dim, varargin) % Concatenate charges. mask = cellfun(@isempty, varargin); - if isempty(a) - firstnonempty = find(~mask, 1); - a = varargin{firstnonempty}; - mask(firstnonempty) = true; + firstnonempty = find(~mask, 1); + if isempty(firstnonempty) + a = varargin{1}; + return end + + a = varargin{firstnonempty}; + mask(firstnonempty) = true; for i = 1:length(a.charges) catcharges = cellfun(@(x) x.charges{i}, varargin(~mask), 'UniformOutput', false); diff --git a/src/tensors/kernels/AbstractBlock.m b/src/tensors/kernels/AbstractBlock.m index 884e5ee..ad459fb 100644 --- a/src/tensors/kernels/AbstractBlock.m +++ b/src/tensors/kernels/AbstractBlock.m @@ -361,5 +361,9 @@ error('tensors:AbstractMethod', 'This method should be overloaded.'); end + + function bool = iszero(X) + bool = isempty(X.var); + end end end \ No newline at end of file diff --git a/src/tensors/spaces/CartesianSpace.m b/src/tensors/spaces/CartesianSpace.m index 380f64b..66a3f5d 100644 --- a/src/tensors/spaces/CartesianSpace.m +++ b/src/tensors/spaces/CartesianSpace.m @@ -212,7 +212,7 @@ space.dimensions = space.dimensions * space2.dimensions; end - function space = prod(spaces) + function space = prod(spaces, ~) % Fuse a product space to a single space. % % Arguments diff --git a/src/tensors/spaces/ComplexSpace.m b/src/tensors/spaces/ComplexSpace.m index b851950..41afb1c 100644 --- a/src/tensors/spaces/ComplexSpace.m +++ b/src/tensors/spaces/ComplexSpace.m @@ -180,7 +180,7 @@ space.dual = false; end - function space = prod(spaces) + function space = prod(spaces, isdual) % Fuse a product space to a single space. % % Arguments @@ -192,8 +192,11 @@ % ------- % space : (1, 1) :class:`ComplexSpace` % Fused space which is isomorphic to the input product space. - - space = ComplexSpace(prod(dims(spaces)), false); + arguments + spaces + isdual = false + end + space = ComplexSpace(prod(dims(spaces)), isdual); end function space1 = infimum(space1, space2) diff --git a/src/tensors/spaces/GradedSpace.m b/src/tensors/spaces/GradedSpace.m index ef1cc4b..5be2a15 100644 --- a/src/tensors/spaces/GradedSpace.m +++ b/src/tensors/spaces/GradedSpace.m @@ -109,6 +109,9 @@ if isstruct(varargin{1}) % default assert(mod(nargin, 2) == 0); + for i = 1:2:nargin + if varargin{i+1}, varargin{i}.charges = conj(varargin{i}.charges); end + end spaces = GradedSpace(varargin{:}); return end @@ -121,7 +124,7 @@ 'degeneracies', varargin{3 * i - 1}); args{2, i} = varargin{3 * i}; end - spaces = GradedSpace(args{:}); + spaces = GradedSpace.new(args{:}); return end @@ -216,7 +219,7 @@ space = prod([space1, space2]); end - function space = prod(spaces) + function space = prod(spaces, isdual) % Fuse a product space to a single space. % % Arguments @@ -228,7 +231,10 @@ % ------- % space : (1, 1) :class:`GradedSpace` % Fused space which is isomorphic to the input product space. - + arguments + spaces + isdual = false + end if fusionstyle(spaces) == FusionStyle.Unique c = prod(charges(spaces), 1); d = prod(degeneracies(spaces), 1); @@ -253,7 +259,7 @@ newdimensions.degeneracies(i) = sum(d(idx)); end - space = GradedSpace(newdimensions, false); + space = GradedSpace.new(newdimensions, isdual); end From 015bc4fa9a69d7475ad35847e03101a0bdc1f44a Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 11 Apr 2023 10:08:55 +0200 Subject: [PATCH 196/245] add docstring `initialize_fixedpoint(FiniteMpo)` --- src/mps/FiniteMpo.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index 9681d87..8e03606 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -58,6 +58,8 @@ end function v = initialize_fixedpoint(mpo) + % Initialize a dense tensor for the fixedpoint of a :class:`FiniteMPO`. + N = prod(cellfun(@(x) size(x, 4), mpo.O)); for i = N:-1:1 v(i) = Tensor.randnc(domain(slice(mpo, i, 1:N)), []); From 4bbaa66025f3cc261430e3784b71460640552d67 Mon Sep 17 00:00:00 2001 From: leburgel Date: Mon, 17 Apr 2023 13:38:45 +0200 Subject: [PATCH 197/245] Fix errors in sphinx build --- docs/requirements.txt | 1 - docs/src/conf.py | 2 +- docs/src/man/tensor.rst | 2 +- src/caches/GetMD5/GetMD5.m | 8 +-- src/caches/GetMD5/GetMD5_helper.m | 30 +++++---- src/caches/GetMD5/InstallMex.m | 59 +++++++++++------ src/caches/GetMD5/uTest_GetMD5.m | 21 ++++--- src/tensors/charges/GtPattern.m | 91 ++++++++++++++++----------- src/tensors/charges/ProductCharge.m | 18 +++--- src/tensors/charges/Z2.m | 28 ++++++--- src/utility/indices/contractinds.m | 7 ++- src/utility/indices/next.m | 13 ++-- src/utility/indices/prev.m | 13 ++-- src/utility/indices/treeindsequence.m | 16 +++-- src/utility/indices/unique1.m | 9 ++- src/utility/linalg/tensorprod.m | 27 ++++---- src/utility/linalg/tensortrace.m | 14 +++-- src/utility/netcon.m | 36 ++++++++--- src/utility/permutations/invperm.m | 8 ++- src/utility/sparse/SparseArray.m | 2 +- src/utility/swapvars.m | 9 ++- 21 files changed, 264 insertions(+), 150 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index dbf71cb..b8c586c 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -4,6 +4,5 @@ sphinxcontrib-matlabdomain==0.12.0 sphinx-prompt nbsphinx==0.8.9 sphinx-gallery==0.10.1 -myst-parser==0.17.2 linkify-it-py==2.0.0 jinja2==3.0.3 diff --git a/docs/src/conf.py b/docs/src/conf.py index c760c53..94f9f07 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -57,7 +57,7 @@ templates_path = ['_templates'] # The suffix(es) of source filenames. -source_suffix = ['.rst', '.md'] +source_suffix = ['.rst'] # The master toctree document. master_doc = 'index' diff --git a/docs/src/man/tensor.rst b/docs/src/man/tensor.rst index def1ebd..14c9ebb 100644 --- a/docs/src/man/tensor.rst +++ b/docs/src/man/tensor.rst @@ -262,7 +262,7 @@ This is only possible if the contracted spaces are compatible, i.e. one is the d In general, all contractions will be performed pairwise, such that contracting ``A`` and ``B`` consists of permuting all uncontracted indices of ``A`` to its codomain, all contracted indices of ``A`` to its domain, and the reverse for ``B``. Then, contraction is just a composition of linear maps, hence the need for the contracted spaces to be compatible. -The syntax for specifying tensor contractions is based on the ``NCon`` (network contraction) algorithm described here (https://arxiv.org/abs/1402.0939). +The syntax for specifying tensor contractions is based on the ``NCon`` (network contraction) algorithm described here (:arxiv:`1402.0939`). The core principle is that contracted indices are indicated by incrementing positive integers, which are then pairwise contracted in ascending order. Uncontracted indices are specified with negative integers, which are sorted in descending order (ascending absolute value). It is also possible to specify the rank of the resulting tensor with a name-value argument ``'Rank'``, and use in-place conjugation with the name-value argument ``'Conj'``. diff --git a/src/caches/GetMD5/GetMD5.m b/src/caches/GetMD5/GetMD5.m index 2174349..993afd1 100644 --- a/src/caches/GetMD5/GetMD5.m +++ b/src/caches/GetMD5/GetMD5.m @@ -10,7 +10,7 @@ function GetMD5(varargin) % Data % input data on which the checksum is computed. % -% Mode : char +% Mode : :code:`char` % optional declaration of the type of the input data. % % - 'File' : `data` is a file name as a `char`. @@ -54,10 +54,10 @@ function GetMD5(varargin) % % See also % -------- -% Other methods for checksums can be found: :code:`CalcCRC32`, :code:`DataHash`, ... +% Other methods for checksums can be found: :code:`CalcCRC32`, :code:`DataHash`, etc. % -% For more checksum methods see: -% http://www.mathworks.com/matlabcentral/fileexchange/31272-datahash +% For more checksum methods see +% `here `_. % Dummy code, which calls the auto-compilation only: --------------------------- % This M-function is not called, if the compiled MEX function is in the path. diff --git a/src/caches/GetMD5/GetMD5_helper.m b/src/caches/GetMD5/GetMD5_helper.m index 4442e41..b0083b6 100644 --- a/src/caches/GetMD5/GetMD5_helper.m +++ b/src/caches/GetMD5/GetMD5_helper.m @@ -5,19 +5,25 @@ % applied processing can depend on the needs of the users, therefore it is % implemented as an M-function, which is easier to modify than the C-code. % -% INPUT: -% V: Array of any type, which is not handled in the C-Mex. -% OUTPUT: -% S: Array or struct containing elementary types only. -% The implementation migth be changed by the user! -% Default: -% - Sparse arrays: Struct containing the indices and values. -% - Function handle: The reply of FUNCTIONS and the size and date of the -% file. -% - User defined and java objects: V.hashCode if existing, else: struct(V). +% Arguments +% --------- +% V +% array of any type, which is not handled in the C-Mex. % -% NOTE: -% For objects the function getByteStreamFromArray() might be exhaustive and +% Returns +% ------- +% S +% array or struct containing elementary types only. +% The implementation migth be changed by the user! +% Default: +% +% - Sparse arrays: Struct containing the indices and values. +% - Function handle: The reply of FUNCTIONS and the size and date of the file. +% - User defined and java objects: :code:`V.hashCode` if existing, else: :code:`struct(V)`. +% +% Note +% ---- +% For objects the function :code:`getByteStreamFromArray()` might be exhaustive and % efficient, but unfortunately it is not documented. % % Tested: Matlab/64 7.8, 7.13, 8.6, 9.1, Win7/64 diff --git a/src/caches/GetMD5/InstallMex.m b/src/caches/GetMD5/InstallMex.m index 7c13880..f360e59 100644 --- a/src/caches/GetMD5/InstallMex.m +++ b/src/caches/GetMD5/InstallMex.m @@ -6,33 +6,51 @@ % Therefore this function can be called automatically from an M-function, when % the compiled Mex-Function does not exist already. % -% Ok = InstallMex(SourceFile, ...) -% INPUT: -% SourceFile: Name of the source file, with or without absolute or partial -% path. The default extension '.c' is appended on demand. -% Optional arguments in arbitrary order: -% Function name: Function is started after compiling, e.g. a unit-test. -% Cell string: Additional arguments for the compilation, e.g. libraries. -% '-debug': Enable debug mode. -% '-force32': Use the compatibleArrayDims flag under 64 bit Matlab. -% '-replace': Overwrite existing mex file without confirmation. +% Usage +% ----- +% :code:`Ok = InstallMex(SourceFile, ...)` % -% OUTPUT: -% Ok: Logical flag, TRUE if compilation was successful. Optional. +% Arguments +% --------- +% SourceFile +% Name of the source file, with or without absolute or partial path. The default extension +% '.c' is appended on demand. % -% COMPATIBILITY: +% Optional Arguments +% ------------------ +% Function name +% Function is started after compiling, e.g. a unit-test. +% Cell string +% additional arguments for the compilation, e.g. libraries. +% '-debug' +% enable debug mode. +% '-force32' +% use the compatibleArrayDims flag under 64 bit Matlab. +% '-replace' +% overwrite existing mex file without confirmation. +% +% Returns +% ------- +% Ok +% logical flag, :code:`true` if compilation was successful. +% +% Note +% ---- +% % - A compiler must be installed and setup before: mex -setup % - For Linux and MacOS the C99 style is enabled for C-files. % - The optimization flag -O is set. -% - Defined compiler directives: -% * MATLABVER: is the current version, e.g. 708 for v7.8. -% * _LITTLE_ENDIAN or _BIG_ENDIAN: according to processor type -% * HAS_HG2: Defined for Matlab >= R2014b with HG2 graphics. -% * -R2017b or -largeArrayDims for 64 bit addressing and C Matrix API +% - Defined compiler directives +% - MATLABVER: is the current version, e.g. 708 for v7.8. +% - _LITTLE_ENDIAN or _BIG_ENDIAN: according to processor type +% - HAS_HG2: Defined for Matlab >= R2014b with HG2 graphics. +% - -R2017b or -largeArrayDims for 64 bit addressing and C Matrix API % -R2018a for C Data API (this is set, when the string "[R2018a API]" % appears anywhere inside the source file. % -% EXAMPLES: +% Examples +% -------- +% % Compile func1.c with LAPACK libraries: % InstallMex('func1', {'libmwlapack.lib', 'libmwblas.lib'}) % Compile func2.cpp, enable debugging and call a test function: @@ -40,7 +58,8 @@ % These commands can be appended after the help section of an M-file, when the % compilation should be started automatically, if the compiled MEX is not found. % -% NOTES: +% Note +% ---- % Suggestions for improvements and comments are welcome! % Feel free to add this function to your FEX submissions, when you change the % URL in the variable "Precompiled" accordingly. diff --git a/src/caches/GetMD5/uTest_GetMD5.m b/src/caches/GetMD5/uTest_GetMD5.m index eb94a20..2313b32 100644 --- a/src/caches/GetMD5/uTest_GetMD5.m +++ b/src/caches/GetMD5/uTest_GetMD5.m @@ -3,13 +3,20 @@ function uTest_GetMD5(doSpeed) % This is a routine for automatic testing. It is not needed for processing and % can be deleted or moved to a folder, where it does not bother. % -% uTest_GetMD5(doSpeed) -% INPUT: -% doSpeed: Optional logical flag to trigger time consuming speed tests. -% Default: TRUE. If no speed test is defined, this is ignored. -% OUTPUT: -% On failure the test stops with an error. -% The speed is compared to a Java method. +% Usage +% ----- +% :code:`uTest_GetMD5(doSpeed)` +% +% Arguments +% --------- +% doSpeed +% Optional logical flag to trigger time consuming speed tests. Defaults to :code:`true.If +% no speed test is defined, this is ignored. +% +% Note +% ---- +% On failure the test stops with an error. +% The speed is compared to a Java method. % % Tested: Matlab 6.5, 7.7, 7.8, 7.13, WinXP/32, Win7/64 % Author: Jan Simon, Heidelberg, (C) 2009-2019 matlab.2010(a)n(MINUS)simon.de diff --git a/src/tensors/charges/GtPattern.m b/src/tensors/charges/GtPattern.m index a71a96e..6fd8724 100644 --- a/src/tensors/charges/GtPattern.m +++ b/src/tensors/charges/GtPattern.m @@ -1,19 +1,23 @@ classdef GtPattern - %GTPATTERN Object that represents a pattern devised by Gelfand and Tsetlin. + % Object that represents a pattern devised by Gelfand and Tsetlin. % - % /m_{1,N} m_{2,N} ... m_{N,N}\ - % | m_{1,N-1} ... m_{N-1,N-1} | - % M = | ... | - % | m_{1,2} m_{2,2} | - % \ m_{1,1} / + % :: % - % These consist of triangular arrangements of integers M_ij with the - % following property: - % - for 1 <= k < l <= N: - % m_{k,l} >= m_{k,l-1} >= m_{k+1,l} + % /m_{1,N} m_{2,N} ... m_{N,N}\ + % | m_{1,N-1} ... m_{N-1,N-1} | + % M = | ... | + % | m_{1,2} m_{2,2} | + % \ m_{1,1} / % - % They will be represented using arrays of size N x N, where the elements - % outside of the triangular region are assumed to be zero. + % These consist of triangular arrangements of integers M_ij with the + % following property: + % + % .. math:: + % + % m_{k,l} \geq m_{k,l-1} \geq m_{k+1,l} \quad \text{for} \quad 1 <= k < l <= N + % + % They will be represented using arrays of size N x N, where the elements + % outside of the triangular region are assumed to be zero. properties % (Access = private) @@ -106,15 +110,17 @@ end function m = get(p, k, l) - %GET Access an element in the pattern. - % m = get(pat, k, l) - % gets the element specified by m_kl. - % - % m = get(pat, ks, l) - % gets a row vector of elements in the l'th row. + % Access an element in the pattern. % - % m = get(pat, k, ls(:) - % gets a col vector of elements in the k'th column. + % Usage + % ----- + % :code:`m = get(pat, k, l)` + % gets the element specified by m_kl. + % :code:`m = get(pat, ks, l)` + % gets a row vector of elements in the l'th row. + % :code:`m = get(pat, k, ls)` + % gets a col vector of elements in the k'th column. + % assert(isscalar(p)); m = p.M(k + bitshift(((l + 1 + p.N) .* (p.N - l)), -1)); end @@ -201,21 +207,32 @@ function disp(p) end function sigma = rowsum(p, l) - %ROWSUM Compute the sum of row `l`. - % sigma = rowsum(pat, l) - % computes sigma_l = \sum_{k=1:l} m_{k, l} + % Compute the sum of row :code:`l`. + % + % Usage + % ----- + % :code:`sigma = rowsum(pat, l)` + % computes :math:`\sigma_l = \sum_{k=1}^l m_{k, l}`. sigma = sum(get(p, 1:l, l)); end function w = pWeight(p) - %PWEIGHT Compute the p-weight of a pattern. - % w = pWeight(pat) - % computes the pattern weight W(pat) = (w_1 w_2 ... w_N) where - % w_i = sigma_i - sigma_{i-1}. + % Compute the p-weight of a pattern. + % + % Usage + % ----- + % :code:`w = pWeight(pat)` + % computes the pattern weight :math:`W(\text{pat}) = (w_1 w_2 ... w_N)` where + % :math:`w_i = \sigma_i - \sigma_{i-1}`. % - % This is an alternative weight definition to the z-weight. - % See also GtPattern.zWeight + % Note + % ---- + % This is an alternative weight definition to the z-weight. + % + % See Also + % -------- + % :meth:`GtPattern.zWeight` M = p.M; ctr = 0; @@ -230,13 +247,17 @@ function disp(p) end function w = zWeight(p) - %ZWEIGHT Compute the z-weight of a pattern - % w = zWeight(pat) - % computes the z-weight L(pat) = (l_1 l_2 ... l_N-1) where - % l_i = sigma_l - 1/2(sigma_{l+1} + sigma_{l-1}) + % Compute the z-weight of a pattern + % + % Usage + % ----- + % :code:`w = zWeight(pat)` + % computes the z-weight :math:`L(\text{pat}) = (l_1 l_2 ... l_N-1)` where + % :math:`l_i = \sigma_l - 1/2(\sigma_{l+1} + \sigma_{l-1})`. % - % This is a generalization of the m-quantum number for angular - % momentum. + % Note + % ---- + % This is a generalization of the m-quantum number for angular momentum. sigma = [0 arrayfun(@(l) rowsum(p, l), 1:p.N)]; w = sigma(2:end-1) - (sigma(1:end-2) + sigma(3:end))/2; diff --git a/src/tensors/charges/ProductCharge.m b/src/tensors/charges/ProductCharge.m index 28ffd07..ca4b70b 100644 --- a/src/tensors/charges/ProductCharge.m +++ b/src/tensors/charges/ProductCharge.m @@ -239,26 +239,28 @@ % % Usage % ----- - % a = subsasgn(a, substruct('()', subs), b) - % a(subs{:}) = b + % :code:`a = subsasgn(a, substruct('()', subs), b)` + % + % :code:`a(subs{:}) = b` % assign array slices. % - % a = subsasgn(a, substruct('{}', subs), c) - % a{i} = c + % :code:`a = subsasgn(a, substruct('{}', subs), c)` + % + % :code:`a{i} = c` % assign to a factor slice. % % Arguments % --------- - % a : ProductCharge + % a : :class:`ProductCharge` % array of charges to assign to. % - % s : substruct + % s : :class:`struct` % structure containing indexing data. % - % b : ProductCharge + % b : :class:`ProductCharge` % slice to assign. % - % c : AbstractCharge + % c : :class:`AbstractCharge` % factor to assign. % % Returns diff --git a/src/tensors/charges/Z2.m b/src/tensors/charges/Z2.m index 58a9376..baf5e36 100644 --- a/src/tensors/charges/Z2.m +++ b/src/tensors/charges/Z2.m @@ -1,13 +1,25 @@ classdef Z2 < AbstractCharge & logical - % Z2 - Irreducible representations of Z2. - % This class represents the trivial or sign representations of Z2, represented using - % {false, true} with multiplication table given by xor: - % | false | true - % --------------------- - % false | false | true - % true | true | false + % Irreducible representations of :math:`Z_2`. % - % See also AbstractCharge + % This class implements the trivial or sign representations of :math:`Z_2`, represented + % using {:code:`false`, :code:`true`} where multiplication is given by + % :math:`\mathrm{XOR}`, giving the multiplication table: + % + % .. list-table:: + % + % * - + % - :code:`false` + % - :code:`true` + % * - :code:`false` + % - :code:`false` + % - :code:`true` + % * - :code:`true` + % - :code:`true` + % - :code:`false` + % + % See Also + % -------- + % :class:`AbstractCharge` methods function A = Asymbol(a, b, c) diff --git a/src/utility/indices/contractinds.m b/src/utility/indices/contractinds.m index 627586f..14ab24e 100644 --- a/src/utility/indices/contractinds.m +++ b/src/utility/indices/contractinds.m @@ -1,7 +1,8 @@ function [dimA, dimB] = contractinds(ia, ib) -% contractinds - Find the contracted dimensions. -% [dimA, dimB] = contractinds(ia, ib) -% locates the repeated indices. +% Find the contracted dimensions. +% +% :code:`[dimA, dimB] = contractinds(ia, ib)` +% locates the repeated indices. ind = find(ia(:) == ib).' - 1; dimA = mod(ind, length(ia)) + 1; diff --git a/src/utility/indices/next.m b/src/utility/indices/next.m index b07a986..60bbaba 100644 --- a/src/utility/indices/next.m +++ b/src/utility/indices/next.m @@ -1,9 +1,14 @@ function j = next(i, total) -%NEXT Give the next index in a cyclic loop. -% j = next(i, total) -% gives the i + 1' th index, but loops back to 1 when j > total. +% Give the next index in a cyclic loop. % -% See also prev +% Usage +% ----- +% :code:`j = next(i, total)` +% gives the :math:`i + 1`'th index, but loops back to 1 when :math:`j > \text{total}`. +% +% See Also +% -------- +% :func:`prev` j = mod(i, total) + 1; diff --git a/src/utility/indices/prev.m b/src/utility/indices/prev.m index 1318aaf..ed2ef71 100644 --- a/src/utility/indices/prev.m +++ b/src/utility/indices/prev.m @@ -1,9 +1,14 @@ function j = prev(i, total) -%PREV Give the previous index in a cyclic loop. -% j = prev(i, total) -% gives the i - 1'th index, but loops back to total when j < 1. +% Give the previous index in a cyclic loop. % -% See also next +% Usage +% ----- +% :code:`j = prev(i, total)` +% gives the :math:`i - 1`'th index, but loops back to total when :math:`j < 1`. +% +% See Also +% -------- +% :func:`next` j = mod(i - 2, total) + 1; diff --git a/src/utility/indices/treeindsequence.m b/src/utility/indices/treeindsequence.m index 28fd144..791e91a 100644 --- a/src/utility/indices/treeindsequence.m +++ b/src/utility/indices/treeindsequence.m @@ -3,7 +3,7 @@ % % Usage % ----- -% ```t = treeindsequence(n)``` +% :code:`t = treeindsequence(n)` % % Arguments % --------- @@ -15,10 +15,16 @@ % t : int % total number of edges % -% Examples -% -------- -% ```treeindsequence(0:4)``` -% ans = [0 1 2 4 6] +% Example +% ------- +% +% .. code-block:: matlab +% +% >> treeindsequence(0:4) +% +% ans = +% +% 0 1 2 4 6 n = max(n, 2 * n - 2); diff --git a/src/utility/indices/unique1.m b/src/utility/indices/unique1.m index e2621af..d1e6b03 100644 --- a/src/utility/indices/unique1.m +++ b/src/utility/indices/unique1.m @@ -1,7 +1,10 @@ function inds = unique1(inds) -% unique1 - Find the elements that appear exactly once. -% inds = unique1(inds) -% deletes all elements that appear more than once. +% Find the elements that appear exactly once. +% +% Usage +% ----- +% :code:`inds = unique1(inds)` +% deletes all elements that appear more than once. inds = inds(sum(inds(:) == inds) == 1); diff --git a/src/utility/linalg/tensorprod.m b/src/utility/linalg/tensorprod.m index 49ea959..f76dde3 100644 --- a/src/utility/linalg/tensorprod.m +++ b/src/utility/linalg/tensorprod.m @@ -1,18 +1,21 @@ function C = tensorprod(A, B, dimA, dimB, cA, cB, options) -% tensorprod - Tensor products between two tensors. -% C = tensorprod(A, B, dimA, dimB) -% returns the tensor product of tensors A and B. The arguments dimA and dimB are -% vectors that specify which dimensions to contract in A and B. The size of the -% output tensor is the size of the uncontracted dimensions of A followed by the size -% of the uncontracted dimensions of B. +% Tensor products between two tensors. % -% C = tensorprod(A, B) -% returns the outer product of tensors A and B. This is equivalent to the previous -% syntax with dimA = dimB = []. +% Usage +% ----- +% :code:`C = tensorprod(A, B, dimA, dimB)` +% returns the tensor product of tensors A and B. The arguments dimA and dimB are +% vectors that specify which dimensions to contract in A and B. The size of the +% output tensor is the size of the uncontracted dimensions of A followed by the size +% of the uncontracted dimensions of B. % -% C = tensorprod(_, NumDimensionsA=ndimsA) -% optionally specifies the number of dimensions in tensor A in addition to combat the -% removal of trailing singleton dimensions. +% :code:`C = tensorprod(A, B)` +% returns the outer product of tensors A and B. This is equivalent to the previous +% syntax with dimA = dimB = []. +% +% :code:`C = tensorprod(_, NumDimensionsA=ndimsA)` +% optionally specifies the number of dimensions in tensor A in addition to combat the +% removal of trailing singleton dimensions. arguments A diff --git a/src/utility/linalg/tensortrace.m b/src/utility/linalg/tensortrace.m index 2c48c18..d71cbd9 100644 --- a/src/utility/linalg/tensortrace.m +++ b/src/utility/linalg/tensortrace.m @@ -1,10 +1,14 @@ function C = tensortrace(A, i1, i2) -% tensortrace - Compute the (partial) trace of a tensor. -% [C, ic] = tensortrace(A, ia) -% traces over the indices that appear twice in ia. +% Compute the (partial) trace of a tensor. % -% [C, ic] = tensortrace(A, ia, ic) -% optionally specifies the output indices' order. +% Usage +% ----- +% +% :code:`[C, ic] = tensortrace(A, ia)` +% traces over the indices that appear twice in ia. +% +% :code:`[C, ic] = tensortrace(A, ia, ic)` +% optionally specifies the output indices' order. if isempty(i1) && isempty(i2), C = A; return; end assert(length(i1) == length(i2), 'invalid indices'); diff --git a/src/utility/netcon.m b/src/utility/netcon.m index 4e18b31..0c127d4 100644 --- a/src/utility/netcon.m +++ b/src/utility/netcon.m @@ -1,18 +1,34 @@ function [sequence, cost] = netcon(legLinks,verbosity,costType,muCap,allowOPs,legCosts) -% function [sequence cost] = netcon(legLinks,verbosity,costType,muCap,allowOPs,legCosts) % Finds most efficient way to contract a tensor network % v2.00 by Robert N. C. Pfeifer 2014 % Contact: rpfeifer.public@gmail.com, robert.pfeifer@mq.edu.au % -% Parameters: (defaults) -% - legLinks: Labelling of tensor indices. (required) -% - verbosity: 0: Quiet. 1: State final result. 2: State intermediate and final results. 3: Also display object sizes during pairwise contractions. (2) -% - costType: 1: Absolute value. 2: Multiple and power of chi. (2) -% - muCap: Initially restrict search to sequences having a cost of muCap (if costType==1) or O(X^muCap) (if costType==2). This value will increment -% automatically if required, and it is recommended that it be left at the default value of 1. (1) -% - allowOPs: Allow contraction sequences including outer products: 0/false: No. 1/true: Yes. (true) -% - legCosts: For costType==1: nx2 table. A row reading [a b] assigns a dimension of b to index a. Default: 2 for all legs. -% For costType==2: nx3 table. A row reading [a b c] assigns a dimension of bX^c to index a, for unspecified X. Default: 1X^1 for all legs. +% Usage +% ----- +% :code:`[sequence, cost] = netcon(legLinks, verbosity, costType, muCap, allowOPs, legCosts)` +% +% Arguments +% --------- +% legLinks +% abelling of tensor indices. +% +% Optional Arguments +% ------------------ +% verbosity +% 0: Quiet. 1: State final result. 2: State intermediate and final results. 3: Also display object sizes during pairwise contractions. (2) +% costType +% 1: Absolute value. 2: Multiple and power of chi. (2) +% muCap +% Initially restrict search to sequences having a cost of muCap (if costType==1) or +% O(X^muCap) (if costType==2). This value will increment automatically if required, and it +% is recommended that it be left at the default value of 1. (1) +% allowOPs +% Allow contraction sequences including outer products: 0/false: No. 1/true: Yes. (true) +% legCosts +% For costType==1: nx2 table. A row reading [a b] assigns a dimension of b to index a. +% Default: 2 for all legs. For costType==2: nx3 table. A row reading [a b c] assigns a +% dimension of bX^c to index a, for unspecified X. Default: 1X^1 for all legs. + arguments legLinks verbosity = 2 % 0: No displayed output. 1: Display final result. 2: Display progress reports. diff --git a/src/utility/permutations/invperm.m b/src/utility/permutations/invperm.m index a18dc13..35a4e49 100644 --- a/src/utility/permutations/invperm.m +++ b/src/utility/permutations/invperm.m @@ -1,7 +1,9 @@ function invp = invperm(p) -% invp - Compute the inverse permutation. -% invp = invperm(p) -% computes the permutation that satisfies invp(p) = 1:length(p). +% Compute the inverse permutation. +% Usage +% ----- +% :code:`invp = invperm(p)` +% computes the permutation that satisfies :code:`invp(p) = 1:length(p)`. invp(p) = 1:length(p); diff --git a/src/utility/sparse/SparseArray.m b/src/utility/sparse/SparseArray.m index 88ebd5b..71a19fb 100644 --- a/src/utility/sparse/SparseArray.m +++ b/src/utility/sparse/SparseArray.m @@ -996,7 +996,7 @@ function disp(a) % Find a few singular values and vectors. % See Also % -------- - % `Documentation for builtin Matlab eig `_. + % `Documentation for builtin Matlab svds `_. assert(ismatrix(a), 'sparse:RankError', 'svds is only defined for 2D sparse arrays.'); [varargout{1:nargout}] = svds(reshape(a.var, a.sz), varargin{:}); varargout = cellfun(@maybesparse_, varargout, 'UniformOutput', false); diff --git a/src/utility/swapvars.m b/src/utility/swapvars.m index f1c6352..881ff3f 100644 --- a/src/utility/swapvars.m +++ b/src/utility/swapvars.m @@ -1,7 +1,10 @@ function [B, A] = swapvars(A, B) -% swapvars - Exchange two variables. -% [a, b] = swapvars(a, b) -% stores the value of a in b, and the value of b in a. +% Exchange two variables. +% +% Usage +% ----- +% :code:`[a, b] = swapvars(a, b)` +% stores the value of a in b, and the value of b in a. end From 13effa729e55fd7248cdd535bdf287167cb20b3b Mon Sep 17 00:00:00 2001 From: leburgel Date: Wed, 19 Apr 2023 21:28:28 +0200 Subject: [PATCH 198/245] Exclude problematic docs file --- docs/src/conf.py | 2 +- docs/src/index.rst | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/src/conf.py b/docs/src/conf.py index 94f9f07..9aadc64 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -75,7 +75,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', '**.ipynb_checkpoints'] +exclude_patterns = ['_build', '**.ipynb_checkpoints', 'lib/tensors.rst'] # -- Options for HTML output ----------------------------------------------- diff --git a/docs/src/index.rst b/docs/src/index.rst index 41126a0..94be98f 100644 --- a/docs/src/index.rst +++ b/docs/src/index.rst @@ -32,6 +32,10 @@ Additionally, for tensors which are invariant under general global symmetries, v :caption: Library :maxdepth: 2 - lib/tensors lib/utility lib/caches + +.. + lib/tensors + + From 320ac7aa53e529cf22e47ab528be421a6d6853ba Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 20 Apr 2023 09:31:00 +0200 Subject: [PATCH 199/245] working version of qp --- src/algorithms/QPAnsatz.m | 10 ++- src/models/quantum1dIsing_dispersion.m | 13 ++++ src/models/quantum1dIsing_energy.m | 9 +++ src/mps/InfJMpo.m | 10 ++- test/TestAlgorithms.m | 86 +++++++++++++------------- 5 files changed, 77 insertions(+), 51 deletions(-) create mode 100644 src/models/quantum1dIsing_dispersion.m create mode 100644 src/models/quantum1dIsing_energy.m diff --git a/src/algorithms/QPAnsatz.m b/src/algorithms/QPAnsatz.m index 2d02ce8..d975b5e 100644 --- a/src/algorithms/QPAnsatz.m +++ b/src/algorithms/QPAnsatz.m @@ -32,14 +32,11 @@ % Renormalization [GL, GR, lambda] = environments(mpo, qp.mpsleft, qp.mpsleft); - for i = 1:period(mpo) + for i = period(mpo):-1:1 T = AC_hamiltonian(mpo, qp.mpsleft, GL, GR, i); offset(i) = dot(qp.mpsleft.AC(i), apply(T{1}, qp.mpsleft.AC(i))); end -% mpo = renormalize(mpo, lambda); -% [GL, GR, lambda] = environments(mpo, qp.mpsleft, qp.mpsleft); - % Algorithm eigkwargs = namedargs2cell(alg.alg_eigs); H_effective = @(x) updateX(alg, mpo, qp, GL, GR, x, offset); @@ -55,17 +52,18 @@ function y = updateX(alg, mpo, qp, GL, GR, x, offset) qp.X = x; qp.B = computeB(qp); + B = qp.B; H_c = B_hamiltonian(mpo, qp, GL, GR, 'Type', 'center'); for i = period(qp):-1:1 - B(i) = MpsTensor(apply(H_c{i}, qp.B(i)), 1); + B(i) = MpsTensor(apply(H_c{i}, B(i)), 1); end H_l = B_hamiltonian(mpo, qp, GL, GR, 'Type', 'left'); for i = 1:period(qp) B(i) = B(i) + repartition(apply(H_l{i}, qp.AR(i)), rank(B(i))); end - + H_r = B_hamiltonian(mpo, qp, GL, GR, 'Type', 'right'); for i = 1:period(qp) B(i) = B(i) + repartition(apply(H_r{i}, qp.AL(i)), rank(B(i))); diff --git a/src/models/quantum1dIsing_dispersion.m b/src/models/quantum1dIsing_dispersion.m new file mode 100644 index 0000000..6cce657 --- /dev/null +++ b/src/models/quantum1dIsing_dispersion.m @@ -0,0 +1,13 @@ +function e = quantum1dIsing_dispersion(k, kwargs) +% Compute the dispersion relation of the transverse field Ising model in 1d. +arguments + k + kwargs.J = 1.0 + kwargs.h = 0.5 +end +g = 2 * kwargs.h; + +e = kwargs.J * sqrt(g^2 + 1 - 2 * g * cos(k)) / 2; + +end + diff --git a/src/models/quantum1dIsing_energy.m b/src/models/quantum1dIsing_energy.m new file mode 100644 index 0000000..81de703 --- /dev/null +++ b/src/models/quantum1dIsing_energy.m @@ -0,0 +1,9 @@ +function E0 = quantum1dIsing_energy(J, h) +% Compute the groundstate energy of the transverse field Ising model in 1d. +% +% + +E0 = -integral(@(k) quantum1dIsing_dispersion(k, 'J', J, 'h', h), 0, pi) / (2 * pi); + +end + diff --git a/src/mps/InfJMpo.m b/src/mps/InfJMpo.m index b1373cd..3d40132 100644 --- a/src/mps/InfJMpo.m +++ b/src/mps/InfJMpo.m @@ -145,7 +145,7 @@ end linkwargs = namedargs2cell(linopts); - expP = exp(-1i*qp.p); + expP = exp(-1i * qp.p); L = period(mpo); needsRegularization = istrivial(qp); @@ -226,7 +226,9 @@ GBR{1} = SparseTensor.zeros(domain(T), auxspace(qp, 1)); for w = L:-1:1 ww = next(w, L); - GBR{w} = expP^(1/L) * (apply(TB(ww), GR{ww}) + apply(T(ww), GBR{ww})); + TB_w = transfermatrix(mpo, qp, qp, w, 'Type', 'BR').'; + T_w = transfermatrix(mpo, qp, qp, w, 'Type', 'LR').'; + GBR{w} = expP^(1/L) * (apply(TB_w, GR{ww}) + apply(T_w, GBR{ww})); end N = size(GBR{1}, 2); @@ -261,7 +263,9 @@ for w = L:-1:2 ww = next(w, L); - GBR{w} = expP^(1/L) * (apply(TB(ww), GR{ww}) + apply(T(ww), GBR{ww})); + TB_w = transfermatrix(mpo, qp, qp, w, 'Type', 'BR').'; + T_w = transfermatrix(mpo, qp, qp, w, 'Type', 'LR').'; + GBR{w} = expP^(1/L) * (apply(TB_w, GR{ww}) + apply(T_w, GBR{ww})); end end diff --git a/test/TestAlgorithms.m b/test/TestAlgorithms.m index 547b476..3b8c861 100644 --- a/test/TestAlgorithms.m +++ b/test/TestAlgorithms.m @@ -38,49 +38,51 @@ function test2dIsing(tc, alg, symm) end end - function test1dIsing(tc) - E0 = -0.2525; - momenta = [0 0.5 pi]; - Delta0 = [0.805833 0.813325 1.00029]; + function test1dIsing_ordered(tc) + J = 1; + h = 1; + e0 = quantum1dIsing_energy(J, h); + d0 = @(k) quantum1dIsing_dispersion(k, 'J', J, 'h', h); + D = 12; + momenta = [0 pi 0.5]; - for L = 1:3 - for symm = ["Z1", "Z2"] - H = quantum1dIsing('Symmetry', symm, 'h', 0.1); - H = repmat(H, 1, L); - if strcmp(symm, 'Z1') - vspace = CartesianSpace.new(12); - else - vspace = GradedSpace.new(Z2(0, 1), [6 6], false); - end - gs = initialize_mps(H, vspace); - - %% Groundstate algorithms - gs = fixedpoint(Vumps('which', 'smallestreal', 'maxiter', 5), ... - H, gs); - tc.assertEqual(expectation_value(gs, H), E0 * L, 'RelTol', 1e-2); -% gs = fixedpoint(IDmrg('which', 'smallestreal', 'maxiter', 5), ... -% H, gs); -% tc.verifyEqual(expectation_value(gs, H), E0 * L, 'RelTol', 1e-2); - if L > 1 - gs2 = fixedpoint(IDmrg2('which', 'smallestreal', 'maxiter', 5), ... - H, gs); - tc.assertEqual(expectation_value(gs2, H), E0 * L, 'RelTol', 1e-2); - gs2 = fixedpoint(Vumps2('which', 'smallestreal', 'maxiter', 6), ... - H, gs); - tc.assertEqual(expectation_value(gs2, H), E0 * L, 'RelTol', 1e-2); - end - - %% Excitations - for i = 1:length(momenta) - if strcmp(symm, 'Z1') - qp = InfQP.randnc(gs, gs, momenta(i)); - else - qp = InfQP.randnc(gs, gs, momenta(i), Z2(1)); - end - [qp, mu] = excitations(QPAnsatz(), H, qp); - tc.verifyEqual(mu, Delta0(i), 'RelTol', 1e-3); - end - + for L = 3 + %% No symmetry + H = repmat(quantum1dIsing('h', h, 'J', J), 1, L); + + vspace = CartesianSpace.new(D, D+1, D+2); + gs = initialize_mps(H, vspace); + + % Groundstate algorithms + gs = fixedpoint(Vumps('which', 'smallestreal', 'maxiter', 5), ... + H, gs); + tc.assertEqual(expectation_value(gs, H), e0 * L, 'RelTol', 1e-2); + + % Excitation algorithms + for k = momenta + qp = InfQP.randnc(gs, gs, k); + [~, mu] = excitations(QPAnsatz(), H, qp); + tc.assertEqual(mu, d0(k/L), 'RelTol', 1e-3, ... + sprintf('qp failed at momentum %.2f', k)); + end + + %% Symmetry + H = repmat(quantum1dIsing('h', h, 'J', J, 'Symmetry', 'Z2'), 1, L); + + vspace = GradedSpace.new(Z2(0, 1), [D D] / 2, false); + gs = initialize_mps(H, vspace); + + % Groundstate algorithms + gs = fixedpoint(Vumps('which', 'smallestreal', 'maxiter', 5), ... + H, gs); + tc.assertEqual(expectation_value(gs, H), e0 * L, 'RelTol', 1e-2); + + % Excitation algorithms + for k = momenta + qp = InfQP.randnc(gs, gs, k, Z2(1)); + [~, mu] = excitations(QPAnsatz(), H, qp); + tc.assertEqual(mu, d0(k / L), 'RelTol', 1e-3, ... + sprintf('qp failed at momentum %.2f', k)); end end end From 2ee0ad2e0c0ae9b4491c37f504a52383b8d7ec0c Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 20 Apr 2023 10:52:59 +0200 Subject: [PATCH 200/245] add Arnoldi eigsolver --- src/algorithms/eigsolvers/Arnoldi.m | 324 ++++++++++++++++++++++ src/algorithms/eigsolvers/KrylovSchur.m | 111 ++++++++ src/algorithms/eigsolvers/selecteigvals.m | 20 ++ 3 files changed, 455 insertions(+) create mode 100644 src/algorithms/eigsolvers/Arnoldi.m create mode 100644 src/algorithms/eigsolvers/KrylovSchur.m create mode 100644 src/algorithms/eigsolvers/selecteigvals.m diff --git a/src/algorithms/eigsolvers/Arnoldi.m b/src/algorithms/eigsolvers/Arnoldi.m new file mode 100644 index 0000000..a0bae0e --- /dev/null +++ b/src/algorithms/eigsolvers/Arnoldi.m @@ -0,0 +1,324 @@ +classdef Arnoldi + % Arnoldi Krylov algorithm for linear algebra problems. + + properties + tol = 1e-10 % convergence tolerance + maxiter = 100 % maximum iterations + krylovdim = 20 % Krylov subspace dimension + deflatedim = 3 % number of Krylov vectors to keep when deflating + reorth = 20 % reorthogonalize basis if larger than this number + nobuild = 1 % frequency of convergence check when building + verbosity = Verbosity.warn % display information + end + + methods + function alg = Arnoldi(kwargs) + arguments + kwargs.?Arnoldi + end + fields = fieldnames(kwargs); + if ~isempty(fields) + for field = fields.' + alg.(field{1}) = kwargs.(field{1}); + end + end + end + + function [V, D, flag] = eigsolve(alg, A, v, howmany, sigma) + arguments + alg + A + v + howmany = 1 + sigma = 'lm' + end + + if isempty(alg.nobuild), alg.nobuild = ceil(alg.krylovdim / 10); end + if isempty(alg.deflatedim), alg.deflatedim = max(round(3/5 * alg.krylovdim), howmany); end + + v = v / norm(v, 'fro'); + + % preallocation + if isnumeric(v) && isvector(v) + V = zeros(length(v), alg.krylovdim, 'like', v); + else + V = zeros(1, alg.krylovdim, 'like', v); % Krylov subspace basis + end + H = zeros(alg.krylovdim, alg.krylovdim, underlyingType(v)); % Hessenberg matrix + + ctr_outer = 0; + ctr_inner = 0; + flag = 0; + + while ctr_outer < alg.maxiter + ctr_outer = ctr_outer + 1; + + while ctr_inner < alg.krylovdim % build Krylov subspace + ctr_inner = ctr_inner + 1; + + V(:, ctr_inner) = v; + v = A(v); + for i = 1:ctr_inner + H(i, ctr_inner) = dot(V(:, i), v); + end + v = v - V * H(:, ctr_inner); + + % reorthogonalize new vector + if ctr_inner >= alg.reorth + if isnumeric(V) + c = V' * v; + else + c = zeros(size(V, 2), 1); + for i = 1:ctr_inner + c(i) = dot(V(:, i), v); + end + end + H(:, ctr_inner) = H(:, ctr_inner) + c; + v = v - V * c; + end + + % normalize + beta = norm(v, 'fro'); + v = v / beta; + + if ctr_inner == alg.krylovdim, break; end + + if ctr_inner >= howmany + invariantsubspace = beta < eps(underlyingType(beta))^(3/4); + if invariantsubspace || ctr_inner == alg.krylovdim + break; + end + + % check for convergence during subspace build + if ~mod(ctr_inner, alg.nobuild) + [U, lambda] = eig(H(1:ctr_inner, 1:ctr_inner), 'vector'); + select = selecteigvals(lambda, howmany, sigma); + conv = beta * max(abs(U(ctr_inner, select))); + + if conv < alg.tol + V = V(:, 1:ctr_inner) * U(:, select); + D = diag(lambda(select)); + if alg.verbosity >= Verbosity.conv + fprintf('Conv %2d (%2d/%2d): error = %.5e.\n', ctr_outer, ... + ctr_inner, alg.krylovdim, conv); + end + return + end + + if alg.verbosity >= Verbosity.detail + fprintf('Iter %2d (%2d/%2d):\tlambda = %.5e + %.5ei;\terror = %.5e\n', ... + ctr_outer, ctr_inner, alg.krylovdim, ... + real(lambda(1)), imag(lambda(1)), conv); + end + end + end + + H(ctr_inner + 1, ctr_inner) = beta; + end + + % stopping criterium reached - irrespective of convergence + if ctr_outer == alg.maxiter || ctr_inner ~= alg.krylovdim + [U, lambda] = eig(H(1:ctr_inner, 1:ctr_inner), 'vector'); + select = selecteigvals(lambda, howmany, sigma); + conv = max(abs(beta * U(end, select))); + V = V(:, 1:ctr_inner) * U(:, select); + D = diag(lambda(select)); + + if conv > alg.tol + if invariantsubspace + fprintf('Found invariant subspace.\n'); + flag = 1; + else + fprintf('Reached maxiter without convergence.\n'); + flag = 2; + end + end + return + end + + % deflate Krylov subspace + [U1, T] = schur(H, 'real'); + E = ordeig(T); + select1 = false(size(E)); + select1(selecteigvals(E, alg.deflatedim, sigma)) = true; + [U1, T] = ordschur(U1, T, select1); + + V = V * U1; + [U, lambda] = eig(T(1:alg.deflatedim, 1:alg.deflatedim), 'vector'); + select = selecteigvals(lambda, howmany, sigma); + conv = max(abs(beta * U1(alg.krylovdim, 1:alg.deflatedim) * U(:, select))); + + % check for convergence + if conv < alg.tol + V = V(:, 1:alg.deflatedim) * U(:, select); + D = diag(lambda(select)); + if alg.verbosity >= Verbosity.conv + fprintf('Conv %2d: error = %.5e.\n', ctr_outer, conv); + end + return + end + + if alg.verbosity >= Verbosity.iter + fprintf('Iter %2d:\tlambda = %.5e + %.5ei;\terror = %.5e\n', ... + ctr_outer, real(lambda(1)), imag(lambda(1)), conv); + end + + % deflate Krylov subspace + H = zeros(alg.krylovdim, alg.krylovdim, underlyingType(v)); + H(1:alg.deflatedim, 1:alg.deflatedim) = ... + T(1:alg.deflatedim, 1:alg.deflatedim); + H(alg.deflatedim + 1, 1:alg.deflatedim) = ... + beta * U1(alg.krylovdim, 1:alg.deflatedim); + V(:, alg.deflatedim + 1:end) = 0 * V(:, alg.deflatedim + 1:end); + ctr_inner = alg.deflatedim; + end + end + + function [w0, flag] = expsolve(alg, A, t, v0) + if isempty(alg.nobuild), alg.nobuild = ceil(alg.krylovdim / 10); end + if isempty(alg.deflatedim), alg.deflatedim = max(round(3/5 * alg.krylovdim), howmany); end + + beta0 = norm(v0, 'fro'); + w0 = v0; + + w = w0; + v = w0; + beta = norm(w, 'fro'); + v = w / beta; + + % preallocation + V = zeros(1, alg.krylovdim, 'like', v0); % Krylov subspace basis + H = zeros(alg.krylovdim, alg.krylovdim, underlyingType(v0)); % Hessenberg matrix + + % time step parameters + eta = alg.tol; + totalerr = 0; + sgn = sign(t); + tau = abs(t); + tau0 = 0; + dtau = tau - tau0; + + delta = 1.2; + gamma = 0.8; + + ctr_outer = 0; + ctr_inner = 0; + flag = 0; + + while ctr_outer < alg.maxiter + ctr_outer = ctr_outer + 1; + + while ctr_inner < alg.krylovdim % build Krylov subspace + ctr_inner = ctr_inner + 1; + + V(1:length(w0), ctr_inner) = w; + w = A(w); + for i = 1:ctr_inner + H(i, ctr_inner) = dot(V(:, i), w); + end + w = w - V * H(:, ctr_inner); + + % reorthogonalize new vector + if ctr_inner >= alg.reorth + c = zeros(size(V, 2), 1); + for i = 1:ctr_inner + c(i) = dot(V(:, i), w); + end + H(:, ctr_inner) = H(:, ctr_inner) + c; + w = w - V * c; + end + + % normalize + beta = norm(w, 'fro'); + w = w / beta; + + if ctr_inner == alg.krylovdim + dtau = min(dtau, tau - tau0); + + HH = zeros(ctr_inner + 1); + HH(1:ctr_inner, 1:ctr_inner) = H(1:ctr_inner, 1:ctr_inner) * (sgn * dtau); + HH(1, ctr_inner + 1) = 1; + expH = expm(HH); + + vareps = abs(beta * H(ctr_inner, ctr_inner) * expH(ctr_inner, ctr_inner + 1)); + omega = vareps / (dtau * eta); + q = ctr_inner / 2; + + while omega > 1 + vareps_prev = vareps; + dtau_prev = dtau; + dtau = dtau * (gamma / omega)^(1 / (q + 1)); + + HH = zeros(ctr_inner + 1); + HH(1:ctr_inner, 1:ctr_inner) = H(1:ctr_inner, 1:ctr_inner) * (sgn * dtau); + HH(1, ctr_inner+1) = 1; + expH = expm(HH); + + vareps = abs(beta * H(ctr_inner, ctr_inner) * expH(ctr_inner, ctr_inner + 1)); + omega = vareps / (dtau * eta); + q = max(0, log(vareps / vareps_prev) / log(dtau / dtau_prev) - 1); + end + + % take time step + totalerr = totalerr + vareps; + w = V * expH(1:ctr_inner, ctr_inner); + w = w + expH(ctr_inner, end) * w0; + w0 = w0 + beta * w; + tau0 = tau0 + dtau; + + % increase time step for next iteration + if omega < gamma, dtau = dtau * (gamma / omega) ^(1 / (q + 1)); end + + if alg.verbosity > Verbosity.iter + fprintf('Iter %d: t = %.2e,\terror = %.5e.\n', ... + ctr_inner, tau0, totalerr); + end + + elseif H(ctr_inner, ctr_inner) <= (tau - tau0) * eta || alg.nobuild + HH = zeros(ctr_inner + 1); + HH(1:ctr_inner, 1:ctr_inner) = H(1:ctr_inner, 1:ctr_inner) * (sgn * (tau - tau0)); + HH(1, ctr_inner+1) = 1; + expH = expm(HH); + + vareps = abs(beta * H(ctr_inner, ctr_inner) * expH(ctr_inner, ctr_inner + 1)); + omega = vareps / ((tau - tau0) * eta); + + if omega < 1 % take time step + totalerr = totalerr + vareps; + w = V(:, 1:ctr_inner) * expH(1:ctr_inner, ctr_inner); + w = w + expH(ctr_inner, end) * v0; + w0 = w0 + beta * w; + tau0 = tau; + end + end + + if tau0 >= tau + if alg.verbosity >= Verbosity.conv + fprintf('Conv %d: error = %.5e.\n', ctr_outer, totalerr); + end + return + end + end + + if ctr_outer == alg.maxiter + if alg.verbosity >= Verbosity.conv + fprintf('Maxiter %d: error = %.5e\n', ctr_outer, totalerr); + end + return + end + + % reinitialize + beta = norm(w, 'fro'); + if beta < alg.tol + warning('converged to fixed point.'); + return + end + v = w / beta; + V = zeros(1, alg.krylovdim, 'like', v0); % Krylov subspace basis + H = zeros(alg.krylovdim, alg.krylovdim, underlyingType(v0)); % Hessenberg matrix + + end + end + end +end + diff --git a/src/algorithms/eigsolvers/KrylovSchur.m b/src/algorithms/eigsolvers/KrylovSchur.m new file mode 100644 index 0000000..302b63c --- /dev/null +++ b/src/algorithms/eigsolvers/KrylovSchur.m @@ -0,0 +1,111 @@ +classdef KrylovSchur + % KrylovSchur wrapper for Matlab implementation of eigs + + properties + tol = 1e-10 % convergence tolerance + maxiter = 100 % maximum iterations + krylovdim = 20 % Krylov subspace dimension + verbosity = Verbosity.warn % display information + end + + methods + function alg = KrylovSchur(kwargs) + arguments + kwargs.?KrylovSchur + end + fields = fieldnames(kwargs); + if ~isempty(fields) + for field = fields.' + alg.(field{1}) = kwargs.(field{1}); + end + end + end + + function varargout = eigsolve(alg, A, v0, howmany, sigma, kwargs) + arguments + alg + A + v0 + howmany = 1 + sigma = 'lm' + kwargs.IsSymmetric logical = false + end + + assert(isnumeric(sigma) || ismember(sigma, {'largestabs', 'smallestabs', ... + 'largestreal', 'smallestreal', 'bothendsreal', ... + 'largestimag', 'smallestimag', 'bothendsimag'}), ... + 'tensors:ArgumentError', 'Invalid choice of eigenvalue selector.'); + nargoutchk(0, 3); + + if isnumeric(v0) + v0_vec = v0; + if isa(A, 'function_handle') + A_fun = @A; + else + A_fun = @(v) A * v; + end + else + v0_vec = vectorize(v0); + if isa(A, 'function_handle') + A_fun = @(v) vectorize(A(devectorize(v, v0))); + else + A_fun = @(v) vectorize(A * devectorize(v, v0)); + end + end + + sz = size(v0_vec); + + if sz(1) < howmany + warning('eigsolve:size', 'requested %d out of %d eigenvalues.', ... + howmany, sz(1)); + howmany = sz(1); + end + + if sz(1) < alg.krylovdim + warning('eigsolve:size', 'requested %d out of %d eigenvalues.', ... + howmany, sz(1)); + alg.krylovdim = sz(1); + end + + % call builtin eigs + if howmany > sz(1) - 2 + % annoying bug with eigs and subspace dimension specification + [V, D, flag] = eigs(A_fun, sz(1), howmany, sigma, ... + 'Tolerance', alg.tol, 'MaxIterations', alg.maxiter, ... + 'IsFunctionSymmetric', ... + kwargs.IsSymmetric, 'StartVector', v0_vec, ... + 'Display', alg.verbosity >= Verbosity.iter); + else + [V, D, flag] = eigs(A_fun, sz(1), howmany, sigma, ... + 'Tolerance', alg.tol, 'MaxIterations', alg.maxiter, ... + 'SubspaceDimension', alg.krylovdim, 'IsFunctionSymmetric', ... + kwargs.IsSymmetric, 'StartVector', v0_vec, ... + 'Display', alg.verbosity >= Verbosity.iter); + end + + % process results + if nargout <= 1 + varargout = {D}; + else + if ~isnumeric(v0) + for i = howmany:-1:1 + varargout{1}(:, i) = devectorize(V(:, i), v0); + end + end + varargout{2} = D; + if nargout == 3 + varargout{3} = flag; + end + end + + % display + if flag + warning('eigsolve did not converge.'); + elseif ~flag && alg.verbosity > Verbosity.warn + fprintf('eigsolve converged.\n'); + end + end + end +end + + diff --git a/src/algorithms/eigsolvers/selecteigvals.m b/src/algorithms/eigsolvers/selecteigvals.m new file mode 100644 index 0000000..16be40c --- /dev/null +++ b/src/algorithms/eigsolvers/selecteigvals.m @@ -0,0 +1,20 @@ +function select = selecteigvals(lambda, howmany, which) + +switch which + case {'largestabs', 'lm'} + [~, p] = sort(abs(lambda), 'descend'); + case {'largestreal', 'lr'} + [~, p] = sort(real(lambda), 'descend'); + case {'largestimag', 'li'} + [~, p] = sort(imag(lambda), 'descend'); + case {'smallestabs', 'sm'} + [~, p] = sort(abs(lambda), 'ascend'); + case {'smallestreal', 'sr'} + [~, p] = sort(real(lambda), 'ascend'); + case {'smallestimag', 'si'} + [~, p] = sort(imag(lambda), 'ascend'); +end + +select = p(1:howmany); + +end \ No newline at end of file From c64c6bb4d4550af6c81447e2b4760286d09b523f Mon Sep 17 00:00:00 2001 From: leburgel Date: Wed, 19 Apr 2023 22:22:42 +0200 Subject: [PATCH 201/245] Fix Read the Docs build --- docs/requirements.txt | 6 +++--- docs/src/conf.py | 21 +-------------------- docs/src/index.rst | 6 +----- src/mps/MpoTensor.m | 2 +- src/mps/MpsTensor.m | 2 +- src/mps/UniformMps.m | 4 ++-- src/tensors/AbstractTensor.m | 2 +- 7 files changed, 10 insertions(+), 33 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index b8c586c..2cacf55 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,7 +1,7 @@ sphinx==4.5.0 -sphinx-rtd-theme==0.5.1 -sphinxcontrib-matlabdomain==0.12.0 -sphinx-prompt +sphinx-rtd-theme==0.5.2 +sphinxcontrib-matlabdomain==0.18.0 +sphinx-prompt==1.5.0 nbsphinx==0.8.9 sphinx-gallery==0.10.1 linkify-it-py==2.0.0 diff --git a/docs/src/conf.py b/docs/src/conf.py index 9aadc64..3d03a10 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -49,7 +49,6 @@ 'sphinx-prompt', 'sphinxcontrib.matlab', 'nbsphinx', - 'myst_parser', 'sphinx_gallery.load_style' ] @@ -75,7 +74,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', '**.ipynb_checkpoints', 'lib/tensors.rst'] +exclude_patterns = ['_build', '**.ipynb_checkpoints'] # -- Options for HTML output ----------------------------------------------- @@ -233,24 +232,6 @@ def linkcode_resolve(domain, info): ] -# -- myst_parser ----------------------------------------------- -# extensions for markdown parser -# myst_enable_extensions = [ -# "colon_fence", -# "deflist", -# "dollarmath", -# "fieldlist", -# "html_admonition", -# "html_image", -# "linkify", -# "replacements", -# "smartquotes", -# "strikethrough", -# "substitution", -# "tasklist", -# ] - - # -- sphinx.ext.mathjax configuration ----------------------------------------------- mathjax3_config = { "tex": { diff --git a/docs/src/index.rst b/docs/src/index.rst index 94be98f..41126a0 100644 --- a/docs/src/index.rst +++ b/docs/src/index.rst @@ -32,10 +32,6 @@ Additionally, for tensors which are invariant under general global symmetries, v :caption: Library :maxdepth: 2 + lib/tensors lib/utility lib/caches - -.. - lib/tensors - - diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index 6e3108a..1381c26 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -437,7 +437,7 @@ function disp(O) % ----------------- % 'Trunc' : cell % optional truncation method for the decomposition. See also - % :method:`Tensor.tsvd` + % :meth:`Tensor.tsvd` arguments H kwargs.Trunc = {'TruncBelow', 1e-14} diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index 6abc475..82797d4 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -594,7 +594,7 @@ function disp(t) % ----------------- % 'Trunc' : cell % optional truncation method for the decomposition. See also - % :method:`Tensor.tsvd` + % :meth:`Tensor.tsvd` arguments psi kwargs.Trunc = {'TruncBelow', 1e-14} diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index 6578990..fa19ae6 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -154,7 +154,7 @@ % % See Also % -------- - % :method:`UniformMps.new` + % :meth:`UniformMps.new` arguments (Repeating) pspaces @@ -480,7 +480,7 @@ % Keyword Arguments % ----------------- % eigopts - % see keyword arguments for :method:`eigs`. + % see keyword arguments for :meth:`eigs`. % % Verbosity : integer % detail level for output. diff --git a/src/tensors/AbstractTensor.m b/src/tensors/AbstractTensor.m index 4ca5d49..27327fb 100644 --- a/src/tensors/AbstractTensor.m +++ b/src/tensors/AbstractTensor.m @@ -475,7 +475,7 @@ function disp(t, details) % ----------------- % 'Trunc' : cell % optional truncation method for the decomposition. See also - % :method:`Tensor.tsvd` + % :meth:`Tensor.tsvd` arguments H kwargs.Trunc = {'TruncBelow', 1e-14} From 52d6a8752318e62de701800784a37e2fc194bac4 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 20 Apr 2023 17:09:54 +0200 Subject: [PATCH 202/245] MpsTensors in cells (WIP) --- src/algorithms/Expand.m | 9 +- src/algorithms/Vomps.m | 20 +- src/algorithms/Vumps.m | 55 +++--- src/algorithms/Vumps2.m | 26 +-- src/algorithms/eigsolvers/Arnoldi.m | 2 +- src/mps/FiniteMpo.m | 17 +- src/mps/FiniteMps.m | 40 ++-- src/mps/InfJMpo.m | 45 +---- src/mps/InfMpo.m | 69 +++++-- src/mps/MpsTensor.m | 181 ++++++------------ src/mps/UniformMps.m | 273 +++++++++++++++++++++------- test/TestAlgorithms.m | 10 +- test/TestUniformMps.m | 47 ++--- 13 files changed, 448 insertions(+), 346 deletions(-) diff --git a/src/algorithms/Expand.m b/src/algorithms/Expand.m index 2fcb927..ac5cb6c 100644 --- a/src/algorithms/Expand.m +++ b/src/algorithms/Expand.m @@ -84,7 +84,7 @@ end flag = addcharge.flag ~= 0 || nnz(addbond) > 0; - old_space = rightvspace(mps(d).AR(w)); + old_space = rightvspace(mps(d).AR{w}); new_space = struct; new_space.charges = charges(old_space); @@ -339,7 +339,12 @@ for d = 1:depth(mps) % expand tensors and convert to UniformMps - mps(d) = UniformMps(expand(mps(d).AR, new_spaces(d, :), alg.noisefactor)); + AR = mps(d).AR; + for w = 1:period(mps) + AR{w} = expand(AR{w}, new_spaces(d, w), alg.noisefactor); + end + + mps(d) = UniformMps(AR); end end diff --git a/src/algorithms/Vomps.m b/src/algorithms/Vomps.m index 1e9196c..8cea5d6 100644 --- a/src/algorithms/Vomps.m +++ b/src/algorithms/Vomps.m @@ -99,7 +99,7 @@ AC = vertcat(ACs{:}); for i = length(sites):-1:1 for d = 1:depth(mpo) - AC(d, i).var = H_AC{i}(d).apply(AC(prev(d, depth(mpo)), i).var); + AC{d, i}.var = H_AC{i}(d).apply(AC{prev(d, depth(mpo)), i}.var); end end end @@ -116,7 +116,7 @@ C = vertcat(Cs{:}); for i = length(sites):-1:1 for d = 1:depth(mpo) - C(d, i) = H_C{i}(d).apply(C(prev(d, depth(mpo)), i)); + C{d, i} = H_C{i}(d).apply(C{prev(d, depth(mpo)), i}); end end end @@ -130,9 +130,9 @@ for d = size(AC, 1):-1:1 for i = length(AC):-1:1 - [Q_AC, ~] = leftorth(AC(d, i), 'polar'); - [Q_C, ~] = leftorth(C(d, i), 1, 2, 'polar'); - mps(d).AL(sites(i)) = multiplyright(Q_AC, Q_C'); + [Q_AC, ~] = leftorth(AC{d, i}, 'polar'); + [Q_C, ~] = leftorth(C{d, i}, 1, 2, 'polar'); + mps(d).AL{sites(i)} = multiplyright(Q_AC, Q_C'); end end @@ -160,17 +160,17 @@ H_C = C_hamiltonian(mpo, mps1, GL, GR); eta = zeros(1, period(mps1)); for w = 1:period(mps1) - AC_ = apply(H_AC{w}, mps1.AC(w)); - lambda_AC = dot(AC_, mps2.AC(w)); + AC_ = apply(H_AC{w}, mps1.AC{w}); + lambda_AC = dot(AC_, mps2.AC{w}); AC_ = normalize(AC_ ./ lambda_AC); ww = prev(w, period(mps1)); - C_ = apply(H_C{ww}, mps1.C(ww)); - lambda_C = dot(C_, mps2.C(ww)); + C_ = apply(H_C{ww}, mps1.C{ww}); + lambda_C = dot(C_, mps2.C{ww}); C_ = normalize(C_ ./ lambda_C); eta(w) = distance(AC_ , ... - repartition(multiplyleft(mps2.AR(w), C_), rank(AC_))); + repartition(multiplyleft(mps2.AR{w}, C_), rank(AC_))); end eta = max(eta, [], 'all'); end diff --git a/src/algorithms/Vumps.m b/src/algorithms/Vumps.m index 18d1752..d0a537f 100644 --- a/src/algorithms/Vumps.m +++ b/src/algorithms/Vumps.m @@ -25,10 +25,11 @@ saveIterations = 1 saveMethod = 'full' name = 'VUMPS' + + alg_eigs = KrylovSchur('MaxIter', 100, 'KrylovDim', 20) end properties (Access = private) - alg_eigs = struct('MaxIter', 100, 'KrylovDim', 20) alg_canonical = struct('Method', 'polar') alg_environments = struct @@ -51,18 +52,18 @@ end if ~isfield('alg_eigs', kwargs) - alg.alg_eigs.Tol = sqrt(alg.tol_min * alg.tol_max); - alg.alg_eigs.Verbosity = alg.verbosity - 2; + alg.alg_eigs.tol = sqrt(alg.tol_min * alg.tol_max); + alg.alg_eigs.verbosity = alg.verbosity - 2; end if ~isfield('alg_canonical', kwargs) - alg.alg_canonical.Tol = sqrt(alg.tol_min * alg.tol_max); - alg.alg_canonical.Verbosity = alg.verbosity - 2; + alg.alg_canonical.tol = sqrt(alg.tol_min * alg.tol_max); + alg.alg_canonical.verbosity = alg.verbosity - 2; end if ~isfield('alg_environments', kwargs) - alg.alg_environments.Tol = sqrt(alg.tol_min * alg.tol_max); - alg.alg_environments.Verbosity = alg.verbosity - 2; + alg.alg_environments.tol = sqrt(alg.tol_min * alg.tol_max); + alg.alg_environments.verbosity = alg.verbosity - 2; end end @@ -112,7 +113,7 @@ %% Subroutines methods function AC = updateAC(alg, iter, mpo, mps, GL, GR) - kwargs = namedargs2cell(alg.alg_eigs); +% kwargs = namedargs2cell(alg.alg_eigs); if strcmp(alg.multiAC, 'sequential') sites = mod1(iter, period(mps)); else @@ -122,17 +123,16 @@ ACs = arrayfun(@(x) x.AC(sites), mps, 'UniformOutput', false); AC = vertcat(ACs{:}); for i = length(sites):-1:1 - [AC(1, i).var, ~] = eigsolve(H_AC{i}, AC(1, i).var, 1, alg.which, ... - kwargs{:}); - + [AC{1, i}.var, ~] = eigsolve(alg.alg_eigs, @(x) H_AC{i}.apply(x), AC{1, i}.var, ... + 1, alg.which); for d = 2:depth(mpo) - AC(d, i).var = H_AC{i}(d).apply(AC(d-1, i).var); + AC{d, i}.var = H_AC{i}(d).apply(AC{d-1, i}.var); end end end function C = updateC(alg, iter, mpo, mps, GL, GR) - kwargs = namedargs2cell(alg.alg_eigs); +% kwargs = namedargs2cell(alg.alg_eigs); if strcmp(alg.multiAC, 'sequential') sites = mod1(iter, period(mps)); else @@ -143,11 +143,9 @@ Cs = arrayfun(@(x) x.C(sites), mps, 'UniformOutput', false); C = vertcat(Cs{:}); for i = length(sites):-1:1 - [C(1, i), ~] = eigsolve(H_C{i}, C(1, i), 1, alg.which, ... - kwargs{:}); - + [C{1, i}, ~] = eigsolve(alg.alg_eigs, @(x) H_C{i}.apply(x), C{1, i}, 1, alg.which); for d = 2:depth(mpo) - C(d, i) = H_C{i}(d).apply(C(d-1, i)); + C{d, i} = H_C{i}(d).apply(C{d-1, i}); end end end @@ -161,9 +159,9 @@ for d = size(AC, 1):-1:1 for i = size(AC, 2):-1:1 - [Q_AC, ~] = leftorth(AC(d, i), 'polar'); - [Q_C, ~] = leftorth(C(d, i), 1, 2, 'polar'); - mps(d).AL(sites(i)) = multiplyright(Q_AC, Q_C'); + [Q_AC, ~] = leftorth(AC{d, i}, 'polar'); + [Q_C, ~] = leftorth(C{d, i}, 1, 2, 'polar'); + mps(d).AL{sites(i)} = multiplyright(Q_AC, Q_C'); end end @@ -184,8 +182,9 @@ D = depth(mpo); lambda = zeros(D, 1); for d = 1:D - [GL(d, :), GR(d, :), lambda(d)] = environments(mpo.slice(d), mps(d), mps(next(d, D)), GL(d, :), GR(d, :), ... - kwargs{:}); + dd = next(d, D); + [GL(d, :), GR(d, :), lambda(d)] = environments(mpo.slice(d), ... + mps(d), mps(dd), GL(d, :), GR(d, :), kwargs{:}); end lambda = prod(lambda); end @@ -198,17 +197,17 @@ for d = 1:depth(mpo) dd = next(d, depth(mpo)); for w = 1:period(mps) - AC_ = apply(H_AC{w}(d), mps(d).AC(w)); - lambda_AC = dot(AC_, mps(dd).AC(w)); + AC_ = apply(H_AC{w}(d), mps(d).AC{w}); + lambda_AC = dot(AC_, mps(dd).AC{w}); AC_ = normalize(AC_ ./ lambda_AC); ww = prev(w, period(mps)); - C_ = apply(H_C{ww}(d), mps(d).C(ww)); - lambda_C = dot(C_, mps(dd).C(ww)); + C_ = apply(H_C{ww}(d), mps(d).C{ww}); + lambda_C = dot(C_, mps(dd).C{ww}); C_ = normalize(C_ ./ lambda_C); eta(dd, w) = distance(AC_ , ... - repartition(multiplyleft(mps(dd).AR(w), C_), rank(AC_))); + repartition(multiplyleft(mps(dd).AR{w}, C_), rank(AC_))); end end eta = max(eta, [], 'all'); @@ -220,7 +219,7 @@ methods function alg = updatetols(alg, iter, eta) if alg.dynamical_tols - alg.alg_eigs.Tol = between(alg.tol_min, eta * alg.eigs_tolfactor / iter, ... + alg.alg_eigs.tol = between(alg.tol_min, eta * alg.eigs_tolfactor / iter, ... alg.tol_max); alg.alg_canonical.Tol = between(alg.tol_min, ... eta * alg.canonical_tolfactor / iter, alg.tol_max); diff --git a/src/algorithms/Vumps2.m b/src/algorithms/Vumps2.m index ab2feb0..d0371af 100644 --- a/src/algorithms/Vumps2.m +++ b/src/algorithms/Vumps2.m @@ -128,8 +128,8 @@ end H_AC2 = AC2_hamiltonian(mpo, mps, GL, GR, sites); for i = length(sites):-1:1 - AC2 = MpsTensor(contract(mps.AC(sites(i)), [-1 -2 1], ... - mps.AR(next(sites(i), period(mps))), [1 -3 -4], ... + AC2 = MpsTensor(contract(mps.AC{sites(i)}, [-1 -2 1], ... + mps.AR{next(sites(i), period(mps))}, [1 -3 -4], ... 'Rank', [2 2])); [AC2.var, ~] = eigsolve(H_AC2{i}, AC2.var, 1, alg.which, ... kwargs{:}); @@ -147,7 +147,7 @@ sites = next(sites, period(mps)); H_C = C_hamiltonian(mpo, mps, GL, GR, sites); for i = length(sites):-1:1 - [C(i), ~] = eigsolve(H_C{i}, mps.C(sites(i)), 1, alg.which, ... + [C{i}, ~] = eigsolve(H_C{i}, mps.C{sites(i)}, 1, alg.which, ... kwargs{:}); end end @@ -160,23 +160,23 @@ sites = sites(mod(sites, 2) == mod(iter, 2)); end for i = length(AC2):-1:1 - [Q_AC, ~] = leftorth(AC2(i), 'polar'); - [Q_C, ~] = leftorth(C(i), 1, 2, 'polar'); + [Q_AC, ~] = leftorth(AC2{i}, 'polar'); + [Q_C, ~] = leftorth(C{i}, 1, 2, 'polar'); AL = multiplyright(Q_AC, Q_C'); if alg.notrunc assert(isempty(alg.trunc) || strcmp(alg.trunc{1}, 'TruncTotalDim'), 'tba', 'notrunc only defined in combination with TruncTotalDim'); alg.trunc{2} = max(alg.trunc{2}, dims(rightvspace(mps, sites(i)))); end [AL1, C, AL2] = tsvd(AL.var, [1 2], [3 4], alg.trunc{:}); - mps.AL(sites(i)) = multiplyright(MpsTensor(AL1), C); - mps.AL(next(sites(i), period(mps))) = AL2; + mps.AL{sites(i)} = multiplyright(MpsTensor(AL1), C); + mps.AL{next(sites(i), period(mps))} = AL2; end kwargs = namedargs2cell(alg.alg_canonical); % mps.C = []; newAL = cell(size(mps.AL)); for i = 1:numel(newAL) - newAL{i} = mps.AL(i); + newAL{i} = mps.AL{i}; end mps = UniformMps(newAL); end @@ -200,17 +200,17 @@ H_C = C_hamiltonian(mpo, mps, GL, GR); eta = zeros(1, period(mps)); for w = 1:period(mps) - AC_ = apply(H_AC{w}, mps.AC(w)); - lambda_AC = dot(AC_, mps.AC(w)); + AC_ = apply(H_AC{w}, mps.AC{w}); + lambda_AC = dot(AC_, mps.AC{w}); AC_ = normalize(AC_ ./ lambda_AC); ww = prev(w, period(mps)); - C_ = apply(H_C{ww}, mps.C(ww)); - lambda_C = dot(C_, mps.C(ww)); + C_ = apply(H_C{ww}, mps.C{ww}); + lambda_C = dot(C_, mps.C{ww}); C_ = normalize(C_ ./ lambda_C); eta(w) = distance(AC_ , ... - repartition(multiplyleft(mps.AR(w), C_), rank(AC_))); + repartition(multiplyleft(mps.AR{w}, C_), rank(AC_))); end eta = max(eta, [], 'all'); end diff --git a/src/algorithms/eigsolvers/Arnoldi.m b/src/algorithms/eigsolvers/Arnoldi.m index a0bae0e..0a3e82d 100644 --- a/src/algorithms/eigsolvers/Arnoldi.m +++ b/src/algorithms/eigsolvers/Arnoldi.m @@ -126,7 +126,7 @@ if conv > alg.tol if invariantsubspace - fprintf('Found invariant subspace.\n'); + fprintf('Found invariant subspace (error = %.5e).\n', conv); flag = 1; else fprintf('Reached maxiter without convergence.\n'); diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index 2acab58..bf6005b 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -132,7 +132,7 @@ end function d = depth(mpo) - d = size(mpo, 1); + d = builtin('length', mpo); end function l = length(mpo) @@ -145,7 +145,7 @@ function mpo = ctranspose(mpo) if depth(mpo) > 1 - mpo = flip(mpo, 1); + mpo = flip(mpo); end for d = 1:depth(mpo) @@ -160,7 +160,7 @@ function mpo = transpose(mpo) if depth(mpo) > 1 - mpo = flip(mpo, 1); + mpo = flip(mpo); end for d = 1:depth(mpo) @@ -272,16 +272,19 @@ Abot % bottom mps tensors (unconjugated) end + assert(length(Atop) == length(O) && length(O) == length(Abot), ... + 'FiniteMpo:argerror', 'unmatching sizes'); + for i = numel(Atop):-1:1 - atop = Atop(i); + atop = Atop{i}; o = rot90(O{i}); - twistinds = 1 + find(isdual(space(Atop(i), 2:nspaces(Atop(i)) - 1))); - abot = twist(Abot(i)', twistinds); + twistinds = 1 + find(isdual(space(atop, 2:nspaces(atop) - 1))); + abot = twist(Abot{i}', twistinds); assert(isequal(pspace(abot)', leftvspace(o))); assert(isequal(rightvspace(o)', pspace(atop))); - T(i, 1) = FiniteMpo(abot, {o}, atop); + T(i) = FiniteMpo(abot, {o}, atop); end end end diff --git a/src/mps/FiniteMps.m b/src/mps/FiniteMps.m index a5cea39..46d4754 100644 --- a/src/mps/FiniteMps.m +++ b/src/mps/FiniteMps.m @@ -2,7 +2,7 @@ % Finite Matrix product states properties - A (1,:) MpsTensor + A (1,:) cell center end @@ -11,9 +11,17 @@ function mps = FiniteMps(varargin) if nargin == 0, return; end if nargin == 1 - mps.A = varargin{1}; + if iscell(varargin{1}) + mps.A = varargin{1}; + else + mps.A = varargin(1); + end elseif nargin == 2 - mps.A = varargin{1}; + if iscell(varargin{1}) + mps.A = varargin{1}; + else + mps.A = varargin(1); + end mps.center = varargin{2}; end end @@ -75,7 +83,7 @@ 'Cannot create a full rank mps with given spaces.'); end - A{w} = Tensor.new(fun, [vspaces{w} pspaces{w}], vspaces{w + 1}); + A{w} = MpsTensor.new(fun, [vspaces{w} pspaces{w}], vspaces{w + 1}); end mps = FiniteMps(A); end @@ -109,7 +117,7 @@ error('mps:rank', ... 'Cannot create a full rank mps with given spaces.'); end - A{w} = Tensor.new(fun, [Vleft P], Vright); + A{w} = MpsTensor.new(fun, [Vleft P], Vright); end mps = FiniteMps(A); @@ -159,7 +167,7 @@ end assert(all(diff(sites) == 1), 'sites must be neighbouring and increasing.'); - T = transfermatrix(mps1.A(sites), mps2.A(sites)); + T = cellfun(@transfermatrix, mps1.A(sites), mps2.A(sites)); end end @@ -186,12 +194,12 @@ end for i = low:(newcenter - 1) - [mps.A(i), L] = leftorth(mps.A(i), alg); - mps.A(i + 1) = multiplyleft(mps.A(i + 1), L); + [mps.A{i}, L] = leftorth(mps.A{i}, alg); + mps.A{i + 1} = multiplyleft(mps.A{i + 1}, L); end for i = high:-1:(newcenter + 1) - [R, mps.A(i)] = rightorth(mps.A(i), alg); - [mps.A(i - 1)] = multiplyright(mps.A(i - 1), R); + [R, mps.A{i}] = rightorth(mps.A{i}, alg); + [mps.A{i - 1}] = multiplyright(mps.A{i - 1}, R); end mps.center = newcenter; end @@ -269,13 +277,13 @@ return end - n = norm(mps.A(mps.center)); + n = norm(mps.A{mps.center}); end function [mps, n] = normalize(mps) if isempty(mps.center), mps = movegaugecenter(mps); end n = norm(mps); - mps.A(mps.center) = mps.A(mps.center) ./ n; + mps.A{mps.center} = mps.A{mps.center} ./ n; end end @@ -285,19 +293,19 @@ function psi = Tensor(mps) % Convert a finite mps to a dense tensor. - tensors = num2cell(mps.A); + tensors = mps.A; indices = cell(size(tensors)); - nout = nspaces(mps.A(1)) - 1; + nout = nspaces(mps.A{1}) - 1; indices{1} = [-(1:nout), 1]; for i = 2:length(indices)-1 - plegs = mps.A(i).plegs; + plegs = mps.A{i}.plegs; indices{i} = [i-1 -(1:plegs)-nout i]; nout = nout + plegs; end - plegs = mps.A(end).plegs; + plegs = mps.A{end}.plegs; indices{end} = [length(indices)-1 -(1:plegs+1)-nout]; args = [tensors; indices]; diff --git a/src/mps/InfJMpo.m b/src/mps/InfJMpo.m index 3d40132..6a38996 100644 --- a/src/mps/InfJMpo.m +++ b/src/mps/InfJMpo.m @@ -66,6 +66,7 @@ GL{1} = full(GL{1}); end +% GL{1} = MpsTensor(GL{1}, 0); for w = 1:period(mps1)-1 T = transfermatrix(mpo, mps1, mps2, w, 'Type', 'LL'); GL{next(w, period(mps1))} = apply(T, GL{w}); @@ -126,6 +127,7 @@ GR{1} = full(GR{1}); end +% GR{1} = MpsTensor(GR{1}, 0); for w = period(mps1):-1:2 T = transfermatrix(mpo, mps1, mps2, w, 'Type', 'RR').'; GR{w} = apply(T, GR{next(w, period(mps1))}); @@ -175,8 +177,9 @@ GBL{1}(i) = rhs; else if needsRegularization && iseye(T, i) + lambda = overlap(rhs, fp_right); fp_left = repartition(fp_left, rank(rhs)); - rhs = rhs - overlap(rhs, fp_right) * fp_left; + rhs = rhs - lambda * fp_left; H_effective = @(x) x - expP * ... apply_regularized(Tdiag, fp_left, fp_right, x); else @@ -191,6 +194,7 @@ GBL{1} = full(GBL{1}); end + GBL{1} = MpsTensor(GBL{1}, 1); for w = 1:L-1 GBL{next(w, L)} = expP^(1 / L) * ... (apply(TB(w), GL{w}) + apply(T(w), GBL{w})); @@ -245,8 +249,9 @@ GBR{1}(i) = rhs; else if needsRegularization && iseye(T, i) + lambda = overlap(rhs, fp_left); fp_right = repartition(fp_right, rank(rhs)); - rhs = rhs - overlap(rhs, fp_left) * fp_right; + rhs = rhs - lambda * fp_right; H_effective = @(x) x - expP * ... apply_regularized(Tdiag, fp_right, fp_left, x); else @@ -261,6 +266,7 @@ GBR{1} = full(GBR{1}); end + GBR{1} = MpsTensor(GBR{1}, 1); for w = L:-1:2 ww = next(w, L); TB_w = transfermatrix(mpo, qp, qp, w, 'Type', 'BR').'; @@ -291,41 +297,6 @@ end end - function fp = fixedpoint(operator, state, type, w) - arguments - operator - state - type - w = 1 - end - - fp = fixedpoint(state, type(1:4), w); - - % add leg to fit operator - switch type(1) - case 'l' - fp = insert_onespace(fp, 2, ~isdual(leftvspace(operator, w))); - case 'r' - fp = insert_onespace(fp, 2, ~isdual(rightvspace(operator, w))); - otherwise - error('invalid fixedpoint type (%s)', type); - end - - % add leg to fit quasiparticle auxiliary leg - if isa(state, 'InfQP') - switch type(6) - case '0' - dual = isdual(auxspace(state, w)); - case '1' - dual = ~isdual(auxspace(state, w)); - otherwise - error('invalid type (%s)', type); - end - fp = MpsTensor(insert_onespace(fp, nspaces(fp) + 1, dual), 1); - end - end - - function mpo = renormalize(mpo, lambda) mpo = mpo - lambda; end diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index fee4596..998d36e 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -118,6 +118,40 @@ %% Environments methods + function fp = fixedpoint(operator, state, type, w) + arguments + operator + state + type + w = 1 + end + + fp = fixedpoint(state, type(1:4), w); + + % add leg to fit operator + switch type(1) + case 'l' + fp = insert_onespace(fp, 2, ~isdual(leftvspace(operator, w))); + case 'r' + fp = insert_onespace(fp, 2, ~isdual(rightvspace(operator, w))); + otherwise + error('invalid fixedpoint type (%s)', type); + end + + % add leg to fit quasiparticle auxiliary leg + if isa(state, 'InfQP') + switch type(6) + case '0' + dual = isdual(auxspace(state, w)); + case '1' + dual = ~isdual(auxspace(state, w)); + otherwise + error('invalid type (%s)', type); + end + fp = MpsTensor(insert_onespace(fp, nspaces(fp) + 1, dual), 1); + end + end + function [GL, lambda] = leftenvironment(mpo, mps1, mps2, GL, eigopts) arguments mpo @@ -140,6 +174,7 @@ T = transfermatrix(mpo, mps1, mps2, i-1, 'Type', 'LL'); GL{i} = apply(T, GL{i-1}) ./ lambda^(1/N); end + GL = cellfun(@MpsTensor, GL, 'UniformOutput', false); end function [GR, lambda] = rightenvironment(mpo, mps1, mps2, GR, eigopts) @@ -164,6 +199,7 @@ T = transfermatrix(mpo, mps1, mps2, i, 'Type', 'RR').'; GR{i} = apply(T, GR{next(i, N)}) ./ lambda^(1/N); end + GR = cellfun(@MpsTensor, GR, 'UniformOutput', false); end function GBL = leftquasienvironment(mpo, qp, GL, GR, GBL, linopts) @@ -184,9 +220,10 @@ linkwargs = namedargs2cell(linopts); expP = exp(-1i * qp.p); + L = period(mpo); rho = GL{1}; - for pos = 1:period(mpo) + for pos = 1:L T_B = transfermatrix(mpo, qp, qp, pos, 'Type', 'BL'); if pos == 1 rho = apply(T_B, GL{pos}); @@ -198,21 +235,24 @@ T_R = transfermatrix(mpo, qp, qp, 1:period(mpo), 'Type', 'RL'); if istrivial(qp) - C = qp.mpsleft.C(1); - FL = insert_onespace(multiplyright(MpsTensor(GL{1}), C), ... - nspaces(GL{1}) + 1, isdual(auxspace(qp, 1))); - FR = insert_onespace(multiplyright(MpsTensor(GR{1}), C'), ... - 1, ~isdual(auxspace(qp, 1))); + fp_left = fixedpoint(mpo, qp, 'l_RL_0', 1); + fp_right = fixedpoint(mpo, qp, 'r_RL_1', L); +% C = qp.mpsleft.C(1); +% FL = insert_onespace(multiplyright(MpsTensor(GL{1}), C), ... +% nspaces(GL{1}) + 1, isdual(auxspace(qp, 1))); +% FR = insert_onespace(multiplyright(MpsTensor(GR{1}), C'), ... +% 1, ~isdual(auxspace(qp, 1))); - rho = rho - overlap(rho, FR) * FL; - GBL{1} = linsolve(@(x) x - expP * apply_regularized(T_R, FL, FR, x), ... + rho = rho - overlap(rho, fp_right) * fp_left; + GBL{1} = linsolve(@(x) x - expP * apply_regularized(T_R, fp_left, fp_right, x), ... expP * rho, [], linkwargs{:}); else GBL{1} = expP * linsolve(@(x) x - expP * apply(T_R, x), ... rho, [], linkwargs{:}); end - for i = 2:period(mpo) + GBL{1} = MpsTensor(GBL{1}, 1); + for i = 2:L T_R = transfermatrix(mpo, qp, qp, i-1, 'Type', 'RL'); T_B = transfermatrix(mpo, qp, qp, i-1, 'Type', 'BL'); GBL{i} = apply(T_R, GBL{i-1}) + apply(T_B, GL{w-1}); @@ -266,6 +306,7 @@ end N = period(mpo); + GBR{1} = MpsTensor(GBR{1}, 1); for i = N:-1:2 T_L = transfermatrix(mpo, qp, qp, i, 'Type', 'LR').'; T_B = transfermatrix(mpo, qp, qp, i, 'Type', 'BR').'; @@ -297,8 +338,8 @@ for w = 1:period(mps1) overlap = sqrt(contract(GL{w}, [1, 3:nspaces(GL{w}), 2], ... - mps1.C(prev(w, period(mps1))), [2, nspaces(GL{w})+1], ... - mps2.C(prev(w, period(mps1)))', [nspaces(GL{w})+2, 1], ... + mps1.C{prev(w, period(mps1))}, [2, nspaces(GL{w})+1], ... + mps2.C{prev(w, period(mps1))}', [nspaces(GL{w})+2, 1], ... GR{w}, [nspaces(GL{w})+1, flip(3:nspaces(GL{w})), nspaces(GL{w})+2])); GL{w} = GL{w} ./ overlap; GR{w} = GR{w} ./ overlap; @@ -366,8 +407,10 @@ gl = twistdual(GL{d, sites(i)}, 1); gr = GR{d, next(sites(i), period(mpo))}; gr = twistdual(gr, nspaces(gr)); - gr_better = tpermute(gr, [1, flip(2:nspaces(gr)-1), nspaces(gr)]); - gl_better = tpermute(gl, [1, flip(2:nspaces(gl)-1), nspaces(gl)]); + gr_better = tpermute(gr, [1, flip(2:nspaces(gr)-1-gr.alegs), ... + nspaces(gr) + (0:gr.alegs)]); + gl_better = tpermute(gl, [1, flip(2:nspaces(gl)-1-gl.alegs), ... + nspaces(gl) + (0:gl.alegs)]); H{i}(d, 1) = FiniteMpo(gl_better, mpo.O(d, sites(i)), gr_better); end end diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index 9d835a1..1c41181 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -252,6 +252,10 @@ [A.var] = twist([A.var], varargin{:}); end + function A = twistdual(A, varargin) + [A.var] = twistdual([A.var], varargin{:}); + end + function t = ctranspose(t) % Compute the adjoint of a tensor. This is defined as swapping the codomain and % domain, while computing the adjoint of the matrix blocks. @@ -291,128 +295,8 @@ C = tensorprod(varargin{:}); end - function [AL, CL, lambda, eta] = uniform_leftorth(A, CL, kwargs) - arguments - A - CL = [] - kwargs.Tol = eps(underlyingType(A))^(3/4) - kwargs.MaxIter = 500 - kwargs.Method = 'polar' - kwargs.Verbosity = 0 - kwargs.Normalize = true - kwargs.EigsInit = 3 - kwargs.EigsFrequence = 2 - end -% - % constants - EIG_TOLFACTOR = 1/50; - EIG_MAXTOL = 1e-4; - MINKRYLOVDIM = 8; - MAXKRYLOVDIM = 30; - - % initialization - N = size(A, 2); - if isempty(CL), CL = initializeC(A, A); end - if kwargs.Normalize, CL(1) = normalize(CL(1)); end - for i = 1:numel(A) - A(i).var = repartition(A(i).var, [nspaces(A(i).var) - 1, 1]); - end - AL = A; - - eta_best = Inf; - ctr_best = 0; - AL_best = AL; - C_best = CL; - lambda_best = 0; - - for ctr = 1:kwargs.MaxIter - if ctr > kwargs.EigsInit && mod(ctr, kwargs.EigsFrequence) == 0 - C_ = repartition(CL(end), [2 0]); - T = transfermatrix(A, AL); - [C_, ~] = eigsolve(T, C_, ... - 1, 'largestabs', ... - 'Tol', min(eta * EIG_TOLFACTOR, EIG_MAXTOL), ... - 'KrylovDim', between(MINKRYLOVDIM, ... - MAXKRYLOVDIM - ctr / 2 + 4, ... - MAXKRYLOVDIM), ... - 'NoBuild', 4, ... - 'Verbosity', kwargs.Verbosity - 1); - [~, CL(end)] = leftorth(C_, 1, 2, kwargs.Method); - if isdual(space(CL(end), 2)) == isdual(space(CL(end), 1)) - CL(end).codomain = conj(CL(end).codomain); - CL(end) = twist(CL(end), 1); - end - end - - C_ = CL(end); - lambdas = ones(1, N); - for w = 1:N - ww = prev(w, N); - CA = multiplyleft(A(w), CL(ww)); - [AL(w), CL(w)] = leftorth(CA, kwargs.Method); - lambdas(w) = norm(CL(w)); - if kwargs.Normalize, CL(w) = CL(w) ./ lambdas(w); end - end - lambda = prod(lambdas); - eta = norm(C_ - CL(end), Inf); - if eta < kwargs.Tol - if kwargs.Verbosity >= Verbosity.conv - fprintf('Conv %2d:\terror = %0.4e\n', ctr, eta); - end - break; - elseif eta < eta_best - eta_best = eta; - ctr_best = ctr; - AL_best = AL; - C_best = CL; - lambda_best = lambda; - elseif ctr > 40 && ctr - ctr_best > 5 - warning('uniform_orthright:stagnate', 'Algorithm stagnated'); - eta = eta_best; - AL = AL_best; - CL = C_best; - lambda = lambda_best; - break; - end - - if kwargs.Verbosity >= Verbosity.iter - fprintf('Iter %2d:\terror = %0.4e\n', ctr, eta); - end - end - - if kwargs.Verbosity >= Verbosity.warn && eta > kwargs.Tol - fprintf('Not converged %2d:\terror = %0.4e\n', ctr, eta); - end - end - function [AR, CR, lambda, eta] = uniform_rightorth(A, CR, kwargs) - arguments - A - CR = [] - kwargs.Tol = eps(underlyingType(A))^(3/4) - kwargs.MaxIter = 500 - kwargs.Method = 'polar' - kwargs.Verbosity = 0 - kwargs.Normalize = true - kwargs.EigsInit = 3 - kwargs.EigsFrequence = 2 - end - - opts = namedargs2cell(kwargs); - - Ad = flip(arrayfun(@ctranspose, A)); - if isempty(CR) - Cd = []; - else - Cd = circshift(flip(arrayfun(@ctranspose, CR)), -1); - end - - [ARd, CRd, lambda, eta] = uniform_leftorth(Ad, Cd, opts{:}); - - AR = flip(arrayfun(@ctranspose, ARd)); - CR = circshift(flip(arrayfun(@ctranspose, CRd)), -1); - lambda = conj(lambda); - end + function T = transfermatrix(A, B) arguments @@ -420,10 +304,8 @@ B MpsTensor = A end - for i = length(A):-1:1 - B(i).var = twist(B(i).var, [isdual(space(B(i), 1:2)) ~isdual(space(B(i), 3))]); - T(i, 1) = FiniteMpo(B(i).var', {}, A(i)); - end + B = twist(B, [isdual(space(B, 1:2)) ~isdual(space(B, 3))]); + T = FiniteMpo(B', {}, A); end function v = applytransfer(L, R, v) @@ -590,13 +472,21 @@ end function A = multiplyright(A, C) + r = rank(A); + r(2) = r(2) + nspaces(C) - 2; + [A.var] = contract([A.var], [-(1:A.plegs+1) 1 -((1:A.alegs)+A.plegs+2)], ... - C, [1 -(2+A.plegs)], 'Rank', rank(A)); + C, [1 -(2 + A.plegs + (1:(nspaces(C) - 1)))], 'Rank', r); end - function C = initializeC(AL, AR) - for i = length(AL):-1:1 - C(i) = AL(i).var.eye(rightvspace(AL(i))', leftvspace(AR(next(i, length(AR))))); + function C = initializeC(A) + arguments (Repeating) + A + end + C = cell(size(A)); + + for i = length(A):-1:1 + C{i} = A{i}.var.eye(rightvspace(A{i})', leftvspace(A{next(i, length(A))})); end end @@ -623,7 +513,8 @@ end function disp(t) - builtin('disp', t); + fprintf('%s with %d plegs and %d alegs:\n', class(t), t.plegs, t.alegs); + disp(t.var); end function n = nnz(t) @@ -632,6 +523,14 @@ function disp(t) end + %% + methods + function bool = isisometry(t, varargin) + bool = isisometry(t.var, varargin{:}); + end + end + + %% Converters methods function t = Tensor(A) @@ -741,6 +640,28 @@ function disp(t) end local_tensors{end} = MpsTensor(repartition(psi, [2 1])); end + + function A = new(varargin, kwargs) + arguments (Repeating) + varargin + end + arguments + kwargs.alegs = 0 + end + + A = MpsTensor(Tensor.new(varargin{:}), kwargs.alegs); + end + + function A = randnc(varargin, kwargs) + arguments (Repeating) + varargin + end + arguments + kwargs.alegs = 0 + end + + A = MpsTensor(Tensor.randnc(varargin{:}), kwargs.alegs); + end end end diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index 6578990..5d83361 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -19,10 +19,10 @@ % center-gauged mps tensors. properties - AL (1,:) MpsTensor - AR (1,:) MpsTensor - C (1,:) Tensor - AC (1,:) MpsTensor + AL (1,:) cell + AR (1,:) cell + C (1,:) cell + AC (1,:) cell end @@ -37,13 +37,13 @@ % % Arguments % --------- - % A : :class:`MpsTensor` or :class:`PeriodicCell` + % A : :class:`cell` of :class:`MpsTensor` % set of tensors per site that define an MPS to be gauged. % - % AL, AR, AC : :class:`MpsTensor` or :class:`PeriodicCell` + % AL, AR, AC : :class:`cell` of :class:`MpsTensor` % set of gauged MpsTensors. % - % C : :class:`Tensor` + % C : :class:`cell` of :class:`Tensor` % gauge tensor. % % Returns @@ -51,6 +51,8 @@ % mps : :class:`UniformMps` % gauged uniform MPS. + narginchk(0, 4); + if nargin == 0, return; end % default empty constructor if nargin == 1 @@ -63,7 +65,14 @@ end mps = reshape(mps, size(mps)); - elseif isa(varargin{1}, 'Tensor') || isa(varargin{1}, 'MpsTensor') + elseif isa(varargin{1}, 'Tensor') + for i = height(varargin{1}):-1:1 + mps.AR = MpsTensor(varargin{1}); + mps.AL = MpsTensor(varargin{1}); + end + mps = canonicalize(mps); + + elseif isa(varargin{1}, 'MpsTensor') % by default we take the input as AR, as canonicalize right % orthogonalizes before left orthogonalization for i = height(varargin{1}):-1:1 @@ -73,31 +82,29 @@ mps = canonicalize(mps); elseif iscell(varargin{1}) - for i = flip(1:width(varargin{1})) - for j = 1:height(varargin{1}) - mps(j).AR(i) = varargin{1}{j, i}; - mps(j).AL(i) = varargin{1}{j, i}; - end + for j = height(varargin{1}):-1:1 + mps(j).AR = varargin{1}(j, :); + mps(j).AL = varargin{1}(j, :); end mps = canonicalize(mps); else error('Invalid constructor for UniformMps.') end - elseif nargin == 3 - mps.AL = varargin{1}; - mps.AR = varargin{2}; - mps.C = varargin{3}; - - elseif nargin == 4 - mps.AL = varargin{1}; - mps.AR = varargin{2}; - mps.C = varargin{3}; - mps.AC = varargin{4}; - else - error('Invalid constructor for UniformMps.') + if iscell(varargin{1}), mps.AL = varargin{1}; else, mps.AL = varargin(1); end + if iscell(varargin{2}), mps.AR = varargin{2}; else, mps.AR = varargin(2); end + if iscell(varargin{3}), mps.C = varargin{3}; else, mps.C = varargin(3); end + if nargout == 4 + if iscell(varargin{4}) + mps.AC = varargin{4}; + else + mps.AC = varargin(4); + end + end end + + end end @@ -142,8 +149,8 @@ 'Cannot create a full rank mps with given spaces.'); end - A{w} = Tensor.new(fun, [vspaces{w} pspaces{w}], ... - vspaces{next(w, L)}); + A{w} = MpsTensor(Tensor.new(fun, [vspaces{w} pspaces{w}], ... + vspaces{next(w, L)}), 0); end mps = UniformMps(A); @@ -178,12 +185,14 @@ d = size(mps, 1); end - function mps = horzcat(varargin) - ALs = cellfun(@(x) x.AL, varargin, 'UniformOutput', false); - ARs = cellfun(@(x) x.AR, varargin, 'UniformOutput', false); - Cs = cellfun(@(x) x.C, varargin, 'UniformOutput', false); - ACs = cellfun(@(x) x.AC, varargin, 'UniformOutput', false); - mps = UniformMps([ALs{:}], [ARs{:}], [Cs{:}], [ACs{:}]); + function mps = horzcat(mps, mps2, varargin) + mps.AL = horzcat(mps.AL, mps2.AL); + mps.AR = horzcat(mps.AR, mps2.AR); + mps.C = horzcat(mps.C, mps2.C); + mps.AC = horzcat(mps.AC, mps2.AC); + if nargin > 2 + mps = horzcat(mps, varargin{:}); + end end function mps = vertcat(varargin) @@ -197,22 +206,22 @@ function s = leftvspace(mps, w) % return the virtual space to the left of site w. if nargin == 1 || isempty(w), w = 1:period(mps); end - s = arrayfun(@leftvspace, mps.AL(w)); + s = arrayfun(@leftvspace, mps.AL{w}); end function s = pspace(mps, w) % return the physical space at site w. - s = pspace(mps.AL(w)); + s = pspace(mps.AL{w}); end function s = rightvspace(mps, w) % return the virtual space to the right of site w. if nargin == 1 || isempty(w), w = 1:period(mps); end - s = arrayfun(@rightvspace, mps.AL(w)); + s = arrayfun(@rightvspace, mps.AL{w}); end function type = underlyingType(mps) - type = underlyingType(mps(1).AR(1)); + type = underlyingType(mps(1).AR{1}); end end @@ -272,20 +281,22 @@ for i = 1:depth(mps) if strcmp(kwargs.Order, 'rl') [mps(i).AR, ~, ~, eta1] = uniform_rightorth(... - mps(i).AR, [], ... + mps(i), [], ... 'Tol', kwargs.Tol, 'MaxIter', kwargs.MaxIter, ... 'Method', kwargs.Method, 'Verbosity', kwargs.Verbosity); + mps(i).AL = mps(i).AR; [mps(i).AL, mps(i).C, lambda, eta2] = uniform_leftorth(... - mps(i).AR, mps(i).C, ... + mps(i), mps(i).C, ... 'Tol', kwargs.Tol, 'MaxIter', kwargs.MaxIter, ... 'Method', kwargs.Method, 'Verbosity', kwargs.Verbosity); else [mps(i).AL, ~, ~, eta1] = uniform_leftorth(... - mps(i).AL, [], ... + mps(i), {}, ... 'Tol', kwargs.Tol, 'MaxIter', kwargs.MaxIter, ... 'Method', kwargs.Method, 'Verbosity', kwargs.Verbosity); + mps(i).AR = mps(i).AL; [mps(i).AR, mps(i).C, lambda, eta2] = uniform_rightorth(... - mps(i).AL, mps(i).C, ... + mps(i), mps(i).C, ... 'Tol', kwargs.Tol, 'MaxIter', kwargs.MaxIter, ... 'Method', kwargs.Method, 'Verbosity', kwargs.Verbosity); end @@ -298,8 +309,8 @@ if kwargs.ComputeAC for i = 1:depth(mps) for w = period(mps(i)):-1:1 - mps(i).AC(w) = multiplyright(mps(i).AL(w), ... - mps(i).C(w)); + mps(i).AC{w} = multiplyright(mps(i).AL{w}, ... + mps(i).C{w}); end end end @@ -310,7 +321,7 @@ for i = 1:depth(mps) for w = 1:period(mps(i)) - C_iw = mps(i).C(w); + C_iw = mps(i).C{w}; [U, S, V] = tsvd(C_iw, 1, 2); if isdual(C_iw.codomain) ~= isdual(S.codomain) @@ -326,13 +337,13 @@ end ww = next(w, period(mps(i))); - mps(i).C(w) = S; + mps(i).C{w} = S; - mps(i).AL(ww) = multiplyleft(mps(i).AL(ww), U'); - mps(i).AL(w) = multiplyright(mps(i).AL(w), U); + mps(i).AL{ww} = multiplyleft(mps(i).AL{ww}, U'); + mps(i).AL{w} = multiplyright(mps(i).AL{w}, U); - mps(i).AR(ww) = multiplyleft(mps(i).AR(ww), V); - mps(i).AR(w) = multiplyright(mps(i).AR(w), V'); + mps(i).AR{ww} = multiplyleft(mps(i).AR{ww}, V); + mps(i).AR{w} = multiplyright(mps(i).AR{w}, V'); end end end @@ -395,7 +406,7 @@ A2 = mps2.AR(sites); end - T = transfermatrix(A1, A2); + T = cellfun(@transfermatrix, A1, A2); end function rho = fixedpoint(mps, type, w) @@ -429,29 +440,29 @@ ww = prev(w, period(mps)); switch type case 'l_RR' - rho = contract(mps.C(ww)', [-1 1], mps.C(ww), [1 -2], 'Rank', [1 1]); + rho = contract(mps.C{ww}', [-1 1], mps.C{ww}, [1 -2], 'Rank', [1 1]); % if isdual(space(rho, 1)), rho = twist(rho, 1); end case 'l_RL' - rho = mps.C(ww); + rho = mps.C{ww}; % if isdual(space(rho, 1)), rho = twist(rho, 1); end case 'l_LR' - rho = mps.C(ww)'; + rho = mps.C{ww}'; % if isdual(space(rho, 1)), rho = twist(rho, 1); end case 'l_LL' - rho = mps.C.eye(leftvspace(mps, w), leftvspace(mps, w)); + rho = mps.C{w}.eye(leftvspace(mps, w), leftvspace(mps, w)); if isdual(space(rho, 1)), rho = twist(rho, 1); end case 'r_RR' - rho = mps.C.eye(rightvspace(mps, w)', rightvspace(mps, w)'); + rho = mps.C{w}.eye(rightvspace(mps, w)', rightvspace(mps, w)'); if isdual(space(rho, 2)), rho = twist(rho, 2); end case 'r_RL' - rho = twist(mps.C(w)', 2); + rho = twist(mps.C{w}', 2); % if isdual(space(rho, 1)), rho = twist(rho, 2); end case 'r_LR' - rho = twist(mps.C(w), 2); + rho = twist(mps.C{w}, 2); % if ~isdual(space(rho, 2)), rho = twist(rho, 2); end case 'r_LL' - rho = contract(mps.C(w), [-1 1], mps.C(w)', [1 -2], 'Rank', [1 1]); + rho = contract(mps.C{w}, [-1 1], mps.C{w}', [1 -2], 'Rank', [1 1]); rho = twist(rho, 2); end end @@ -556,16 +567,16 @@ N = size(H.R.var, 2); H.R.var = H.R.var(1, N, 1); H.O{1} = H.O{1}(:, :, N, :); - AC_ = apply(H, mps1.AC(end)); - E = dot(AC_, mps2.AC(end)); + AC_ = apply(H, mps1.AC{end}); + E = dot(AC_, mps2.AC{end}); elseif isa(O, 'InfMpo') [GL, GR] = environments(O, mps1, mps2); % should be normalized Hs = AC_hamiltonian(O, mps1, GL, GR); E = zeros(size(Hs)); for i = 1:length(Hs) - AC_ = apply(Hs{i}, mps1.AC(i)); - E(i) = dot(AC_, mps2.AC(i)); + AC_ = apply(Hs{i}, mps1.AC{i}); + E(i) = dot(AC_, mps2.AC{i}); end E = prod(E); @@ -603,7 +614,7 @@ w = 1 end - [svals, charges] = matrixblocks(tsvd(mps.C(w))); + [svals, charges] = matrixblocks(tsvd(mps.C{w})); svals = cellfun(@diag, svals, 'UniformOutput', false); end @@ -848,5 +859,141 @@ function plot_entanglementspectrum(mps, d, w, ax, kwargs) end + + %% Subroutines + methods (Access = protected) + function [AL, CL, lambda, eta] = uniform_leftorth(mps, CL, kwargs) + arguments + mps + CL = {} + kwargs.Tol = eps(underlyingType(mps))^(3/4) + kwargs.MaxIter = 500 + kwargs.Method = 'polar' + kwargs.Verbosity = Verbosity.off + kwargs.Normalize = true + kwargs.EigsInit = 3 + kwargs.EigsFrequence = 2 + end + + A = mps.AL; +% + % constants + EIG_TOLFACTOR = 1/50; + EIG_MAXTOL = 1e-4; + MINKRYLOVDIM = 8; + MAXKRYLOVDIM = 30; + + + + % initialization + N = period(mps); + if isempty(CL) + CL = initializeC(A{:}); + end + + if kwargs.Normalize, CL{1} = normalize(CL{1}); end + + for i = 1:numel(A) + A{i} = repartition(A{i}, [nspaces(A{i}) - 1, 1]); + end + AL = A; + + eta_best = Inf; + ctr_best = 0; + AL_best = AL; + C_best = CL; + lambda_best = 0; + + for ctr = 1:kwargs.MaxIter + if ctr > kwargs.EigsInit && mod(ctr, kwargs.EigsFrequence) == 0 + C_ = repartition(CL{end}, [2 0]); + T = cellfun(@transfermatrix, A, AL); + + eigalg = Arnoldi('Tol', min(eta * EIG_TOLFACTOR, EIG_MAXTOL), ... + 'KrylovDim', between(MINKRYLOVDIM, ... + MAXKRYLOVDIM - ctr / 2 + 4, ... + MAXKRYLOVDIM), ... + 'NoBuild', 4, ... + 'Verbosity', kwargs.Verbosity - 1); + + [C_, ~] = eigsolve(eigalg, @(x) T.apply(x), C_, 1, 'largestabs'); + [~, CL{end}] = leftorth(C_, 1, 2, kwargs.Method); + if isdual(space(CL{end}, 2)) == isdual(space(CL{end}, 1)) + CL{end}.codomain = conj(CL{end}.codomain); + CL{end} = twist(CL{end}, 1); + end + end + + C_ = CL{end}; + lambdas = ones(1, N); + for w = 1:N + ww = prev(w, N); + CA = multiplyleft(A{w}, CL{ww}); + [AL{w}, CL{w}] = leftorth(CA, kwargs.Method); + lambdas(w) = norm(CL{w}); + if kwargs.Normalize, CL{w} = CL{w} ./ lambdas(w); end + end + lambda = prod(lambdas); + eta = norm(C_ - CL{end}, Inf); + if eta < kwargs.Tol + if kwargs.Verbosity >= Verbosity.conv + fprintf('Conv %2d:\terror = %0.4e\n', ctr, eta); + end + break; + elseif eta < eta_best + eta_best = eta; + ctr_best = ctr; + AL_best = AL; + C_best = CL; + lambda_best = lambda; + elseif ctr > 40 && ctr - ctr_best > 5 + warning('uniform_orthright:stagnate', 'Algorithm stagnated'); + eta = eta_best; + AL = AL_best; + CL = C_best; + lambda = lambda_best; + break; + end + + if kwargs.Verbosity >= Verbosity.iter + fprintf('Iter %2d:\terror = %0.4e\n', ctr, eta); + end + end + + if kwargs.Verbosity >= Verbosity.warn && eta > kwargs.Tol + fprintf('Not converged %2d:\terror = %0.4e\n', ctr, eta); + end + end + + function [AR, CR, lambda, eta] = uniform_rightorth(mps, CR, kwargs) + arguments + mps + CR = mps.C + kwargs.Tol = eps(underlyingType(mps))^(3/4) + kwargs.MaxIter = 500 + kwargs.Method = 'polar' + kwargs.Verbosity = 0 + kwargs.Normalize = true + kwargs.EigsInit = 3 + kwargs.EigsFrequence = 2 + end + + opts = namedargs2cell(kwargs); + mps_d = mps; + mps_d.AL = flip(cellfun(@ctranspose, mps.AR, 'UniformOutput', false)); + + if isempty(CR) + Cd = {}; + else + Cd = circshift(flip(cellfun(@ctranspose, CR, 'UniformOutput', false)), -1); + end + + [ARd, CRd, lambda, eta] = uniform_leftorth(mps_d, Cd, opts{:}); + + AR = flip(cellfun(@ctranspose, ARd, 'UniformOutput', false)); + CR = circshift(flip(cellfun(@ctranspose, CRd, 'UniformOutput', false)), -1); + lambda = conj(lambda); + end + end end diff --git a/test/TestAlgorithms.m b/test/TestAlgorithms.m index fdf9365..91060a6 100644 --- a/test/TestAlgorithms.m +++ b/test/TestAlgorithms.m @@ -50,15 +50,17 @@ function test1dIsing_ordered(tc) D = 12; momenta = [0 pi 0.5]; - for L = 3 + for L = 1:3 %% No symmetry H = repmat(quantum1dIsing('h', h, 'J', J), 1, L); - vspace = CartesianSpace.new(D, D+1, D+2); - gs = initialize_mps(H, vspace); + vspace = arrayfun(@(w) CartesianSpace.new(D + w), 1:L, ... + 'UniformOutput', false); + gs = initialize_mps(H, vspace{:}); % Groundstate algorithms - gs = fixedpoint(Vumps('which', 'smallestreal', 'maxiter', 5), ... + gs = fixedpoint(Vumps('which', 'smallestreal', 'maxiter', 5, ... + 'alg_eigs', Arnoldi('maxiter', 100, 'krylovdim', 20)), ... H, gs); tc.assertEqual(expectation_value(gs, H), e0 * L, 'RelTol', 1e-2); diff --git a/test/TestUniformMps.m b/test/TestUniformMps.m index 7e96f4e..a7cdef9 100644 --- a/test/TestUniformMps.m +++ b/test/TestUniformMps.m @@ -3,27 +3,27 @@ properties (TestParameter) A = struct(... - 'trivial', {{Tensor.randnc(CartesianSpace.new([4 2]), CartesianSpace.new(4))}}, ... - 'trivial2', {{Tensor.randnc(CartesianSpace.new([4 2]), CartesianSpace.new(5)), ... - Tensor.randnc(CartesianSpace.new([5 2]), CartesianSpace.new(4))}}, ... - 'trivial3', {{Tensor.randnc(CartesianSpace.new([4 2]), CartesianSpace.new(5)), ... - Tensor.randnc(CartesianSpace.new([5 2]), CartesianSpace.new(6)), ... - Tensor.randnc(CartesianSpace.new([6 2]), CartesianSpace.new(4))}}, ... - 'fermion1', {{Tensor.randnc(... + 'trivial', {{MpsTensor.randnc(CartesianSpace.new([4 2]), CartesianSpace.new(4))}}, ... + 'trivial2', {{MpsTensor.randnc(CartesianSpace.new([4 2]), CartesianSpace.new(5)), ... + MpsTensor.randnc(CartesianSpace.new([5 2]), CartesianSpace.new(4))}}, ... + 'trivial3', {{MpsTensor.randnc(CartesianSpace.new([4 2]), CartesianSpace.new(5)), ... + MpsTensor.randnc(CartesianSpace.new([5 2]), CartesianSpace.new(6)), ... + MpsTensor.randnc(CartesianSpace.new([6 2]), CartesianSpace.new(4))}}, ... + 'fermion1', {{MpsTensor.randnc(... GradedSpace.new(fZ2(0,1), [2 2], false, fZ2(0,1), [1 1], false), ... GradedSpace.new(fZ2(0,1), [2 2], false))}}, ... - 'fermion2', {{Tensor.randnc(... + 'fermion2', {{MpsTensor.randnc(... GradedSpace.new(fZ2(0,1), [2 2], true, fZ2(0,1), [1 1], false), ... GradedSpace.new(fZ2(0,1), [2 2], true))}}, ... - 'fermion3', {{Tensor.randnc(... + 'fermion3', {{MpsTensor.randnc(... GradedSpace.new(fZ2(0,1), [2 2], false, fZ2(0,1), [1 1], true), ... GradedSpace.new(fZ2(0,1), [2 2], false))}}, ... - 'fermion4', {{Tensor.randnc(... + 'fermion4', {{MpsTensor.randnc(... GradedSpace.new(fZ2(0,1), [2 2], true, fZ2(0,1), [1 1], true), ... GradedSpace.new(fZ2(0,1), [2 2], true))}}, ... - 'haldane', {{Tensor.randnc(GradedSpace.new(SU2(1:2:5), [5 3 2], false, SU2(2), 1, false), ... + 'haldane', {{MpsTensor.randnc(GradedSpace.new(SU2(1:2:5), [5 3 2], false, SU2(2), 1, false), ... GradedSpace.new(SU2(2:2:6), [5 2 1], false)), ... - Tensor.randnc(GradedSpace.new(SU2(2:2:6), [5 2 1], false, SU2(2), 1, false), ... + MpsTensor.randnc(GradedSpace.new(SU2(2:2:6), [5 2 1], false, SU2(2), 1, false), ... GradedSpace.new(SU2(1:2:5), [5 3 2], false))}} ... ) end @@ -31,27 +31,30 @@ methods (Test) function testCanonical(tc, A) mps = UniformMps(A); + for i = 1:length(A) tc.assertTrue(... - isequal(space(A{i}), space(mps.AL(i)), ... - space(mps.AR(i)), space(mps.AC(i)))); + isequal(space(A{i}), space(mps.AL{i}), ... + space(mps.AR{i}), space(mps.AC{i}))); end - T = transfermatrix(MpsTensor(A), mps.AL); - [v, d] = eigsolve(transfermatrix(MpsTensor(A), mps.AL), [], 1, 'largestabs'); - [v2, d2] = eigsolve(transfermatrix(MpsTensor(A), MpsTensor(A)), [], 1, 'largestabs'); + + T = cellfun(@transfermatrix, A, mps.AL); + [v, d] = eigsolve(T, [], 1, 'largestabs'); + T2 = cellfun(@transfermatrix, A, A); + [v2, d2] = eigsolve(T2, [], 1, 'largestabs'); tc.verifyTrue(isapprox(d^2, d2), 'canonical changed state?'); mps = canonicalize(mps); AL = mps.AL; AR = mps.AR; C = mps.C; AC = mps.AC; - tc.assertTrue(all(isisometry([mps.AL.var], 'left')), ... + tc.assertTrue(all(cellfun(@(x) isisometry(x, 'left'), AL)), ... 'AL should be a left isometry.'); - tc.assertTrue(all(isisometry([mps.AR.var], 'right')), ... + tc.assertTrue(all(cellfun(@(x) isisometry(x, 'right'), AR)), ... 'AR should be a right isometry.'); for w = 1:period(mps) - ALC = multiplyright(AL(w), C(w)); - CAR = repartition(multiplyleft(AR(w), C(prev(w, period(mps)))), rank(AC(w))); - tc.assertTrue(isapprox(ALC, AC(w)) && isapprox(AC(w), CAR), ... + ALC = multiplyright(AL{w}, C{w}); + CAR = repartition(multiplyleft(AR{w}, C{prev(w, period(mps))}), rank(AC{w})); + tc.assertTrue(isapprox(ALC, AC{w}) && isapprox(AC{w}, CAR), ... 'AL, AR, C and AC should be properly related.'); end end From 2e24bc00f34ee05e500e5eaed83e31d768353962 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 20 Apr 2023 18:02:15 +0200 Subject: [PATCH 203/245] WIP part II --- src/algorithms/Expand.m | 3 +- src/algorithms/IDmrg2.m | 64 ++++++++++++++--------------- src/algorithms/Vumps2.m | 8 ++-- src/algorithms/eigsolvers/Arnoldi.m | 4 +- src/mps/MpsTensor.m | 26 +++++++----- src/mps/UniformMps.m | 45 ++++++++++++++++++-- 6 files changed, 94 insertions(+), 56 deletions(-) diff --git a/src/algorithms/Expand.m b/src/algorithms/Expand.m index ac5cb6c..390aa22 100644 --- a/src/algorithms/Expand.m +++ b/src/algorithms/Expand.m @@ -341,7 +341,8 @@ % expand tensors and convert to UniformMps AR = mps(d).AR; for w = 1:period(mps) - AR{w} = expand(AR{w}, new_spaces(d, w), alg.noisefactor); + AR{w} = expand(AR{w}, new_spaces(d, prev(w, period(mps))), ... + new_spaces(d, w), alg.noisefactor); end mps(d) = UniformMps(AR); diff --git a/src/algorithms/IDmrg2.m b/src/algorithms/IDmrg2.m index 73c5d88..1c77ca7 100644 --- a/src/algorithms/IDmrg2.m +++ b/src/algorithms/IDmrg2.m @@ -72,17 +72,15 @@ % sweep from left to right for pos = 1:period(mps)-1 - AC2 = contract(mps.AC(pos), [-1 -2 1], ... - mps.AR(pos + 1), [1 -3 -4], ... - 'Rank', [2 2]); + AC2 = computeAC2(mps, 1, pos); H = AC2_hamiltonian(mpo, mps, GL, GR, pos); - [AC2, lambdas(pos)] = ... - eigsolve(H{1}, AC2, 1, alg.which, kwargs{:}); - [mps.AL(pos), C, mps.AR(pos + 1), delta] = ... - tsvd(AC2, [1 2], [3 4], alg.trunc{:}); + [AC2.var, lambdas(pos)] = ... + eigsolve(H{1}, AC2.var, 1, alg.which, kwargs{:}); + [mps.AL{pos}.var, C, mps.AR{pos+1}.var] = ... + tsvd(AC2.var, [1 2], [3 4], alg.trunc{:}); - mps.C(pos) = normalize(C); - mps.AC(pos + 1) = multiplyleft(mps.AR(pos + 1), mps.C(pos)); + mps.C{pos} = normalize(C); + mps.AC{pos + 1} = computeAC(mps, 1, pos + 1, 'R'); TL = transfermatrix(mpo, mps, mps, pos, 'Type', 'LL'); GL{pos + 1} = apply(TL, GL{pos}); @@ -91,17 +89,18 @@ end % update edge - AC2 = contract(mps.AC(end), [-1 -2 1], inv(mps.C(end)), [1 2], ... - mps.AL(1), [2 -3 3], mps.C(1), [3 -4], 'Rank', [2 2]); + AC2 = contract(mps.AC{end}, [-1 -2 1], inv(mps.C{end}), [1 2], ... + mps.AL{1}, [2 -3 3], mps.C{1}, [3 -4], 'Rank', [2 2]); H = AC2_hamiltonian(mpo, mps, GL, GR, period(mps)); [AC2, lambdas(end)] = eigsolve(H{1}, AC2, 1, alg.which, kwargs{:}); - [mps.AL(end), C, mps.AR(1)] = ... + [mps.AL{end}.var, C, mps.AR{1}.var] = ... tsvd(AC2, [1 2], [3 4], alg.trunc{:}); - mps.C(end) = normalize(C); - mps.AC(end) = multiplyright(mps.AL(end), mps.C(end)); - mps.AC(1) = multiplyleft(mps.AR(1), mps.C(end)); - mps.AL(1) = multiplyright(mps.AC(1), inv(mps.C(1))); + + mps.C{end} = normalize(C); + mps.AC{end} = computeAC(mps, 1, period(mps), 'L'); + mps.AC{1} = computeAC(mps, 1, 1, 'R'); + mps.AL{1} = multiplyright(mps.AC{1}, inv(mps.C{1})); TL = transfermatrix(mpo, mps, mps, period(mps), 'Type', 'LL'); GL{1} = apply(TL, GL{end}); @@ -110,16 +109,15 @@ % sweep from right to left for pos = period(mps)-1:-1:1 - AC2 = contract(mps.AL(pos), [-1 -2 1], ... - mps.AC(pos + 1), [1 -3 -4], 'Rank', [2 2]); + AC2 = computeAC2(mps, 1, pos, 'L'); H = AC2_hamiltonian(mpo, mps, GL, GR, pos); [AC2, lambdas(pos)] = eigsolve(H{1}, AC2, 1, alg.which, kwargs{:}); - [mps.AL(pos), C, mps.AR(pos + 1)] = ... - tsvd(AC2, [1 2], [3 4], alg.trunc{:}); - mps.C(pos) = normalize(C); - mps.AC(pos) = multiplyright(mps.AL(pos), mps.C(pos)); - mps.AC(pos + 1) = multiplyleft(mps.AR(pos + 1), mps.C(pos)); + [mps.AL{pos}.var, C, mps.AR{pos + 1}.var] = ... + tsvd(AC2, [1 2], [3 4], alg.trunc{:}); + mps.C{pos} = normalize(C); + mps.AC{pos} = computeAC(mps, 1, pos, 'L'); + mps.AC{pos + 1} = computeAC(mps, 1, pos + 1, 'R'); TL = transfermatrix(mpo, mps, mps, pos, 'Type', 'LL'); GL{pos + 1} = apply(TL, GL{pos}); @@ -128,18 +126,18 @@ end % update edge - AC2 = contract(mps.C(end-1), [-1 1], mps.AR(end), [1 -2 2], ... - inv(mps.C(end)), [2 3], mps.AC(1), [3 -3 -4], 'Rank', [2 2]); + AC2 = contract(mps.C{end-1}, [-1 1], mps.AR{end}, [1 -2 2], ... + inv(mps.C{end}), [2 3], mps.AC{1}, [3 -3 -4], 'Rank', [2 2]); H = AC2_hamiltonian(mpo, mps, GL, GR, period(mps)); [AC2, lambdas(1)] = eigsolve(H{1}, AC2, 1, alg.which, kwargs{:}); - [mps.AL(end), C, mps.AR(1)] = ... + [mps.AL{end}.var, C, mps.AR{1}.var] = ... tsvd(AC2, [1 2], [3 4], alg.trunc{:}); - mps.C(end) = normalize(C); - mps.AC(1) = multiplyleft(mps.AR(1), mps.C(end)); + mps.C{end} = normalize(C); + mps.AC{1} = computeAC(mps, 1, 1, 'R'); - mps.AR(end) = multiplyleft(multiplyright(mps.AL(end), mps.C(end)), ... - inv(mps.C(end - 1))); + mps.AR{end} = multiplyleft(multiplyright(mps.AL{end}, mps.C{end}), ... + inv(mps.C{end - 1})); TL = transfermatrix(mpo, mps, mps, period(mps), 'Type', 'LL'); @@ -148,11 +146,11 @@ GR{1} = apply(TR.', GR{2}); % error measure - infspace = infimum(space(C_, 1), space(mps.C(end), 1)); - e1 = C_.eye(space(mps.C(end), 1), infspace); + infspace = infimum(space(C_, 1), space(mps.C{end}, 1)); + e1 = C_.eye(space(mps.C{end}, 1), infspace); e2 = C_.eye(space(C_, 1), infspace); - eta = distance(e2' * C_ * e2, e1' * mps.C(end) * e1); + eta = distance(e2' * C_ * e2, e1' * mps.C{end} * e1); lambda = prod(sqrt(lambdas)); if iter > alg.miniter && eta < alg.tol diff --git a/src/algorithms/Vumps2.m b/src/algorithms/Vumps2.m index d0371af..05d3aa1 100644 --- a/src/algorithms/Vumps2.m +++ b/src/algorithms/Vumps2.m @@ -128,10 +128,8 @@ end H_AC2 = AC2_hamiltonian(mpo, mps, GL, GR, sites); for i = length(sites):-1:1 - AC2 = MpsTensor(contract(mps.AC{sites(i)}, [-1 -2 1], ... - mps.AR{next(sites(i), period(mps))}, [1 -3 -4], ... - 'Rank', [2 2])); - [AC2.var, ~] = eigsolve(H_AC2{i}, AC2.var, 1, alg.which, ... + AC2{i} = computeAC2(mps, 1, sites(i)); + [AC2{i}.var, ~] = eigsolve(H_AC2{i}, AC2{i}.var, 1, alg.which, ... kwargs{:}); end end @@ -169,7 +167,7 @@ end [AL1, C, AL2] = tsvd(AL.var, [1 2], [3 4], alg.trunc{:}); mps.AL{sites(i)} = multiplyright(MpsTensor(AL1), C); - mps.AL{next(sites(i), period(mps))} = AL2; + mps.AL{next(sites(i), period(mps))} = MpsTensor(AL2); end kwargs = namedargs2cell(alg.alg_canonical); diff --git a/src/algorithms/eigsolvers/Arnoldi.m b/src/algorithms/eigsolvers/Arnoldi.m index 0a3e82d..31f33f0 100644 --- a/src/algorithms/eigsolvers/Arnoldi.m +++ b/src/algorithms/eigsolvers/Arnoldi.m @@ -126,10 +126,10 @@ if conv > alg.tol if invariantsubspace - fprintf('Found invariant subspace (error = %.5e).\n', conv); + warning('Found invariant subspace (error = %.5e).\n', conv); flag = 1; else - fprintf('Reached maxiter without convergence.\n'); + warning('Reached maxiter without convergence.\n'); flag = 2; end end diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index 1c41181..01f536c 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -122,6 +122,10 @@ end end + function varargout = tsvd(t, varargin) + [varargout{1:nargout}] = tsvd(t.var, varargin{:}); + end + function A = leftnull(A, alg) arguments A @@ -481,7 +485,7 @@ function C = initializeC(A) arguments (Repeating) - A + A MpsTensor end C = cell(size(A)); @@ -490,22 +494,22 @@ end end - function A = expand(A, addspace, noisefactor) + function A = expand(A, addspace_left, addspace_right, noisefactor) arguments A - addspace + addspace_left + addspace_right noisefactor = 1e-3 end - for i = length(A):-1:1 - spaces = space(A(i)); - spaces(nspaces(A(i)) - A(i).alegs) = addspace(i); - spaces(1) = conj(addspace(prev(i, length(A)))); - r = rank(A(i)); - A(i).var = embed(A(i).var, ... + spaces = space(A); + spaces(nspaces(A) - A.alegs) = addspace_right; + spaces(1) = conj(addspace_left); + r = rank(A); + A.var = embed(A.var, ... noisefactor * ... - normalize(A(i).var.randnc(spaces(1:r(1)), spaces(r(1)+1:end)'))); - end + normalize(A.var.randnc(spaces(1:r(1)), spaces(r(1)+1:end)'))); + end function type = underlyingType(A) diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index 5d83361..639f090 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -307,15 +307,52 @@ end if kwargs.ComputeAC - for i = 1:depth(mps) - for w = period(mps(i)):-1:1 - mps(i).AC{w} = multiplyright(mps(i).AL{w}, ... - mps(i).C{w}); + for d = 1:depth(mps) + for w = 1:period(mps) + mps(d).AC{w} = computeAC(mps, d, w); end end end end + function AC = computeAC(mps, d, w, type) + arguments + mps + d + w + type = 'L' + end + if strcmp(type, 'L') + AC = multiplyright(mps(d).AL{w}, mps(d).C{w}); + elseif strcmp(type, 'R') + AC = multiplyleft(mps(d).AR{w}, mps(d).C{prev(w, period(mps))}); + else + error('mps:argerror', 'invalid type %s', type) + end + end + + function AC2 = computeAC2(mps, d, w, type) + arguments + mps + d + w + type = 'R' + end + + ww = next(w, period(mps)); + if strcmp(type, 'R') + assert(mps(d).AC{w}.plegs == 1 && mps(d).AC{w}.alegs == 0, 'tba'); + AC2 = MpsTensor(contract(mps(d).AC{w}, [-1 -2 1], ... + mps(d).AR{ww}, [1 -3 -4], 'Rank', [2 2])); + elseif strcmp(type, 'L') + assert(mps(d).AC{ww}.plegs == 1 && mps(d).AC{ww}.alegs == 0, 'tba'); + AC2 = MpsTensor(contract(mps.AL{w}, [-1 -2 1], ... + mps.AC{ww}, [1 -3 -4], 'Rank', [2 2])); + else + error('mps:argerror', 'invalid type %s', type) + end + end + function mps = diagonalizeC(mps) % gauge transform an mps such that C is diagonal. From f9826a22a327eedeab824eeba56f6434c922baca Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 20 Apr 2023 18:13:40 +0200 Subject: [PATCH 204/245] WIP III --- src/algorithms/IDmrg2.m | 2 +- src/mps/InfJMpo.m | 4 ++-- src/mps/MpsTensor.m | 4 ++++ test/TestAlgorithms.m | 4 ++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/algorithms/IDmrg2.m b/src/algorithms/IDmrg2.m index 1c77ca7..3f90b01 100644 --- a/src/algorithms/IDmrg2.m +++ b/src/algorithms/IDmrg2.m @@ -66,7 +66,7 @@ for iter = 1:alg.maxiter t_iter = tic; - C_ = mps.C(end); + C_ = mps.C{end}; kwargs = {}; lambdas = zeros(1, period(mps)); diff --git a/src/mps/InfJMpo.m b/src/mps/InfJMpo.m index 6a38996..4ceb442 100644 --- a/src/mps/InfJMpo.m +++ b/src/mps/InfJMpo.m @@ -66,7 +66,7 @@ GL{1} = full(GL{1}); end -% GL{1} = MpsTensor(GL{1}, 0); + GL{1} = MpsTensor(GL{1}, 0); for w = 1:period(mps1)-1 T = transfermatrix(mpo, mps1, mps2, w, 'Type', 'LL'); GL{next(w, period(mps1))} = apply(T, GL{w}); @@ -127,7 +127,7 @@ GR{1} = full(GR{1}); end -% GR{1} = MpsTensor(GR{1}, 0); + GR{1} = MpsTensor(GR{1}, 0); for w = period(mps1):-1:2 T = transfermatrix(mpo, mps1, mps2, w, 'Type', 'RR').'; GR{w} = apply(T, GR{next(w, period(mps1))}); diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index 01f536c..d4f331c 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -34,6 +34,10 @@ %% Properties methods + function varargout = size(A, varargin) + [varargout{1:nargout}] = size(A.var, varargin{:}); + end + function s = space(A, varargin) s = space(A.var, varargin{:}); end diff --git a/test/TestAlgorithms.m b/test/TestAlgorithms.m index 91060a6..730b548 100644 --- a/test/TestAlgorithms.m +++ b/test/TestAlgorithms.m @@ -2,9 +2,9 @@ % Unit tests for algorithms properties (TestParameter) - alg = {Vumps('which', 'smallestreal', 'maxiter', 5), ... + alg = {...Vumps('which', 'smallestreal', 'maxiter', 5), ... ...IDmrg('which', 'smallestreal', 'maxiter', 5), ... - Vumps2('which', 'smallestreal', 'maxiter', 6), ... + ...Vumps2('which', 'smallestreal', 'maxiter', 6), ... IDmrg2('which', 'smallestreal', 'maxiter', 5) ... } symm = {'Z1', 'Z2'} From b99f5f6bd8140ed37ea1010d7ad85d57f41965b5 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Fri, 21 Apr 2023 11:24:17 +0200 Subject: [PATCH 205/245] fix nullspaces? --- src/tensors/Tensor.m | 78 +++++++++++++++++++++++++------------------- test/TestTensor.m | 13 +++++--- 2 files changed, 53 insertions(+), 38 deletions(-) diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 78e1dd3..6ffbba2 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -1726,14 +1726,13 @@ ctr = ctr + 1; u = Ublocks{i}; s = Sblocks{ctr}; - if size(s, 2) == 1 + if isvector(s) diags = s(1); else diags = diag(s); end r = sum(diags > atol); Ublocks{i} = u(:, (r + 1):size(u, 1)); - end dims.degeneracies = cellfun(@(x) size(x, 2), Ublocks); @@ -1747,27 +1746,6 @@ W = t.codomain.new(dims, false); N = t.eye(U.codomain, W); N.var = fill_matrix_data(N.var, Ns, dims.charges); - -% t = tpermute(t, [p1 p2], [length(p1) length(p2)]); -% -% dims = struct; -% [mblocks, dims.charges] = matrixblocks(t); -% Ns = cell(size(mblocks)); -% dims.degeneracies = zeros(size(mblocks)); -% -% for i = 1:length(mblocks) -% Ns{i} = leftnull(mblocks{i}, alg, atol); -% dims.degeneracies(i) = size(Ns{i}, 2); -% end -% -% mask = dims.degeneracies > 0; -% dims.charges = dims.charges(mask); -% dims.degeneracies = dims.degeneracies(mask); -% Ns = Ns(mask); -% -% W = t.codomain.new(dims, false); -% N = t.eye(t.codomain, W); -% N.var = fill_matrix_data(N.var, Ns, dims.charges); end function N = rightnull(t, p1, p2, alg, atol) @@ -1805,25 +1783,59 @@ if isempty(p1), p1 = 1:rank(t, 1); end if isempty(p2), p2 = rank(t, 1) + (1:rank(t, 2)); end - t = tpermute(t, [p1 p2], [length(p1) length(p2)]); + [~, S, V] = tsvd(t, p1, p2); dims = struct; - [mblocks, dims.charges] = matrixblocks(t); - Ns = cell(size(mblocks)); - dims.degeneracies = zeros(size(mblocks)); + [Sblocks, c1] = matrixblocks(S); + [Vblocks, c2] = matrixblocks(V); - for i = 1:length(mblocks) - Ns{i} = rightnull(mblocks{i}, alg, atol); - dims.degeneracies(i) = size(Ns{i}, 1); + lia = ismember_sorted(c2, c1); + ctr = 0; + for i = 1:length(Vblocks) + if ~lia(i), continue; end + ctr = ctr + 1; + v = Vblocks{i}; + s = Sblocks{ctr}; + if isvector(s) + diags = s(1); + else + diags = diag(s); + end + r = sum(diags > atol); + Vblocks{i} = v((r + 1):size(v, 2), :); end + dims.degeneracies = cellfun(@(x) size(x, 1), Vblocks); + dims.charges = c2; + mask = dims.degeneracies > 0; - dims.charges = dims.charges(mask); + dims.charges = c2(mask); dims.degeneracies = dims.degeneracies(mask); - Ns = Ns(mask); + Ns = Vblocks(mask); - N = Tensor.eye(t.domain.new(dims, false), t.domain); + W = t.codomain.new(dims, false); + N = t.eye(W, V.domain); N.var = fill_matrix_data(N.var, Ns, dims.charges); +% +% [~, S, V] = tpermute(t, [p1 p2], [length(p1) length(p2)]); +% +% dims = struct; +% [mblocks, dims.charges] = matrixblocks(t); +% Ns = cell(size(mblocks)); +% dims.degeneracies = zeros(size(mblocks)); +% +% for i = 1:length(mblocks) +% Ns{i} = rightnull(mblocks{i}, alg, atol); +% dims.degeneracies(i) = size(Ns{i}, 1); +% end +% +% mask = dims.degeneracies > 0; +% dims.charges = dims.charges(mask); +% dims.degeneracies = dims.degeneracies(mask); +% Ns = Ns(mask); +% +% N = Tensor.eye(t.domain.new(dims, false), t.domain); +% N.var = fill_matrix_data(N.var, Ns, dims.charges); end function [U, S, V, eta] = tsvd(t, p1, p2, trunc) diff --git a/test/TestTensor.m b/test/TestTensor.m index 9cc6c3a..f957a1b 100644 --- a/test/TestTensor.m +++ b/test/TestTensor.m @@ -36,10 +36,10 @@ function classSetup(tc) ProductCharge(U1(-1:1), SU2(2,1,2)), [1 2 2], false), ... 'Hubbard', GradedSpace.new(... ProductCharge(U1(0, 1, 2), SU2(1, 2, 1), fZ2(0, 1, 0)), [1 1 1], false, ... - ProductCharge(U1(-2:2), SU2(1, 2, 1, 2, 1), fZ2(0, 1, 0, 1, 0)), [1 1 3 1 1], false, ... + ProductCharge(U1(-3:1), SU2(1, 2, 1, 2, 1), fZ2(0, 1, 0, 1, 0)), [1 1 3 1 1], false, ... ProductCharge(U1(0, 1), SU2(1, 2), fZ2(0, 1)), [1 1], true, ... ProductCharge(U1(0, 1, 2), SU2(1, 2, 1), fZ2(0, 1, 0)), [1 1 1], false, ... - ProductCharge(U1(0, 1), SU2(1, 2), fZ2(0, 1)), [1 1], true) ... + ProductCharge(U1(0, 1), SU2(2, 1), fZ2(1, 0)), [1 1], true) ... ) end @@ -320,14 +320,15 @@ function orthogonalize(tc, spaces) function nullspace(tc, spaces) t = Tensor.randnc(spaces, []); - + tc.assumeTrue(spaces(3) * spaces(4) * spaces(2) >= spaces(1)' * spaces(5)', ... + 'tensor not full rank') %% Left nullspace for alg = ["qr", "svd"] N = leftnull(t, [3 4 2], [1 5], alg); dimN = dims(N, nspaces(N)); dimW = prod(dims(t, [3 4 2])); dimV = prod(dims(t, [1 5])); - tc.assertTrue(dimW == dimN + dimV); + tc.assertEqual(dimW, dimN + dimV, 'Nullspace should be full rank'); assertTrue(tc, norm(N' * tpermute(t, [3 4 2 1 5], [3 2])) < ... 100 * eps(norm(t)), ... 'N should be a left nullspace.'); @@ -338,12 +339,14 @@ function nullspace(tc, spaces) %% Right nullspace + tc.assumeTrue(spaces(3) * spaces(4) <= spaces(1)' * spaces(2)' * spaces(5)', ... + 'tensor not full rank'); for alg = ["lq", "svd"] N = rightnull(t, [3 4], [2 1 5], alg); dimN = dims(N, 1); dimW = prod(dims(t, [3 4])); dimV = prod(dims(t, [2 1 5])); - tc.assertTrue(dimV == dimW + dimN); + tc.assertEqual(dimV, dimW + dimN, 'Nullspace should be full rank'); assertTrue(tc, norm(tpermute(t, [3 4 2 1 5], [2 3]) * N') < ... 100 * eps(norm(t)), ... 'N should be a right nullspace.'); From 4b9c391c86ed52b9866a5086bbf821ad9043a3af Mon Sep 17 00:00:00 2001 From: lkdvos Date: Fri, 21 Apr 2023 11:24:42 +0200 Subject: [PATCH 206/245] re-enable test --- test/TestAlgorithms.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/TestAlgorithms.m b/test/TestAlgorithms.m index 730b548..91060a6 100644 --- a/test/TestAlgorithms.m +++ b/test/TestAlgorithms.m @@ -2,9 +2,9 @@ % Unit tests for algorithms properties (TestParameter) - alg = {...Vumps('which', 'smallestreal', 'maxiter', 5), ... + alg = {Vumps('which', 'smallestreal', 'maxiter', 5), ... ...IDmrg('which', 'smallestreal', 'maxiter', 5), ... - ...Vumps2('which', 'smallestreal', 'maxiter', 6), ... + Vumps2('which', 'smallestreal', 'maxiter', 6), ... IDmrg2('which', 'smallestreal', 'maxiter', 5) ... } symm = {'Z1', 'Z2'} From 8b5052377dbda00c81baf8b22de680c75d893bea Mon Sep 17 00:00:00 2001 From: leburgel Date: Fri, 21 Apr 2023 14:54:56 +0200 Subject: [PATCH 207/245] Fix bug in subsref --- src/utility/sparse/SparseArray.m | 5 +++-- test/TestSparseArray.m | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/utility/sparse/SparseArray.m b/src/utility/sparse/SparseArray.m index 71a19fb..6775f8e 100644 --- a/src/utility/sparse/SparseArray.m +++ b/src/utility/sparse/SparseArray.m @@ -865,6 +865,7 @@ function disp(a) % >> a(2, :, :) %<-- returns a 1 x 4 x 4 sparse array % >> a([0, 4, 0]) %<-- returns a 4 x 1 x 4 sparse array % >> a([4, 4, 0]) %<-- returns a 1 x 1 x 4 sparse array + switch s(1).type case '()' % regular subscript indexing @@ -929,9 +930,9 @@ function disp(a) % order [~, ~, temp] = unique([A(:); subs(f, i)], 'stable'); subs(f, i) = temp(length(A)+1:end); - % adjust size in this dimension - new_sz(i) = length(A); end + % adjust size in this dimension + new_sz(i) = length(A); end if isempty(subs) a_sub = SparseArray([], [], new_sz); diff --git a/test/TestSparseArray.m b/test/TestSparseArray.m index 5917664..a9d9e43 100644 --- a/test/TestSparseArray.m +++ b/test/TestSparseArray.m @@ -7,5 +7,23 @@ methods (Test) + function test_subsref(tc) + a = SparseArray([4, 4, 4; 2, 2, 1; 2, 3, 2], [3; 5; 1], [4, 4, 4]); + tc.verifyTrue(isequal(a(1, 2, 1), 0)); + tc.verifyTrue(isequal(a(4, 4, 4), 3)); + tc.verifyTrue(isequal(size(a(2, :, :)), [1, 4, 4])); + tc.verifyTrue(isequal(size(a([0, 4, 0])), [4, 1, 4])); + tc.verifyTrue(isequal(size(a([4, 4, 0])), [1, 1, 4])); + end + + function test_basics(tc) + a = SparseArray.zeros([2, 2, 2]); + sz1 = size(a(:, :, 1)); + b = SparseArray.zeros([2, 2, 2]); + b(1, 1, 1) = 1; + sz2 = size(b(:, :, 1)); + tc.verifyTrue(isequal(sz1, sz2)); + end + end end From bc53927d4cb7bf2524bfdbee2d5a0dd42becd862 Mon Sep 17 00:00:00 2001 From: leburgel Date: Fri, 21 Apr 2023 15:30:51 +0200 Subject: [PATCH 208/245] Add some tests --- src/utility/sparse/SparseArray.m | 24 +++++++--- test/TestSparseArray.m | 80 ++++++++++++++++++++++++++++---- 2 files changed, 88 insertions(+), 16 deletions(-) diff --git a/src/utility/sparse/SparseArray.m b/src/utility/sparse/SparseArray.m index 6775f8e..7fe097f 100644 --- a/src/utility/sparse/SparseArray.m +++ b/src/utility/sparse/SparseArray.m @@ -568,6 +568,11 @@ function disp(a) n = nnz(a.var); end + function nz = nonzeros(a) + % Returns a full column vector of the nonzero elements of :code:`a`. + [~, ~, nz] = find(a); + end + function nrm = norm(a) % Frobenius norm of a sparse array. nrm = norm(a.var); @@ -705,13 +710,15 @@ function disp(a) % >> 5 ./ a %<-- dense % >> a ./ full(a) %<-- sparse % >> full(a) ./ a %<-- dense - if isa(a, 'SparseArray') && isa(b, 'SparseArray') + if isa(a, 'SparseArray') c = a; - c.var = c.var ./ b.var; - elseif isa(a, 'SparseArray') - c = a.var ./ b; + if isa(b, 'SparseArray') + c.var = c.var ./ b.var; + else + c.var = a.var ./ b(:); + end elseif isa(b, 'SparseArray') - c = a ./ b.var; + c = a ./ full(b); end end @@ -801,10 +808,12 @@ function disp(a) % % >> squeeze(SparseArray.random([2, 1, 3], 0.5)) %<-- returns a 2 x 3 SparseArray % >> squeeze(SparseArray([1, 1, 1], 1, [1, 1, 1])) %<-- returns a scalar + if sum(a.sz > 1) == 0 a = full(a.var); return end + % always give n x 1 SparseArray in case of only 1 non-singleton dimension, % consistent with class constructor a.sz = [a.size(a.size>1), ones(1, 2-sum(a.size>1))]; @@ -1016,13 +1025,14 @@ function disp(a) % .. code-block:: matlab % % >> a = SparseArray.random([4 3 2], .1); + % >> b = SparseArray.random([4 3 2], .1); % >> a .* b %<-- sparse % >> a .* 5 %<-- sparse % >> a .* 0 %<-- sparse % >> a .* full(a) %<-- sparse - if isscalar(b) || ~isa(b,'SparseArray') + if isscalar(b) || ~isa(b, 'SparseArray') c = SparseArray(a.var .* b(:), a.sz); - elseif isscalar(a) || ~isa(a,'SparseArray') + elseif isscalar(a) || ~isa(a, 'SparseArray') c = SparseArray(b.var .* a(:), b.sz); else c = SparseArray(b.var .* a.var, b.sz); diff --git a/test/TestSparseArray.m b/test/TestSparseArray.m index a9d9e43..fbb00c5 100644 --- a/test/TestSparseArray.m +++ b/test/TestSparseArray.m @@ -7,15 +7,6 @@ methods (Test) - function test_subsref(tc) - a = SparseArray([4, 4, 4; 2, 2, 1; 2, 3, 2], [3; 5; 1], [4, 4, 4]); - tc.verifyTrue(isequal(a(1, 2, 1), 0)); - tc.verifyTrue(isequal(a(4, 4, 4), 3)); - tc.verifyTrue(isequal(size(a(2, :, :)), [1, 4, 4])); - tc.verifyTrue(isequal(size(a([0, 4, 0])), [4, 1, 4])); - tc.verifyTrue(isequal(size(a([4, 4, 0])), [1, 1, 4])); - end - function test_basics(tc) a = SparseArray.zeros([2, 2, 2]); sz1 = size(a(:, :, 1)); @@ -24,6 +15,77 @@ function test_basics(tc) sz2 = size(b(:, :, 1)); tc.verifyTrue(isequal(sz1, sz2)); end + + function test_doctests(tc) + % test examples in docstrings + + % constructor + subs = [1 1 1; 1 1 3; 2 2 2; 4 4 4; 1 1 1; 1 1 1]; + vals = [0.5; 1.5; 2.5; 3.5; 4.5; 5.5]; + sz = [4, 4, 4]; + a = SparseArray(subs, vals, sz); + tc.verifyTrue(isequal(size(a), [4, 4, 4])); + tc.verifyTrue(isequal(a(1, 1, 3), 1.5)); + tc.verifyTrue(isequal(a(4, 4, 4), 3.5)); + tc.verifyTrue(isequal(a(1, 1, 1), 10.5)); + + % groupind + a = SparseArray.random([2, 3, 4, 5, 6], .1); + b = groupind(a, [3, 2]); + tc.verifyTrue(isequal(size(b), [24, 30])); + + % minus + a = SparseArray.random([4 3 2], .1); + b = SparseArray.random([4 3 2], .1); + tc.verifyTrue(isa(a - b, 'SparseArray')); + tc.verifyTrue(isa(a - 5, 'double')); + tc.verifyTrue(isa(a - 0, 'double')); + tc.verifyTrue(isa(a - full(a), 'double')); + + % mrdivide + b = a / 3; + tc.verifyTrue(isa(b, 'SparseArray')); + tc.verifyTrue(isequal(nonzeros(b), nonzeros(a) / 3)); + + % ndims + tc.verifyTrue(isequal(ndims(a), 3)); + + % plus + tc.verifyTrue(isa(a + b, 'SparseArray')); + tc.verifyTrue(isa(a + 5, 'double')); + tc.verifyTrue(isa(a + 0, 'double')); + tc.verifyTrue(isa(a + full(a), 'double')); + + % rdivide + tc.verifyTrue(isa(a ./ 5, 'SparseArray')); + tc.verifyTrue(isa(5 ./ a, 'double')); + tc.verifyTrue(isa(a ./ full(a), 'SparseArray')); + tc.verifyTrue(isa(full(a) ./ a, 'double')); + + % squeeze + c = squeeze(SparseArray.random([2, 1, 3], 0.5)); + tc.verifyTrue(isa(c, 'SparseArray')); + tc.verifyTrue(isequal(size(c), [2, 3])); + d = squeeze(SparseArray([1, 1, 1], 1, [1, 1, 1])); + tc.verifyTrue(isa(d, 'double')); + tc.verifyTrue(isequal(size(d), [1, 1])); + + % subsref + a = SparseArray([4, 4, 4; 2, 2, 1; 2, 3, 2], [3; 5; 1], [4, 4, 4]); + tc.verifyTrue(isequal(a(1, 2, 1), 0)); + tc.verifyTrue(isequal(a(4, 4, 4), 3)); + tc.verifyTrue(isequal(size(a(2, :, :)), [1, 4, 4])); + tc.verifyTrue(isequal(size(a([0, 4, 0])), [4, 1, 4])); + tc.verifyTrue(isequal(size(a([4, 4, 0])), [1, 1, 4])); + + % times + a = SparseArray.random([4 3 2], .1); + b = SparseArray.random([4 3 2], .1); + tc.verifyTrue(isa(a .* b, 'SparseArray')); + tc.verifyTrue(isa(a .* 5, 'SparseArray')); + tc.verifyTrue(isa(a .* 0, 'SparseArray')); + tc.verifyTrue(isa(a .* full(a), 'SparseArray')); + end end end From bd84c47895742e56a145bbfe85e1c73b8a104d9e Mon Sep 17 00:00:00 2001 From: sanderdemeyer <74001142+sanderdemeyer@users.noreply.github.com> Date: Sat, 22 Apr 2023 15:09:32 +0200 Subject: [PATCH 209/245] bugfix Without this, the error 'Not enough input arguments' is given --- src/mps/InfMpo.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index 580a7a9..3c180e2 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -223,8 +223,8 @@ gl = twistdual(GL{d, sites(i)}, 1); gr = GR{d, next(sites(i), period(mps))}; gr = twistdual(gr, nspaces(gr)); - gr_better = tpermute(gr, [1, flip(2:nspaces(gr)-1), nspaces(gr)]); - gl_better = tpermute(gl, [1, flip(2:nspaces(gl)-1), nspaces(gl)]); + gr_better = tpermute(gr, [1, flip(2:nspaces(gr)-1), nspaces(gr)], rank(gr)); + gl_better = tpermute(gl, [1, flip(2:nspaces(gl)-1), nspaces(gl)], rank(gl)); H{i}(d, 1) = FiniteMpo(gl_better, mpo.O(d, sites(i)), gr_better); end end From b7571af6d8c8cbee47236b27174aed64b3815035 Mon Sep 17 00:00:00 2001 From: leburgel Date: Mon, 24 Apr 2023 11:03:26 +0200 Subject: [PATCH 210/245] Give `SparseTensor.tpermute` same default behavior as `Tensor.tpermute` --- src/sparse/SparseTensor.m | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index aa49de1..0453269 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -694,6 +694,15 @@ function disp(t) end function t = tpermute(t, p, r) + arguments + t + p = [] + r = [] + end + + if isempty(p), p = 1:nspaces(t); end + if isempty(r), r = rank(t); end + for i = 1:numel(t.var) t.var(i) = tpermute(t.var(i), p, r); end From 9c4371e048549f8857805d97f64aa6f7f9c0d548 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 24 Apr 2023 13:39:54 +0200 Subject: [PATCH 211/245] fZ2Space convenience constructor --- src/tensors/spaces/fZ2Space.m | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/tensors/spaces/fZ2Space.m diff --git a/src/tensors/spaces/fZ2Space.m b/src/tensors/spaces/fZ2Space.m new file mode 100644 index 0000000..0572a07 --- /dev/null +++ b/src/tensors/spaces/fZ2Space.m @@ -0,0 +1,11 @@ +function V = fZ2Space(charges, bonds, isdual) +% Convenience constructor for fZ2-graded spaces. +arguments + charges + bonds + isdual = false +end + +V = GradedSpace.new(fZ2(charges), bonds, isdual); + +end From c445449ecd5ac231f868f813513d6e89dda26e32 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 24 Apr 2023 13:40:26 +0200 Subject: [PATCH 212/245] cleanup --- src/algorithms/Vumps.m | 2 - src/mps/InfMpo.m | 13 +- src/mps/MpsTensor.m | 481 ++++++++++++++++++----------------------- 3 files changed, 212 insertions(+), 284 deletions(-) diff --git a/src/algorithms/Vumps.m b/src/algorithms/Vumps.m index d0a537f..b0cfa63 100644 --- a/src/algorithms/Vumps.m +++ b/src/algorithms/Vumps.m @@ -113,7 +113,6 @@ %% Subroutines methods function AC = updateAC(alg, iter, mpo, mps, GL, GR) -% kwargs = namedargs2cell(alg.alg_eigs); if strcmp(alg.multiAC, 'sequential') sites = mod1(iter, period(mps)); else @@ -132,7 +131,6 @@ end function C = updateC(alg, iter, mpo, mps, GL, GR) -% kwargs = namedargs2cell(alg.alg_eigs); if strcmp(alg.multiAC, 'sequential') sites = mod1(iter, period(mps)); else diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index 998d36e..aea95ca 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -450,18 +450,9 @@ for i = 1:length(sites) for d = depth(mpo):-1:1 gl = GL{d, next(sites(i), period(mps))}; - for j = 1:numel(gl) - if nnz(gl(j)) ~= 0 - gl(j) = twist(gl(j), find(isdual(space(gl(j), 1)))); - end - end + gl = twistdual(gl, 1); gr = GR{d, next(sites(i), period(mps))}; - for j = 1:numel(gr) - if nnz(gr(j)) ~= 0 - gr(j) = twist(gr(j), find(isdual(space(gr(j), nspaces(gr(j))))) + ... - nspaces(gr(j)) - 1); - end - end + gr = twistdual(gr, nspaces(gr)); H{i}(d, 1) = FiniteMpo(gl, {}, gr); end end diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index 16f2dfd..05e2034 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -31,6 +31,30 @@ end end + methods (Static) + function A = new(varargin, kwargs) + arguments (Repeating) + varargin + end + arguments + kwargs.alegs = 0 + end + + A = MpsTensor(Tensor.new(varargin{:}), kwargs.alegs); + end + + function A = randnc(varargin, kwargs) + arguments (Repeating) + varargin + end + arguments + kwargs.alegs = 0 + end + + A = MpsTensor(Tensor.randnc(varargin{:}), kwargs.alegs); + end + end + %% Properties methods @@ -38,24 +62,51 @@ [varargout{1:nargout}] = size(A.var, varargin{:}); end - function s = space(A, varargin) - s = space(A.var, varargin{:}); + function n = numel(A) + n = numel(A.var); end - function n = nspaces(A) - n = nspaces(A.var); + function l = length(A) + l = length(A.var); end - function s = pspace(A) - s = space(A, 1 + (1:A.plegs)); + function A = cat(dim, varargin) + ismpstensor = cellfun(@(x) isa(x, 'MpsTensor')); + i = find(ismpstensor, 1); + A = varargin{i}; + for j = 1:i-1 + B = varargin{j}; + if ismpstensor(j) + assert(B.alegs == A.alegs && B.plegs == A.plegs); + A.var = cat(dim, B.var, A.var); + else + A.var = cat(dim, B, A.var); + end + end end - function s = leftvspace(A) - s = space(A.var(1), 1); + function A = horzcat(varargin) + A = cat(2, varargin{:}); end - function s = rightvspace(A) - s = space(A.var(1), nspaces(A.var(1)) - A.alegs); + function A = vertcat(varargin) + A = cat(1, varargin{:}); + end + + function tdst = insert_onespace(tsrc, varargin) + % insert a trivial space at position i. + tdst = MpsTensor(insert_onespace(tsrc.var, varargin{:}), tsrc.alegs + 1); + end + end + + %% Spaces + methods + function s = space(A, varargin) + s = space(A.var, varargin{:}); + end + + function n = nspaces(A) + n = nspaces(A.var); end function cod = codomain(A) @@ -70,100 +121,27 @@ r = rank([A.var]); end - function tdst = insert_onespace(tsrc, varargin) - % insert a trivial space at position i. - tdst = MpsTensor(insert_onespace(tsrc.var, varargin{:}), tsrc.alegs + 1); + function s = pspace(A) + % The physical space of an :class:`MpsTensor`. + s = space(A, 1 + (1:A.plegs)); + end + + function s = leftvspace(A) + % The left virtual space of an :class:`MpsTensor`. + s = space(A.var(1), 1); + end + + function s = rightvspace(A) + % The right virtual space of an :class:`MpsTensor`. + s = space(A.var(1), nspaces(A.var(1)) - A.alegs); end end + %% Linear Algebra methods - function [A, L] = leftorth(A, alg) - arguments - A - alg = 'polar' - end - - if numel(A) > 1 - for i = numel(A):-1:1 - [A(i), L(i)] = leftorth(A(i), alg); - end - return - end - - if A.alegs == 0 - [A.var, L] = leftorth(A.var, 1:nspaces(A)-1, nspaces(A), alg); - if isdual(space(L, 1)) == isdual(space(L, 2)) - L.codomain = conj(L.codomain); - L = twist(L, 1); - A.var.domain = conj(A.var.domain); - end - else - [A.var, L] = leftorth(A.var, [1:A.plegs+1 A.plegs+3], A.plegs+2, alg); - A.var = permute(A.var, [1:A.plegs+1 A.plegs+3 A.plegs+2], rank(A)); - end - end - function [R, A] = rightorth(A, alg) - arguments - A - alg = 'rqpos' - end - - if numel(A) > 1 - for i = numel(A):-1:1 - [R(i), A(i)] = rightorth(A, alg); - end - return - end - - [R, A.var] = rightorth(A.var, 1, 2:nspaces(A), alg); - if isdual(space(R, 1)) == isdual(space(R, 2)) - R.domain = conj(R.domain); - R = twist(R, 2); - A.var.codomain = conj(A.var.codomain); - end - end - - function varargout = tsvd(t, varargin) - [varargout{1:nargout}] = tsvd(t.var, varargin{:}); - end - - function A = leftnull(A, alg) - arguments - A - alg = 'svd' - end - - if numel(A) > 1 - for i = numel(A):-1:1 - A(i) = leftnull(A(i), alg); - end - return - end - - p = 1:nspaces(A); - p1 = p(1:(end - A.alegs - 1)); - p2 = p((end - A.alegs):end); - A.var = leftnull(A.var, p1, p2, alg); - end - - function A = rightnull(A, alg) - arguments - A - alg = 'svd' - end - - if numel(A) > 1 - for i = numel(A):-1:1 - A(i) = rightnull(A(i), alg); - end - return - end - - A.var = rightnull(A.var, 1, 2:nspaces(A), alg); - end function d = dot(A, B) if isa(A, 'MpsTensor') @@ -303,9 +281,6 @@ C = tensorprod(varargin{:}); end - - - function T = transfermatrix(A, B) arguments A MpsTensor @@ -316,6 +291,130 @@ T = FiniteMpo(B', {}, A); end + + + function C = initializeC(A) + % Initialize a set of gauge tensors for the given mpstensors. + arguments (Repeating) + A MpsTensor + end + C = cell(size(A)); + + for i = length(A):-1:1 + C{i} = A{i}.var.eye(rightvspace(A{i})', leftvspace(A{next(i, length(A))})); + end + end + + function A = expand(A, leftvspace, rightvspace, noisefactor) + % Expand a tensor to the given virtual spaces. + arguments + A + leftvspace + rightvspace + noisefactor = 1e-3 + end + + spaces = space(A); + spaces(nspaces(A) - A.alegs) = rightvspace; + spaces(1) = conj(leftvspace); + r = rank(A); + A.var = embed(A.var, ... + noisefactor * ... + normalize(A.var.randnc(spaces(1:r(1)), spaces(r(1)+1:end)'))); + end + + function type = underlyingType(A) + type = underlyingType(A.var); + end + + function disp(t) + fprintf('%s with %d plegs and %d alegs:\n', class(t), t.plegs, t.alegs); + disp(t.var); + end + + function n = nnz(t) + n = nnz(t.var); + end + end + + + %% Factorizations + methods + function [A, L] = leftorth(A, alg) + arguments + A + alg = 'polar' + end + + if A.alegs == 0 + [A.var, L] = leftorth(A.var, 1:nspaces(A)-1, nspaces(A), alg); + if isdual(space(L, 1)) == isdual(space(L, 2)) + L.codomain = conj(L.codomain); + L = twist(L, 1); + A.var.domain = conj(A.var.domain); + end + else + [A.var, L] = leftorth(A.var, [1:A.plegs+1 A.plegs+3], A.plegs+2, alg); + A.var = permute(A.var, [1:A.plegs+1 A.plegs+3 A.plegs+2], rank(A)); + end + end + + function [R, A] = rightorth(A, alg) + arguments + A + alg = 'rqpos' + end + + [R, A.var] = rightorth(A.var, 1, 2:nspaces(A), alg); + if isdual(space(R, 1)) == isdual(space(R, 2)) + R.domain = conj(R.domain); + R = twist(R, 2); + A.var.codomain = conj(A.var.codomain); + end + end + + function varargout = tsvd(t, varargin) + [varargout{1:nargout}] = tsvd(t.var, varargin{:}); + end + + function A = leftnull(A, alg) + arguments + A + alg = 'svd' + end + + if numel(A) > 1 + for i = numel(A):-1:1 + A(i) = leftnull(A(i), alg); + end + return + end + + p = 1:nspaces(A); + p1 = p(1:(end - A.alegs - 1)); + p2 = p((end - A.alegs):end); + A.var = leftnull(A.var, p1, p2, alg); + end + + function A = rightnull(A, alg) + arguments + A + alg = 'svd' + end + + if numel(A) > 1 + for i = numel(A):-1:1 + A(i) = rightnull(A(i), alg); + end + return + end + + A.var = rightnull(A.var, 1, 2:nspaces(A), alg); + end + end + + %% Contractions + methods function v = applytransfer(L, R, v) arguments L MpsTensor @@ -375,162 +474,24 @@ 'Rank', newrank); %#ok end - function rho = applyleft(T, B, rho) - if nargin == 2 - rho = []; - return - end - assert(isequal(size(T), size(B)), 'mpstensor:dimagree', ... - 'dimensions should agree.'); - if length(T) > 1 - for i = 1:length(T) - rho = applyleft(T(i), B(i), rho); - end - return - end - - if isempty(rho) - switch num2str([T1.plegs T1.alegs T2.alegs]) - case '1 0 0' - indices = {[1 2 -2] [1 2 -1]}; - - case '2 0 0' - indices = {[1 2 3 -2] [1 2 3 -1]}; - case '1 1 0' - indices = {[1 2 -2 -3] [1 2 -1]}; - case '1 0 1' - indices = {[1 2 -2] [1 2 -1 -3]}; - case '1 1 1' - indices = {[1 2 -2 3] [1 2 -1 3]}; - otherwise - error('mps:ArgError', 'leftapply ill-defined.'); - end - rho = contract(T1, indices{1}, T2, indices{2}); - return - end - - switch num2str([nspaces(rho) T.plegs T.alegs B.alegs]) - case '2 1 0 0' - tmp = repartition(rho, [1 1]) * repartition(T, [1 2]); - tmp = tpermute(B, [3 2 1], [1 2]) * repartition(tmp, [2 1]); - rho = repartition(tmp, rank(rho)); -% indices = {[1 2] [2 3 -2] [1 3 -1]}; -% r = rank(rho); -% T = twist(T, [isdual(space(T, [1 2])) false]); - case '3 1 0 0' - tmp = tpermute(rho, [1 3 2], [2 1]) * repartition(T, [1 2]); - tmp = tpermute(B, [3 2 1], [1 2]) * tpermute(tmp, [1 3 4 2], [2 2]); - rho = repartition(tmp, rank(rho)); - otherwise - error('mps:ArgError', 'applyleft ill-defined'); - end -% rho = contract(rho, indices{1}, T, indices{2}, B, indices{3}, ... -% 'Rank', r); - end - - function rho = applyright(T, B, rho) - if nargin == 2 - rho = []; - return - end - assert(isequal(size(T), size(B)), 'mpstensor:dimagree', ... - 'dimensions should agree.'); - if length(T) > 1 - for i = length(T):-1:1 - rho = applyright(T(i), B(i), rho); - end - return - end - if isempty(rho) - switch num2str([T.plegs T.alegs B.alegs]) - case '1 0 0' - rho = contract(T, [-1 2 1], B, [-2 2 1]); -% rhoR=Contract({T1,T2},{[-1,2,1],[-2,2,1]}); - case '2 0 0' - rhoR=Contract({T1,T2},{[-1,2,3,1],[-2,2,3,1]}); - case '1 1 0' - rhoR=Contract({T1,T2},{[-1,2,1,-3],[-2,2,1]}); - case '1 0 1' - rhoR=Contract({T1,T2},{[-1,2,1],[-2,2,1,-3]}); - case '1 1 1' - rhoR=Contract({T1,T2},{[-1,2,1,3],[-2,2,1,3]}); - otherwise - error('mps:ArgError', 'applyright ill-defined.'); - end - return - end - - switch num2str([nspaces(rho) T.plegs T.alegs B.alegs]) - case '2 1 0 0' - tmp = repartition(T, [2 1]) * repartition(rho, [1 1]); - rho = repartition(... - repartition(tmp, [1 2]) * tpermute(B, [3 2 1], [2 1]), rank(rho)); - -% T = twist(T, [false ~isdual(space(T, 2:3))]); -% rho = contract(rho, [1 2], T, [-1 3 1], B, [-2 3 2], ... -% 'Rank', rank(rho)); - otherwise - error('mps:ArgError', 'applyright ill-defined.'); - end - end - function A = multiplyleft(A, C) - [A.var] = contract(C, [-1 1], ... - [A.var], [1 -((1:A.plegs)+1) -((1:A.alegs+1)+A.plegs+1)], 'Rank', rank(A)); + % Multiply a gauge matrix from the left. + assert(nspaces(C) == 2, 'mps:tba', 'not implemented yet'); + A.var = contract(C, [-1 1], ... + A.var, [1 -((1:A.plegs)+1) -((1:A.alegs+1)+A.plegs+1)], 'Rank', rank(A)); end function A = multiplyright(A, C) - r = rank(A); - r(2) = r(2) + nspaces(C) - 2; - - [A.var] = contract([A.var], [-(1:A.plegs+1) 1 -((1:A.alegs)+A.plegs+2)], ... - C, [1 -(2 + A.plegs + (1:(nspaces(C) - 1)))], 'Rank', r); - end - - function C = initializeC(A) - arguments (Repeating) - A MpsTensor - end - C = cell(size(A)); - - for i = length(A):-1:1 - C{i} = A{i}.var.eye(rightvspace(A{i})', leftvspace(A{next(i, length(A))})); - end - end - - function A = expand(A, addspace_left, addspace_right, noisefactor) - arguments - A - addspace_left - addspace_right - noisefactor = 1e-3 - end - - spaces = space(A); - spaces(nspaces(A) - A.alegs) = addspace_right; - spaces(1) = conj(addspace_left); - r = rank(A); - A.var = embed(A.var, ... - noisefactor * ... - normalize(A.var.randnc(spaces(1:r(1)), spaces(r(1)+1:end)'))); - - end - - function type = underlyingType(A) - type = underlyingType(A.var); - end - - function disp(t) - fprintf('%s with %d plegs and %d alegs:\n', class(t), t.plegs, t.alegs); - disp(t.var); - end - - function n = nnz(t) - n = nnz(t.var); + % Multiply a gauge matrix from the right. + r = rank(A); npA = A.plegs; naA = A.alegs; + nC = nspaces(C); naC = nC - 2; + r(2) = r(2) + naC; + A.var = contract(A.var, [-(1:npA + 1) 1 -((1:naA) + npA + 2)], ... + C, [1 -(2 + npA + (1:(nC - 1)))], 'Rank', r); + A.alegs = A.alegs + naC; end end - %% methods function bool = isisometry(t, varargin) @@ -648,28 +609,6 @@ function disp(t) end local_tensors{end} = MpsTensor(repartition(psi, [2 1])); end - - function A = new(varargin, kwargs) - arguments (Repeating) - varargin - end - arguments - kwargs.alegs = 0 - end - - A = MpsTensor(Tensor.new(varargin{:}), kwargs.alegs); - end - - function A = randnc(varargin, kwargs) - arguments (Repeating) - varargin - end - arguments - kwargs.alegs = 0 - end - - A = MpsTensor(Tensor.randnc(varargin{:}), kwargs.alegs); - end end end From 813fe53c1e13dfaafbabf8bf3a7996b82ac3338c Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 24 Apr 2023 13:40:52 +0200 Subject: [PATCH 213/245] fermion tests I --- test/TestAlgorithms.m | 21 ++++++++++++++++++++- test/ssh.mat | Bin 0 -> 2674 bytes 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 test/ssh.mat diff --git a/test/TestAlgorithms.m b/test/TestAlgorithms.m index 91060a6..eb2795d 100644 --- a/test/TestAlgorithms.m +++ b/test/TestAlgorithms.m @@ -75,7 +75,7 @@ function test1dIsing_ordered(tc) %% Symmetry H = repmat(quantum1dIsing('h', h, 'J', J, 'Symmetry', 'Z2'), 1, L); - vspace = GradedSpace.new(Z2(0, 1), [D D] / 2, false); + vspace = Z2Space([0, 1], [D D] / 2, false); gs = initialize_mps(H, vspace); % Groundstate algorithms @@ -92,6 +92,25 @@ function test1dIsing_ordered(tc) end end end + + function test1dSSH(tc) + load('ssh.mat', 'E0', 'gap', 'H_ssh'); + D = 16; + vspace = fZ2Space([0 1], [D D] / 2, false); + gs = initialize_mps(H_ssh, vspace); + + % Groundstate algorithms + gs = fixedpoint(Vumps('which', 'smallestreal', 'maxiter', 5), ... + H_ssh, gs); + tc.assertEqual(expectation_value(gs, H_ssh), E0, 'RelTol', 1e-2); + + % Excitation algorithms + k = 0; + qp = InfQP.randnc(gs, gs, k, fZ2(0)); + [~, mu] = excitations(QPAnsatz(), H, qp); + tc.assertEqual(mu, gap, 'RelTol', 1e-3, ... + sprintf('qp failed at momentum %.2f', k)); + end end end diff --git a/test/ssh.mat b/test/ssh.mat new file mode 100644 index 0000000000000000000000000000000000000000..d4c23be0fb0d76620ecc9917d1582bb1e8b7da77 GIT binary patch literal 2674 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 AdjJ3c literal 0 HcmV?d00001 From acf0bbc1b979fd0880555f8af64756bc18179378 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 24 Apr 2023 13:51:41 +0200 Subject: [PATCH 214/245] add SparseTensor randc, randnc --- src/sparse/SparseTensor.m | 18 ++++++++-------- test/TestSparseTensor.m | 43 +++++++++++---------------------------- 2 files changed, 22 insertions(+), 39 deletions(-) diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index 0453269..8bd2d46 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -128,13 +128,16 @@ t = SparseTensor.new(@rand, varargin{:}); end - function t = zeros(codomain, domain, kwargs) - arguments - codomain - domain - kwargs.Density = 0 - end - t = SparseTensor.new(@zeros, codomain, domain, 'Density', kwargs.Density); + function t = randc(varargin) + t = SparseTensor.new(@randc, varargin{:}); + end + + function t = randnc(varargin) + t = SparseTensor.new(@randnc, varargin{:}); + end + + function t = zeros(varargin) + t = SparseTensor.new(@zeros, varargin{:}); end function t = eye(codomain, domain) @@ -999,4 +1002,3 @@ function disp(t) end - diff --git a/test/TestSparseTensor.m b/test/TestSparseTensor.m index 2137997..82caf42 100644 --- a/test/TestSparseTensor.m +++ b/test/TestSparseTensor.m @@ -7,13 +7,12 @@ methods (Test) function basic_linear_algebra(tc) - spaces = CartesianSpace.new([2 3 4 5]); - szs = cell(1, 4); - for i = 1:length(szs) - szs{i} = spaces(randperm(length(spaces), randi([2 3]))); + smallspaces = CartesianSpace.new([2 3 4 5]); + for i = 4:-1:1 + jointspaces(i) = SumSpace(smallspaces(randperm(length(spaces), randi([2 3])))); end - t1 = generatesparsetensor([2 2], szs, 0.3); - a = rand(); + t1 = SparseTensor.randc(jointspaces(1:2), jointspaces(3:4), 'Density', 0.3); + a = randc(); tc.verifyTrue(isapprox(norm(t1)^2, dot(t1, t1), ... 'AbsTol', tc.tol, 'RelTol', tc.tol), 'norm and dot incompatible.'); @@ -30,8 +29,8 @@ function basic_linear_algebra(tc) tc.verifyTrue(isapprox(norm(normalize(t1)), 1), ... 'normalize should result in unit norm.'); - t2 = generatesparsetensor([2 2], szs, 0.3); - b = rand(); + t2 = t1.randc(t1.codomain, t1.domain, 'Density', 0.3); + b = randc(); tc.verifyTrue(isapprox(dot(b .* t2, a .* t1), conj(b) * a * dot(t2, t1), ... 'AbsTol', tc.tol, 'RelTol', tc.tol) && ... @@ -41,13 +40,12 @@ function basic_linear_algebra(tc) end function permute_via_inner(tc) - spaces = CartesianSpace.new([2 3 4 5]); - szs = cell(1, 4); - for i = 1:length(szs) - szs{i} = spaces(randperm(length(spaces), randi([2 3]))); + smallspaces = CartesianSpace.new([2 3 4 5]); + for i = 4:-1:1 + jointspaces(i) = SumSpace(smallspaces(randperm(length(spaces), randi([2 3])))); end - t1 = generatesparsetensor([2 2], szs, 0.3); - t2 = generatesparsetensor([2 2], szs, 0.3); + t1 = SparseTensor.randc(jointspaces(1:2), jointspaces(3:4), 'Density', 0.3); + t2 = t1.randc(jointspaces(1:2), jointspaces(3:4), 'Density', 0.3); inner = dot(t1, t2); for i = 0:4 @@ -75,20 +73,3 @@ function permute_via_inner(tc) end end end - -function t = generatesparsetensor(rank, spaces, sparsity) - -sz = cellfun(@length, spaces); -ind = ind2sub_(sz, 1:prod(sz)); - -for i = prod(sz):-1:1 - for j = length(sz):-1:1 - localsz(j) = spaces{j}(ind(i,j)); - end - var(i) = Tensor.rand(localsz(1:rank(1)), localsz(rank(1)+1:end)', 'Rank', rank); -end - -li1 = rand([1, length(var)]) < sparsity; -t = SparseTensor(ind(li1, :), var(li1), sz); - -end \ No newline at end of file From 363a7fd1a11afad4ec8a8e8bbcbd0bc26eabccad Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 24 Apr 2023 13:53:41 +0200 Subject: [PATCH 215/245] Fix typos --- test/TestSparseTensor.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/TestSparseTensor.m b/test/TestSparseTensor.m index 82caf42..014ed80 100644 --- a/test/TestSparseTensor.m +++ b/test/TestSparseTensor.m @@ -9,7 +9,7 @@ function basic_linear_algebra(tc) smallspaces = CartesianSpace.new([2 3 4 5]); for i = 4:-1:1 - jointspaces(i) = SumSpace(smallspaces(randperm(length(spaces), randi([2 3])))); + jointspaces(i) = SumSpace(smallspaces(randperm(length(smallspaces), randi([2 3])))); end t1 = SparseTensor.randc(jointspaces(1:2), jointspaces(3:4), 'Density', 0.3); a = randc(); @@ -42,7 +42,7 @@ function basic_linear_algebra(tc) function permute_via_inner(tc) smallspaces = CartesianSpace.new([2 3 4 5]); for i = 4:-1:1 - jointspaces(i) = SumSpace(smallspaces(randperm(length(spaces), randi([2 3])))); + jointspaces(i) = SumSpace(smallspaces(randperm(length(smallspaces), randi([2 3])))); end t1 = SparseTensor.randc(jointspaces(1:2), jointspaces(3:4), 'Density', 0.3); t2 = t1.randc(jointspaces(1:2), jointspaces(3:4), 'Density', 0.3); From 6490002055a75088fae5b2bf6ba1f2f1a8569782 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 24 Apr 2023 14:19:21 +0200 Subject: [PATCH 216/245] `SparseTensor.zeros` defaults empty --- src/sparse/SparseTensor.m | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index 8bd2d46..bc60668 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -136,8 +136,13 @@ t = SparseTensor.new(@randnc, varargin{:}); end - function t = zeros(varargin) - t = SparseTensor.new(@zeros, varargin{:}); + function t = zeros(codomain, domain, kwargs) + arguments + codomain + domain + kwargs.Density = 0 + end + t = SparseTensor.new(@zeros, codomain, domain, 'Density', kwargs.Density); end function t = eye(codomain, domain) From f144349345859eccb68efe32e83de0ab12235a49 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 24 Apr 2023 14:23:29 +0200 Subject: [PATCH 217/245] better istriu check --- src/sparse/SparseTensor.m | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index bc60668..2bc606f 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -772,7 +772,14 @@ function disp(t) function bool = istriu(a) assert(ismatrix(a), 'sparse:matrix', 'istriu is only defined for matrices'); - bool = all(a.ind(:, 1) <= a.ind(:, 2)); + inds = a.ind; vars = a.var; + for i = 1:size(inds, 1) + if inds(i, 1) > inds(i, 2) && norm(vars(i)) > 1e-10 + bool = false; + return + end + end + bool = true; end end From ab1d55101f7b8b1ab89da8018d1f0046b96c0172 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 24 Apr 2023 20:52:25 +0200 Subject: [PATCH 218/245] progress --- src/algorithms/QPAnsatz.m | 15 +-- src/algorithms/Vumps.m | 22 +++- src/algorithms/eigsolvers/Arnoldi.m | 2 +- src/models/fermionoperators/fannihilation.m | 25 +++++ src/models/fermionoperators/fcreation.m | 25 +++++ src/models/fermionoperators/fnumber.m | 7 ++ src/models/quantum1dIsing.m | 96 ++++++++++------- src/mps/InfJMpo.m | 112 +++++++++++--------- src/mps/InfMpo.m | 92 +++++++++------- src/mps/InfQP.m | 34 +++--- src/mps/MpoTensor.m | 11 +- src/mps/MpsTensor.m | 39 ++++--- src/sparse/SparseTensor.m | 1 + src/tensors/Tensor.m | 2 +- test/TestAlgorithms.m | 69 +++++++----- test/TestInfJMpo.m | 38 ++++--- test/TestInfMpo.m | 110 +++++++++---------- 17 files changed, 430 insertions(+), 270 deletions(-) create mode 100644 src/models/fermionoperators/fannihilation.m create mode 100644 src/models/fermionoperators/fcreation.m create mode 100644 src/models/fermionoperators/fnumber.m diff --git a/src/algorithms/QPAnsatz.m b/src/algorithms/QPAnsatz.m index d975b5e..bbfe9f4 100644 --- a/src/algorithms/QPAnsatz.m +++ b/src/algorithms/QPAnsatz.m @@ -34,13 +34,13 @@ for i = period(mpo):-1:1 T = AC_hamiltonian(mpo, qp.mpsleft, GL, GR, i); - offset(i) = dot(qp.mpsleft.AC(i), apply(T{1}, qp.mpsleft.AC(i))); + offset(i) = dot(qp.mpsleft.AC{i}, apply(T{1}, qp.mpsleft.AC{i})); end % Algorithm eigkwargs = namedargs2cell(alg.alg_eigs); H_effective = @(x) updateX(alg, mpo, qp, GL, GR, x, offset); - [X, mu] = eigsolve(H_effective, qp.X, alg.howmany, alg.which, ... + [X, mu] = eigsolve(H_effective, [qp.X{:}], alg.howmany, alg.which, ... eigkwargs{:}); for i = alg.howmany:-1:2 @@ -50,29 +50,30 @@ end function y = updateX(alg, mpo, qp, GL, GR, x, offset) - qp.X = x; + qp.X = num2cell(x); qp.B = computeB(qp); B = qp.B; H_c = B_hamiltonian(mpo, qp, GL, GR, 'Type', 'center'); for i = period(qp):-1:1 - B(i) = MpsTensor(apply(H_c{i}, B(i)), 1); + B{i} = MpsTensor(apply(H_c{i}, B{i}), 1); end H_l = B_hamiltonian(mpo, qp, GL, GR, 'Type', 'left'); for i = 1:period(qp) - B(i) = B(i) + repartition(apply(H_l{i}, qp.AR(i)), rank(B(i))); + B{i} = B{i} + repartition(apply(H_l{i}, qp.AR{i}), rank(B{i})); end H_r = B_hamiltonian(mpo, qp, GL, GR, 'Type', 'right'); for i = 1:period(qp) - B(i) = B(i) + repartition(apply(H_r{i}, qp.AL(i)), rank(B(i))); + B{i} = B{i} + repartition(apply(H_r{i}, qp.AL{i}), rank(B{i})); end for i = 1:period(qp) - qp.B(i) = B(i) - qp.B(i) * offset(i); + qp.B{i} = B{i} - qp.B{i} * offset(i); end y = computeX(qp); + y = [y{:}]; end end end diff --git a/src/algorithms/Vumps.m b/src/algorithms/Vumps.m index b0cfa63..571ac9c 100644 --- a/src/algorithms/Vumps.m +++ b/src/algorithms/Vumps.m @@ -26,7 +26,7 @@ saveMethod = 'full' name = 'VUMPS' - alg_eigs = KrylovSchur('MaxIter', 100, 'KrylovDim', 20) + alg_eigs = Arnoldi('MaxIter', 100, 'KrylovDim', 20) end properties (Access = private) @@ -65,6 +65,10 @@ alg.alg_environments.tol = sqrt(alg.tol_min * alg.tol_max); alg.alg_environments.verbosity = alg.verbosity - 2; end + + if isfield('verbosity', kwargs) + alg.alg_eigs.verbosity = alg.verbosity - 2; + end end function [mps, lambda, GL, GR, eta, time] = fixedpoint(alg, mpo, mps) @@ -122,6 +126,9 @@ ACs = arrayfun(@(x) x.AC(sites), mps, 'UniformOutput', false); AC = vertcat(ACs{:}); for i = length(sites):-1:1 + if alg.verbosity >= Verbosity.detail + fprintf('\nAC{%d} eigenvalue solver:\n------------------------\n', sites(i)); + end [AC{1, i}.var, ~] = eigsolve(alg.alg_eigs, @(x) H_AC{i}.apply(x), AC{1, i}.var, ... 1, alg.which); for d = 2:depth(mpo) @@ -141,6 +148,9 @@ Cs = arrayfun(@(x) x.C(sites), mps, 'UniformOutput', false); C = vertcat(Cs{:}); for i = length(sites):-1:1 + if alg.verbosity >= Verbosity.detail + fprintf('\nC{%d} eigenvalue solver:\n-----------------------\n', sites(i)); + end [C{1, i}, ~] = eigsolve(alg.alg_eigs, @(x) H_C{i}.apply(x), C{1, i}, 1, alg.which); for d = 2:depth(mpo) C{d, i} = H_C{i}(d).apply(C{d-1, i}); @@ -149,6 +159,10 @@ end function mps = updatemps(alg, iter, mps, AC, C) + if alg.verbosity >= Verbosity.detail + fprintf('\nCanonicalize:\n------------------\n'); + end + if strcmp(alg.multiAC, 'sequential') sites = mod1(iter, period(mps)); else @@ -175,7 +189,9 @@ GL = cell(depth(mpo), period(mps)) GR = cell(depth(mpo), period(mps)) end - + if alg.verbosity >= Verbosity.detail + fprintf('\nEnvironments:\n------------------\n'); + end kwargs = namedargs2cell(alg.alg_environments); D = depth(mpo); lambda = zeros(D, 1); @@ -226,7 +242,7 @@ if alg.verbosity > Verbosity.iter fprintf('Updated subalgorithm tolerances: (%e,\t%e,\t%e)\n', ... - alg.alg_eigs.Tol, alg.alg_canonical.Tol, alg.alg_environments.Tol); + alg.alg_eigs.tol, alg.alg_canonical.Tol, alg.alg_environments.Tol); end end diff --git a/src/algorithms/eigsolvers/Arnoldi.m b/src/algorithms/eigsolvers/Arnoldi.m index 31f33f0..be9d0d6 100644 --- a/src/algorithms/eigsolvers/Arnoldi.m +++ b/src/algorithms/eigsolvers/Arnoldi.m @@ -79,7 +79,7 @@ % normalize beta = norm(v, 'fro'); - v = v / beta; + v = v ./ beta; if ctr_inner == alg.krylovdim, break; end diff --git a/src/models/fermionoperators/fannihilation.m b/src/models/fermionoperators/fannihilation.m new file mode 100644 index 0000000..41d0197 --- /dev/null +++ b/src/models/fermionoperators/fannihilation.m @@ -0,0 +1,25 @@ +function c = fannihilation(kwargs) +arguments + kwargs.side = 'left' +end + + +pspace = fZ2Space([0 1], [1 1], false); +vspace = fZ2Space(1, 1, false); + +switch kwargs.side + case 'right' + c = Tensor.zeros([vspace pspace], pspace); + c = fill_matrix(c, {0 1}); + + + case 'left' + c = Tensor.zeros(pspace, [pspace vspace]); + c = fill_matrix(c, {1 0}); + + otherwise + error('models:argerror', 'invalid side') +end + +end + diff --git a/src/models/fermionoperators/fcreation.m b/src/models/fermionoperators/fcreation.m new file mode 100644 index 0000000..ef7799f --- /dev/null +++ b/src/models/fermionoperators/fcreation.m @@ -0,0 +1,25 @@ +function c_dagger = fcreation(kwargs) +arguments + kwargs.side = 'left' +end + + +pspace = fZ2Space([0 1], [1 1], false); +vspace = fZ2Space(1, 1, false); + +switch kwargs.side + case 'right' + c_dagger = Tensor.zeros([vspace pspace], pspace); + c_dagger = fill_matrix(c_dagger, {1 0}); + + + case 'left' + c_dagger = Tensor.zeros(pspace, [pspace vspace]); + c_dagger = fill_matrix(c_dagger, {0 1}); + + otherwise + error('models:argerror', 'invalid side') +end + +end + diff --git a/src/models/fermionoperators/fnumber.m b/src/models/fermionoperators/fnumber.m new file mode 100644 index 0000000..ed84761 --- /dev/null +++ b/src/models/fermionoperators/fnumber.m @@ -0,0 +1,7 @@ +function n = fnumber() + +pspace = fZ2Space([0 1], [1 1], false); + +n = fill_matrix(Tensor.zeros(pspace, pspace), {0, 1}); + +end diff --git a/src/models/quantum1dIsing.m b/src/models/quantum1dIsing.m index 1e100c2..85e7047 100644 --- a/src/models/quantum1dIsing.m +++ b/src/models/quantum1dIsing.m @@ -3,7 +3,7 @@ kwargs.J = 1 kwargs.h = 0.5 kwargs.L = Inf % size of system - kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'Z2'})} = 'Z1' + kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'Z2', 'fZ2'})} = 'Z1' end J = kwargs.J; @@ -12,41 +12,65 @@ sigma_x = [0 1; 1 0] / 2; sigma_z = [1 0; 0 -1] / 2; -if strcmp(kwargs.Symmetry, 'Z1') - pSpace = CartesianSpace.new(2); - vSpace = one(pSpace); - trivSpace = one(pSpace); - - S = Tensor([vSpace pSpace], [pSpace vSpace]); - Sx = fill_matrix(S, sigma_x); - Sz = fill_matrix(S, sigma_z); - - cod = SumSpace([vSpace vSpace vSpace], pSpace); - dom = SumSpace(pSpace, [vSpace vSpace vSpace]); - O = MpoTensor.zeros(cod, dom); - O(1, 1, 1, 1) = 1; - O(3, 1, 3, 1) = 1; - O(1, 1, 2, 1) = -J * Sx; - O(2, 1, 3, 1) = Sx; - O(1, 1, 3, 1) = (-J * h) * Sz; - -else - pSpace = GradedSpace.new(Z2(0, 1), [1 1], false); - vSpace = GradedSpace.new(Z2(1), 1, false); - trivSpace = one(pSpace); - - Sx_l = fill_matrix(Tensor([trivSpace pSpace], [pSpace vSpace]), {1 1}) / 2; - Sx_r = fill_matrix(Tensor([vSpace pSpace], [pSpace trivSpace]), {1 1}) / 2; - Sz = fill_matrix(Tensor([trivSpace pSpace], [pSpace trivSpace]), {1 -1}) / 2; - - cod = SumSpace([one(vSpace) vSpace one(vSpace)], pSpace); - dom = SumSpace(pSpace, [one(vSpace), vSpace, one(vSpace)]); - O = MpoTensor.zeros(cod, dom); - O(1, 1, 1, 1) = 1; - O(3, 1, 3, 1) = 1; - O(1, 1, 2, 1) = -J * Sx_l; - O(2, 1, 3, 1) = Sx_r; - O(1, 1, 3, 1) = (-J * h) * Sz; +switch kwargs.Symmetry + case 'Z1' + pSpace = CartesianSpace.new(2); + vSpace = one(pSpace); + trivSpace = one(pSpace); + + S = Tensor([vSpace pSpace], [pSpace vSpace]); + Sx = fill_matrix(S, sigma_x); + Sz = fill_matrix(S, sigma_z); + + cod = SumSpace([vSpace vSpace vSpace], pSpace); + dom = SumSpace(pSpace, [vSpace vSpace vSpace]); + O = MpoTensor.zeros(cod, dom); + O(1, 1, 1, 1) = 1; + O(3, 1, 3, 1) = 1; + O(1, 1, 2, 1) = -J * Sx; + O(2, 1, 3, 1) = Sx; + O(1, 1, 3, 1) = (-J * h) * Sz; + + case 'Z2' + pSpace = GradedSpace.new(Z2(0, 1), [1 1], false); + vSpace = GradedSpace.new(Z2(1), 1, false); + trivSpace = one(pSpace); + + Sx_l = fill_matrix(Tensor([trivSpace pSpace], [pSpace vSpace]), {1 1}) / 2; + Sx_r = fill_matrix(Tensor([vSpace pSpace], [pSpace trivSpace]), {1 1}) / 2; + Sz = fill_matrix(Tensor([trivSpace pSpace], [pSpace trivSpace]), {1 -1}) / 2; + + cod = SumSpace([one(vSpace) vSpace one(vSpace)], pSpace); + dom = SumSpace(pSpace, [one(vSpace), vSpace, one(vSpace)]); + O = MpoTensor.zeros(cod, dom); + O(1, 1, 1, 1) = 1; + O(3, 1, 3, 1) = 1; + O(1, 1, 2, 1) = -J * Sx_l; + O(2, 1, 3, 1) = Sx_r; + O(1, 1, 3, 1) = (-J * h) * Sz; + + case 'fZ2' + c_left = fannihilation('side', 'left'); + c_right = fannihilation('side', 'right'); + cdag_left = fcreation('side', 'left'); + cdag_right = fcreation('side', 'right'); + + % twosite terms + cdagc = contract(cdag_left, [-1 1 -3], c_right, [1 -2 -4], 'Rank', [2 2]); + ccdag = contract(c_left, [-1 1 -3], cdag_right, [1 -2 -4], 'Rank', [2 2]); + cc = contract(c_left, [-1 1 -3], c_right, [1 -2 -4], 'Rank', [2 2]); + cdagcdag = contract(cdag_left, [-1 1 -3], cdag_right, [1 -2 -4], 'Rank', [2 2]); + H_twosite = -J * (cdagc - ccdag + cdagcdag - cc); + + % onesite terms + n = fnumber(); + H_onesite = J * 2 * h * (n - 1 / 2 * n.eye(n.codomain, n.domain)); + + mpo = InfJMpo.twosite(H_twosite, H_onesite); + return + + otherwise + error('models:argerror', 'invalid symmetry'); end mpo = InfJMpo(O); diff --git a/src/mps/InfJMpo.m b/src/mps/InfJMpo.m index 4ceb442..cbf18fb 100644 --- a/src/mps/InfJMpo.m +++ b/src/mps/InfJMpo.m @@ -28,45 +28,49 @@ end linkwargs = namedargs2cell(linopts); + L = period(mps1); T = transfermatrix(mpo, mps1, mps2, 'Type', 'LL'); + fp_left = fixedpoint(mpo, mps1, 'l_LL', 1); + fp_right = fixedpoint(mpo, mps1, 'r_LL', L); - if isempty(GL) || isempty(GL{1}) + if isempty(GL) || isempty(GL{1}) % initialize environment GL = cell(1, period(mps1)); - GL{1} = SparseTensor.zeros(domain(T), []); - pSpace = space(T(1).O{1}(:,:,:,1), 4); - fp1 = insert_onespace(fixedpoint(mps1, 'l_LL'), ... - 2, ~isdual(pSpace(1))); - GL{1}(1) = repartition(fp1, [nspaces(fp1) 0]); + GL{1} = repartition(MpsTensor(SparseTensor.zeros(domain(T), [])), ... + rank(fp_left)); + GL{1}.var(1) = fp_left; end for i = 2:size(GL{1}, 2) - rhs = apply(slice(T, i, 1:i-1), GL{1}(1, 1:i-1, 1)); + rhs = apply(slice(T, i, 1:i-1), slice(GL{1}, 1, 1:i-1, 1)); Tdiag = slice(T, i, i); if iszero(Tdiag) - GL{1}(i) = rhs; - elseif iseye(T, i) - fp_left = repartition(insert_onespace(fixedpoint(mps1, 'l_LL'), ... - 2, isdual(space(rhs, 2))), rank(rhs)); - fp_right = insert_onespace(fixedpoint(mps1, 'r_LL'), ... - 2, ~isdual(space(rhs, 2))); - lambda = overlap(rhs, fp_right); - - rhs = rhs - lambda * fp_left; - [GL{1}(i), ~] = linsolve(@(x) x - apply(Tdiag, x), rhs, GL{1}(i), ... - linkwargs{:}); - GL{1}(i) = GL{1}(i) - overlap(GL{1}(i), fp_right) * fp_left; + GL{1}.var(i) = rhs; else - [GL{1}(i), ~] = linsolve(@(x) x - apply(Tdiag, x), rhs, GL{1}(i), ... + if iseye(T, i) + lambda = overlap(rhs, fp_right); + rhs = rhs - lambda * fp_left; + end + [GL{1}.var(i), flag, relres, iter] = linsolve(@(x) x - apply(Tdiag, x), rhs, GL{1}.var(i), ... linkwargs{:}); + if iseye(T, i) + GL{1}.var(i) = GL{1}.var(i) - overlap(GL{1}.var(i), fp_right) * fp_left; + end + + if flag == 1 && linopts.Verbosity >= Verbosity.warn + fprintf('GL(%d) reached maxiter, error = %3e\n', i, relres); + elseif flag == 2 && linopts.Verbosity >= Verbosity.warn + fprintf('GL(%d) stagnated, error = %3e\n', i, relres); + elseif linopts.Verbosity >= Verbosity.conv + fprintf('GL(%d) converged after %d iterations, error = %3e\n', i, iter, relres); + end end end - if nnz(GL{1}) == numel(GL{1}) - GL{1} = full(GL{1}); + if nnz(GL{1}.var) == numel(GL{1}.var) + GL{1}.var = full(GL{1}.var); end - GL{1} = MpsTensor(GL{1}, 0); for w = 1:period(mps1)-1 T = transfermatrix(mpo, mps1, mps2, w, 'Type', 'LL'); GL{next(w, period(mps1))} = apply(T, GL{w}); @@ -92,18 +96,18 @@ if isempty(GR) || isempty(GR{1}) GR = cell(1, period(mps1)); - GR{1} = SparseTensor.zeros(domain(T), []); + GR{1} = MpsTensor(SparseTensor.zeros(domain(T), [])); pSpace = space(T(1).O{1}(:, end, :, :), 2); fp1 = insert_onespace(fixedpoint(mps1, 'r_RR'), ... 2, isdual(pSpace(end))); - GR{1}(1, N, 1) = repartition(fp1, [nspaces(fp1) 0]); + GR{1}.var(1, N, 1) = repartition(fp1, [nspaces(fp1) 0]); end for i = N-1:-1:1 - rhs = apply(slice(T, i, i+1:N), GR{1}(1, i+1:N, 1)); + rhs = apply(slice(T, i, i+1:N), slice(GR{1}, 1, i+1:N, 1)); Tdiag = slice(T, i, i); if iszero(Tdiag) - GR{1}(i) = rhs; + GR{1}.var(i) = rhs; elseif iseye(T, i) fp_left = insert_onespace(fixedpoint(mps1, 'l_RR'), ... 2, ~isdual(space(rhs, 2))); @@ -112,22 +116,21 @@ lambda = contract(rhs, 1:3, fp_left, 3:-1:1); rhs = rhs - lambda * fp_right; - [GR{1}(i), ~] = ... - linsolve(@(x) x - apply(Tdiag, x), rhs, GR{1}(i), linkwargs{:}); + [GR{1}.var(i), ~] = ... + linsolve(@(x) x - apply(Tdiag, x), rhs, GR{1}.var(i), linkwargs{:}); - GR{1}(i) = GR{1}(i) - ... - contract(GR{1}(i), 1:3, fp_left, 3:-1:1) * fp_right; + GR{1}.var(i) = GR{1}.var(i) - ... + overlap(GR{1}.var(i), fp_left) * fp_right; else - [GR{1}(i), ~] = linsolve(@(x) x - apply(Tdiag, x), rhs, GR{1}(i), ... + [GR{1}.var(i), ~] = linsolve(@(x) x - apply(Tdiag, x), rhs, GR{1}.var(i), ... linkwargs{:}); end end - if nnz(GR{1}) == numel(GR{1}) - GR{1} = full(GR{1}); + if nnz(GR{1}.var) == numel(GR{1}.var) + GR{1}.var = full(GR{1}.var); end - GR{1} = MpsTensor(GR{1}, 0); for w = period(mps1):-1:2 T = transfermatrix(mpo, mps1, mps2, w, 'Type', 'RR').'; GR{w} = apply(T, GR{next(w, period(mps1))}); @@ -161,7 +164,7 @@ % initialize and precompute GL * TB GBL = cell(size(GL)); - GBL{1} = SparseTensor.zeros(domain(T), auxspace(qp, 1)); + GBL{1} = MpsTensor(SparseTensor.zeros(domain(T), auxspace(qp, 1)), 1); for w = 1:L GBL{next(w, L)} = ... (apply(TB(w), GL{w}) + apply(T(w), GBL{w})) * (expP^(1/L)); @@ -169,8 +172,8 @@ N = size(GBL{1}, 2); for i = 2:N % GBL{1}(1) = 0 because of quasiparticle gauge - rhs = apply(slice(T, i, 1:i-1), GBL{1}(1, 1:i-1, 1, 1)) * expP; - rhs = rhs + GBL{1}(i); + rhs = apply(slice(T, i, 1:i-1), slice(GBL{1}, 1, 1:i-1, 1, 1)) * expP; + rhs = rhs + slice(GBL{1}, i); Tdiag = slice(T, i, i); if iszero(Tdiag) @@ -186,12 +189,12 @@ H_effective = @(x) x - expP * apply(Tdiag, x); end - [GBL{1}(i), ~] = linsolve(H_effective, rhs, [], linkwargs{:}); + [GBL{1}.var(i), ~] = linsolve(H_effective, rhs, [], linkwargs{:}); end end - if nnz(GBL{1}) == numel(GBL{1}) - GBL{1} = full(GBL{1}); + if nnz(GBL{1}) == numel(GBL{1}.var) + GBL{1}.var = full(GBL{1}.var); end GBL{1} = MpsTensor(GBL{1}, 1); @@ -227,7 +230,7 @@ TB = transfermatrix(mpo, qp, qp, 'Type', 'BR').'; GBR = cell(size(GR)); - GBR{1} = SparseTensor.zeros(domain(T), auxspace(qp, 1)); + GBR{1} = MpsTensor(SparseTensor.zeros(domain(T), auxspace(qp, 1)), 1); for w = L:-1:1 ww = next(w, L); TB_w = transfermatrix(mpo, qp, qp, w, 'Type', 'BR').'; @@ -238,15 +241,15 @@ N = size(GBR{1}, 2); for i = N:-1:1 if i == N - rhs = GBR{1}(1, i, 1, 1); + rhs = slice(GBR{1}, 1, i, 1, 1); else - rhs = apply(slice(T, i, i+1:N), GBR{1}(1, i+1:N, 1, 1)) * expP; - rhs = rhs + GBR{1}(1, i, 1, 1); + rhs = apply(slice(T, i, i+1:N), slice(GBR{1}, 1, i+1:N, 1, 1)) * expP; + rhs = rhs + slice(GBR{1}, 1, i, 1, 1); end Tdiag = slice(T, i, i); if iszero(Tdiag) - GBR{1}(i) = rhs; + GBR{1}.var(i) = rhs; else if needsRegularization && iseye(T, i) lambda = overlap(rhs, fp_left); @@ -258,7 +261,7 @@ H_effective = @(x) x - expP * apply(Tdiag, x); end - [GBR{1}(i), ~] = linsolve(H_effective, rhs, [], linkwargs{:}); + [GBR{1}.var(i), ~] = linsolve(H_effective, rhs, [], linkwargs{:}); end end @@ -300,7 +303,7 @@ function mpo = renormalize(mpo, lambda) mpo = mpo - lambda; end - + function mpo = plus(a, b) if isa(a, 'InfJMpo') && isnumeric(b) if period(a) > 1 && isscalar(b) @@ -358,11 +361,16 @@ L = local_ops{1}; R = local_ops{2}; - assert(pspace(L) == pspace(R), 'operators:spacemismatch', ... + P = pspace(L); + assert(P == pspace(R), 'operators:spacemismatch', ... sprintf('incompatible physical spaces %s and %s', pspace(L), pspace(R))); - cod = SumSpace([leftvspace(L), leftvspace(R), rightvspace(R)'], pspace(L)); - dom = SumSpace(pspace(L), [leftvspace(L)', rightvspace(L), rightvspace(R)]); + total_leftvspace = [leftvspace(L) leftvspace(R) rightvspace(R)']; + total_rightvspace = [leftvspace(L)' rightvspace(L) rightvspace(R)]; + assert(all(conj(total_rightvspace) == total_leftvspace)) + + cod = SumSpace(total_leftvspace, P); + dom = SumSpace(P, conj(total_rightvspace)); O = MpoTensor.zeros(cod, dom); O(1, 1, 1, 1) = 1; @@ -374,7 +382,7 @@ local_op = MpoTensor.decompose_local_operator(Honesite, newkwargs{:}); assert(leftvspace(local_op{1}) == subspaces(leftvspace(O), 1) && ... rightvspace(local_op{1}) == subspaces(rightvspace(O), 3) && ... - pspace(local_op) == subspaces(pspace(O), 1), ... + pspace(local_op{1}) == subspaces(pspace(O), 1), ... 'operators:spacemismatch', ... 'onesite operator incompatible with twosite operator.'); O(1, 1, 3, 1) = local_op{1}; diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index a562283..07744d0 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -218,44 +218,59 @@ assert(period(qp) == period(mpo), 'quasiparticles have different period'); linkwargs = namedargs2cell(linopts); - expP = exp(-1i * qp.p); L = period(mpo); - rho = GL{1}; - for pos = 1:L - T_B = transfermatrix(mpo, qp, qp, pos, 'Type', 'BL'); - if pos == 1 - rho = apply(T_B, GL{pos}); - else - T_R = transfermatrix(mpo, qp, qp, pos, 'Type', 'RL'); - rho = apply(T_B, GL{pos}) + apply(T_R, rho); - end - end - - T_R = transfermatrix(mpo, qp, qp, 1:period(mpo), 'Type', 'RL'); - if istrivial(qp) + needsRegularization = istrivial(qp); + if needsRegularization || true fp_left = fixedpoint(mpo, qp, 'l_RL_0', 1); fp_right = fixedpoint(mpo, qp, 'r_RL_1', L); -% C = qp.mpsleft.C(1); -% FL = insert_onespace(multiplyright(MpsTensor(GL{1}), C), ... -% nspaces(GL{1}) + 1, isdual(auxspace(qp, 1))); -% FR = insert_onespace(multiplyright(MpsTensor(GR{1}), C'), ... -% 1, ~isdual(auxspace(qp, 1))); - - rho = rho - overlap(rho, fp_right) * fp_left; - GBL{1} = linsolve(@(x) x - expP * apply_regularized(T_R, fp_left, fp_right, x), ... - expP * rho, [], linkwargs{:}); + end + + T = transfermatrix(mpo, qp, qp, 'Type', 'RL'); + TB = transfermatrix(mpo, qp, qp, 'Type', 'BL'); + + % initialize and precompute GL * TB + GBL = cell(size(GL)); + GBL{1} = MpsTensor(SparseTensor.zeros(domain(T), auxspace(qp, 1)), 1); + for w = 1:L + TB_w = transfermatrix(mpo, qp, qp, w, 'Type', 'BL'); + T_w = transfermatrix(mpo, qp, qp, w, 'Type', 'RL'); + GBL{next(w, L)} = ... + (apply(TB_w, GL{w}) + apply(T_w, GBL{w})) * (expP^(1/L)); + end + +% rho = GL{1}; +% for pos = 1:L +% T_B = transfermatrix(mpo, qp, qp, pos, 'Type', 'BL'); +% if pos == 1 +% rho = apply(T_B, GL{pos}); +% else +% T_R = transfermatrix(mpo, qp, qp, pos, 'Type', 'RL'); +% rho = apply(T_B, GL{pos}) + apply(T_R, rho); +% end +% end + T = transfermatrix(mpo, qp, qp, 'Type', 'RL'); + TB = transfermatrix(mpo, qp, qp, 'Type', 'BL'); + + + rhs = GBL{1}; + if needsRegularization + lambda = overlap(rhs, fp_right); + rhs = rhs - lambda * fp_left; + GBL{1} = linsolve(@(x) x - expP * apply_regularized(T, fp_left, fp_right, x), ... + rhs, [], linkwargs{:}); else - GBL{1} = expP * linsolve(@(x) x - expP * apply(T_R, x), ... - rho, [], linkwargs{:}); + GBL{1} = expP * linsolve(@(x) x - expP * apply(T, x), ... + rhs, [], linkwargs{:}); end - GBL{1} = MpsTensor(GBL{1}, 1); - for i = 2:L - T_R = transfermatrix(mpo, qp, qp, i-1, 'Type', 'RL'); - T_B = transfermatrix(mpo, qp, qp, i-1, 'Type', 'BL'); - GBL{i} = apply(T_R, GBL{i-1}) + apply(T_B, GL{w-1}); + if nnz(GBL{1}) == numel(GBL{1}.var) + GBL{1}.var = full(GBL{1}.var); + end + for w = 1:L-1 + GBL{next(w, L)} = expP^(1 / L) * ... + (apply(TB(w), GL{w}) + apply(T(w), GBL{w})); end end @@ -405,12 +420,17 @@ for i = 1:length(sites) for d = depth(mpo):-1:1 gl = twistdual(GL{d, sites(i)}, 1); - gl_better = tpermute(gl, [1, flip(2:nspaces(gl)-1-gl.alegs), ... - nspaces(gl) + (0:gl.alegs)], rank(gl)); - gr = twistdual(GR{d, next(sites(i), period(mpo))};, nspaces(gr)); - gr_better = tpermute(gr, [1, flip(2:nspaces(gr)-1-gr.alegs), ... - nspaces(gr) + (0:gr.alegs)], rank(gr)); - H{i}(d, 1) = FiniteMpo(gl_better, mpo.O(d, sites(i)), gr_better); + p = 1:nspaces(gl); + p(2:(nspaces(gl)-1-gl.alegs)) = flip(p(2:(nspaces(gl)-1-gl.alegs))); + gl = tpermute(gl, p, rank(gl)); + + gr = GR{d, next(sites(i), period(mpo))}; + gr = twistdual(gr, nspaces(gr)); + p = 1:nspaces(gr); + p(2:(nspaces(gr)-1-gr.alegs)) = flip(p(2:(nspaces(gr)-1-gr.alegs))); + gr = tpermute(gr, p, rank(gr)); + + H{i}(d, 1) = FiniteMpo(gl, mpo.O(d, sites(i)), gr); end end end diff --git a/src/mps/InfQP.m b/src/mps/InfQP.m index 2f01c4a..da3d12c 100644 --- a/src/mps/InfQP.m +++ b/src/mps/InfQP.m @@ -12,6 +12,10 @@ p end + properties (Dependent) + AL + AR + end %% Constructors methods @@ -46,15 +50,15 @@ AL = mpsleft.AL; for i = period(mpsleft):-1:1 - VL(i) = leftnull(AL(i)); - rVspace = rightvspace(VL(i)); + VL{i} = leftnull(AL{i}); + rVspace = rightvspace(VL{i}); lVspace = leftvspace(mpsright, next(i, period(mpsleft))); if isempty(charge) aspace = one(rVspace); else aspace = rVspace.new(dims, false); end - X(i) = Tensor.new(fun, rVspace', [aspace lVspace]); + X{i} = Tensor.new(fun, rVspace', [aspace lVspace]); end qp = InfQP(mpsleft, mpsright, X, VL, [], p); @@ -70,34 +74,26 @@ %% Derived Properties methods function s = auxspace(qp, i) - s = space(qp.X(i), 3); + s = space(qp.X{i}, 3); end - function al = AL(qp, sites) - if nargin > 1 - al = qp.mpsleft.AL(sites); - else - al = qp.mpsleft.AL; - end + function al = get.AL(qp) + al = qp.mpsleft.AL; end - function ar = AR(qp, sites) - if nargin > 1 - ar = qp.mpsright.AR(sites); - else - ar = qp.mpsright.AR; - end + function ar = get.AR(qp) + ar = qp.mpsright.AR; end function B = computeB(qp) for w = period(qp):-1:1 - B(w) = multiplyright(qp.VL(w), qp.X(w)); + B{w} = multiplyright(qp.VL{w}, qp.X{w}); end end function X = computeX(qp) for w = period(qp):-1:1 - X(w) = tracetransfer(qp.VL(w)', qp.B(w)); + X{w} = tracetransfer(qp.VL{w}', qp.B{w}); end end @@ -150,7 +146,7 @@ end function type = underlyingType(qp) - type = underlyingType(qp.X); + type = underlyingType(qp.X{1}); end end end diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index 19d977c..e128769 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -106,24 +106,25 @@ t.scalars = t.scalars / a; end - function v = applychannel(O, L, R, v) + function dst = applychannel(O, L, R, src) arguments O MpoTensor L MpsTensor R MpsTensor - v + src MpsTensor end - auxlegs_v = nspaces(v) - 3; + auxlegs_v = nspaces(src) - 3; auxlegs_l = nspaces(L) - 3; auxlegs_r = nspaces(R) - 3; - newrank = rank(v); newrank(2) = newrank(2) + auxlegs_l + auxlegs_r; + newrank = rank(src); newrank(2) = newrank(2) + auxlegs_l + auxlegs_r; - v = contract(v, [1 3 5 (-(1:auxlegs_v) - 3 - auxlegs_l)], ... + dst = contract(src, [1 3 5 (-(1:auxlegs_v) - 3 - auxlegs_l)], ... L, [-1 2 1 (-(1:auxlegs_l) - 3)], ... O, [2 -2 4 3], ... R, [5 4 -3 (-(1:auxlegs_r) - 3 - auxlegs_l - auxlegs_v)], ... 'Rank', newrank); + dst = MpsTensor(dst, auxlegs_v + auxlegs_l + auxlegs_r); end function v = applympo(varargin) diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index 05e2034..fc36828 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -71,7 +71,7 @@ end function A = cat(dim, varargin) - ismpstensor = cellfun(@(x) isa(x, 'MpsTensor')); + ismpstensor = cellfun(@(x) isa(x, 'MpsTensor'), varargin); i = find(ismpstensor, 1); A = varargin{i}; for j = 1:i-1 @@ -93,6 +93,15 @@ A = cat(1, varargin{:}); end + function n = nnz(A) + n = nnz(A.var); + end + + function A = slice(A, varargin) + s = substruct('()', varargin); + A.var = subsref(A.var, s); + end + function tdst = insert_onespace(tsrc, varargin) % insert a trivial space at position i. tdst = MpsTensor(insert_onespace(tsrc.var, varargin{:}), tsrc.alegs + 1); @@ -118,7 +127,7 @@ end function r = rank(A) - r = rank([A.var]); + r = rank(A.var); end function s = pspace(A) @@ -181,9 +190,7 @@ end function A = repartition(A, varargin) - for i = 1:numel(A) - A(i).var = repartition(A(i).var, varargin{:}); - end + A.var = repartition(A.var, varargin{:}); end function A = plus(varargin) @@ -226,20 +233,24 @@ A = MpsTensor(ldivide(varargin{:}), alegs); end - function n = norm(A) - n = norm([A.var]); + function n = norm(A, varargin) + n = norm(A.var, varargin{:}); + end + + function [A, n] = normalize(A) + [A.var, n] = normalize(A.var); end function A = conj(A) - [A.var] = conj([A.var]); + A.var = conj(A.var); end function A = twist(A, varargin) - [A.var] = twist([A.var], varargin{:}); + A.var = twist(A.var, varargin{:}); end function A = twistdual(A, varargin) - [A.var] = twistdual([A.var], varargin{:}); + A.var = twistdual(A.var, varargin{:}); end function t = ctranspose(t) @@ -331,10 +342,6 @@ function disp(t) fprintf('%s with %d plegs and %d alegs:\n', class(t), t.plegs, t.alegs); disp(t.var); end - - function n = nnz(t) - n = nnz(t.var); - end end @@ -503,9 +510,7 @@ function disp(t) %% Converters methods function t = Tensor(A) - for i = numel(A):-1:1 - t(i) = full(A(i).var); - end + t = full(A.var); end function t = SparseTensor(A) diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index 4deedad..c0015e5 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -107,6 +107,7 @@ domain SumSpace kwargs.Density = 1 end + sz = [nsubspaces(codomain) flip(nsubspaces(domain))]; inds = sort(randperm(prod(sz), round(prod(sz) * kwargs.Density))); diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 6ffbba2..86ae3f8 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -1561,7 +1561,7 @@ end if isempty(p1), p1 = 1:rank(t, 1); end - if isempty(p2), p2 = rank(t, 1) + (1:rank(t,2)); end + if isempty(p2), p2 = rank(t, 1) + (1:rank(t, 2)); end t = tpermute(t, [p1 p2], [length(p1) length(p2)]); diff --git a/test/TestAlgorithms.m b/test/TestAlgorithms.m index eb2795d..75d38c4 100644 --- a/test/TestAlgorithms.m +++ b/test/TestAlgorithms.m @@ -51,31 +51,50 @@ function test1dIsing_ordered(tc) momenta = [0 pi 0.5]; for L = 1:3 - %% No symmetry - H = repmat(quantum1dIsing('h', h, 'J', J), 1, L); +% %% No symmetry +% H = repmat(quantum1dIsing('h', h, 'J', J), 1, L); +% +% vspace = arrayfun(@(w) CartesianSpace.new(D + w), 1:L, ... +% 'UniformOutput', false); +% gs = initialize_mps(H, vspace{:}); +% +% % Groundstate algorithms +% gs = fixedpoint(Vumps('which', 'smallestreal', 'maxiter', 5, ... +% 'alg_eigs', Arnoldi('maxiter', 100, 'krylovdim', 20)), ... +% H, gs); +% tc.assertEqual(expectation_value(gs, H), e0 * L, 'RelTol', 1e-2); +% +% % Excitation algorithms +% for k = momenta +% qp = InfQP.randnc(gs, gs, k); +% [~, mu] = excitations(QPAnsatz(), H, qp); +% tc.assertEqual(mu, d0(k/L), 'RelTol', 1e-3, ... +% sprintf('qp failed at momentum %.2f', k)); +% end +% +% %% Z2 +% H = repmat(quantum1dIsing('h', h, 'J', J, 'Symmetry', 'Z2'), 1, L); +% +% vspace = Z2Space([0, 1], [D D] / 2, false); +% gs = initialize_mps(H, vspace); +% +% % Groundstate algorithms +% gs = fixedpoint(Vumps('which', 'smallestreal', 'maxiter', 5), ... +% H, gs); +% tc.assertEqual(expectation_value(gs, H), e0 * L, 'RelTol', 1e-2); +% +% % Excitation algorithms +% for k = momenta +% qp = InfQP.randnc(gs, gs, k, Z2(1)); +% [~, mu] = excitations(QPAnsatz(), H, qp); +% tc.assertEqual(mu, d0(k / L), 'RelTol', 1e-3, ... +% sprintf('qp failed at momentum %.2f', k)); +% end +% + %% fZ2 + H = repmat(quantum1dIsing('h', h, 'J', J, 'Symmetry', 'fZ2'), 1, L); - vspace = arrayfun(@(w) CartesianSpace.new(D + w), 1:L, ... - 'UniformOutput', false); - gs = initialize_mps(H, vspace{:}); - - % Groundstate algorithms - gs = fixedpoint(Vumps('which', 'smallestreal', 'maxiter', 5, ... - 'alg_eigs', Arnoldi('maxiter', 100, 'krylovdim', 20)), ... - H, gs); - tc.assertEqual(expectation_value(gs, H), e0 * L, 'RelTol', 1e-2); - - % Excitation algorithms - for k = momenta - qp = InfQP.randnc(gs, gs, k); - [~, mu] = excitations(QPAnsatz(), H, qp); - tc.assertEqual(mu, d0(k/L), 'RelTol', 1e-3, ... - sprintf('qp failed at momentum %.2f', k)); - end - - %% Symmetry - H = repmat(quantum1dIsing('h', h, 'J', J, 'Symmetry', 'Z2'), 1, L); - - vspace = Z2Space([0, 1], [D D] / 2, false); + vspace = fZ2Space([0, 1], [D D] / 2, false); gs = initialize_mps(H, vspace); % Groundstate algorithms @@ -100,7 +119,7 @@ function test1dSSH(tc) gs = initialize_mps(H_ssh, vspace); % Groundstate algorithms - gs = fixedpoint(Vumps('which', 'smallestreal', 'maxiter', 5), ... + gs = fixedpoint(Vumps('which', 'smallestreal', 'maxiter', 50, 'verbosity', Verbosity.detail), ... H_ssh, gs); tc.assertEqual(expectation_value(gs, H_ssh), E0, 'RelTol', 1e-2); diff --git a/test/TestInfJMpo.m b/test/TestInfJMpo.m index a6f193c..fbe7d79 100644 --- a/test/TestInfJMpo.m +++ b/test/TestInfJMpo.m @@ -3,18 +3,20 @@ properties (TestParameter) mpo = struct(... - 'trivial', quantum1dIsing() ... + 'trivial', quantum1dIsing(), ... + 'fermion', quantum1d_randomfermion() ... ) mps = struct(... - 'trivial', UniformMps.randnc(CartesianSpace.new(2), CartesianSpace.new(4)) ... + 'trivial', UniformMps.randnc(CartesianSpace.new(2), CartesianSpace.new(4)), ... + 'fermion', UniformMps.randnc(fZ2Space([0 1], [1 1], false), fZ2Space([0 1], [2 2], false)) ... ) end methods (Test, ParameterCombination='sequential') - function testEnvironments(tc) - D = 16; - mpo = quantum1dIsing('Symmetry', 'Z2'); - mps = initialize_mps(mpo, GradedSpace.new(Z2(0, 1), D ./ [2 2], false)); + function testEnvironments(tc, mpo, mps) +% D = 16; +% mpo = quantum1dIsing('Symmetry', 'Z2'); +% mps = initialize_mps(mpo, GradedSpace.new(Z2(0, 1), D ./ [2 2], false)); [GL, GR, lambda] = environments(mpo, mps); @@ -26,8 +28,8 @@ function testEnvironments(tc) TL = transfermatrix(mpo, mps, mps, 'Type', 'LL'); GL_2 = apply(TL, GL{1}); - lambda2 = overlap(GL_2(end), fp_right); - GL_2(end) = GL_2(end) - lambda2 * fp_left; + lambda2 = overlap(slice(GL_2, length(GL_2)), fp_right); + GL_2.var(end) = GL_2.var(end) - lambda2 * fp_left; tc.assertTrue(isapprox(GL_2, GL{1}), ... 'left environment fixed point equation unfulfilled.'); tc.assertEqual(lambda, lambda2, 'RelTol', 1e-10); @@ -40,8 +42,8 @@ function testEnvironments(tc) TR = transfermatrix(mpo, mps, mps, 'Type', 'RR').'; GR_2 = apply(TR, GR{1}); - lambda3 = overlap(GR_2(1), fp_left); - GR_2(1) = GR_2(1) - lambda3 * fp_right; + lambda3 = overlap(slice(GR_2, 1), fp_left); + GR_2.var(1) = GR_2.var(1) - lambda3 * fp_right; tc.assertTrue(isapprox(GR_2, GR{1}), ... 'right environment fixed point equation unfulfilled.'); tc.assertEqual(lambda, lambda3, 'RelTol', 1e-10); @@ -163,14 +165,14 @@ function testDerivatives(tc, mpo, mps) H_AC = AC_hamiltonian(mpo, mps, GL, GR); for i = 1:numel(H_AC) - AC_ = mps.AC(i); - [AC_.var, lambda] = eigsolve(H_AC{i}, mps.AC(i).var, 1, 'largestabs'); + AC_ = mps.AC{i}; + [AC_.var, lambda] = eigsolve(H_AC{i}, mps.AC{i}.var, 1, 'smallestreal'); tc.assertTrue(isapprox(apply(H_AC{i}, AC_), lambda * AC_.var)); end H_C = C_hamiltonian(mpo, mps, GL, GR); for i = 1:numel(H_C) - [C_, lambda] = eigsolve(H_C{i}, mps.C(i), 1, 'largestabs'); + [C_, lambda] = eigsolve(H_C{i}, mps.C{i}, 1, 'smallestreal'); tc.assertTrue(isapprox(apply(H_C{i}, C_), lambda * C_)); end end @@ -240,3 +242,13 @@ function test1dHeisenberg(tc) end end +function H = quantum1d_randomfermion() + +pspace = fZ2Space([0 1], [1 1], false); +D = Tensor.randnc([pspace pspace], [pspace pspace]); +D = normalize(D + D'); + +H = InfJMpo.twosite(D); + +end + diff --git a/test/TestInfMpo.m b/test/TestInfMpo.m index 0fa2c8a..ac60746 100644 --- a/test/TestInfMpo.m +++ b/test/TestInfMpo.m @@ -49,66 +49,66 @@ function testEnvironments(tc) % normalization for i = 1:length(GL) - gl = multiplyright(MpsTensor(GL{i}), mps.C(i)); - gr = multiplyright(MpsTensor(GR{i}), mps.C(i)'); + gl = multiplyright(MpsTensor(GL{i}), mps.C{i}); + gr = multiplyright(MpsTensor(GR{i}), mps.C{i}'); tc.assertEqual(overlap(gl, gr), 1, 'AbsTol', 1e-10, ... 'environment normalization incorrect.'); end %% testset 2: quasiparticle environments - mpo = mpo / lambda; - [GL, GR, lambda] = environments(mpo, mps, mps, GL, GR); - tc.assertEqual(lambda, 1, 'AbsTol', 1e-10); - - for charge = Z2([0 1]) - for p = [0 pi 0.5] - qp = InfQP.randnc(mps, mps, p, charge); - - % left environments - GBL = leftquasienvironment(mpo, qp, GL, GR); - T_R = transfermatrix(mpo, qp, qp, 'Type', 'RL'); - T_B = transfermatrix(mpo, qp, qp, 1, 'Type', 'BL'); - if istrivial(qp) - C = qp.mpsleft.C(1); - FL_L = insert_onespace(multiplyright(MpsTensor(GL{1}), C), ... - nspaces(GL{1}) + 1, isdual(auxspace(qp, 1))); - FR_L = insert_onespace(multiplyright(MpsTensor(GR{1}), C'), ... - 1, ~isdual(auxspace(qp, 1))); - - tc.verifyTrue(isapprox(... - apply_regularized(T_B, FL_L, FR_L, GL{1}) + ... - apply_regularized(T_R, FL_L, FR_L, GBL{1}), ... - exp(1i*p) * GBL{1}), ... - sprintf('left quasi environment fixed point equation unfulfilled for p=%e, c=%d', p, charge)); - else - tc.verifyTrue(isapprox(apply(T_B, GL{1}) + apply(T_R, GBL{1}), ... - exp(1i*p) * GBL{1}), ... - sprintf('left quasi environment fixed point equation unfulfilled for p=%e, c=%d', p, charge)); - end - - % right environments - GBR = rightquasienvironment(mpo, qp, GL, GR); - T_L = transfermatrix(mpo, qp, qp, 'Type', 'LR').'; - T_B = transfermatrix(mpo, qp, qp, 'Type', 'BR').'; - if istrivial(qp) - C = qp.mpsright.C(1); - FL_R = insert_onespace(multiplyleft(MpsTensor(GL{1}), C'), ... - 1, ~isdual(auxspace(qp, 1))); - FR_R = insert_onespace(multiplyleft(MpsTensor(GR{1}), C), ... - nspaces(GR{1}) + 1, isdual(auxspace(qp, 1))); - - tc.verifyTrue(isapprox(... - apply_regularized(T_B, FR_R, FL_R, GR{1}) + ... - apply_regularized(T_L, FR_R, FL_R, GBR{1}), ... - exp(-1i*p) * GBR{1}), ... - sprintf('right quasi environment fixed point equation unfulfilled for p=%e, c=%d.', p, charge)); - else - tc.verifyTrue(isapprox(apply(T_B, GR{1}) + apply(T_L, GBR{1}), ... - exp(-1i*p) * GBR{1}), ... - sprintf('right quasi environment fixed point equation unfulfilled for p=%e, c=%d.', p, charge)); - end - end - end +% mpo = mpo / lambda; +% [GL, GR, lambda] = environments(mpo, mps, mps, GL, GR); +% tc.assertEqual(lambda, 1, 'AbsTol', 1e-10); +% +% for charge = Z2([0 1]) +% for p = [0 pi 0.5] +% qp = InfQP.randnc(mps, mps, p, charge); +% +% % left environments +% GBL = leftquasienvironment(mpo, qp, GL, GR); +% T_R = transfermatrix(mpo, qp, qp, 'Type', 'RL'); +% T_B = transfermatrix(mpo, qp, qp, 1, 'Type', 'BL'); +% if istrivial(qp) +% C = qp.mpsleft.C1); +% FL_L = insert_onespace(multiplyright(MpsTensor(GL{1}), C), ... +% nspaces(GL{1}) + 1, isdual(auxspace(qp, 1))); +% FR_L = insert_onespace(multiplyright(MpsTensor(GR{1}), C'), ... +% 1, ~isdual(auxspace(qp, 1))); +% +% tc.verifyTrue(isapprox(... +% apply_regularized(T_B, FL_L, FR_L, GL{1}) + ... +% apply_regularized(T_R, FL_L, FR_L, GBL{1}), ... +% exp(1i*p) * GBL{1}), ... +% sprintf('left quasi environment fixed point equation unfulfilled for p=%e, c=%d', p, charge)); +% else +% tc.verifyTrue(isapprox(apply(T_B, GL{1}) + apply(T_R, GBL{1}), ... +% exp(1i*p) * GBL{1}), ... +% sprintf('left quasi environment fixed point equation unfulfilled for p=%e, c=%d', p, charge)); +% end +% +% % right environments +% GBR = rightquasienvironment(mpo, qp, GL, GR); +% T_L = transfermatrix(mpo, qp, qp, 'Type', 'LR').'; +% T_B = transfermatrix(mpo, qp, qp, 'Type', 'BR').'; +% if istrivial(qp) +% C = qp.mpsright.C(1); +% FL_R = insert_onespace(multiplyleft(MpsTensor(GL{1}), C'), ... +% 1, ~isdual(auxspace(qp, 1))); +% FR_R = insert_onespace(multiplyleft(MpsTensor(GR{1}), C), ... +% nspaces(GR{1}) + 1, isdual(auxspace(qp, 1))); +% +% tc.verifyTrue(isapprox(... +% apply_regularized(T_B, FR_R, FL_R, GR{1}) + ... +% apply_regularized(T_L, FR_R, FL_R, GBR{1}), ... +% exp(-1i*p) * GBR{1}), ... +% sprintf('right quasi environment fixed point equation unfulfilled for p=%e, c=%d.', p, charge)); +% else +% tc.verifyTrue(isapprox(apply(T_B, GR{1}) + apply(T_L, GBR{1}), ... +% exp(-1i*p) * GBR{1}), ... +% sprintf('right quasi environment fixed point equation unfulfilled for p=%e, c=%d.', p, charge)); +% end +% end +% end end function testDerivatives(tc, mpo, mps) From 1c55ef75b83cf8c56c3ddcced3e20e1ea2bb5ba9 Mon Sep 17 00:00:00 2001 From: leburgel Date: Tue, 23 May 2023 18:16:16 +0200 Subject: [PATCH 219/245] Bugfix in `InfMpo.AC2_hamiltonian` environments --- src/mps/InfMpo.m | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index 3c180e2..6413ec6 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -242,10 +242,16 @@ H = cell(1, length(sites)); for i = 1:length(sites) for d = depth(mpo):-1:1 - gl = GL{d, sites(i)}; - gl = twistdual(gl, 1); - gr = GR{d, mod1(sites(i) + 2, period(mps))}; - gr = twistdual(gr, nspaces(gr)); + gl = twistdual(GL{d, sites(i)}, 1); + p = 1:nspaces(gl); + p(2:(nspaces(gl)-1-gl.alegs)) = flip(p(2:(nspaces(gl)-1-gl.alegs))); + gl = tpermute(gl, p, rank(gl)); + + gr = twistdual(GR{d, mod1(sites(i) + 2, period(mps))}, nspaces(gr)); + p = 1:nspaces(gr); + p(2:(nspaces(gr)-1-gr.alegs)) = flip(p(2:(nspaces(gr)-1-gr.alegs))); + gr = tpermute(gr, p, rank(gr)); + H{i}(d, 1) = FiniteMpo(gl, mpo.O(d, mod1(sites(i) + [0 1], period(mps))), gr); end end From 3aa011f9d314bff7dfdd489788fa5fa73ac2bbb4 Mon Sep 17 00:00:00 2001 From: leburgel Date: Tue, 23 May 2023 20:04:19 +0200 Subject: [PATCH 220/245] Temporary patch --- src/mps/InfMpo.m | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index 6413ec6..153d87b 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -223,8 +223,8 @@ gl = twistdual(GL{d, sites(i)}, 1); gr = GR{d, next(sites(i), period(mps))}; gr = twistdual(gr, nspaces(gr)); - gr_better = tpermute(gr, [1, flip(2:nspaces(gr)-1), nspaces(gr)], rank(gr)); - gl_better = tpermute(gl, [1, flip(2:nspaces(gl)-1), nspaces(gl)], rank(gl)); + gr_better = tpermute(gr, [1, flip(2:nspaces(gr)-1), nspaces(gr)], rank(gr)); % TODO: account for potential auxiliary legs + gl_better = tpermute(gl, [1, flip(2:nspaces(gl)-1), nspaces(gl)], rank(gl)); % TODO: account for potential auxiliary legs H{i}(d, 1) = FiniteMpo(gl_better, mpo.O(d, sites(i)), gr_better); end end @@ -243,15 +243,9 @@ for i = 1:length(sites) for d = depth(mpo):-1:1 gl = twistdual(GL{d, sites(i)}, 1); - p = 1:nspaces(gl); - p(2:(nspaces(gl)-1-gl.alegs)) = flip(p(2:(nspaces(gl)-1-gl.alegs))); - gl = tpermute(gl, p, rank(gl)); - + gl = tpermute(gl, [1, flip(2:nspaces(gl)-1), nspaces(gl)], rank(gl)); % TODO: account for potential auxiliary legs gr = twistdual(GR{d, mod1(sites(i) + 2, period(mps))}, nspaces(gr)); - p = 1:nspaces(gr); - p(2:(nspaces(gr)-1-gr.alegs)) = flip(p(2:(nspaces(gr)-1-gr.alegs))); - gr = tpermute(gr, p, rank(gr)); - + gr = tpermute(gr, [1, flip(2:nspaces(gr)-1), nspaces(gr)], rank(gr)); % TODO: account for potential auxiliary legs H{i}(d, 1) = FiniteMpo(gl, mpo.O(d, mod1(sites(i) + [0 1], period(mps))), gr); end end From 16c878c8bbae23793caa1e8ca3c30ca584bd1c89 Mon Sep 17 00:00:00 2001 From: leburgel Date: Wed, 24 May 2023 10:41:05 +0200 Subject: [PATCH 221/245] Typo --- src/mps/InfMpo.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index 153d87b..defe219 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -244,7 +244,8 @@ for d = depth(mpo):-1:1 gl = twistdual(GL{d, sites(i)}, 1); gl = tpermute(gl, [1, flip(2:nspaces(gl)-1), nspaces(gl)], rank(gl)); % TODO: account for potential auxiliary legs - gr = twistdual(GR{d, mod1(sites(i) + 2, period(mps))}, nspaces(gr)); + gr = GR{d, mod1(sites(i) + 2, period(mps))}; + gr = twistdual(gr, nspaces(gr)); gr = tpermute(gr, [1, flip(2:nspaces(gr)-1), nspaces(gr)], rank(gr)); % TODO: account for potential auxiliary legs H{i}(d, 1) = FiniteMpo(gl, mpo.O(d, mod1(sites(i) + [0 1], period(mps))), gr); end From 153539268cfc79a8df52aae1901aa6715b1b8fa2 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 25 May 2023 09:59:48 +0200 Subject: [PATCH 222/245] Working! --- .gitignore | 3 +- src/algorithms/QPAnsatz.m | 24 +-- src/algorithms/Vumps.m | 1 + .../{fannihilation.m => c_min.m} | 4 +- .../{fnumber.m => c_number.m} | 2 +- .../{fcreation.m => c_plus.m} | 4 +- src/models/quantum1dHeisenberg.m | 2 +- src/models/quantum1dIsing.m | 34 ++--- src/models/quantum1dIsing_dispersion.m | 4 +- src/models/spinoperators/sigma_exchange.m | 6 +- src/mps/FiniteMpo.m | 4 +- src/mps/InfJMpo.m | 70 ++++++--- src/mps/InfMpo.m | 2 +- src/mps/InfQP.m | 138 ++++++++++++++++++ src/mps/MpoTensor.m | 24 +-- src/tensors/AbstractTensor.m | 10 +- src/tensors/Tensor.m | 1 - test/TestAlgorithms.m | 64 +++++--- test/TestInfJMpo.m | 10 +- 19 files changed, 282 insertions(+), 125 deletions(-) rename src/models/fermionoperators/{fannihilation.m => c_min.m} (90%) rename src/models/fermionoperators/{fnumber.m => c_number.m} (80%) rename src/models/fermionoperators/{fcreation.m => c_plus.m} (90%) diff --git a/.gitignore b/.gitignore index b298dfc..01d7e59 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ _build .ipynb_checkpoints docs/src/examples/**/*.m resources/ -*.prj \ No newline at end of file +*.prj +scripts/ \ No newline at end of file diff --git a/src/algorithms/QPAnsatz.m b/src/algorithms/QPAnsatz.m index bbfe9f4..a2764ee 100644 --- a/src/algorithms/QPAnsatz.m +++ b/src/algorithms/QPAnsatz.m @@ -2,8 +2,8 @@ % Quasi-Particle excitation ansatz properties - alg_eigs = struct('MaxIter', 100, 'KrylovDim', 30, 'Tol', 1e-8) - alg_environments = struct('Tol', 1e-10); + alg_eigs = Arnoldi('MaxIter', 100, 'KrylovDim', 30, 'Tol', 1e-8, 'verbosity', Verbosity.diagnostics) + alg_environments = struct('Tol', 1e-10, 'Algorithm', 'bicgstabl'); howmany = 1 which = 'smallestreal' end @@ -17,7 +17,13 @@ fields = fieldnames(kwargs); if ~isempty(fields) for field = fields.' - alg.(field{1}) = kwargs.(field{1}); + if isstruct(kwargs.(field{1})) + for field2 = fieldnames(kwargs.(field{1})).' + alg.(field{1}).(field2{1}) = kwargs.(field{1}).(field2{1}); + end + else + alg.(field{1}) = kwargs.(field{1}); + end end end end @@ -38,10 +44,8 @@ end % Algorithm - eigkwargs = namedargs2cell(alg.alg_eigs); - H_effective = @(x) updateX(alg, mpo, qp, GL, GR, x, offset); - [X, mu] = eigsolve(H_effective, [qp.X{:}], alg.howmany, alg.which, ... - eigkwargs{:}); + H_effective = @(qp) updateX(alg, mpo, qp, GL, GR, offset); + [qp, mu] = eigsolve(alg.alg_eigs, H_effective, qp, alg.howmany, alg.which); for i = alg.howmany:-1:2 qp(i).X = X(i); @@ -49,8 +53,7 @@ end end - function y = updateX(alg, mpo, qp, GL, GR, x, offset) - qp.X = num2cell(x); + function qp = updateX(alg, mpo, qp, GL, GR, offset) qp.B = computeB(qp); B = qp.B; @@ -72,8 +75,7 @@ for i = 1:period(qp) qp.B{i} = B{i} - qp.B{i} * offset(i); end - y = computeX(qp); - y = [y{:}]; + qp.X = computeX(qp); end end end diff --git a/src/algorithms/Vumps.m b/src/algorithms/Vumps.m index 571ac9c..4be85f1 100644 --- a/src/algorithms/Vumps.m +++ b/src/algorithms/Vumps.m @@ -68,6 +68,7 @@ if isfield('verbosity', kwargs) alg.alg_eigs.verbosity = alg.verbosity - 2; + alg.alg_environments.verbosity = alg.verbosity - 2; end end diff --git a/src/models/fermionoperators/fannihilation.m b/src/models/fermionoperators/c_min.m similarity index 90% rename from src/models/fermionoperators/fannihilation.m rename to src/models/fermionoperators/c_min.m index 41d0197..2e04c81 100644 --- a/src/models/fermionoperators/fannihilation.m +++ b/src/models/fermionoperators/c_min.m @@ -1,9 +1,8 @@ -function c = fannihilation(kwargs) +function c = c_min(kwargs) arguments kwargs.side = 'left' end - pspace = fZ2Space([0 1], [1 1], false); vspace = fZ2Space(1, 1, false); @@ -12,7 +11,6 @@ c = Tensor.zeros([vspace pspace], pspace); c = fill_matrix(c, {0 1}); - case 'left' c = Tensor.zeros(pspace, [pspace vspace]); c = fill_matrix(c, {1 0}); diff --git a/src/models/fermionoperators/fnumber.m b/src/models/fermionoperators/c_number.m similarity index 80% rename from src/models/fermionoperators/fnumber.m rename to src/models/fermionoperators/c_number.m index ed84761..b6e1a28 100644 --- a/src/models/fermionoperators/fnumber.m +++ b/src/models/fermionoperators/c_number.m @@ -1,4 +1,4 @@ -function n = fnumber() +function n = c_number() pspace = fZ2Space([0 1], [1 1], false); diff --git a/src/models/fermionoperators/fcreation.m b/src/models/fermionoperators/c_plus.m similarity index 90% rename from src/models/fermionoperators/fcreation.m rename to src/models/fermionoperators/c_plus.m index ef7799f..795df05 100644 --- a/src/models/fermionoperators/fcreation.m +++ b/src/models/fermionoperators/c_plus.m @@ -1,9 +1,8 @@ -function c_dagger = fcreation(kwargs) +function c_dagger = c_plus(kwargs) arguments kwargs.side = 'left' end - pspace = fZ2Space([0 1], [1 1], false); vspace = fZ2Space(1, 1, false); @@ -12,7 +11,6 @@ c_dagger = Tensor.zeros([vspace pspace], pspace); c_dagger = fill_matrix(c_dagger, {1 0}); - case 'left' c_dagger = Tensor.zeros(pspace, [pspace vspace]); c_dagger = fill_matrix(c_dagger, {0 1}); diff --git a/src/models/quantum1dHeisenberg.m b/src/models/quantum1dHeisenberg.m index e869bb1..b8a6dd8 100644 --- a/src/models/quantum1dHeisenberg.m +++ b/src/models/quantum1dHeisenberg.m @@ -19,7 +19,7 @@ J = J(1); assert(h == 0, 'Magnetic field not invariant under SU2'); - H2 = sigma_exchange(kwargs.Spin, 'SU2'); + H2 = J * sigma_exchange(kwargs.Spin, 'SU2'); H1 = []; case 'U1' diff --git a/src/models/quantum1dIsing.m b/src/models/quantum1dIsing.m index 85e7047..3c06b59 100644 --- a/src/models/quantum1dIsing.m +++ b/src/models/quantum1dIsing.m @@ -1,7 +1,7 @@ function mpo = quantum1dIsing(kwargs) arguments kwargs.J = 1 - kwargs.h = 0.5 + kwargs.h = 1 kwargs.L = Inf % size of system kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'Z2', 'fZ2'})} = 'Z1' end @@ -9,8 +9,8 @@ J = kwargs.J; h = kwargs.h; -sigma_x = [0 1; 1 0] / 2; -sigma_z = [1 0; 0 -1] / 2; +sigma_x = [0 1; 1 0]; +sigma_z = [1 0; 0 -1]; switch kwargs.Symmetry case 'Z1' @@ -36,9 +36,9 @@ vSpace = GradedSpace.new(Z2(1), 1, false); trivSpace = one(pSpace); - Sx_l = fill_matrix(Tensor([trivSpace pSpace], [pSpace vSpace]), {1 1}) / 2; - Sx_r = fill_matrix(Tensor([vSpace pSpace], [pSpace trivSpace]), {1 1}) / 2; - Sz = fill_matrix(Tensor([trivSpace pSpace], [pSpace trivSpace]), {1 -1}) / 2; + Sx_l = fill_matrix(Tensor([trivSpace pSpace], [pSpace vSpace]), {1 1}); + Sx_r = fill_matrix(Tensor([vSpace pSpace], [pSpace trivSpace]), {1 1}); + Sz = fill_matrix(Tensor([trivSpace pSpace], [pSpace trivSpace]), {1 -1}); cod = SumSpace([one(vSpace) vSpace one(vSpace)], pSpace); dom = SumSpace(pSpace, [one(vSpace), vSpace, one(vSpace)]); @@ -50,21 +50,21 @@ O(1, 1, 3, 1) = (-J * h) * Sz; case 'fZ2' - c_left = fannihilation('side', 'left'); - c_right = fannihilation('side', 'right'); - cdag_left = fcreation('side', 'left'); - cdag_right = fcreation('side', 'right'); + c_left = c_min('side', 'left'); + c_right = c_min('side', 'right'); + cdag_left = c_plus('side', 'left'); + cdag_right = c_plus('side', 'right'); % twosite terms - cdagc = contract(cdag_left, [-1 1 -3], c_right, [1 -2 -4], 'Rank', [2 2]); - ccdag = contract(c_left, [-1 1 -3], cdag_right, [1 -2 -4], 'Rank', [2 2]); - cc = contract(c_left, [-1 1 -3], c_right, [1 -2 -4], 'Rank', [2 2]); - cdagcdag = contract(cdag_left, [-1 1 -3], cdag_right, [1 -2 -4], 'Rank', [2 2]); - H_twosite = -J * (cdagc - ccdag + cdagcdag - cc); + cdagc = contract(cdag_left, [-1 1 -4], c_right, [1 -2 -3], 'Rank', [2 2]); + ccdag = contract(c_left, [-1 1 -4], cdag_right, [1 -2 -3], 'Rank', [2 2]); + cc = contract(c_left, [-1 1 -4], c_right, [1 -2 -3], 'Rank', [2 2]); + cdagcdag = contract(cdag_left, [-1 1 -4], cdag_right, [1 -2 -3], 'Rank', [2 2]); + H_twosite = -J * (cdagc + ccdag + cdagcdag + cc); % onesite terms - n = fnumber(); - H_onesite = J * 2 * h * (n - 1 / 2 * n.eye(n.codomain, n.domain)); + n = c_number(); + H_onesite = -J * 2 * h * (n - 1 / 2 * n.eye(n.codomain, n.domain)); mpo = InfJMpo.twosite(H_twosite, H_onesite); return diff --git a/src/models/quantum1dIsing_dispersion.m b/src/models/quantum1dIsing_dispersion.m index 6cce657..3beb03e 100644 --- a/src/models/quantum1dIsing_dispersion.m +++ b/src/models/quantum1dIsing_dispersion.m @@ -5,9 +5,9 @@ kwargs.J = 1.0 kwargs.h = 0.5 end -g = 2 * kwargs.h; +g = kwargs.h; -e = kwargs.J * sqrt(g^2 + 1 - 2 * g * cos(k)) / 2; +e = kwargs.J * sqrt(g^2 + 1 - 2 * g * cos(k)) * 2; end diff --git a/src/models/spinoperators/sigma_exchange.m b/src/models/spinoperators/sigma_exchange.m index 24400b7..80150e6 100644 --- a/src/models/spinoperators/sigma_exchange.m +++ b/src/models/spinoperators/sigma_exchange.m @@ -11,10 +11,10 @@ pspace = GradedSpace.new(SU2(2 * spin + 1), 1, false); aspace = GradedSpace.new(SU2(3), 1, false); - Sleft = Tensor.ones([pspace aspace], pspace); - Sright = Tensor.ones(pspace, [aspace pspace]); + Sleft = Tensor.ones(pspace, [pspace aspace]); + Sright = -Tensor.ones([aspace pspace], pspace); - S = (spin^2 + spin) * contract(Sleft, [-1 1 -3], Sright, [-2 1 -4], 'Rank', [2 2]); + S = (spin^2 + spin) * contract(Sleft, [-1 1 -4], Sright, [1 -2 -3], 'Rank', [2 2]); otherwise error('models:TBA', 'not implemented'); diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index bf6005b..b7fb473 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -278,8 +278,8 @@ for i = numel(Atop):-1:1 atop = Atop{i}; o = rot90(O{i}); - twistinds = 1 + find(isdual(space(atop, 2:nspaces(atop) - 1))); - abot = twist(Abot{i}', twistinds); + twistinds = find(isdual(space(atop, 2:(nspaces(atop) - 1 - atop.alegs)))); + abot = twist(Abot{i}', flip(twistinds) + 1 + Abot{i}.alegs); assert(isequal(pspace(abot)', leftvspace(o))); assert(isequal(rightvspace(o)', pspace(atop))); diff --git a/src/mps/InfJMpo.m b/src/mps/InfJMpo.m index cbf18fb..0e0537a 100644 --- a/src/mps/InfJMpo.m +++ b/src/mps/InfJMpo.m @@ -90,6 +90,10 @@ end linkwargs = namedargs2cell(linopts); + L = period(mpo); + + fp_left = fixedpoint(mpo, mps1, 'l_RR', 1); + fp_right = repartition(fixedpoint(mpo, mps1, 'r_RR', L), [3 0]); T = transfermatrix(mpo, mps1, mps2, 'Type', 'RR').'; N = size(T(1).O{1}, 2); @@ -97,10 +101,7 @@ if isempty(GR) || isempty(GR{1}) GR = cell(1, period(mps1)); GR{1} = MpsTensor(SparseTensor.zeros(domain(T), [])); - pSpace = space(T(1).O{1}(:, end, :, :), 2); - fp1 = insert_onespace(fixedpoint(mps1, 'r_RR'), ... - 2, isdual(pSpace(end))); - GR{1}.var(1, N, 1) = repartition(fp1, [nspaces(fp1) 0]); + GR{1}.var(1, N, 1) = fp_right; end for i = N-1:-1:1 @@ -108,22 +109,25 @@ Tdiag = slice(T, i, i); if iszero(Tdiag) GR{1}.var(i) = rhs; - elseif iseye(T, i) - fp_left = insert_onespace(fixedpoint(mps1, 'l_RR'), ... - 2, ~isdual(space(rhs, 2))); - fp_right = repartition(insert_onespace(fixedpoint(mps1, 'r_RR'), ... - 2, isdual(space(rhs, 2))), rank(rhs)); - lambda = contract(rhs, 1:3, fp_left, 3:-1:1); - - rhs = rhs - lambda * fp_right; - [GR{1}.var(i), ~] = ... + else + if iseye(T, i) + lambda = overlap(rhs, fp_left); + rhs = rhs - lambda * fp_right; + end + [GR{1}.var(i), flag, relres, iter] = ... linsolve(@(x) x - apply(Tdiag, x), rhs, GR{1}.var(i), linkwargs{:}); + if iseye(T, i) + GR{1}.var(i) = GR{1}.var(i) - ... + overlap(GR{1}.var(i), fp_left) * fp_right; + end - GR{1}.var(i) = GR{1}.var(i) - ... - overlap(GR{1}.var(i), fp_left) * fp_right; - else - [GR{1}.var(i), ~] = linsolve(@(x) x - apply(Tdiag, x), rhs, GR{1}.var(i), ... - linkwargs{:}); + if flag == 1 && linopts.Verbosity >= Verbosity.warn + fprintf('GR(%d) reached maxiter, error = %3e\n', i, relres); + elseif flag == 2 && linopts.Verbosity >= Verbosity.warn + fprintf('GR(%d) stagnated, error = %3e\n', i, relres); + elseif linopts.Verbosity >= Verbosity.conv + fprintf('GR(%d) converged after %d iterations, error = %3e\n', i, iter, relres); + end end end @@ -146,7 +150,7 @@ linopts.Algorithm = 'bicgstab' linopts.MaxIter = 500 linopts.Verbosity = Verbosity.warn - linopts.Tol = eps(underlyingType(qp))^(3/4) + linopts.Tol = eps(underlyingType(qp))^(3/5) end linkwargs = namedargs2cell(linopts); @@ -177,7 +181,7 @@ Tdiag = slice(T, i, i); if iszero(Tdiag) - GBL{1}(i) = rhs; + GBL{1}.var(i) = rhs; else if needsRegularization && iseye(T, i) lambda = overlap(rhs, fp_right); @@ -189,7 +193,16 @@ H_effective = @(x) x - expP * apply(Tdiag, x); end - [GBL{1}.var(i), ~] = linsolve(H_effective, rhs, [], linkwargs{:}); + [GBL{1}.var(i), flag, relres, iter, resvec] = ... + linsolve(H_effective, rhs, [], linkwargs{:}); + + if flag == 1 && linopts.Verbosity >= Verbosity.warn + fprintf('GBL(%d) reached maxiter, error = %3e\n', i, relres); + elseif flag == 2 && linopts.Verbosity >= Verbosity.warn + fprintf('GBL(%d) stagnated, error = %3e\n', i, relres); + elseif linopts.Verbosity >= Verbosity.conv + fprintf('GBL(%d) converged after %.1f iterations, error = %3e\n', i, iter, relres); + end end end @@ -213,7 +226,7 @@ linopts.Algorithm = 'bicgstab' linopts.MaxIter = 500 linopts.Verbosity = Verbosity.warn - linopts.Tol = eps(underlyingType(qp))^(3/4) + linopts.Tol = eps(underlyingType(qp))^(3/5) end linkwargs = namedargs2cell(linopts); @@ -223,7 +236,7 @@ needsRegularization = istrivial(qp); if needsRegularization || true fp_left = fixedpoint(mpo, qp, 'l_LR_1', 1); - fp_right = fixedpoint(mpo, qp, 'r_LR_0', L); + fp_right = repartition(fixedpoint(mpo, qp, 'r_LR_0', L), [3, 1]); end T = transfermatrix(mpo, qp, qp, 'Type', 'LR').'; @@ -261,7 +274,16 @@ H_effective = @(x) x - expP * apply(Tdiag, x); end - [GBR{1}.var(i), ~] = linsolve(H_effective, rhs, [], linkwargs{:}); + [GBR{1}.var(i), flag, relres, iter, resvec] = ... + linsolve(H_effective, rhs, [], linkwargs{:}); + + if flag == 1 && linopts.Verbosity >= Verbosity.warn + fprintf('GBR(%d) reached maxiter, error = %3e\n', i, relres); + elseif flag == 2 && linopts.Verbosity >= Verbosity.warn + fprintf('GBR(%d) stagnated, error = %3e\n', i, relres); + elseif linopts.Verbosity >= Verbosity.conv + fprintf('GBR(%d) converged after %.1f iterations, error = %3e\n', i, iter, relres); + end end end diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index 07744d0..c131303 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -425,7 +425,7 @@ gl = tpermute(gl, p, rank(gl)); gr = GR{d, next(sites(i), period(mpo))}; - gr = twistdual(gr, nspaces(gr)); + gr = twistdual(gr, nspaces(gr)-gr.alegs); p = 1:nspaces(gr); p(2:(nspaces(gr)-1-gr.alegs)) = flip(p(2:(nspaces(gr)-1-gr.alegs))); gr = tpermute(gr, p, rank(gr)); diff --git a/src/mps/InfQP.m b/src/mps/InfQP.m index da3d12c..b47f6b1 100644 --- a/src/mps/InfQP.m +++ b/src/mps/InfQP.m @@ -70,6 +70,144 @@ end end + methods (Hidden) + function qp = zerosLike(qp, varargin) + qp = repmat(0 .* qp, varargin{:}); + end + end + + + %% Linear Algebra + methods + function n = norm(qp, p) + arguments + qp + p = 'fro' + end + assert(isscalar(qp)); + n = norm([qp.X{:}], p); +% n = sum(cellfun(@(x) norm(x, p), qp.X)); + end + + function qp = mrdivide(qp, lambda) + assert(isscalar(qp)); + qp.X = cellfun(@(x) x / lambda, qp.X, 'UniformOutput', false); + end + + function A = rdivide(A, B) + if isscalar(A) && ~isscalar(B) + A = repmat(A, size(B)); + end + if isscalar(B) && ~isscalar(A) + B = repmat(B, size(B)); + end + for i = 1:numel(A) + for j = 1:numel(A(i).X) + A(i).X{j} = A(i).X{j} ./ B(i); + end + end + end + + function qp_out = mtimes(A, B) + for i = flip(1:size(A, 1)) + for j = flip(1:size(B, 2)) + qp_out(i, j) = sum(A(i, :).' .* B(:, j), 'all'); + end + end + end + + function qp_out = times(A, B) + if isscalar(A) && ~isscalar(B) + A = repmat(A, size(B)); + end + if isscalar(B) && ~isscalar(A) + B = repmat(B, size(B)); + end + + if isnumeric(A) + qp_out = B; + for i = 1:numel(A) + qp_out(i).X = cellfun(@(x) A(i) * x, qp_out(i).X, 'UniformOutput', false); + end + else + qp_out = A; + for i = 1:numel(A) + qp_out(i).X = cellfun(@(x) x * B(i), qp_out(i).X, 'UniformOutput', false); + end + end + end + + function d = dot(qp1, qp2) + assert(isscalar(qp1) && isscalar(qp2)); + d = sum(cellfun(@dot, qp1.X, qp2.X)); + end + + function C = sum(A, dim) + arguments + A + dim = [] + end + + if isscalar(A), C = A; return; end + + if isempty(dim), dim = find(size(A) ~= 1, 1); end + + if strcmp(dim, 'all') + C = A(1); + for i = 2:numel(A) + C = C + A(i); + end + return + end + + if ismatrix(A) + if dim == 1 + C = A(1, :); + for i = 2:size(A, 1) + C = C + A(i, :); + end + return + end + + if dim == 2 + C = A(:, 1); + for i = 2:size(A, 2) + C = C + A(:, i); + end + return + end + end + + error('TBA'); + end + + function A = plus(A, B) + if isscalar(A) && ~isscalar(B) + A = repmat(A, size(B)); + end + if isscalar(B) && ~isscalar(A) + B = repmat(B, size(B)); + end + + for i = 1:numel(A) + A(i).X = cellfun(@plus, A(i).X, B(i).X, 'UniformOutput', false); + end + end + + function A = minus(A, B) + if isscalar(A) && ~isscalar(B) + A = repmat(A, size(B)); + end + if isscalar(B) && ~isscalar(A) + B = repmat(B, size(B)); + end + + for i = 1:numel(A) + A(i).X = cellfun(@minus, A(i).X, B(i).X, 'UniformOutput', false); + end + end + end + %% Derived Properties methods diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index e128769..48072f8 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -455,29 +455,7 @@ function disp(O) kwargs.Trunc = {'TruncBelow', 1e-14} end - assert(mod(nspaces(H), 2) == 0, ... - 'MpoTensor:Argerror', 'local operator must have an even amount of legs.'); - H = repartition(H, nspaces(H) ./ [2 2]); - assert(isequal(H.domain, H.codomain), ... - 'MpoTensor:ArgError', 'local operator must be square.'); - - N = indin(H); - local_operators = cell(1, N); - if N == 1 - local_operators{1} = insert_onespace(insert_onespace(H, 1), 3, true); - else - [u, s, v] = tsvd(H, [1 N+1], [2:N N+2:2*N], kwargs.Trunc{:}); - local_operators{1} = insert_onespace(tpermute(u * s, [1 3 2], [1 2]), 1); - - for i = 2:N-1 - [u, s, v] = tsvd(v, [1 2 N-i+3], [3:(N-i+2) (N-i+4):nspaces(v)], ... - kwargs.Trunc{:}); - local_operators{i} = tpermute(u * s, [1 2 4 3], [2 2]); - end - - local_operators{N} = insert_onespace(repartition(v, [2 1]), 3, true); - end - + local_operators = decompose_local_operator(H, 'Trunc', kwargs.Trunc); local_operators = cellfun(@MpoTensor, local_operators, 'UniformOutput', false); end end diff --git a/src/tensors/AbstractTensor.m b/src/tensors/AbstractTensor.m index 27327fb..1ed4fe6 100644 --- a/src/tensors/AbstractTensor.m +++ b/src/tensors/AbstractTensor.m @@ -478,7 +478,7 @@ function disp(t, details) % :meth:`Tensor.tsvd` arguments H - kwargs.Trunc = {'TruncBelow', 1e-14} + kwargs.Trunc = {'TruncBelow', 1e-12} end assert(mod(nspaces(H), 2) == 0, ... @@ -490,18 +490,18 @@ function disp(t, details) N = indin(H); local_operators = cell(1, N); if N == 1 - local_operators{1} = insert_onespace(insert_onespace(H, 1), 3); + local_operators{1} = insert_onespace(insert_onespace(H, 1), 3, true); else - [u, s, v] = tsvd(H, [1 N+1], [2:N N+2:2*N], kwargs.Trunc{:}); + [u, s, v] = tsvd(H, [1 2*N], 2:(2*N-1), kwargs.Trunc{:}); local_operators{1} = insert_onespace(tpermute(u * s, [1 3 2], [1 2]), 1); for i = 2:N-1 - [u, s, v] = tsvd(v, [1 2 N-i+3], [3:(N-i+2) (N-i+4):nspaces(v)], ... + [u, s, v] = tsvd(v, [1 2 nspaces(v)], 3:(nspaces(v) - 1), ... kwargs.Trunc{:}); local_operators{i} = tpermute(u * s, [1 2 4 3], [2 2]); end - local_operators{N} = insert_onespace(repartition(v, [2 1]), 3); + local_operators{N} = insert_onespace(repartition(v, [2 1]), 3, true); end end diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 86ae3f8..e540a48 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -434,7 +434,6 @@ function tdst = zerosLike(t, varargin) tdst = repmat(0 * t, varargin{:}); end - end diff --git a/test/TestAlgorithms.m b/test/TestAlgorithms.m index 75d38c4..54b7381 100644 --- a/test/TestAlgorithms.m +++ b/test/TestAlgorithms.m @@ -44,14 +44,14 @@ function test2dIsing(tc, alg, symm) function test1dIsing_ordered(tc) J = 1; - h = 1; + h = 0.5; e0 = quantum1dIsing_energy(J, h); d0 = @(k) quantum1dIsing_dispersion(k, 'J', J, 'h', h); - D = 12; + D = 20; momenta = [0 pi 0.5]; for L = 1:3 -% %% No symmetry + %% No symmetry % H = repmat(quantum1dIsing('h', h, 'J', J), 1, L); % % vspace = arrayfun(@(w) CartesianSpace.new(D + w), 1:L, ... @@ -72,24 +72,25 @@ function test1dIsing_ordered(tc) % sprintf('qp failed at momentum %.2f', k)); % end % -% %% Z2 -% H = repmat(quantum1dIsing('h', h, 'J', J, 'Symmetry', 'Z2'), 1, L); -% -% vspace = Z2Space([0, 1], [D D] / 2, false); -% gs = initialize_mps(H, vspace); -% -% % Groundstate algorithms -% gs = fixedpoint(Vumps('which', 'smallestreal', 'maxiter', 5), ... -% H, gs); -% tc.assertEqual(expectation_value(gs, H), e0 * L, 'RelTol', 1e-2); -% -% % Excitation algorithms -% for k = momenta -% qp = InfQP.randnc(gs, gs, k, Z2(1)); -% [~, mu] = excitations(QPAnsatz(), H, qp); -% tc.assertEqual(mu, d0(k / L), 'RelTol', 1e-3, ... -% sprintf('qp failed at momentum %.2f', k)); -% end + %% Z2 + H = repmat(quantum1dIsing('h', h, 'J', J, 'Symmetry', 'Z2'), 1, L); + + vspace = Z2Space([0, 1], [D D] / 2, false); + gs = initialize_mps(H, vspace); + + % Groundstate algorithms + gs = fixedpoint(Vumps('which', 'smallestreal', 'maxiter', 5), ... + H, gs); + tc.assertEqual(expectation_value(gs, H), e0 * L, 'RelTol', 1e-2); + + % Excitation algorithms + for k = momenta + qp = InfQP.randnc(gs, gs, k, Z2(1)); + [~, mu] = excitations(QPAnsatz(), H, qp); + tc.assertEqual(mu, d0(k / L), 'RelTol', 1e-3, ... + sprintf('qp failed at momentum %.2f', k)); + fprintf('ok for %.2f\n', k); + end % %% fZ2 H = repmat(quantum1dIsing('h', h, 'J', J, 'Symmetry', 'fZ2'), 1, L); @@ -104,7 +105,7 @@ function test1dIsing_ordered(tc) % Excitation algorithms for k = momenta - qp = InfQP.randnc(gs, gs, k, Z2(1)); + qp = InfQP.randnc(gs, gs, k, fZ2(1)); [~, mu] = excitations(QPAnsatz(), H, qp); tc.assertEqual(mu, d0(k / L), 'RelTol', 1e-3, ... sprintf('qp failed at momentum %.2f', k)); @@ -130,6 +131,25 @@ function test1dSSH(tc) tc.assertEqual(mu, gap, 'RelTol', 1e-3, ... sprintf('qp failed at momentum %.2f', k)); end + + function test1dHeisenberg(tc) + alg = Vumps('which', 'smallestreal', 'maxiter', 100); + + mpo = quantum1dHeisenberg('Spin', 1, 'Symmetry', 'SU2'); + vspace1 = GradedSpace.new(SU2(2:2:6), [5 5 1], false); + mps = initialize_mps(mpo, vspace1); + + [gs_mps] = fixedpoint(alg, mpo, mps); + lambda = expectation_value(gs_mps, mpo); + tc.verifyEqual(lambda, -1.401, 'RelTol', 1e-2); + + p = pi; + charge = SU2(3); + qp = InfQP.randnc(gs_mps, gs_mps, p, charge); + + [qp, mu] = excitations(QPAnsatz(), mpo, qp); + tc.verifyEqual(mu, 0.4104, 'AbsTol', 1e-2); + end end end diff --git a/test/TestInfJMpo.m b/test/TestInfJMpo.m index fbe7d79..456c6ca 100644 --- a/test/TestInfJMpo.m +++ b/test/TestInfJMpo.m @@ -122,15 +122,15 @@ function testQuasiEnvironments2(tc) T_B = transfermatrix(mpo, qp, qp, 'Type', 'BL'); for w = 1:period(mpo) - tc.assertEqual(norm(GBL{w}(1)), 0, 'AbsTol', 1e-10, ... + tc.assertEqual(norm(GBL{w}.var(1)), 0, 'AbsTol', 1e-10, ... 'left gauge quasiparticle violation.'); GBL2 = apply(T_R(w), GBL{w}) + apply(T_B(w), GL{w}); if istrivial(qp) && w == prev(1, period(mpo)) - r = rank(GBL2(end)); + r = rank(GBL2); fp_left = repartition(fixedpoint(mpo, qp, 'l_RL_0', next(w, period(mpo))), r); fp_right = fixedpoint(mpo, qp, 'r_RL_1', w); - GBL2(end) = GBL2(end) - overlap(GBL2(end), fp_right) * fp_left; + GBL2.var(end) = GBL2.var(end) - overlap(GBL2.var(end), fp_right) * fp_left; end tc.verifyTrue(isapprox(GBL2, GBL{next(w, period(mpo))} * exp(+1i*p/period(mpo))), ... @@ -146,10 +146,10 @@ function testQuasiEnvironments2(tc) GBR2 = apply(T_L(w), GBR{w}) + apply(T_B(w), GR{w}); if istrivial(qp) && w == next(1, period(mpo)) - r = rank(GBR2(1)); + r = rank(GBR2); fp_left = fixedpoint(mpo, qp, 'l_LR_1', prev(w, period(mpo))); fp_right = repartition(fixedpoint(mpo, qp, 'r_LR_0', w), r); - GBR2(1) = GBR2(1) - overlap(GBR2(1), fp_left) * fp_right; + GBR2.var(1) = GBR2.var(1) - overlap(GBR2.var(1), fp_left) * fp_right; end tc.verifyTrue(isapprox(GBR2, GBR{prev(w, period(mpo))} * exp(-1i*p/period(mpo))), ... From 6ce754759f3538fa6154968d49f191c51b2905fc Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 25 May 2023 10:51:23 +0200 Subject: [PATCH 223/245] Update Arnoldi display --- src/algorithms/eigsolvers/Arnoldi.m | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/algorithms/eigsolvers/Arnoldi.m b/src/algorithms/eigsolvers/Arnoldi.m index be9d0d6..5a27bf6 100644 --- a/src/algorithms/eigsolvers/Arnoldi.m +++ b/src/algorithms/eigsolvers/Arnoldi.m @@ -7,7 +7,7 @@ krylovdim = 20 % Krylov subspace dimension deflatedim = 3 % number of Krylov vectors to keep when deflating reorth = 20 % reorthogonalize basis if larger than this number - nobuild = 1 % frequency of convergence check when building + nobuild = 3 % frequency of convergence check when building verbosity = Verbosity.warn % display information end @@ -33,6 +33,8 @@ sigma = 'lm' end + t_total = tic; + if isempty(alg.nobuild), alg.nobuild = ceil(alg.krylovdim / 10); end if isempty(alg.deflatedim), alg.deflatedim = max(round(3/5 * alg.krylovdim), howmany); end @@ -51,9 +53,11 @@ flag = 0; while ctr_outer < alg.maxiter + t_outer = tic; ctr_outer = ctr_outer + 1; while ctr_inner < alg.krylovdim % build Krylov subspace + t_inner = tic; ctr_inner = ctr_inner + 1; V(:, ctr_inner) = v; @@ -99,16 +103,19 @@ V = V(:, 1:ctr_inner) * U(:, select); D = diag(lambda(select)); if alg.verbosity >= Verbosity.conv - fprintf('Conv %2d (%2d/%2d): error = %.5e.\n', ctr_outer, ... - ctr_inner, alg.krylovdim, conv); + fprintf('Conv %2d (%2d/%2d):\tlambda = %.5e + %.5ei;\terror = %.5e;\ttime = %s.\n', ... + ctr_outer, ctr_inner, alg.krylovdim, ... + real(lambda(1)), imag(lambda(1)), conv, ... + time2str(toc(t_total))); end return end if alg.verbosity >= Verbosity.detail - fprintf('Iter %2d (%2d/%2d):\tlambda = %.5e + %.5ei;\terror = %.5e\n', ... + fprintf('Iter %2d (%2d/%2d):\tlambda = %.5e + %.5ei;\terror = %.5e;\ttime = %s.\n', ... ctr_outer, ctr_inner, alg.krylovdim, ... - real(lambda(1)), imag(lambda(1)), conv); + real(lambda(1)), imag(lambda(1)), conv, ... + time2str(toc(t_inner))); end end end @@ -153,14 +160,15 @@ V = V(:, 1:alg.deflatedim) * U(:, select); D = diag(lambda(select)); if alg.verbosity >= Verbosity.conv - fprintf('Conv %2d: error = %.5e.\n', ctr_outer, conv); + fprintf('Conv %2d:\tlambda = %.5e + %.5ei;\terror = %.5e;\ttime = %s.\n', ... + ctr_outer, real(lambda(1)), imag(lambda(1)), conv, time2str(toc(t_outer))); end return end if alg.verbosity >= Verbosity.iter - fprintf('Iter %2d:\tlambda = %.5e + %.5ei;\terror = %.5e\n', ... - ctr_outer, real(lambda(1)), imag(lambda(1)), conv); + fprintf('Iter %2d:\tlambda = %.5e + %.5ei;\terror = %.5e;\ttime = %s.\n', ... + ctr_outer, real(lambda(1)), imag(lambda(1)), conv, time2str(toc(t_total))); end % deflate Krylov subspace From ba54aab2b811e6f996d177810421f7396235be93 Mon Sep 17 00:00:00 2001 From: qmortier Date: Tue, 30 May 2023 09:36:58 +0200 Subject: [PATCH 224/245] fixes and extras for Peps * fix MPS transfermatrix when plegs>2 * fix FiniteMpo for PEPS * inverse more general * rot90 & rot270 functions for PepsTensor --- src/mps/FiniteMpo.m | 2 +- src/mps/MpsTensor.m | 2 +- src/mps/PepsTensor.m | 16 ++++++++++++++++ src/tensors/Tensor.m | 10 ++++++++-- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index 8e03606..457e1f2 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -276,7 +276,7 @@ atop = Atop(i); o = rot90(O{i}); twistinds = 1 + find(isdual(space(Atop(i), 2:nspaces(Atop(i)) - 1))); - abot = twist(Abot(i)', twistinds); + abot = twist(Abot(i), twistinds)'; %IMPORTANT CHANGE: ' out of twist assert(isequal(pspace(abot)', leftvspace(o))); assert(isequal(rightvspace(o)', pspace(atop))); diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index 82797d4..b303c0a 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -354,7 +354,7 @@ end for i = length(A):-1:1 - B(i).var = twist(B(i).var, [isdual(space(B(i), 1:2)) ~isdual(space(B(i), 3))]); + B(i).var = twist(B(i).var, [isdual(space(B(i), 1:B.plegs+1)) ~isdual(space(B(i), B.plegs+2))]); T(i, 1) = FiniteMpo(B(i).var', {}, A(i)); end end diff --git a/src/mps/PepsTensor.m b/src/mps/PepsTensor.m index 4ce27f6..3f49ca4 100644 --- a/src/mps/PepsTensor.m +++ b/src/mps/PepsTensor.m @@ -122,6 +122,22 @@ t.var = t.var'; t = permute(t, ndims(t):-1:1); end + + function t = dagger(t) + leftflip = Tensor.eye(westvspace(t), eastvspace(t)); + rightflip = twist(leftflip', 2); + pflip = Tensor.eye(pspace(t), pspace(t)'); + t.var = tpermute(conj(t.var), [1,2,5,4,3]); + t.var = contract(t.var, [1,2,-3,3,-5], pflip, [1,-1], leftflip, [-2,2], rightflip, [3,-4]); + end + + function t = rot90(t) + t = tpermute(t, [1, 3, 4, 5, 2], rank(t)); + end + + function t = rot270(t) + t = tpermute(t, [1, 5, 2, 3, 4], rank(t)); + end function C = tensorprod(varargin) for i = 1:2 diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 998eff3..c948f59 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -1977,14 +1977,20 @@ % t : :class:`Tensor` % output tensor. - assert(isequal(t.codomain, t.domain), 'tensors:ArgumentError', ... + assert(isisometric(t.codomain, t.domain), 'tensors:ArgumentError', ... 'Input should be square.'); mblocks = matrixblocks(t); for i = 1:length(mblocks) mblocks{i} = inv(mblocks{i}); end - t.var = fill_matrix_data(t.var, mblocks); + + if isequal(t.codomain, t.domain) + t.var = fill_matrix_data(t.var, mblocks); + else + t = t'; + t.var = fill_matrix_data(t.var, mblocks); + end end function A = mpower(X, Y) From 65952f86ad61601e0fe244a8fbfe698ce9ffa0f0 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 7 Jun 2023 17:14:42 +0200 Subject: [PATCH 225/245] update twosite expansion --- src/algorithms/Expand.m | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/algorithms/Expand.m b/src/algorithms/Expand.m index 390aa22..82b7f7b 100644 --- a/src/algorithms/Expand.m +++ b/src/algorithms/Expand.m @@ -143,13 +143,11 @@ % perform twosite update, take SVD and normalize [GL, GR] = environments(Vumps, mpo, mps); % should be able to input this... H_AC2 = AC2_hamiltonian(mpo, mps, GL, GR, w); - AC2 = MpsTensor(contract(mps(dd).AC(w), [-(1:mps(dd).AC(w).plegs+1), 1], ... - mps(dd).AR(ww), [1, -(1:mps(dd).AR(ww).plegs+1) - 1 - mps(dd).AC(ww).plegs], ... - 'Rank', [1 + mps(dd).AC(w).plegs, 1 + mps(dd).AR(ww).plegs])); + AC2 = computeAC2(mps, 1, w); [AC2.var, ~] = eigsolve(H_AC2{1}, AC2.var, 1, alg.which); [~, C2, ~] = tsvd(AC2.var, ... - 1:mps(dd).AC(w).plegs+1, ... - (1:mps(dd).AR(ww).plegs+1) + 1 + mps(dd).AC(w).plegs, ... + 1:mps(dd).AC{w}.plegs+1, ... + (1:mps(dd).AR{ww}.plegs+1) + 1 + mps(dd).AC{w}.plegs, ... 'TruncBelow', alg.schmidtcut, ... 'TruncDim', alg.maxbond); [svals2, charges2] = matrixblocks(C2); From c60c3f67f2e915bbab0f6a8627269c046de431ee Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 7 Jun 2023 17:18:05 +0200 Subject: [PATCH 226/245] add quantum1dHubbard --- src/models/quantum1dHubbard.m | 49 ++++++++++++++++++++++++++++ src/models/quantum1dHubbard_energy.m | 7 ++++ 2 files changed, 56 insertions(+) create mode 100644 src/models/quantum1dHubbard.m create mode 100644 src/models/quantum1dHubbard_energy.m diff --git a/src/models/quantum1dHubbard.m b/src/models/quantum1dHubbard.m new file mode 100644 index 0000000..b538d95 --- /dev/null +++ b/src/models/quantum1dHubbard.m @@ -0,0 +1,49 @@ +function mpo = quantum1dHubbard(u, mu, kwargs) +% Hamiltonian for the 1D Hubbard model. +% +% `math`:-\sum (c^+_i c_j + c^+_j c_i) + u\sum (1 - 2n_{up}) * (1-2n_{down}) - mu\sum (n_{up} + n_{down}): +% +% Arguments +% --------- + +arguments + u + mu = 0 + kwargs.filling = 1 + kwargs.symmetry = 'fZ2xSU2xU1' +end + +switch kwargs.symmetry + case 'fZ2xSU2xU1' + [p, q] = rat(kwargs.filling); + if q > 30 + warning('filling %f is not a nice rational (%d // %d)', kwargs.filling, p, q) + end + + pcharges = ProductCharge(fZ2(0, 1, 0), SU2(1, 2, 1), U1([0, 1, 2] * q - p)); + pspace = GradedSpace.new(pcharges, ones(size(pcharges)), false); + + acharge = ProductCharge(fZ2(1), SU2(2), U1(q)); + aspace = GradedSpace.new(acharge, 1, false); + + creation_L = fill_tensor(Tensor(pspace, [pspace aspace]), {sqrt(2) 1}); + annihilation_R = fill_tensor(Tensor([aspace pspace], pspace), {sqrt(2) 1}); + + hopping = contract(creation_L, [-1 1 -4], annihilation_R, [1 -2 -3], 'Rank', [2 2]); + hopping = hopping + hopping'; + + interaction = fill_tensor(Tensor(pspace, pspace), {1 1 -1}); + + chemical_potential = fill_tensor(Tensor(pspace, pspace), {0 2 1}); + + mpo = InfJMpo.twosite(-hopping, ... + u * interaction - mu * chemical_potential); + + otherwise + error('tba:model', 'symmetry %s not implemented', kwargs.symmetry); +end + + + +end + diff --git a/src/models/quantum1dHubbard_energy.m b/src/models/quantum1dHubbard_energy.m new file mode 100644 index 0000000..80503f0 --- /dev/null +++ b/src/models/quantum1dHubbard_energy.m @@ -0,0 +1,7 @@ +function E = quantum1dHubbard_energy(u) +% computes the Hubbard groundstate energy at half-filling +f = @(x) x.^(-1) .* besselj(0, x) .* besselj(1, x) ./ (1 + exp(2 * u * x)); +I = integral(f, 0, Inf); +E = -(u + 4 * I); + +end From c1828fef172262935142b1b7f38e6e6bb06207a1 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 8 Jun 2023 08:34:45 +0200 Subject: [PATCH 227/245] symmetric offset for domain walls --- src/algorithms/QPAnsatz.m | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/algorithms/QPAnsatz.m b/src/algorithms/QPAnsatz.m index a2764ee..8b7fbfc 100644 --- a/src/algorithms/QPAnsatz.m +++ b/src/algorithms/QPAnsatz.m @@ -35,13 +35,23 @@ period(mpo), period(qp)); end + % Renormalization [GL, GR, lambda] = environments(mpo, qp.mpsleft, qp.mpsleft); for i = period(mpo):-1:1 T = AC_hamiltonian(mpo, qp.mpsleft, GL, GR, i); - offset(i) = dot(qp.mpsleft.AC{i}, apply(T{1}, qp.mpsleft.AC{i})); + offsetL(i) = dot(qp.mpsleft.AC{i}, apply(T{1}, qp.mpsleft.AC{i})); + end + [GL, GR, lambda] = environments(mpo, qp.mpsright, qp.mpsright); + + for i = period(mpo):-1:1 + T = AC_hamiltonian(mpo, qp.mpsright, GL, GR, i); + offsetR(i) = dot(qp.mpsright.AC{i}, apply(T{1}, qp.mpsright.AC{i})); end + offset = (offsetL + offsetR) / 2; + + GL = leftenvironment(mpo, qp.mpsleft, qp.mpsleft); % Algorithm H_effective = @(qp) updateX(alg, mpo, qp, GL, GR, offset); @@ -75,6 +85,7 @@ for i = 1:period(qp) qp.B{i} = B{i} - qp.B{i} * offset(i); end + qp.X = computeX(qp); end end From f0aa7d48fa62e11ce7d130d0c3c554a8c4af3c32 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 8 Jun 2023 08:35:05 +0200 Subject: [PATCH 228/245] Add circshift for UniformMPS --- src/mps/UniformMps.m | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index d28771c..5133813 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -392,6 +392,16 @@ mps.AC = arrayfun(@normalize, mps.AC); end + function mps = circshift(mps, k) + if length(k) > 1 + error('tba'); + end + mps.AR = circshift(mps.AR, k); + mps.AL = circshift(mps.AL, k); + mps.C = circshift(mps.C, k); + mps.AC = circshift(mps.AC, k); + end + function T = transfermatrix(mps1, mps2, sites, kwargs) % A finite matrix product operator that represents the transfer matrix of an % mps. From 16534d3d8b8e48ddaa71be82f0a549864d08288c Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 8 Jun 2023 08:37:09 +0200 Subject: [PATCH 229/245] Add safe saving --- src/utility/clear_path.m | 20 ++++++++++++++++++++ src/utility/safesave.m | 14 ++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 src/utility/clear_path.m create mode 100644 src/utility/safesave.m diff --git a/src/utility/clear_path.m b/src/utility/clear_path.m new file mode 100644 index 0000000..41b1a89 --- /dev/null +++ b/src/utility/clear_path.m @@ -0,0 +1,20 @@ +function clear_path(filename) + + +if isfile(filename) + [filepath, name, ext] = fileparts(filename); + + pat = "_backup" + digitsPattern; + if endsWith(name, pat) + number = extractAfter(name, "_backup"); + newname = replace(name, pat, "_backup" + (double(number) + 1)); + else + newname = name + "_backup1"; + end + newfilename = fullfile(filepath, newname + ext); + clear_path(newfilename); + movefile(filename, newfilename); +end + + +end diff --git a/src/utility/safesave.m b/src/utility/safesave.m new file mode 100644 index 0000000..c3c671c --- /dev/null +++ b/src/utility/safesave.m @@ -0,0 +1,14 @@ +function safesave(filename, variable) +% Safe wrapper around save. +% Clears path and makes backup of existing files, and creates directories if not existing. + +[filepath, name, ext] = fileparts(filename); +if ~isdir(filepath) + mkdir(filepath); +else + clear_path(filename); +end + +save(filename, '-struct', 'variable'); + +end From 543f0d7b86d4896d5883d3f80a63c92f7e0e2677 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Tue, 13 Jun 2023 15:29:41 +0200 Subject: [PATCH 230/245] update unitcell hubbard --- src/models/quantum1dHubbard.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/models/quantum1dHubbard.m b/src/models/quantum1dHubbard.m index b538d95..a7c8cf1 100644 --- a/src/models/quantum1dHubbard.m +++ b/src/models/quantum1dHubbard.m @@ -36,8 +36,8 @@ chemical_potential = fill_tensor(Tensor(pspace, pspace), {0 2 1}); - mpo = InfJMpo.twosite(-hopping, ... - u * interaction - mu * chemical_potential); + mpo = repmat(InfJMpo.twosite(-hopping, ... + u * interaction - mu * chemical_potential), 1, 2*q); otherwise error('tba:model', 'symmetry %s not implemented', kwargs.symmetry); @@ -45,5 +45,5 @@ -end +end From bf3fdd4fe5252b7e640601544f24fe8f3c9e44ad Mon Sep 17 00:00:00 2001 From: leburgel Date: Tue, 29 Aug 2023 14:48:49 +0200 Subject: [PATCH 231/245] Small fixes --- src/mps/MpsTensor.m | 2 +- test/TestInfJMpo.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index b303c0a..282e78f 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -354,7 +354,7 @@ end for i = length(A):-1:1 - B(i).var = twist(B(i).var, [isdual(space(B(i), 1:B.plegs+1)) ~isdual(space(B(i), B.plegs+2))]); + B(i).var = twist(B(i).var, [isdual(space(B(i), 1:B(i).plegs+1)) ~isdual(space(B(i), B(i).plegs+2))]); T(i, 1) = FiniteMpo(B(i).var', {}, A(i)); end end diff --git a/test/TestInfJMpo.m b/test/TestInfJMpo.m index e6f812e..6f38dee 100644 --- a/test/TestInfJMpo.m +++ b/test/TestInfJMpo.m @@ -3,7 +3,7 @@ properties (TestParameter) mpo = struct(... - 'trivial', InfJMpo.Ising() ... + 'trivial', quantum1dIsing() ... ) mps = struct(... 'trivial', UniformMps.randnc(CartesianSpace.new(2), CartesianSpace.new(4)) ... From 05dcecb91da18320e0795a7b4ef3412cf83053ca Mon Sep 17 00:00:00 2001 From: qmortier Date: Thu, 31 Aug 2023 14:59:06 +0200 Subject: [PATCH 232/245] Update AbstractSpace.m --- src/tensors/spaces/AbstractSpace.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tensors/spaces/AbstractSpace.m b/src/tensors/spaces/AbstractSpace.m index a970898..6718fed 100644 --- a/src/tensors/spaces/AbstractSpace.m +++ b/src/tensors/spaces/AbstractSpace.m @@ -362,6 +362,10 @@ (isequal(size(spaces{1}), size(spaces{2})) && ... all(spaces{1} == spaces{2})); end + + function bool = isisometric(space1, space2) + bool = prod(space1)==prod(space2); + end function hashable = GetMD5_helper(spaces) % Helper function for hash algorithm. This converts the space object to a data From 0bb6fde2e41d0f8ef1469fe3d04edd0ff06f3af6 Mon Sep 17 00:00:00 2001 From: leburgel Date: Thu, 31 Aug 2023 18:46:18 +0200 Subject: [PATCH 233/245] Ufilter and unbreak fermionic peps tests --- src/mps/FiniteMpo.m | 2 +- src/tensors/spaces/AbstractSpace.m | 2 +- test/TestInfJMpo.m | 2 +- test/TestPeps.m | 16 +++++----------- 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index 457e1f2..a94e242 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -276,7 +276,7 @@ atop = Atop(i); o = rot90(O{i}); twistinds = 1 + find(isdual(space(Atop(i), 2:nspaces(Atop(i)) - 1))); - abot = twist(Abot(i), twistinds)'; %IMPORTANT CHANGE: ' out of twist + abot = twist(Abot(i), twistinds)'; % IMPORTANT: ' needs to be outside of twist assert(isequal(pspace(abot)', leftvspace(o))); assert(isequal(rightvspace(o)', pspace(atop))); diff --git a/src/tensors/spaces/AbstractSpace.m b/src/tensors/spaces/AbstractSpace.m index 6718fed..f9617e3 100644 --- a/src/tensors/spaces/AbstractSpace.m +++ b/src/tensors/spaces/AbstractSpace.m @@ -364,7 +364,7 @@ end function bool = isisometric(space1, space2) - bool = prod(space1)==prod(space2); + bool = prod(space1) == prod(space2); end function hashable = GetMD5_helper(spaces) diff --git a/test/TestInfJMpo.m b/test/TestInfJMpo.m index 6f38dee..5e99f06 100644 --- a/test/TestInfJMpo.m +++ b/test/TestInfJMpo.m @@ -62,7 +62,7 @@ function test1dIsing(tc) function test1dHeisenberg(tc) alg = Vumps('which', 'smallestreal', 'maxiter', 5); - mpo = InfJMpo.Heisenberg('Spin', SU2(3), 'Symmetry', 'SU2'); + mpo = quantum1dHeisenberg('Spin', 1, 'Symmetry', 'SU2'); mpo = [mpo mpo]; vspace1 = GradedSpace.new(SU2(1:2:5), [5 5 1], false); diff --git a/test/TestPeps.m b/test/TestPeps.m index 905a0eb..a449725 100644 --- a/test/TestPeps.m +++ b/test/TestPeps.m @@ -24,7 +24,6 @@ methods (Test, ParameterCombination='exhaustive') function testFiniteMpo(tc, spaces, depth, width, dualdepth, dualwidth, samebot) - tc.assumeTrue(istwistless(braidingstyle(spaces)), 'Fermionic tests broken.') tc.assumeTrue(depth == 1, 'Test ill-defined for multiline PEPS.') mpo = tc.random_mpo(spaces, depth, width, dualdepth, dualwidth, samebot); @@ -48,10 +47,6 @@ function testFiniteMpo(tc, spaces, depth, width, dualdepth, dualwidth, samebot) tc.assertTrue(isequal(mpo.codomain, mpo_tensor.codomain), ... 'codomain should remain fixed after conversion.'); - % test apply - v = initialize_fixedpoint(mpo); - tc.assertTrue(isapprox(mpo.apply(v), mpo_tensor * v)); - % test transpose tc.assertTrue(isapprox(Tensor(mpo).', Tensor(mpo.')), ... 'transpose should not change mpo'); @@ -59,11 +54,14 @@ function testFiniteMpo(tc, spaces, depth, width, dualdepth, dualwidth, samebot) % test ctranspose tc.assertTrue(isapprox(Tensor(mpo)', Tensor(mpo')), ... 'ctranspose should not change mpo'); + + % test apply + v = initialize_fixedpoint(mpo); + twistinds = find(isdual(space(v, 1:nspaces(v)))); % compensate for supertrace rules when using contract versus mtimes + tc.assertTrue(isapprox(mpo.apply(v), mpo_tensor * twist(v, twistinds))); end function testFixedpoints(tc, spaces, depth, width, dualdepth, dualwidth, samebot) - tc.assumeTrue(istwistless(braidingstyle(spaces)), 'Fermionic tests broken.') - mpo = tc.random_mpo(spaces, depth, width, dualdepth, dualwidth, samebot); [V, D] = eigsolve(mpo, 'MaxIter', 1e3, 'KrylovDim', 32); @@ -74,8 +72,6 @@ function testFixedpoints(tc, spaces, depth, width, dualdepth, dualwidth, samebot end function testDerivatives(tc, spaces, depth, width, dualdepth, dualwidth, samebot) - tc.assumeTrue(istwistless(braidingstyle(spaces)), 'Fermionic tests broken.') - mpo = tc.random_inf_mpo(spaces, depth, width, dualdepth, dualwidth, samebot); vspaces = repmat({spaces(end)}, 1, width); mps = mpo.initialize_mps(vspaces{:}); @@ -98,8 +94,6 @@ function testDerivatives(tc, spaces, depth, width, dualdepth, dualwidth, samebot end function testMpoTensor(tc, spaces, dualdepth, dualwidth) - tc.assumeTrue(istwistless(braidingstyle(spaces)), 'Fermionic tests broken.') - pspace = spaces(1); horzspace = spaces(2); vertspace = spaces(3); top = TestPeps.random_peps_unitcell(pspace, horzspace, vertspace, 1, 1, dualdepth, dualwidth); bot = TestPeps.random_peps_unitcell(pspace', vertspace, horzspace, 1, 1, dualdepth, dualwidth); From ea9c74d14e123e85f391913129732ca6b21b7dbc Mon Sep 17 00:00:00 2001 From: leburgel Date: Fri, 1 Sep 2023 10:48:37 +0200 Subject: [PATCH 234/245] Try harder in test --- test/TestAlgorithms.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/TestAlgorithms.m b/test/TestAlgorithms.m index 0c0406a..9c03e57 100644 --- a/test/TestAlgorithms.m +++ b/test/TestAlgorithms.m @@ -36,7 +36,7 @@ function test2dIsing(tc, alg, symm) [gs, lambda] = fixedpoint(alg, mpo, mps); tc.verifyEqual(expectation_value(gs, mpo, gs), E0, 'RelTol', 1e-2); - alg_expand = Expand('which', alg.which, 'schmidtcut', 1e-7, 'finalize', Vomps('maxiter', 5)); + alg_expand = Expand('which', alg.which, 'schmidtcut', 1e-7, 'finalize', Vomps('maxiter', 10)); gs2 = changebonds(alg_expand, mpo, mps); tc.verifyGreaterThan(abs(fidelity(gs, gs2)), 0.9^unitcell) end From 696712e3fea2172a9c7b96fd383412db9fdf4b9c Mon Sep 17 00:00:00 2001 From: leburgel Date: Fri, 1 Sep 2023 13:44:55 +0200 Subject: [PATCH 235/245] Relax fidelity tolerance --- test/TestAlgorithms.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/TestAlgorithms.m b/test/TestAlgorithms.m index 9c03e57..7afdfbf 100644 --- a/test/TestAlgorithms.m +++ b/test/TestAlgorithms.m @@ -38,7 +38,7 @@ function test2dIsing(tc, alg, symm) alg_expand = Expand('which', alg.which, 'schmidtcut', 1e-7, 'finalize', Vomps('maxiter', 10)); gs2 = changebonds(alg_expand, mpo, mps); - tc.verifyGreaterThan(abs(fidelity(gs, gs2)), 0.9^unitcell) + tc.verifyGreaterThan(abs(fidelity(gs, gs2)), 0.85^unitcell) end end From 85dc0a1200576f08d5d7caa169d94967e2407527 Mon Sep 17 00:00:00 2001 From: qmortier Date: Wed, 6 Sep 2023 15:01:12 +0200 Subject: [PATCH 236/245] Charge fix and MPS additions ProductCharge degeneracies fix (TO DO check other Charge objects) Expand (more options) Truncate (in UniformMps) --- src/algorithms/Expand.m | 25 +++++++++++++++++++------ src/mps/UniformMps.m | 26 +++++++++++++++++++++++++- src/tensors/charges/ProductCharge.m | 2 +- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/algorithms/Expand.m b/src/algorithms/Expand.m index 2fcb927..4827005 100644 --- a/src/algorithms/Expand.m +++ b/src/algorithms/Expand.m @@ -4,10 +4,10 @@ %% Options properties bondsmethod {mustBeMember(bondsmethod, ... - {'off', 'factor', 'extrapolate', 'twosite'})} = 'factor' + {'off', 'factor', 'explicit', 'extrapolate', 'twosite', 'twosite_simple'})} = 'factor' chargesmethod {mustBeMember(chargesmethod, ... - {'off', 'fusionproduct', 'twosite'})} = 'off' + {'off', 'fusionproduct', 'twosite', 'twosite_simple'})} = 'off' % general expansion options schmidtcut = 1e-5 @@ -22,6 +22,7 @@ tolbond = 0.2 bondfactor = 1.2 cutfactor = 1 + explicitbonds = [] % charge expansion options mincharges = 2 @@ -70,7 +71,7 @@ for d = depth(mps):-1:1 for w = period(mps):-1:1 - if strcmp(alg.bondsmethod, 'twosite') || strcmp(alg.chargesmethod, 'twosite') + if startsWith(alg.bondsmethod, 'twosite') || startsWith(alg.chargesmethod, 'twosite') % twosite expansion takes care of both bonds and charges at the same time [addbond, addcharge] = expand_twosite(alg, mpo, mps, d, w); else @@ -145,8 +146,14 @@ H_AC2 = AC2_hamiltonian(mpo, mps, GL, GR, w); AC2 = MpsTensor(contract(mps(dd).AC(w), [-(1:mps(dd).AC(w).plegs+1), 1], ... mps(dd).AR(ww), [1, -(1:mps(dd).AR(ww).plegs+1) - 1 - mps(dd).AC(ww).plegs], ... - 'Rank', [1 + mps(dd).AC(w).plegs, 1 + mps(dd).AR(ww).plegs])); - [AC2.var, ~] = eigsolve(H_AC2{1}, AC2.var, 1, alg.which); + 'Rank', [1 + mps(dd).AC(w).plegs, 1 + mps(dd).AR(ww).plegs])); + if strcmp(alg.bondsmethod, 'twosite') + %twosite fixed point + [AC2.var, ~] = eigsolve(H_AC2{1}, AC2.var, 1, alg.which); + else + %single application + AC2.var = apply(H_AC2{1},AC2.var); + end [~, C2, ~] = tsvd(AC2.var, ... 1:mps(dd).AC(w).plegs+1, ... (1:mps(dd).AR(ww).plegs+1) + 1 + mps(dd).AC(w).plegs, ... @@ -260,6 +267,12 @@ % :code:`addbond` of integers, where :code:`addbond(i)` is the bond dimension to % be added to/subtracted from sector :code:`i`. + %new bonds are explicitly provided + if strcmp(alg.bondsmethod, 'explicit') + addbond = alg.explicitbonds; + return + end + % recursive if iscell(svals) addbond = zeros(size(svals)); @@ -297,7 +310,7 @@ if svals(end) > cut % need to add bond chi_new = extrapolate_schmidt(svals, cut) + floor(chi_tol / 2); - elseif chi > alg.minond && svals(max(1, chi-chi_tol)) < cut && ~alg.notrunc + elseif chi > alg.minbond && svals(max(1, chi-chi_tol)) < cut && ~alg.notrunc % need to subtract bond chi_new = min(nnz(svals > cut) + 1 + ceil(chi_tol / 2), chi); else diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index fa19ae6..41dc081 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -202,6 +202,7 @@ function s = pspace(mps, w) % return the physical space at site w. + if nargin == 1 || isempty(w), w = 1:period(mps); end s = pspace(mps.AL(w)); end @@ -818,6 +819,29 @@ function plot_entanglementspectrum(mps, d, w, ax, kwargs) end S = 1 / (1 - n) * log(S); end + + function out = truncate(mps, trunc) + arguments + mps + trunc.TruncDim + trunc.TruncTotalDim + trunc.TruncBelow + trunc.TruncSpace + end + + trunc = [fieldnames(trunc),struct2cell(trunc)]'; + out = repmat(UniformMps, size(mps, 1), 1); + for d = 1:depth(mps) + for w = 1:period(mps(d)) + [~, ~, V] = tsvd(mps(d).C(w), 1, 2, trunc{:}); + ww = next(w, period(mps(d))); + mps(d).AR(ww) = multiplyleft(mps(d).AR(ww), V); + mps(d).AR(w) = multiplyright(mps(d).AR(w), V'); + end + % bring truncated mps to canonical form + out(d) = UniformMps(mps(d).AR); + end + end S = EntanglementEntropy(mps, loc); S = RenyiEntropy(mps,n, loc); @@ -828,7 +852,7 @@ function plot_entanglementspectrum(mps, d, w, ax, kwargs) out = Block(mps, opts) out = Split(mps, varargin) - [out, lambda] = Truncate(mps, control, opts) + %[out, lambda] = Truncate(mps, control, opts) [f, rho] = Fidelity(mps1, mps2, tol) diff --git a/src/tensors/charges/ProductCharge.m b/src/tensors/charges/ProductCharge.m index ca4b70b..cc0ebf9 100644 --- a/src/tensors/charges/ProductCharge.m +++ b/src/tensors/charges/ProductCharge.m @@ -123,7 +123,7 @@ d_ = subsref(d, substruct('()', {1})) * ... a_i; if nargout > 1 - N_(1:length(d_)) = N(1); + N_ = repmat(N(1),1,length(d_)); end for j = 2:length(d) From 4d0f2ef39b9691b5530f53b7601b2fb059222c76 Mon Sep 17 00:00:00 2001 From: leburgel Date: Wed, 6 Sep 2023 21:41:56 +0200 Subject: [PATCH 237/245] Update * Complete move to cells instead of arrays of MpsTensors * Consistently use MpsTensors in local update and environment eigenvalue problems * Finish eigsolve rewrite to consistently use either Arnoldi or KrylovShur algorithms * Fix unexpted behavior for tensors with no allowed fusion channels * Fixed/unbroke all of the tests I could --- src/algorithms/Dmrg.m | 22 ++- src/algorithms/IDmrg.m | 32 ++-- src/algorithms/IDmrg2.m | 27 +-- src/algorithms/Vumps.m | 11 +- src/algorithms/Vumps2.m | 18 +- src/algorithms/eigsolvers/Arnoldi.m | 11 ++ src/mps/FiniteMpo.m | 5 +- src/mps/InfJMpo.m | 6 +- src/mps/InfMpo.m | 5 +- src/mps/MpsTensor.m | 124 ++++++++++-- src/mps/PepsSandwich.m | 8 +- src/mps/UniformMps.m | 6 +- src/tensors/AbstractTensor.m | 80 +++----- src/tensors/kernels/MatrixBlock.m | 13 +- src/utility/linalg/eigsolve.m | 281 ++++++++++++---------------- test/TestAlgorithms.m | 5 +- test/TestFiniteMpo.m | 2 +- test/TestInfJMpo.m | 8 +- test/TestInfMpo.m | 16 +- test/TestPeps.m | 15 +- test/TestTensor.m | 5 +- 21 files changed, 376 insertions(+), 324 deletions(-) diff --git a/src/algorithms/Dmrg.m b/src/algorithms/Dmrg.m index a10ffaf..6c6b107 100644 --- a/src/algorithms/Dmrg.m +++ b/src/algorithms/Dmrg.m @@ -1,6 +1,7 @@ classdef Dmrg % Density Matrix Renormalisation Group algorithm for marix product states. + %% Options properties tol = 1e-10 miniter = 5 @@ -20,10 +21,11 @@ saveIterations = 1 saveMethod = 'full' name = 'DMRG' + + alg_eigs = KrylovSchur('MaxIter', 100, 'KrylovDim', 20) % TODO: Arnoldi has issues for DMRG specifically, need to figure out why end properties (Access = private) - alg_eigs = struct('MaxIter', 100, 'KrylovDim', 20); progressfig end @@ -41,8 +43,8 @@ end if ~isfield('alg_eigs', kwargs) - alg.alg_eigs.Tol = sqrt(alg.tol_min * alg.tol_max); - alg.alg_eigs.Verbosity = alg.verbosity - 2; + alg.alg_eigs.tol = sqrt(alg.tol_min * alg.tol_max); + alg.alg_eigs.verbosity = alg.verbosity - 2; end end @@ -99,15 +101,15 @@ % compute update H_AC = AC_hamiltonian(mpo, mps, envs, pos); - AC = mps.A(pos); - [AC.var, lambda] = eigsolve(H_AC, AC.var, 1, alg.which); + AC = mps.A{pos}; + [AC, lambda] = eigsolve(alg.alg_eigs, @(x) H_AC.apply(x), AC, 1, alg.which); % determine error - phase = dot(AC.var, mps.A(pos)); - eta = distance(AC.var ./ sign(phase), mps.A(pos)); + phase = dot(AC, mps.A{pos}); + eta = distance(AC ./ sign(phase), mps.A{pos}); % take step - mps.A(pos) = AC; + mps.A{pos} = AC; envs = invalidate(envs, pos); end end @@ -116,10 +118,10 @@ methods function alg = updatetols(alg, iter, eta) if alg.dynamical_tols - alg.alg_eigs.Tol = between(alg.tol_min, eta * alg.eigs_tolfactor / iter, ... + alg.alg_eigs.tol = between(alg.tol_min, eta * alg.eigs_tolfactor / iter, ... alg.tol_max); if alg.verbosity > Verbosity.iter - fprintf('Updated eigsolver tolerances: (%e)\n', alg.alg_eigs.Tol); + fprintf('Updated eigsolver tolerances: (%e)\n', alg.alg_eigs.tol); end end end diff --git a/src/algorithms/IDmrg.m b/src/algorithms/IDmrg.m index d99a3a7..e4d7863 100644 --- a/src/algorithms/IDmrg.m +++ b/src/algorithms/IDmrg.m @@ -12,10 +12,12 @@ tol_min = 1e-12 tol_max = 1e-6 eigs_tolfactor = 1e-4 + + alg_eigs = Arnoldi('MaxIter', 100, 'KrylovDim', 20) end properties (Access = private) - alg_eigs = struct('MaxIter', 100, 'KrylovDim', 20) + % TODO: alg_canonical, alg_environments? cf Vumps end methods @@ -32,8 +34,8 @@ end if ~isfield('alg_eigs', kwargs) - alg.alg_eigs.Tol = sqrt(alg.tol_min * alg.tol_max); - alg.alg_eigs.Verbosity = alg.verbosity - 2; + alg.alg_eigs.tol = sqrt(alg.tol_min * alg.tol_max); + alg.alg_eigs.verbosity = alg.verbosity - 2; end end @@ -52,14 +54,15 @@ for iter = 1:alg.maxiter t_iter = tic; - C_ = mps.C(end); + C_ = mps.C{end}; kwargs = {}; lambdas = zeros(1, period(mps)); for pos = 1:period(mps) H = AC_hamiltonian(mpo, mps, GL, GR, pos); - [mps.AC(pos).var, lambdas(pos)] = ... - eigsolve(H{1}, mps.AC(pos).var, 1, alg.which, kwargs{:}); - [mps.AL(pos), mps.C(pos)] = leftorth(mps.AC(pos)); + [mps.AC{pos}, lambdas(pos)] = ... + eigsolve(alg.alg_eigs, @(x) H{1}.apply(x), mps.AC{pos}, 1, ... + alg.which); + [mps.AL{pos}, mps.C{pos}] = leftorth(mps.AC{pos}); T = transfermatrix(mpo, mps, mps, pos, 'Type', 'LL'); GL{next(pos, period(mps))} = apply(T, GL{pos}) / lambdas(pos); @@ -67,15 +70,16 @@ for pos = period(mps):-1:1 H = AC_hamiltonian(mpo, mps, GL, GR, pos); - [mps.AC(pos).var, lambdas(pos)] = ... - eigsolve(H{1}, mps.AC(pos).var, 1, alg.which, kwargs{:}); - [mps.C(prev(pos, period(mps))), mps.AR(pos)] = rightorth(mps.AC(pos)); + [mps.AC{pos}, lambdas(pos)] = ... + eigsolve(alg.alg_eigs, @(x) H{1}.apply(x), mps.AC{pos}, 1, ... + alg.which); + [mps.C{prev(pos, period(mps))}, mps.AR{pos}] = rightorth(mps.AC{pos}); T = transfermatrix(mpo, mps, mps, pos, 'Type', 'RR').'; GR{pos} = apply(T, GR{next(pos, period(mps))}) / lambdas(pos); end - eta = distance(C_, mps.C(end)); + eta = distance(C_, mps.C{end}); lambda = prod(lambdas); if iter > alg.miniter && eta < alg.tol @@ -109,12 +113,12 @@ function alg = updatetols(alg, iter, eta) if alg.dynamical_tols - alg.alg_eigs.Tol = between(alg.tol_min, eta * alg.eigs_tolfactor, ... + alg.alg_eigs.tol = between(alg.tol_min, eta * alg.eigs_tolfactor, ... alg.tol_max / iter); if alg.verbosity > Verbosity.iter - fprintf('Updated subalgorithm tolerances: (%e,\t%e,\t%e)\n', ... - alg.alg_eigs.Tol, alg.alg_canonical.Tol, alg.alg_environments.Tol); + fprintf('Updated subalgorithm tolerances: (%e)\n', ... + alg.alg_eigs.tol); end end end diff --git a/src/algorithms/IDmrg2.m b/src/algorithms/IDmrg2.m index 3f90b01..ba29758 100644 --- a/src/algorithms/IDmrg2.m +++ b/src/algorithms/IDmrg2.m @@ -21,10 +21,12 @@ saveIterations = false saveMethod = 'full' name = 'IDmrg2' + + alg_eigs = Arnoldi('MaxIter', 100, 'KrylovDim', 20) end properties (Access = private) - alg_eigs = struct('MaxIter', 100, 'KrylovDim', 20) + % TODO: alg_canonical, alg_environments? cf Vumps progressfig end @@ -42,8 +44,8 @@ end if ~isfield('alg_eigs', kwargs) - alg.alg_eigs.Tol = sqrt(alg.tol_min * alg.tol_max); - alg.alg_eigs.Verbosity = alg.verbosity - 2; + alg.alg_eigs.tol = sqrt(alg.tol_min * alg.tol_max); + alg.alg_eigs.verbosity = alg.verbosity - 2; end end @@ -74,8 +76,8 @@ for pos = 1:period(mps)-1 AC2 = computeAC2(mps, 1, pos); H = AC2_hamiltonian(mpo, mps, GL, GR, pos); - [AC2.var, lambdas(pos)] = ... - eigsolve(H{1}, AC2.var, 1, alg.which, kwargs{:}); + [AC2, lambdas(pos)] = ... + eigsolve(alg.alg_eigs, @(x) H{1}.apply(x), AC2, 1, alg.which); [mps.AL{pos}.var, C, mps.AR{pos+1}.var] = ... tsvd(AC2.var, [1 2], [3 4], alg.trunc{:}); @@ -92,7 +94,8 @@ AC2 = contract(mps.AC{end}, [-1 -2 1], inv(mps.C{end}), [1 2], ... mps.AL{1}, [2 -3 3], mps.C{1}, [3 -4], 'Rank', [2 2]); H = AC2_hamiltonian(mpo, mps, GL, GR, period(mps)); - [AC2, lambdas(end)] = eigsolve(H{1}, AC2, 1, alg.which, kwargs{:}); + [AC2, lambdas(end)] = ... + eigsolve(alg.alg_eigs, @(x) H{1}.apply(x), AC2, 1, alg.which); [mps.AL{end}.var, C, mps.AR{1}.var] = ... tsvd(AC2, [1 2], [3 4], alg.trunc{:}); @@ -111,7 +114,8 @@ for pos = period(mps)-1:-1:1 AC2 = computeAC2(mps, 1, pos, 'L'); H = AC2_hamiltonian(mpo, mps, GL, GR, pos); - [AC2, lambdas(pos)] = eigsolve(H{1}, AC2, 1, alg.which, kwargs{:}); + [AC2, lambdas(pos)] = ... + eigsolve(alg.alg_eigs, @(x) H{1}.apply(x), AC2, 1, alg.which); [mps.AL{pos}.var, C, mps.AR{pos + 1}.var] = ... tsvd(AC2, [1 2], [3 4], alg.trunc{:}); @@ -129,7 +133,8 @@ AC2 = contract(mps.C{end-1}, [-1 1], mps.AR{end}, [1 -2 2], ... inv(mps.C{end}), [2 3], mps.AC{1}, [3 -3 -4], 'Rank', [2 2]); H = AC2_hamiltonian(mpo, mps, GL, GR, period(mps)); - [AC2, lambdas(1)] = eigsolve(H{1}, AC2, 1, alg.which, kwargs{:}); + [AC2, lambdas(1)] = ... + eigsolve(alg.alg_eigs, @(x) H{1}.apply(x), AC2, 1, alg.which); [mps.AL{end}.var, C, mps.AR{1}.var] = ... tsvd(AC2, [1 2], [3 4], alg.trunc{:}); @@ -189,12 +194,12 @@ function alg = updatetols(alg, iter, eta) if alg.dynamical_tols - alg.alg_eigs.Tol = between(alg.tol_min, eta * alg.eigs_tolfactor, ... + alg.alg_eigs.tol = between(alg.tol_min, eta * alg.eigs_tolfactor, ... alg.tol_max / iter); if alg.verbosity > Verbosity.iter - fprintf('Updated subalgorithm tolerances: (%e,\t%e,\t%e)\n', ... - alg.alg_eigs.Tol, alg.alg_canonical.Tol, alg.alg_environments.Tol); + fprintf('Updated subalgorithm tolerances: (%e)\n', ... + alg.alg_eigs.tol); end end end diff --git a/src/algorithms/Vumps.m b/src/algorithms/Vumps.m index b3d6bcf..4d0010f 100644 --- a/src/algorithms/Vumps.m +++ b/src/algorithms/Vumps.m @@ -72,7 +72,7 @@ end end - function [mps, lambda, GL, GR, eta, time] = fixedpoint(alg, mpo, mps) + function [mps, lambda, GL, GR, eta] = fixedpoint(alg, mpo, mps) if period(mpo) ~= period(mps) error('vumps:argerror', ... @@ -98,7 +98,6 @@ if iter > alg.miniter && eta < alg.tol disp_conv(alg, iter, lambda, eta, toc(t_total)); - time = toc(t_total); return end alg = updatetols(alg, iter, eta); @@ -109,7 +108,6 @@ save_iteration(alg, mps, lambda, iter, eta, toc(t_total)); end end - time = toc(t_total); disp_maxiter(alg, iter, lambda, eta, toc(t_total)); end end @@ -130,8 +128,8 @@ if alg.verbosity >= Verbosity.detail fprintf('\nAC{%d} eigenvalue solver:\n------------------------\n', sites(i)); end - [AC{1, i}.var, ~] = eigsolve(alg.alg_eigs, @(x) H_AC{i}.apply(x), AC{1, i}.var, ... - 1, alg.which); % function handle returns MpsTensor, which is then sneakily converted to a tensor by eigsolve + [AC{1, i}, ~] = eigsolve(alg.alg_eigs, @(x) H_AC{i}.apply(x), AC{1, i}, ... + 1, alg.which); for d = 2:depth(mpo) AC{d, i} = H_AC{i}(d).apply(AC{d-1, i}); end @@ -152,7 +150,8 @@ if alg.verbosity >= Verbosity.detail fprintf('\nC{%d} eigenvalue solver:\n-----------------------\n', sites(i)); end - [C{1, i}, ~] = eigsolve(alg.alg_eigs, @(x) H_C{i}.apply(x), C{1, i}, 1, alg.which); + [C{1, i}, ~] = eigsolve(alg.alg_eigs, @(x) H_C{i}.apply(x), C{1, i}, 1, ... + alg.which); for d = 2:depth(mpo) C{d, i} = H_C{i}(d).apply(C{d-1, i}); end diff --git a/src/algorithms/Vumps2.m b/src/algorithms/Vumps2.m index 05d3aa1..f655920 100644 --- a/src/algorithms/Vumps2.m +++ b/src/algorithms/Vumps2.m @@ -29,10 +29,10 @@ saveMethod = 'full' name = 'VUMPS' + alg_eigs = Arnoldi('MaxIter', 100, 'KrylovDim', 20) end properties (Access = private) - alg_eigs = struct('MaxIter', 100, 'KrylovDim', 20) alg_canonical = struct('Method', 'polar') alg_environments = struct @@ -55,8 +55,8 @@ end if ~isfield('alg_eigs', kwargs) - alg.alg_eigs.Tol = sqrt(alg.tol_min * alg.tol_max); - alg.alg_eigs.Verbosity = alg.verbosity - 2; + alg.alg_eigs.tol = sqrt(alg.tol_min * alg.tol_max); + alg.alg_eigs.verbosity = alg.verbosity - 2; end if ~isfield('alg_canonical', kwargs) @@ -119,7 +119,6 @@ %% Subroutines methods function AC2 = updateAC2(alg, iter, mpo, mps, GL, GR) - kwargs = namedargs2cell(alg.alg_eigs); if strcmp(alg.multiAC, 'sequential') sites = mod1(iter, period(mps)); else @@ -129,13 +128,12 @@ H_AC2 = AC2_hamiltonian(mpo, mps, GL, GR, sites); for i = length(sites):-1:1 AC2{i} = computeAC2(mps, 1, sites(i)); - [AC2{i}.var, ~] = eigsolve(H_AC2{i}, AC2{i}.var, 1, alg.which, ... - kwargs{:}); + [AC2{i}, ~] = eigsolve(alg.alg_eigs, @(x) H_AC2{i}.apply(x), AC2{i}, ... + 1, alg.which); end end function C = updateC(alg, iter, mpo, mps, GL, GR) - kwargs = namedargs2cell(alg.alg_eigs); if strcmp(alg.multiAC, 'sequential') sites = mod1(iter, period(mps)); else @@ -145,8 +143,8 @@ sites = next(sites, period(mps)); H_C = C_hamiltonian(mpo, mps, GL, GR, sites); for i = length(sites):-1:1 - [C{i}, ~] = eigsolve(H_C{i}, mps.C{sites(i)}, 1, alg.which, ... - kwargs{:}); + [C{i}, ~] = eigsolve(alg.alg_eigs, @(x) H_C{i}.apply(x), mps.C{sites(i)}, ... + 1, alg.which); end end @@ -219,7 +217,7 @@ methods function alg = updatetols(alg, iter, eta) if alg.dynamical_tols - alg.alg_eigs.Tol = between(alg.tol_min, eta * alg.eigs_tolfactor, ... + alg.alg_eigs.tol = between(alg.tol_min, eta * alg.eigs_tolfactor, ... alg.tol_max / iter); alg.alg_canonical.Tol = between(alg.tol_min, ... eta * alg.canonical_tolfactor, alg.tol_max / iter); diff --git a/src/algorithms/eigsolvers/Arnoldi.m b/src/algorithms/eigsolvers/Arnoldi.m index 5a27bf6..162284e 100644 --- a/src/algorithms/eigsolvers/Arnoldi.m +++ b/src/algorithms/eigsolvers/Arnoldi.m @@ -35,8 +35,19 @@ t_total = tic; + if ~isa(A, 'function_handle') + A = @(x) A * x; + end + + if norm(v) < eps(underlyingType(v))^(3/4) + error('eigsolve:inputnorm', 'starting vector should not have zero norm.'); + end + + % some input validation if isempty(alg.nobuild), alg.nobuild = ceil(alg.krylovdim / 10); end if isempty(alg.deflatedim), alg.deflatedim = max(round(3/5 * alg.krylovdim), howmany); end + assert(alg.deflatedim < alg.krylovdim, 'eigsolve:argerror', ... + 'Deflate size should be smaller than krylov dimension.') v = v / norm(v, 'fro'); diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index 04bef0c..d8309ed 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -42,7 +42,7 @@ sigma = 'largestabs' options.Tol = eps(underlyingType(mpo))^(3/4) - options.Algorithm = 'eigs' + options.Algorithm = 'Arnoldi' options.MaxIter = 100 options.KrylovDim = 20 options.IsSymmetric logical = false @@ -53,6 +53,7 @@ end if isempty(v0), v0 = initialize_fixedpoint(mpo(1)); end + kwargs = namedargs2cell(options); [V, D, flag] = eigsolve(@(x) mpo.apply(x), v0, howmany, sigma, kwargs{:}); end @@ -62,7 +63,7 @@ N = prod(cellfun(@(x) size(x, 4), mpo.O)); for i = N:-1:1 - v(i) = Tensor.randnc(domain(slice(mpo, i, 1:N)), []); + v(i) = MpsTensor.randnc(domain(slice(mpo, i, 1:N)), []); end end diff --git a/src/mps/InfJMpo.m b/src/mps/InfJMpo.m index 0e0537a..935efa6 100644 --- a/src/mps/InfJMpo.m +++ b/src/mps/InfJMpo.m @@ -41,7 +41,7 @@ GL{1}.var(1) = fp_left; end - for i = 2:size(GL{1}, 2) + for i = 2:size(GL{1}.var, 2) % TODO: specify interface to get dimensions of single MpsTensor rhs = apply(slice(T, i, 1:i-1), slice(GL{1}, 1, 1:i-1, 1)); Tdiag = slice(T, i, i); if iszero(Tdiag) @@ -174,7 +174,7 @@ (apply(TB(w), GL{w}) + apply(T(w), GBL{w})) * (expP^(1/L)); end - N = size(GBL{1}, 2); + N = size(GBL{1}.var, 2); % TODO: specify interface to get dimensions of single MpsTensor for i = 2:N % GBL{1}(1) = 0 because of quasiparticle gauge rhs = apply(slice(T, i, 1:i-1), slice(GBL{1}, 1, 1:i-1, 1, 1)) * expP; rhs = rhs + slice(GBL{1}, i); @@ -251,7 +251,7 @@ GBR{w} = expP^(1/L) * (apply(TB_w, GR{ww}) + apply(T_w, GBR{ww})); end - N = size(GBR{1}, 2); + N = size(GBR{1}.var, 2); % TODO: specify interface to get dimensions of single MpsTensor for i = N:-1:1 if i == N rhs = slice(GBR{1}, 1, i, 1, 1); diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index 2fb6130..7286073 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -174,7 +174,6 @@ T = transfermatrix(mpo, mps1, mps2, i-1, 'Type', 'LL'); GL{i} = apply(T, GL{i-1}) ./ lambda^(1/N); end - GL = cellfun(@MpsTensor, GL, 'UniformOutput', false); end function [GR, lambda] = rightenvironment(mpo, mps1, mps2, GR, eigopts) @@ -199,7 +198,6 @@ T = transfermatrix(mpo, mps1, mps2, i, 'Type', 'RR').'; GR{i} = apply(T, GR{next(i, N)}) ./ lambda^(1/N); end - GR = cellfun(@MpsTensor, GR, 'UniformOutput', false); end function GBL = leftquasienvironment(mpo, qp, GL, GR, GBL, linopts) @@ -425,6 +423,9 @@ gl = tpermute(gl, p, rank(gl)); gr = GR{d, next(sites(i), period(mpo))}; + if isa(gr, 'Tensor') + disp('eh?') + end gr = twistdual(gr, nspaces(gr)-gr.alegs); p = 1:nspaces(gr); p(2:(nspaces(gr)-1-gr.alegs)) = flip(p(2:(nspaces(gr)-1-gr.alegs))); diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index 5a72744..9adb842 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -2,7 +2,7 @@ % Generic mps tensor objects that have a notion of virtual, physical and auxiliary legs. properties - var %Tensor + var plegs alegs = 0 end @@ -60,17 +60,17 @@ %% Properties methods - function varargout = size(A, varargin) - [varargout{1:nargout}] = size(A.var, varargin{:}); - end +% function varargout = size(A, varargin) +% [varargout{1:nargout}] = size(A.var, varargin{:}); +% end - function n = numel(A) - n = numel(A.var); - end +% function n = numel(A) +% n = numel(A.var); +% end - function l = length(A) - l = length(A.var); - end +% function l = length(A) +% l = length(A.var); +% end function A = cat(dim, varargin) ismpstensor = cellfun(@(x) isa(x, 'MpsTensor'), varargin); @@ -87,6 +87,10 @@ end end + function bool = isscalar(~) + bool = false; + end + function A = horzcat(varargin) A = cat(2, varargin{:}); end @@ -153,6 +157,9 @@ %% Linear Algebra methods + function varargout = matrixblocks(t) + [varargout{1:nargout}] = matrixblocks(t.var); + end function d = dot(A, B) if isa(A, 'MpsTensor') @@ -175,20 +182,93 @@ d = contract(A, inds, B, [fliplr(inds(1:end-B.alegs)) inds(end-B.alegs+1:end)]); end + function t = mrdivide(t1, t2) + if isa(t1, 'MpsTensor') + t = t1; + t1 = t1.var; + else + t = t2; + end + if isa(t2, 'MpsTensor') + t2 = t2.var; + end + + t.var = t1 * inv(t2); + end + function C = mtimes(A, B) + + if isscalar(A) || isscalar(B) + C = A .* B; + return + end + + szA = size(A); + szB = size(B); + if szA(2) ~= szB(1) + error('mtimes:dimagree', ... + 'incompatible dimensions (%d) (%d)', szA(2), szB(1)); + end + + if ~(isnumeric(A) || isnumeric(B)) + error('not implemented when neither input is numeric.'); + end + + % TODO: fairly hacky for now but it works; should be revisited properly + if isnumeric(A) + al = repmat([B(1, :).alegs], szA(1), 1); + B = reshape([B.var], szB); + else + al = repmat([A(:, 1).alegs], 1, szB(2)); + A = reshape([A.var], szA); + end + Cv = A * B; + + for i = szA(1):-1:1 + for j = szB(2):-1:1 + C(i, j) = MpsTensor(Cv(i, j), al(i, j)); + end + end + + +% if isnumeric(A) +% al = repmat([B(1, :).alegs], szA(1), 1); +% %B = reshape([B.var], szB); +% else +% al = repmat([A(:, 1).alegs], 1, szB(2)); +% %A = reshape([A.var], szA); % this does not zork becquse it qlso needs to hqndle the cqse zhen the vqrs the,selves qre qlreqdy qrrqys; you should reqlly do the ,qnuql loop +% end +% +% for i = szA(1):-1:1 +% for j = szB(2):-1:1 +% C(i, j) = MpsTensor(A(i, 1) * B(1, j), al(i, j)); +% for k = 2:szA(2) +% C(i, j) = C(i, j) + ... +% MpsTensor(A(i, k).var * B(k, j), al(i, j)); +% end +% end +% end + + end + + function C = times(A, B) + if isscalar(A) C = B; - C.var = A * C.var; + for i = 1:numel(C) + C(i).var = A .* B(i).var; + end return end - - if isnumeric(B) + if isscalar(B) C = A; - C.var = C.var * B; + for i = 1:numel(C) + C(i).var = A(i).var .* B; + end return end - error('not implemented when both inputs not numeric.'); + error('not implemented when neither input is scalar.'); end function A = repartition(A, varargin) @@ -239,6 +319,11 @@ n = norm(A.var, varargin{:}); end + function d = distance(A, B) + d = norm(A - B); + end + + function [A, n] = normalize(A) [A.var, n] = normalize(A.var); end @@ -304,8 +389,6 @@ T = FiniteMpo(B', {}, A); end - - function C = initializeC(A) % Initialize a set of gauge tensors for the given mpstensors. arguments (Repeating) @@ -617,5 +700,12 @@ function disp(t) local_tensors{end} = MpsTensor(repartition(psi, [2 1])); end end + + methods (Hidden) + function tdst = zerosLike(t, varargin) + tdst = repmat(0 * t, varargin{:}); + end + end + end diff --git a/src/mps/PepsSandwich.m b/src/mps/PepsSandwich.m index d14e729..8ef343d 100644 --- a/src/mps/PepsSandwich.m +++ b/src/mps/PepsSandwich.m @@ -68,7 +68,7 @@ T PepsSandwich L MpsTensor R MpsTensor - v + v MpsTensor end auxlegs_v = nspaces(v) - 4; auxlegs_l = nspaces(L) - 4; @@ -82,6 +82,7 @@ T.bot.var, [6, 2, -3, 9, 3], ... R, [7, 8, 9, -4, (-(1:auxlegs_r) - 4 - auxlegs_l - auxlegs_v)], ... 'Rank', newrank); + v = MpsTensor(v, auxlegs_v + auxlegs_l + auxlegs_r); end function v = applympo(varargin) @@ -168,10 +169,11 @@ end function t = MpoTensor(T) - fuse_east = Tensor.eye(prod(rightvspace(T)), rightvspace(T)); + fuse_east = Tensor.eye(prod(rightvspace(T)), rightvspace(T)'); fuse_south = Tensor.eye(prod(codomainspace(T)), codomainspace(T)); - fuse_west = Tensor.eye(prod(leftvspace(T))', leftvspace(T)); + fuse_west = Tensor.eye(prod(leftvspace(T)), leftvspace(T)'); fuse_north = Tensor.eye(prod(domainspace(T))', domainspace(T)); + t = MpoTensor(contract(... T.top.var, [1, 2, 4, 6, 8], ... T.bot.var, [1, 3, 5, 7, 9], ... diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index 5133813..d8ea4b0 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -577,10 +577,10 @@ v0 = []; end - eigkwargs.Verbosity = kwargs.Verbosity; + eigopts.Verbosity = kwargs.Verbosity; eigkwargs = namedargs2cell(eigopts); [V, D] = eigsolve(T, v0, howmany, which, eigkwargs{:}); - + if kwargs.Type(1) == 'r', V = V'; end if nargout < 2, V = diag(D); end end @@ -820,7 +820,7 @@ function plot_entanglementspectrum(mps, d, w, ax, kwargs) charge = [] kwargs.Angle kwargs.AngleTol = 1e-1 - kwargs.HowMany = 20 + kwargs.HowMany = 5 end spectrum = transfereigs(mps, mps, kwargs.HowMany, 'largestabs', 'Charge', charge); diff --git a/src/tensors/AbstractTensor.m b/src/tensors/AbstractTensor.m index 1ed4fe6..af197cc 100644 --- a/src/tensors/AbstractTensor.m +++ b/src/tensors/AbstractTensor.m @@ -195,8 +195,10 @@ % tolerance of the algorithm. % % Algorithm : char - % choice of algorithm. Currently only 'eigs' is available, which leverages the - % default Matlab eigs. + % choice of eigensolver algorithm. Currently there is a choice between the use + % of Matlab's buitin `eigs` specified by the identifiers 'eigs' or + % 'KrylovSchur', or the use of a custom Arnolid algorithm specified by + % the identifier 'Arnoldi'. % % MaxIter : int % maximum number of iterations, 100 by default. @@ -233,67 +235,47 @@ A x0 = A.randnc(A.domain, []) howmany = 1 - sigma = 'largestabs' + sigma = 'lm' + + options.Algorithm {mustBeMember(options.Algorithm, ... + {'eigs', 'KrylovSchur', 'Arnoldi'})} = 'Arnoldi' options.Tol = eps(underlyingType(x0))^(3/4) - options.Algorithm = 'eigs' options.MaxIter = 100 options.KrylovDim = 20 - options.IsSymmetric logical = false - options.DeflateDim + options.DeflateDim = 3 options.ReOrth = 2 - options.NoBuild - options.Verbosity = 0 - end - - assert(isnumeric(sigma) || ismember(sigma, {'largestabs', 'smallestabs', ... - 'largestreal', 'smallestreal', 'bothendsreal', ... - 'largestimag', 'smallestimag', 'bothendsimag'}), ... - 'tensors:ArgumentError', 'Invalid choice of eigenvalue selector.'); - nargoutchk(0, 3); - - - x0_vec = vectorize(x0); - sz = size(x0_vec); - if sz(1) < howmany - warning('requested %d out of %d eigenvalues.', howmany, sz(1)); - howmany = min(howmany, sz(1)); - end - - if isa(A, 'function_handle') - A_fun = @(x) vectorize(A(devectorize(x, x0))); - else - A_fun = @(x) vectorize(A * devectorize(x, x0)); + options.NoBuild = 3 + options.Verbosity = Verbosity.warn + options.IsSymmetric logical = false end - - options.KrylovDim = min(sz(1), options.KrylovDim); - - if howmany > sz(1) - 2 - [V, D, flag] = eigs(A_fun, sz(1), howmany, sigma, ... - 'Tolerance', options.Tol, 'MaxIterations', options.MaxIter, ... - 'IsFunctionSymmetric', ... - options.IsSymmetric, 'StartVector', x0_vec, ... - 'Display', options.Verbosity >= 3, 'FailureTreatment', 'keep'); - else - [V, D, flag] = eigs(A_fun, sz(1), howmany, sigma, ... - 'Tolerance', options.Tol, 'MaxIterations', options.MaxIter, ... - 'SubspaceDimension', options.KrylovDim, 'IsFunctionSymmetric', ... - options.IsSymmetric, 'StartVector', x0_vec, ... - 'Display', options.Verbosity >= 3, 'FailureTreatment', 'keep'); + + switch options.Algorithm + + case {'Arnoldi'} + alg_opts = rmfield(options, {'Algorithm', 'IsSymmetric'}); + kwargs = namedargs2cell(alg_opts); + alg = Arnoldi(kwargs{:}); + [V, D, flag] = eigsolve(alg, A, x0, howmany, sigma); + + case {'eigs', 'KrylovSchur'} + alg_opts = rmfield(options, ... + {'Algorithm', 'DeflateDim', 'ReOrth', 'NoBuild', 'IsSymmetric'}); + kwargs = namedargs2cell(alg_opts); + alg = KrylovSchur(kwargs{:}); + [V, D, flag] = eigsolve(alg, A, x0, howmany, sigma, ... + 'IsSymmetric', options.IsSymmetric); + end if nargout <= 1 varargout = {D}; elseif nargout == 2 - for i = howmany:-1:1 - varargout{1}(:, i) = devectorize(V(:, i), x0); - end + varargout{1} = V; varargout{2} = D; else + varargout{1} = V; varargout{2} = D; - for i = howmany:-1:1 - varargout{1}(:, i) = devectorize(V(:, i), x0); - end varargout{3} = flag; end diff --git a/src/tensors/kernels/MatrixBlock.m b/src/tensors/kernels/MatrixBlock.m index 97655ab..fe70412 100644 --- a/src/tensors/kernels/MatrixBlock.m +++ b/src/tensors/kernels/MatrixBlock.m @@ -5,10 +5,10 @@ %#ok<*INUSD> properties charge - var - rowsizes - colsizes - tdims + var = {} + rowsizes = {} + colsizes = {} + tdims = {} rank end @@ -17,6 +17,7 @@ if nargin == 0, return; end rank = [length(codomain) length(domain)]; + b.rank = rank; trees = fusiontrees(codomain, domain); if isempty(trees) @@ -41,8 +42,8 @@ splits = split(trees); fuses = fuse(trees); - b.rank = rank; - b.charge = reshape(c, 1, []); + + b.charge = reshape(c, 1, []); b.var = cell(size(b.charge)); b.rowsizes = cell(size(b.charge)); b.colsizes = cell(size(b.charge)); diff --git a/src/utility/linalg/eigsolve.m b/src/utility/linalg/eigsolve.m index 71d2e80..357b243 100644 --- a/src/utility/linalg/eigsolve.m +++ b/src/utility/linalg/eigsolve.m @@ -1,184 +1,133 @@ -function [V, D, flag] = eigsolve(A, v, howmany, which, kwargs) -%EIGSOLVE Summary of this function goes here -% Detailed explanation goes here +function varargout = eigsolve(A, v, howmany, sigma, options) +% Find a few eigenvalues and eigenvectors of an operator. +% +% Usage +% ----- +% :code:`[V, D, flag] = eigsolve(A, v, howmany, sigma, kwargs)` +% :code:`D = eigsolve(A, v, ...)` +% +% Arguments +% --------- +% A : matrix or function_handle +% A square matrix. +% A function handle which implements one of the following, depending on sigma: +% +% - A \ x, if `sigma` is 0 or 'smallestabs' +% - (A - sigma * I) \ x, if sigma is a nonzero scalar +% - A * x, for all other cases +% +% v : vector +% initial guess for the eigenvector. If A is a :class:`Tensor`, this defaults +% to a random complex :class:`Tensor`, for function handles this is a required +% argument. +% +% howmany : int +% amount of eigenvalues and eigenvectors that should be computed. By default +% this is 1, and this should not be larger than the total dimension of A. +% +% sigma : 'char' or numeric +% selector for the eigenvalues, should be either one of the following: +% +% - 'largestabs', 'largestreal', 'largestimag' : default, eigenvalues of +% largest magnitude, real part or imaginary part. +% - 'smallestabs', 'smallestreal', 'smallestimag' : eigenvalues of smallest +% magnitude, real part or imaginary part. +% - numeric : eigenvalues closest to sigma. +% +% Keyword Arguments +% ----------------- +% Tol : numeric +% tolerance of the algorithm. +% +% Algorithm : char +% choice of eigensolver algorithm. Currently there is a choice between the use +% of Matlab's buitin `eigs` specified by the identifiers 'eigs' or +% 'KrylovSchur', or the use of a custom Arnolid algorithm specified by +% the identifier 'Arnoldi'. +% +% MaxIter : int +% maximum number of iterations, 100 by default. +% +% KrylovDim : int +% number of vectors kept in the Krylov subspace. +% +% IsSymmetric : logical +% flag to speed up the algorithm if the operator is symmetric, false by +% default. +% +% Verbosity : int +% Level of output information, by default nothing is printed if `flag` is +% returned, otherwise only warnings are given. +% +% - 0 : no information +% - 1 : information at failure +% - 2 : information at convergence +% - 3 : information at every iteration +% +% Returns +% ------- +% V : (1, howmany) array +% vector of eigenvectors. +% +% D : numeric +% vector of eigenvalues if only a single output argument is asked, diagonal +% matrix of eigenvalues otherwise. +% +% flag : int +% if flag = 0 then all eigenvalues are converged, otherwise not. arguments A v howmany = 1 - which {mustBeMember(which, {'lm', 'largestabs', 'lr', 'largestreal', ... - 'li', 'largestimag', 'sm', 'smallestabs', 'sr', 'smallestreal', ... - 'si', 'smallestimag'})} = 'lm' - kwargs.Tol = eps(underlyingType(v))^(3/4) - kwargs.Algorithm = 'Arnoldi' - kwargs.MaxIter = 100 - kwargs.KrylovDim {mustBeGreaterThan(kwargs.KrylovDim, howmany)} = 20 - kwargs.DeflateDim - kwargs.ReOrth = 2 - kwargs.NoBuild - kwargs.Verbosity = Verbosity.warn -end + sigma = 'lm' -% Input validations -if ~isfield(kwargs, 'NoBuild') - kwargs.NoBuild = ceil(kwargs.KrylovDim / 10); -end -if ~isfield(kwargs, 'DeflateDim') - kwargs.DeflateDim = max(round(3/5 * kwargs.KrylovDim), howmany); -else - assert(kwargs.DeflateDim < kwargs.KrylovDim, 'eigsolve:argerror', ... - 'Deflate size should be smaller than krylov dimension.') -end -if ~isa(A, 'function_handle') - A = @(x) A * x; -end -if norm(v) < eps(underlyingType(v))^(3/4) - error('eigsolve:inputnorm', 'starting vector should not have zero norm.'); + options.Algorithm {mustBeMember(options.Algorithm, ... + {'eigs', 'KrylovSchur', 'Arnoldi'})} = 'KrylovSchur' + + options.Tol = eps(underlyingType(x0))^(3/4) + options.MaxIter = 100 + options.KrylovDim = 20 + options.DeflateDim + options.ReOrth = 2 + options.NoBuild + options.Verbosity = Verbosity.warn + options.IsSymmetric logical = false end -% Arnoldi storage for Krylov subspace basis -V = zeros(0, kwargs.KrylovDim, 'like', v); +switch options.Algorithm -% Arnoldi storage for Hessenberg matrix -H = zeros(kwargs.KrylovDim, kwargs.KrylovDim, underlyingType(v)); + case {'Arnoldi'} + alg_opts = rmfield(options, {'Algorithm', 'IsSymmetric'}); + kwargs = namedargs2cell(alg_opts); + alg = Arnoldi(kwargs{:}); + [V, D, flag] = eigsolve(alg, A, x0, howmany, sigma); -ctr_outer = 0; -ctr_inner = 0; -flag = 0; + case {'eigs', 'KrylovSchur'} + alg_opts = rmfield(options, ... + {'Algorithm', 'DeflateDim', 'ReOrth', 'NoBuild', 'IsSymmetric'}); + kwargs = namedargs2cell(alg_opts); + alg = KrylovSchur(kwargs{:}); + [V, D, flag] = eigsolve(alg, A, x0, howmany, sigma, ... + 'IsSymmetric', options.IsSymmetric); -while ctr_outer < kwargs.MaxIter - ctr_outer = ctr_outer + 1; - - while ctr_inner < kwargs.KrylovDim % build Krylov subspace - ctr_inner = ctr_inner + 1; - - V(1:length(v), ctr_inner) = v; - v = A(v); - H(1:ctr_inner, ctr_inner) = V(:, 1:ctr_inner)' * v; - v = v - V * H(:, ctr_inner); - - % reorthogonalize new vector - if ctr_inner >= kwargs.ReOrth - c = V(:, 1:ctr_inner)' * v; - H(1:ctr_inner, ctr_inner) = H(1:ctr_inner, ctr_inner) + c; - v = v - V(:, 1:ctr_inner) * c; - end - - % normalize - beta = norm(v, 'fro'); - v = v / beta; - - if ctr_inner == kwargs.KrylovDim, break; end - - if ctr_inner >= howmany - invariantsubspace = beta < eps(underlyingType(beta))^(3/4); - if invariantsubspace || ctr_inner == kwargs.KrylovDim - break; - end - - % check for convergence during subspace build - if ~mod(ctr_inner, kwargs.NoBuild) - [U, lambda] = eig(H(1:ctr_inner, 1:ctr_inner), 'vector'); - select = selecteigvals(lambda, howmany, which); - conv = max(abs(beta * U(ctr_inner, select))); - - if conv < kwargs.Tol - V = V(:, 1:ctr_inner) * U(:, select); - D = diag(lambda(select)); - if kwargs.Verbosity >= Verbosity.conv - fprintf('Conv %2d (%2d/%2d): error = %.5e.\n', ctr_outer, ... - ctr_inner, kwargs.KrylovDim, conv); - end - return - end - - if kwargs.Verbosity >= Verbosity.detail - fprintf('Iter %2d (%2d/%2d):\tlambda = %.5e + %.5ei;\terror = %.5e\n', ... - ctr_outer, ctr_inner, kwargs.KrylovDim, ... - real(lambda(1)), imag(lambda(1)), conv); - end - end - end - - H(ctr_inner + 1, ctr_inner) = beta; - end - - % stopping criterium reached - irrespective of convergence - if ctr_outer == kwargs.MaxIter || ctr_inner ~= kwargs.KrylovDim - [U, lambda] = eig(H(1:ctr_inner, 1:ctr_inner), 'vector'); - select = selecteigvals(lambda, howmany, which); - conv = max(abs(beta * U(ctr_inner, select))); - V = V(:, 1:ctr_inner) * U(:, select); - D = diag(lambda(select)); - - if conv > kwargs.Tol - if invariantsubspace - fprintf('Found invariant subspace.\n'); - flag = 1; - else - fprintf('Reached maxiter without convergence.\n'); - flag = 2; - end - end - return - end - - % deflate Krylov subspace - [U1, T] = schur(H, 'real'); - E = ordeig(T); - select1 = false(size(E)); - select1(selecteigvals(E, kwargs.DeflateDim, which)) = true; - [U1, T] = ordschur(U1, T, select1); - - V = V * U1; - [U, lambda] = eig(T(1:kwargs.DeflateDim, 1:kwargs.DeflateDim), 'vector'); - select = selecteigvals(lambda, howmany, which); - conv = max(abs(beta * U1(kwargs.KrylovDim, 1:kwargs.DeflateDim) * U(:, select))); - - % check for convergence - if conv < kwargs.Tol - V = V(:, 1:kwargs.DeflateDim) * U(:, select); - D = diag(lambda(select)); - if kwargs.Verbosity >= Verbosity.conv - fprintf('Conv %2d: error = %.5e.\n', ctr_outer, conv); - end - return - end - - if kwargs.Verbosity >= Verbosity.iter - fprintf('Iter %2d:\tlambda = %.5e + %.5ei;\terror = %.5e\n', ... - ctr_outer, real(lambda(1)), imag(lambda(1)), conv); - end - - % deflate Krylov subspace - H = zeros(kwargs.KrylovDim, kwargs.KrylovDim, underlyingType(v)); - H(1:kwargs.DeflateDim, 1:kwargs.DeflateDim) = ... - T(1:kwargs.DeflateDim, 1:kwargs.DeflateDim); - H(kwargs.DeflateDim + 1, 1:kwargs.DeflateDim) = ... - beta * U1(kwargs.KrylovDim, 1:kwargs.DeflateDim); -% V(:, kwargs.DeflateDim + 1:end) = 0 * V(:, kwargs.DeflateDim + 1:end); - ctr_inner = kwargs.DeflateDim; end +if nargout <= 1 + varargout = {D}; +elseif nargout == 2 + varargout{1} = V; + varargout{2} = D; +else + varargout{1} = V; + varargout{2} = D; + varargout{3} = flag; end -function select = selecteigvals(lambda, howmany, which) - -switch which - case {'largestabs', 'lm'} - [~, p] = sort(abs(lambda), 'descend'); - case {'largestreal', 'lr'} - [~, p] = sort(real(lambda), 'descend'); - case {'largestimag', 'li'} - [~, p] = sort(imag(lambda), 'descend'); - case {'smallestabs', 'sm'} - [~, p] = sort(abs(lambda), 'ascend'); - case {'smallestreal', 'sr'} - [~, p] = sort(real(lambda), 'ascend'); - case {'smallestimag', 'si'} - [~, p] = sort(imag(lambda), 'ascend'); +if any(flag) + warning('eigsolve did not converge on eigenvalues %s.', num2str(find(flag))); +elseif ~any(flag) && options.Verbosity > 1 + fprintf('eigsolve converged.\n'); end -select = p(1:howmany); - end diff --git a/test/TestAlgorithms.m b/test/TestAlgorithms.m index 05d6814..7254f43 100644 --- a/test/TestAlgorithms.m +++ b/test/TestAlgorithms.m @@ -2,8 +2,9 @@ % Unit tests for algorithms properties (TestParameter) - alg = {Vumps('which', 'smallestreal', 'maxiter', 5), ... - ...IDmrg('which', 'smallestreal', 'maxiter', 5), ... + alg = {... + Vumps('which', 'smallestreal', 'maxiter', 5), ... + IDmrg('which', 'smallestreal', 'maxiter', 5), ... Vumps2('which', 'smallestreal', 'maxiter', 6), ... IDmrg2('which', 'smallestreal', 'maxiter', 5) ... } diff --git a/test/TestFiniteMpo.m b/test/TestFiniteMpo.m index 2a92038..fa44ed8 100644 --- a/test/TestFiniteMpo.m +++ b/test/TestFiniteMpo.m @@ -64,7 +64,7 @@ function test2dIsing(tc) L = 16; D = 64; - alg = Dmrg('maxiter', 10, 'which', 'largestabs'); + alg = Dmrg('maxiter', 10, 'which', 'largestabs', 'verbosity', 2); mpo = statmech2dIsing('beta', beta, 'L', L); vspace_max = CartesianSpace.new(D); diff --git a/test/TestInfJMpo.m b/test/TestInfJMpo.m index 689a6dc..7bf0b5b 100644 --- a/test/TestInfJMpo.m +++ b/test/TestInfJMpo.m @@ -28,7 +28,7 @@ function testEnvironments(tc, mpo, mps) TL = transfermatrix(mpo, mps, mps, 'Type', 'LL'); GL_2 = apply(TL, GL{1}); - lambda2 = overlap(slice(GL_2, length(GL_2)), fp_right); + lambda2 = overlap(slice(GL_2, length(GL_2.var)), fp_right); % TODO: specify interface to get dimensions of single MpsTensor GL_2.var(end) = GL_2.var(end) - lambda2 * fp_left; tc.assertTrue(isapprox(GL_2, GL{1}), ... 'left environment fixed point equation unfulfilled.'); @@ -183,7 +183,7 @@ function test1dIsing(tc) mpo = quantum1dIsing('J', 1, 'h', 1, 'L', Inf); mps = initialize_mps(mpo, CartesianSpace.new(D)); [gs, lambda] = fixedpoint(alg, mpo, mps); - tc.verifyTrue(isapprox(lambda, -0.53, 'RelTol', 1e-2)) + tc.verifyTrue(isapprox(lambda, -1.27, 'RelTol', 1e-2)) p = 0; qp = InfQP.randnc(gs, gs, p); @@ -202,7 +202,7 @@ function test1dIsing(tc) mpo = quantum1dIsing('J', 1, 'h', 1, 'L', Inf, 'Symmetry', 'Z2'); mps = initialize_mps(mpo, GradedSpace.new(Z2(0, 1), [D D] ./ 2, false)); [mps2, lambda2] = fixedpoint(alg, mpo, mps); - tc.verifyTrue(isapprox(lambda2, -0.53, 'RelTol', 1e-2)) + tc.verifyTrue(isapprox(lambda2, -1.27, 'RelTol', 1e-2)) p = 0; qp = InfQP.randnc(mps2, mps2, p, Z2(0)); @@ -212,7 +212,7 @@ function test1dIsing(tc) mpo = [mpo mpo]; mps2 = [mps2 mps2]; [mps2, lambda2] = fixedpoint(alg, mpo, mps2); - tc.verifyTrue(isapprox(lambda2/2, -0.53, 'RelTol', 1e-2)) + tc.verifyTrue(isapprox(lambda2/2, -1.27, 'RelTol', 1e-2)) p = 0; qp = InfQP.randnc(mps2, mps2, p, Z2(0)); diff --git a/test/TestInfMpo.m b/test/TestInfMpo.m index ac60746..fb94490 100644 --- a/test/TestInfMpo.m +++ b/test/TestInfMpo.m @@ -116,15 +116,14 @@ function testDerivatives(tc, mpo, mps) H_AC = AC_hamiltonian(mpo, mps, GL, GR); for i = 1:numel(H_AC) - AC_ = mps.AC(i); - [AC_.var, lambda] = eigsolve(H_AC{i}, mps.AC(i).var, 1, 'largestabs'); + [AC_, lambda] = eigsolve(H_AC{i}, mps.AC{i}, 1, 'largestabs'); AC_2 = apply(H_AC{i}, AC_); - tc.assertTrue(isapprox(AC_2, lambda * AC_.var)); + tc.assertTrue(isapprox(AC_2, lambda * AC_)); end H_C = C_hamiltonian(mpo, mps, GL, GR); for i = 1:numel(H_C) - [C_, lambda] = eigsolve(H_C{i}, mps.C(i), 1, 'largestabs'); + [C_, lambda] = eigsolve(H_C{i}, mps.C{i}, 1, 'largestabs'); tc.assertTrue(isapprox(apply(H_C{i}, C_), lambda * C_)); end end @@ -140,14 +139,17 @@ function test2dIsing(tc) alg = Vumps('MaxIter', 10); mpo = statmech2dIsing('beta', beta, 'Symmetry', 'Z1'); mps = mpo.initialize_mps(CartesianSpace.new(D)); - [mps2, lambda] = fixedpoint(alg, mpo, mps); + [mps2, lambda, GL, GR] = fixedpoint(alg, mpo, mps); tc.assertEqual(-log(lambda) / beta, f, 'RelTol', 1e-5); % compute excitations p = 0; qp = InfQP.randnc(mps2, mps2, p); - GBL = leftquasienvironment(mpo, qp); - + try + GBL = leftquasienvironment(mpo, qp, GL, GR); + catch + tc.verifyFail('InfMpo.leftquasienvironment broken') + end mps = UniformMps.randnc(GradedSpace.new(Z2(0, 1), [1 1], false), ... GradedSpace.new(Z2(0, 1), [D D] ./ 2, false)); diff --git a/test/TestPeps.m b/test/TestPeps.m index a449725..0681661 100644 --- a/test/TestPeps.m +++ b/test/TestPeps.m @@ -11,8 +11,8 @@ fZ2(0, 1), [2 1], false, fZ2(0, 1), [5 5], false), ... 'U1', GradedSpace.new(U1(0, 1, -1), [1 1 1], false, U1(0, 1, -1), [1 2 2], false, ... U1(0, 1, -1), [2 1 1], false, U1(0, 1, -1), [2 1 1], false), ... - 'SU2', GradedSpace.new(SU2(1, 2), [1 1], false, SU2(2), [2], false, ... - SU2(2), [1], false, SU2(1, 2), [2 1], false) ... + 'SU2', GradedSpace.new(SU2(1, 2), [1 1], false, SU2(2, 3), [2, 1], false, ... + SU2(1, 2), [1, 2], false, SU2(1, 2), [2 1], false) ... ) depth = {1} width = {1, 2} @@ -80,15 +80,18 @@ function testDerivatives(tc, spaces, depth, width, dualdepth, dualwidth, samebot H_AC = AC_hamiltonian(mpo, mps, GL, GR); for i = 1:numel(H_AC) - AC_ = mps.AC(i); - [AC_.var, lambda] = eigsolve(H_AC{i}, mps.AC(i).var, 1, 'largestabs'); + [AC_, lambda] = eigsolve(H_AC{i}, mps.AC{i}, 1, 'largestabs'); AC_2 = apply(H_AC{i}, AC_); - tc.assertTrue(isapprox(AC_2, lambda * AC_.var, 'RelTol', 1e-6)); + dist = distance(AC_2, lambda * AC_) / norm(AC_); + if dist > 1e-5 + fprintf('Distance between AC_2 and lambda * AC: %.5e\n', distance(AC_2, lambda * AC_)) + end + tc.assertTrue(isapprox(AC_2, lambda * AC_, 'RelTol', 1e-6)); end H_C = C_hamiltonian(mpo, mps, GL, GR); for i = 1:numel(H_C) - [C_, lambda] = eigsolve(H_C{i}, mps.C(i), 1, 'largestabs'); + [C_, lambda] = eigsolve(H_C{i}, mps.C{i}, 1, 'largestabs'); tc.assertTrue(isapprox(apply(H_C{i}, C_), lambda * C_, 'RelTol', 1e-6)); end end diff --git a/test/TestTensor.m b/test/TestTensor.m index f957a1b..3e545a8 100644 --- a/test/TestTensor.m +++ b/test/TestTensor.m @@ -36,13 +36,14 @@ function classSetup(tc) ProductCharge(U1(-1:1), SU2(2,1,2)), [1 2 2], false), ... 'Hubbard', GradedSpace.new(... ProductCharge(U1(0, 1, 2), SU2(1, 2, 1), fZ2(0, 1, 0)), [1 1 1], false, ... - ProductCharge(U1(-3:1), SU2(1, 2, 1, 2, 1), fZ2(0, 1, 0, 1, 0)), [1 1 3 1 1], false, ... + ProductCharge(U1(-3:1), SU2(2, 1, 2, 1, 2), fZ2(0, 1, 0, 1, 0)), [1 1 3 1 1], false, ... ProductCharge(U1(0, 1), SU2(1, 2), fZ2(0, 1)), [1 1], true, ... ProductCharge(U1(0, 1, 2), SU2(1, 2, 1), fZ2(0, 1, 0)), [1 1 1], false, ... - ProductCharge(U1(0, 1), SU2(2, 1), fZ2(1, 0)), [1 1], true) ... + ProductCharge(U1(0, 1), SU2(1, 2), fZ2(1, 0)), [1 1], true) ... ) end + methods (Test) %% General properties function basic_linear_algebra(tc, spaces) From fede5775e9ad3ed8b1dc55036ecbfb15bffae353 Mon Sep 17 00:00:00 2001 From: leburgel Date: Wed, 6 Sep 2023 23:46:37 +0200 Subject: [PATCH 238/245] Fix merge artefacts --- src/algorithms/Expand.m | 2 +- src/mps/UniformMps.m | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/algorithms/Expand.m b/src/algorithms/Expand.m index b1fec87..2420e01 100644 --- a/src/algorithms/Expand.m +++ b/src/algorithms/Expand.m @@ -144,7 +144,7 @@ % perform twosite update, take SVD and normalize [GL, GR] = environments(Vumps, mpo, mps); % should be able to input this... H_AC2 = AC2_hamiltonian(mpo, mps, GL, GR, w); - AC2 = computeAC2(mps, 1, w); + AC2 = computeAC2(mps, dd, w); [AC2, ~] = eigsolve(H_AC2{1}, AC2.var, 1, alg.which); if strcmp(alg.bondsmethod, 'twosite') %twosite fixed point diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index 793a889..bd83e6c 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -346,18 +346,18 @@ assert(mps(d).AC{w}.alegs == 0, 'tba'); AC2 = MpsTensor(contract(... mps(d).AC{w}, ... - [-(1:mps(dd).AC{w}.plegs+1), 1], ... + [-(1:mps(d).AC{w}.plegs+1), 1], ... mps(d).AR{ww}, ... - [1, -(1:mps(dd).AR{ww}.plegs+1) - 1 - mps(dd).AR{ww}.plegs], ... - 'Rank', [1 + mps(dd).AC{w}.plegs, 1 + mps(dd).AR{ww}.plegs])); + [1, -(1:mps(d).AR{ww}.plegs+1) - 1 - mps(d).AR{ww}.plegs], ... + 'Rank', [1 + mps(d).AC{w}.plegs, 1 + mps(d).AR{ww}.plegs])); elseif strcmp(type, 'L') assert(mps(d).AC{ww}.alegs == 0, 'tba'); AC2 = MpsTensor(contract(... mps(d).AL{w}, ... - [-(1:mps(dd).AL{w}.plegs+1), 1], ... + [-(1:mps(d).AL{w}.plegs+1), 1], ... mps(d).AC{ww}, ... - [1, -(1:mps(dd).AR{ww}.plegs+1) - 1 - mps(dd).AC{ww}.plegs], ... - 'Rank', [1 + mps(dd).AL{w}.plegs, 1 + mps(dd).AR{ww}.plegs])); + [1, -(1:mps(d).AR{ww}.plegs+1) - 1 - mps(d).AC{ww}.plegs], ... + 'Rank', [1 + mps(d).AL{w}.plegs, 1 + mps(d).AR{ww}.plegs])); else error('mps:argerror', 'invalid type %s', type) end From 43ade9b11d19363b6df00697ab0253e226e031b1 Mon Sep 17 00:00:00 2001 From: leburgel Date: Thu, 7 Sep 2023 14:26:54 +0200 Subject: [PATCH 239/245] Final merge artefact and aesthetics --- src/algorithms/Expand.m | 7 +++---- src/algorithms/QPAnsatz.m | 2 +- src/tensors/charges/ProductCharge.m | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/algorithms/Expand.m b/src/algorithms/Expand.m index 2420e01..2d19685 100644 --- a/src/algorithms/Expand.m +++ b/src/algorithms/Expand.m @@ -145,12 +145,11 @@ [GL, GR] = environments(Vumps, mpo, mps); % should be able to input this... H_AC2 = AC2_hamiltonian(mpo, mps, GL, GR, w); AC2 = computeAC2(mps, dd, w); - [AC2, ~] = eigsolve(H_AC2{1}, AC2.var, 1, alg.which); if strcmp(alg.bondsmethod, 'twosite') - %twosite fixed point - [AC2, ~] = eigsolve(H_AC2{1}, AC2, 1, alg); + % twosite fixed point + [AC2, ~] = eigsolve(H_AC2{1}, AC2, 1, alg.which); else - %single application + % single application AC2 = apply(H_AC2{1}, AC2); end [~, C2, ~] = tsvd(AC2.var, ... diff --git a/src/algorithms/QPAnsatz.m b/src/algorithms/QPAnsatz.m index 8b7fbfc..5a59c98 100644 --- a/src/algorithms/QPAnsatz.m +++ b/src/algorithms/QPAnsatz.m @@ -2,7 +2,7 @@ % Quasi-Particle excitation ansatz properties - alg_eigs = Arnoldi('MaxIter', 100, 'KrylovDim', 30, 'Tol', 1e-8, 'verbosity', Verbosity.diagnostics) + alg_eigs = Arnoldi('MaxIter', 100, 'KrylovDim', 30, 'Tol', 1e-8, 'Verbosity', Verbosity.diagnostics) alg_environments = struct('Tol', 1e-10, 'Algorithm', 'bicgstabl'); howmany = 1 which = 'smallestreal' diff --git a/src/tensors/charges/ProductCharge.m b/src/tensors/charges/ProductCharge.m index cc0ebf9..5f228c7 100644 --- a/src/tensors/charges/ProductCharge.m +++ b/src/tensors/charges/ProductCharge.m @@ -123,7 +123,7 @@ d_ = subsref(d, substruct('()', {1})) * ... a_i; if nargout > 1 - N_ = repmat(N(1),1,length(d_)); + N_ = repmat(N(1), 1, length(d_)); end for j = 2:length(d) From e4803b961c97f3ffaccc280e46baa854971022f0 Mon Sep 17 00:00:00 2001 From: Lander Burgelman <39218680+leburgel@users.noreply.github.com> Date: Thu, 14 Sep 2023 14:11:18 +0200 Subject: [PATCH 240/245] Speed up `Arnoldi` solver (#21) * Use plain arrays in `Arnoldi.eigsolve` * Add `InfQP` solver utilities * Better Krylov dimension warning * Fix Krylov subspace deflation * Add/tweak `eigsolve` docstrings, improve warning handling and remove redundant 'optimizations' * Doc and warning update * One more warning fix --- src/algorithms/eigsolvers/Arnoldi.m | 148 +++++++++++++++++++++--- src/algorithms/eigsolvers/KrylovSchur.m | 90 ++++++++++++-- src/mps/FiniteMpo.m | 5 +- src/mps/InfQP.m | 66 +++++++++++ src/mps/MpsTensor.m | 21 ---- src/mps/UniformMps.m | 1 + src/tensors/AbstractTensor.m | 38 +++--- src/tensors/Tensor.m | 2 +- src/utility/linalg/eigsolve.m | 41 +++---- 9 files changed, 307 insertions(+), 105 deletions(-) diff --git a/src/algorithms/eigsolvers/Arnoldi.m b/src/algorithms/eigsolvers/Arnoldi.m index 162284e..2902bb7 100644 --- a/src/algorithms/eigsolvers/Arnoldi.m +++ b/src/algorithms/eigsolvers/Arnoldi.m @@ -5,7 +5,7 @@ tol = 1e-10 % convergence tolerance maxiter = 100 % maximum iterations krylovdim = 20 % Krylov subspace dimension - deflatedim = 3 % number of Krylov vectors to keep when deflating + deflatedim % number of Krylov vectors to keep when deflating reorth = 20 % reorthogonalize basis if larger than this number nobuild = 3 % frequency of convergence check when building verbosity = Verbosity.warn % display information @@ -24,39 +24,116 @@ end end - function [V, D, flag] = eigsolve(alg, A, v, howmany, sigma) + function varargout = eigsolve(alg, A, v0, howmany, sigma) + % Find a few eigenvalues and eigenvectors of an operator using an Arnoldi + % routine. + % + % Usage + % ----- + % :code:`[V, D, flag] = eigsolve(A, v, howmany, sigma)` + % :code:`D = eigsolve(A, v, ...)` + % + % Arguments + % --------- + % A : matrix or function_handle + % A square matrix. + % A function handle which implements one of the following, depending on sigma: + % + % - A \ x, if `sigma` is 0 or 'smallestabs' + % - (A - sigma * I) \ x, if sigma is a nonzero scalar + % - A * x, for all other cases + % + % v : vector + % initial guess for the eigenvector. + % + % howmany : int + % amount of eigenvalues and eigenvectors that should be computed. By default + % this is 1, and this should not be larger than the total dimension of A. + % + % sigma : `char` or numeric + % selector for the eigenvalues, should be either one of the following: + % + % - 'largestabs', 'lm': default, eigenvalues of largest magnitude + % - 'largestreal', 'lr': eigenvalues with largest real part + % - 'largestimag', 'li': eigenvalues with largest imaginary part. + % - 'smallestabs', 'sm': default, eigenvalues of smallest magnitude + % - 'smallestreal', 'sr': eigenvalues with smallest real part + % - 'smallestimag', 'si': eigenvalues with smallest imaginary part. + % + % Returns + % ------- + % V : (1, howmany) array + % vector of eigenvectors. + % + % D : numeric + % vector of eigenvalues if only a single output argument is asked, diagonal + % matrix of eigenvalues otherwise. + % + % flag : int + % - flag = 0: all eigenvalues are converged. + % - flag = 1: invariant subspace was found and the algorithm was aborted. + % - flag = 2: algorithm did not converge after maximum number of iterations. + arguments alg A - v + v0 howmany = 1 sigma = 'lm' end t_total = tic; - if ~isa(A, 'function_handle') - A = @(x) A * x; + if isnumeric(v0) + v0_vec = v0; + if isa(A, 'function_handle') + A_fun = @A; + else + A_fun = @(v) A * v; + end + else + v0_vec = vectorize(v0); + if isa(A, 'function_handle') + A_fun = @(v) vectorize(A(devectorize(v, v0))); + else + A_fun = @(v) vectorize(A * devectorize(v, v0)); + end end - if norm(v) < eps(underlyingType(v))^(3/4) + if norm(v0_vec) < eps(underlyingType(v0_vec))^(3/4) error('eigsolve:inputnorm', 'starting vector should not have zero norm.'); end + sz = size(v0_vec); + + if sz(1) < howmany + howmany = sz(1); + if alg.verbosity >= Verbosity.warn + warning('eigsolve:size', 'requested %d out of %d eigenvalues.', ... + howmany, sz(1)); + end + end + + if sz(1) < alg.krylovdim + alg.krylovdim = sz(1); + if alg.verbosity >= Verbosity.warn + warning('eigsolve:size', ... + 'Krylov subspace dimension is larger than total number of eigenvalues, reducing Krylov dimension to %d.', ... + sz(1)); + end + end + % some input validation if isempty(alg.nobuild), alg.nobuild = ceil(alg.krylovdim / 10); end if isempty(alg.deflatedim), alg.deflatedim = max(round(3/5 * alg.krylovdim), howmany); end + alg.deflatedim = max(alg.deflatedim, howmany); assert(alg.deflatedim < alg.krylovdim, 'eigsolve:argerror', ... 'Deflate size should be smaller than krylov dimension.') - v = v / norm(v, 'fro'); + v = v0_vec / norm(v0_vec, 'fro'); % preallocation - if isnumeric(v) && isvector(v) - V = zeros(length(v), alg.krylovdim, 'like', v); - else - V = zeros(1, alg.krylovdim, 'like', v); % Krylov subspace basis - end + V = zeros(length(v), alg.krylovdim, 'like', v); % Krylov subspace basis H = zeros(alg.krylovdim, alg.krylovdim, underlyingType(v)); % Hessenberg matrix ctr_outer = 0; @@ -67,12 +144,13 @@ t_outer = tic; ctr_outer = ctr_outer + 1; + flag_inner = 0; while ctr_inner < alg.krylovdim % build Krylov subspace t_inner = tic; ctr_inner = ctr_inner + 1; V(:, ctr_inner) = v; - v = A(v); + v = A_fun(v); for i = 1:ctr_inner H(i, ctr_inner) = dot(V(:, i), v); end @@ -119,7 +197,8 @@ real(lambda(1)), imag(lambda(1)), conv, ... time2str(toc(t_total))); end - return + flag_inner = 1; + break end if alg.verbosity >= Verbosity.detail @@ -133,6 +212,9 @@ H(ctr_inner + 1, ctr_inner) = beta; end + if flag_inner + break + end % stopping criterium reached - irrespective of convergence if ctr_outer == alg.maxiter || ctr_inner ~= alg.krylovdim @@ -144,14 +226,12 @@ if conv > alg.tol if invariantsubspace - warning('Found invariant subspace (error = %.5e).\n', conv); flag = 1; else - warning('Reached maxiter without convergence.\n'); flag = 2; end end - return + break end % deflate Krylov subspace @@ -164,6 +244,7 @@ V = V * U1; [U, lambda] = eig(T(1:alg.deflatedim, 1:alg.deflatedim), 'vector'); select = selecteigvals(lambda, howmany, sigma); + conv = max(abs(beta * U1(alg.krylovdim, 1:alg.deflatedim) * U(:, select))); % check for convergence @@ -174,7 +255,7 @@ fprintf('Conv %2d:\tlambda = %.5e + %.5ei;\terror = %.5e;\ttime = %s.\n', ... ctr_outer, real(lambda(1)), imag(lambda(1)), conv, time2str(toc(t_outer))); end - return + break end if alg.verbosity >= Verbosity.iter @@ -191,6 +272,37 @@ V(:, alg.deflatedim + 1:end) = 0 * V(:, alg.deflatedim + 1:end); ctr_inner = alg.deflatedim; end + + % process results + if nargout <= 1 + varargout = {diag(D)}; + else + if ~isnumeric(v0) + for i = howmany:-1:1 + varargout{1}(:, i) = devectorize(V(:, i), v0); + end + else + for i = howmany:-1:1 + varargout{1}(:, i) = V(:, i); + end + end + varargout{2} = D; + if nargout == 3 + varargout{3} = flag; + end + end + + % display + if nargout < 3 + if flag == 1 + warning('Found invariant subspace (error = %.5e).\n', conv); + elseif flag == 2 + warning('Reached maxiter without convergence.\n'); + end + end + if ~flag && alg.verbosity > Verbosity.warn + fprintf('eigsolve converged.\n'); + end end function [w0, flag] = expsolve(alg, A, t, v0) diff --git a/src/algorithms/eigsolvers/KrylovSchur.m b/src/algorithms/eigsolvers/KrylovSchur.m index 302b63c..bce97ee 100644 --- a/src/algorithms/eigsolvers/KrylovSchur.m +++ b/src/algorithms/eigsolvers/KrylovSchur.m @@ -22,6 +22,64 @@ end function varargout = eigsolve(alg, A, v0, howmany, sigma, kwargs) + % Find a few eigenvalues and eigenvectors of an operator using the builtin + % Matlab eigs routine. + % + % Usage + % ----- + % :code:`[V, D, flag] = eigsolve(A, v, howmany, sigma, kwargs)` + % :code:`D = eigsolve(A, v, ...)` + % + % Arguments + % --------- + % A : matrix or function_handle + % A square matrix. + % A function handle which implements one of the following, depending on sigma: + % + % - A \ x, if `sigma` is 0 or 'smallestabs' + % - (A - sigma * I) \ x, if sigma is a nonzero scalar + % - A * x, for all other cases + % + % v : vector + % initial guess for the eigenvector. + % + % howmany : int + % amount of eigenvalues and eigenvectors that should be computed. By default + % this is 1, and this should not be larger than the total dimension of A. + % + % sigma : `char` or numeric + % selector for the eigenvalues, should be either one of the following: + % + % - 'largestabs', 'lm': default, eigenvalues of largest magnitude + % - 'largestreal', 'lr': eigenvalues with largest real part + % - 'largestimag', 'li': eigenvalues with largest imaginary part. + % - 'smallestabs', 'sm': default, eigenvalues of smallest magnitude + % - 'smallestreal', 'sr': eigenvalues with smallest real part + % - 'smallestimag', 'si': eigenvalues with smallest imaginary part. + % - 'bothendsreal', 'be': both ends, with howmany/2 values with largest and + % smallest real part respectively. + % - 'bothendsimag', 'li': both ends, with howmany/2 values with largest and + % smallest imaginary part respectively. + % - numeric : eigenvalues closest to sigma. + % + % Keyword Arguments + % ----------------- + % IsSymmetric : logical + % flag to speed up the algorithm if the operator is symmetric, false by + % default. + % + % Returns + % ------- + % V : (1, howmany) array + % vector of eigenvectors. + % + % D : numeric + % vector of eigenvalues if only a single output argument is asked, diagonal + % matrix of eigenvalues otherwise. + % + % flag : int + % if flag = 0 then all eigenvalues are converged, otherwise not. + arguments alg A @@ -31,10 +89,6 @@ kwargs.IsSymmetric logical = false end - assert(isnumeric(sigma) || ismember(sigma, {'largestabs', 'smallestabs', ... - 'largestreal', 'smallestreal', 'bothendsreal', ... - 'largestimag', 'smallestimag', 'bothendsimag'}), ... - 'tensors:ArgumentError', 'Invalid choice of eigenvalue selector.'); nargoutchk(0, 3); if isnumeric(v0) @@ -56,15 +110,20 @@ sz = size(v0_vec); if sz(1) < howmany - warning('eigsolve:size', 'requested %d out of %d eigenvalues.', ... - howmany, sz(1)); howmany = sz(1); + if alg.verbosity >= Verbosity.warn + warning('eigsolve:size', 'requested %d out of %d eigenvalues.', ... + howmany, sz(1)); + end end if sz(1) < alg.krylovdim - warning('eigsolve:size', 'requested %d out of %d eigenvalues.', ... - howmany, sz(1)); alg.krylovdim = sz(1); + if alg.verbosity >= Verbosity.warn + warning('eigsolve:size', ... + 'Krylov subspace dimension is larger than total number of eigenvalues, reducing Krylov dimension to %d.', ... + sz(1)); + end end % call builtin eigs @@ -85,12 +144,16 @@ % process results if nargout <= 1 - varargout = {D}; + varargout = {diag(D)}; else if ~isnumeric(v0) for i = howmany:-1:1 varargout{1}(:, i) = devectorize(V(:, i), v0); end + else + for i = howmany:-1:1 + varargout{1}(:, i) = V(:, i); + end end varargout{2} = D; if nargout == 3 @@ -99,9 +162,12 @@ end % display - if flag - warning('eigsolve did not converge.'); - elseif ~flag && alg.verbosity > Verbosity.warn + if nargout < 3 + if flag + warning('eigsolve did not converge.'); + end + end + if ~flag && alg.verbosity > Verbosity.warn fprintf('eigsolve converged.\n'); end end diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index d8309ed..a1b870f 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -34,7 +34,7 @@ v = v - overlap(v, fp2) * repartition(fp1, rank(v)); end - function [V, D, flag] = eigsolve(mpo, v0, howmany, sigma, options) + function varargout = eigsolve(mpo, v0, howmany, sigma, options) arguments mpo v0 = [] @@ -55,7 +55,8 @@ if isempty(v0), v0 = initialize_fixedpoint(mpo(1)); end kwargs = namedargs2cell(options); - [V, D, flag] = eigsolve(@(x) mpo.apply(x), v0, howmany, sigma, kwargs{:}); + [varargout{1:nargout}] = ... + eigsolve(@(x) mpo.apply(x), v0, howmany, sigma, kwargs{:}); end function v = initialize_fixedpoint(mpo) diff --git a/src/mps/InfQP.m b/src/mps/InfQP.m index b47f6b1..170bf57 100644 --- a/src/mps/InfQP.m +++ b/src/mps/InfQP.m @@ -278,6 +278,72 @@ end end + + %% Solvers + methods + function v = vectorize(qp, type) + % Collect all parameters in a vector, weighted to reproduce the correct + % inner product. + % + % Arguments + % --------- + % qp : :class:`InfQP` + % input quasi-particle state. + % + % type : 'real' or 'complex' + % optionally specify if complex entries should be seen as 1 or 2 parameters. + % Defaults to 'complex', with complex parameters. + % + % Returns + % ------- + % v : numeric + % real or complex vector containing the parameters of the quasi-particle + % state. + + arguments + qp + type = 'complex' + end + + v = vectorize([qp.X{:}], type); + end + + function qp = devectorize(v, qp, type) + % Collect all parameters from a vector, and insert into a quasi-particle state. + % + % Arguments + % --------- + % v : numeric + % real or complex vector containing the parameters of the quasi-particle + % state. + % + % qp : :class:`InfQP` + % input quasi-particle state. + % + % type : 'real' or 'complex' + % optionally specify if complex entries should be seen as 1 or 2 parameters. + % Defaults to 'complex', with complex parameters. + % + % Returns + % ------- + % qp : :class:`InfQP` + % output quasi-particle state, filled with the parameters. + + arguments + v + qp + type = 'complex' + end + + Xs = devectorize(v, [qp.X{:}], type); + for i = 1:numel(qp.X) + qp.X{i} = Xs(i); + end + end + end + + + %% methods function p = period(qp) p = length(qp.X); diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index 9adb842..8385343 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -197,7 +197,6 @@ end function C = mtimes(A, B) - if isscalar(A) || isscalar(B) C = A .* B; return @@ -230,26 +229,6 @@ C(i, j) = MpsTensor(Cv(i, j), al(i, j)); end end - - -% if isnumeric(A) -% al = repmat([B(1, :).alegs], szA(1), 1); -% %B = reshape([B.var], szB); -% else -% al = repmat([A(:, 1).alegs], 1, szB(2)); -% %A = reshape([A.var], szA); % this does not zork becquse it qlso needs to hqndle the cqse zhen the vqrs the,selves qre qlreqdy qrrqys; you should reqlly do the ,qnuql loop -% end -% -% for i = szA(1):-1:1 -% for j = szB(2):-1:1 -% C(i, j) = MpsTensor(A(i, 1) * B(1, j), al(i, j)); -% for k = 2:szA(2) -% C(i, j) = C(i, j) + ... -% MpsTensor(A(i, k).var * B(k, j), al(i, j)); -% end -% end -% end - end function C = times(A, B) diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index bd83e6c..feb1d6b 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -564,6 +564,7 @@ mps2 = mps1 howmany = min(20, dim(leftvspace(mps1, 1) * leftvspace(mps2, 1)')) which = 'largestabs' + eigopts.Algorithm = 'Arnoldi' eigopts.KrylovDim = 100 eigopts.MaxIter = 1000 eigopts.ReOrth = 2 diff --git a/src/tensors/AbstractTensor.m b/src/tensors/AbstractTensor.m index af197cc..9680142 100644 --- a/src/tensors/AbstractTensor.m +++ b/src/tensors/AbstractTensor.m @@ -180,13 +180,19 @@ % amount of eigenvalues and eigenvectors that should be computed. By default % this is 1, and this should not be larger than the total dimension of A. % - % sigma : 'char' or numeric + % sigma : `char` or numeric % selector for the eigenvalues, should be either one of the following: % - % - 'largestabs', 'largestreal', 'largestimag' : default, eigenvalues of - % largest magnitude, real part or imaginary part. - % - 'smallestabs', 'smallestreal', 'smallestimag' : eigenvalues of smallest - % magnitude, real part or imaginary part. + % - 'largestabs', 'lm': default, eigenvalues of largest magnitude + % - 'largestreal', 'lr': eigenvalues with largest real part + % - 'largestimag', 'li': eigenvalues with largest imaginary part. + % - 'smallestabs', 'sm': default, eigenvalues of smallest magnitude + % - 'smallestreal', 'sr': eigenvalues with smallest real part + % - 'smallestimag', 'si': eigenvalues with smallest imaginary part. + % - 'bothendsreal', 'be': both ends, with howmany/2 values with largest and + % smallest real part respectively. + % - 'bothendsimag', 'li': both ends, with howmany/2 values with largest and + % smallest imaginary part respectively. % - numeric : eigenvalues closest to sigma. % % Keyword Arguments @@ -243,7 +249,6 @@ options.Tol = eps(underlyingType(x0))^(3/4) options.MaxIter = 100 options.KrylovDim = 20 - options.DeflateDim = 3 options.ReOrth = 2 options.NoBuild = 3 options.Verbosity = Verbosity.warn @@ -256,34 +261,17 @@ alg_opts = rmfield(options, {'Algorithm', 'IsSymmetric'}); kwargs = namedargs2cell(alg_opts); alg = Arnoldi(kwargs{:}); - [V, D, flag] = eigsolve(alg, A, x0, howmany, sigma); + [varargout{1:nargout}] = eigsolve(alg, A, x0, howmany, sigma); case {'eigs', 'KrylovSchur'} alg_opts = rmfield(options, ... {'Algorithm', 'DeflateDim', 'ReOrth', 'NoBuild', 'IsSymmetric'}); kwargs = namedargs2cell(alg_opts); alg = KrylovSchur(kwargs{:}); - [V, D, flag] = eigsolve(alg, A, x0, howmany, sigma, ... + [varargout{1:nargout}] = eigsolve(alg, A, x0, howmany, sigma, ... 'IsSymmetric', options.IsSymmetric); end - - if nargout <= 1 - varargout = {D}; - elseif nargout == 2 - varargout{1} = V; - varargout{2} = D; - else - varargout{1} = V; - varargout{2} = D; - varargout{3} = flag; - end - - if any(flag) - warning('eigsolve did not converge on eigenvalues %s.', num2str(find(flag))); - elseif ~any(flag) && options.Verbosity > 1 - fprintf('eigsolve converged.\n'); - end end function C = contract(tensors, indices, kwargs) diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 04d4acc..99da0c0 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -2296,7 +2296,7 @@ methods function v = vectorize(t, type) % Collect all parameters in a vector, weighted to reproduce the correct - % inproduct. + % inner product. % % Arguments % --------- diff --git a/src/utility/linalg/eigsolve.m b/src/utility/linalg/eigsolve.m index 357b243..860486c 100644 --- a/src/utility/linalg/eigsolve.m +++ b/src/utility/linalg/eigsolve.m @@ -25,13 +25,19 @@ % amount of eigenvalues and eigenvectors that should be computed. By default % this is 1, and this should not be larger than the total dimension of A. % -% sigma : 'char' or numeric +% sigma : `char` or numeric % selector for the eigenvalues, should be either one of the following: % -% - 'largestabs', 'largestreal', 'largestimag' : default, eigenvalues of -% largest magnitude, real part or imaginary part. -% - 'smallestabs', 'smallestreal', 'smallestimag' : eigenvalues of smallest -% magnitude, real part or imaginary part. +% - 'largestabs', 'lm': default, eigenvalues of largest magnitude +% - 'largestreal', 'lr': eigenvalues with largest real part +% - 'largestimag', 'li': eigenvalues with largest imaginary part. +% - 'smallestabs', 'sm': default, eigenvalues of smallest magnitude +% - 'smallestreal', 'sr': eigenvalues with smallest real part +% - 'smallestimag', 'si': eigenvalues with smallest imaginary part. +% - 'bothendsreal', 'be': both ends, with howmany/2 values with largest and +% smallest real part respectively. +% - 'bothendsimag', 'li': both ends, with howmany/2 values with largest and +% smallest imaginary part respectively. % - numeric : eigenvalues closest to sigma. % % Keyword Arguments @@ -83,9 +89,9 @@ sigma = 'lm' options.Algorithm {mustBeMember(options.Algorithm, ... - {'eigs', 'KrylovSchur', 'Arnoldi'})} = 'KrylovSchur' + {'eigs', 'KrylovSchur', 'Arnoldi'})} = 'Arnoldi' - options.Tol = eps(underlyingType(x0))^(3/4) + options.Tol = eps(underlyingType(v))^(3/4) options.MaxIter = 100 options.KrylovDim = 20 options.DeflateDim @@ -101,33 +107,16 @@ alg_opts = rmfield(options, {'Algorithm', 'IsSymmetric'}); kwargs = namedargs2cell(alg_opts); alg = Arnoldi(kwargs{:}); - [V, D, flag] = eigsolve(alg, A, x0, howmany, sigma); + [varargout{1:nargout}] = eigsolve(alg, A, v, howmany, sigma); case {'eigs', 'KrylovSchur'} alg_opts = rmfield(options, ... {'Algorithm', 'DeflateDim', 'ReOrth', 'NoBuild', 'IsSymmetric'}); kwargs = namedargs2cell(alg_opts); alg = KrylovSchur(kwargs{:}); - [V, D, flag] = eigsolve(alg, A, x0, howmany, sigma, ... + [varargout{1:nargout}] = eigsolve(alg, A, v, howmany, sigma, ... 'IsSymmetric', options.IsSymmetric); end -if nargout <= 1 - varargout = {D}; -elseif nargout == 2 - varargout{1} = V; - varargout{2} = D; -else - varargout{1} = V; - varargout{2} = D; - varargout{3} = flag; -end - -if any(flag) - warning('eigsolve did not converge on eigenvalues %s.', num2str(find(flag))); -elseif ~any(flag) && options.Verbosity > 1 - fprintf('eigsolve converged.\n'); -end - end From 2f3a9d58765073bb981cca429614034f95805b01 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 25 Sep 2023 22:00:51 +0200 Subject: [PATCH 241/245] Add various convertors --- src/mps/MpoTensor.m | 6 +++++ src/sparse/SparseTensor.m | 27 ++++++++++++++++++-- src/utility/jl2mat.m | 43 ++++++++++++++++++++++++++++++++ src/utility/mat2jl.m | 26 +++++++++++++++++++ src/utility/sparse/SparseArray.m | 5 ++++ 5 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 src/utility/jl2mat.m create mode 100644 src/utility/mat2jl.m diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index 48072f8..0c99587 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -424,6 +424,12 @@ function disp(O) builtin('disp', O); end + + function jl = mat2jl(a) + jl = struct('classname', 'MpoTensor'); + jl.tensors = mat2jl(a.tensors); + jl.scalars = mat2jl(a.scalars); + end end methods (Static) diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index c0015e5..6298790 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -169,13 +169,27 @@ function [codomain, domain] = deduce_spaces(t) spaces = cell(1, ndims(t)); for i = 1:length(spaces) + todo = false(1, size(t, i)); for j = flip(1:size(t, i)) idx = find(t.ind(:, i) == j, 1); if isempty(idx) - error('sparse:argerror', ... + warning('sparse:argerror', ... 'Cannot deduce %dth space at index %d.', i, j); + todo(j) = true; + else + spaces{i}(j) = space(t.var(idx), i); + end + end + + if any(todo) + I = find(~todo, 1); + E = one(spaces{i}(I)); + if isdual(spaces{i}(I)) + E = conj(E); + end + for J = find(todo) + spaces{i}(J) = E; end - spaces{i}(j) = space(t.var(idx), i); end end Nout = indout(t.var(1)); @@ -338,6 +352,15 @@ function disp(t) bools(sub2ind_(b.sz, b.ind)) = false; bools(sub2ind_(a.sz, inds)) = a.var(ia) == b.var(ib); end + + function jl = mat2jl(a) + jl = struct('classname', 'SparseTensor'); + jl.codomain = mat2jl(a.codomain); + jl.domain = mat2jl(a.domain); + jl.ind = mat2jl(a.ind); + jl.sz = mat2jl(a.sz); + jl.var = mat2jl(a.var); + end end %% Linear Algebra diff --git a/src/utility/jl2mat.m b/src/utility/jl2mat.m new file mode 100644 index 0000000..dd1ef29 --- /dev/null +++ b/src/utility/jl2mat.m @@ -0,0 +1,43 @@ +function obj = jl2mat(obj) +% Recursively convert a Julia object back into something read by TensorTrack. + +if isstruct(obj) + fns = fieldnames(obj); + for i = 1:numel(obj) + for fn = fns' + obj(i).(fn{1}) = jl2mat(obj(i).(fn{1})); + end + end + if isfield(obj, 'classname') + obj = str2obj(obj); + end +elseif iscell(obj) + obj = cellfun(@jl2mat, obj, 'UniformOutput', false); +end + +end + +function o = str2obj(s) + +if numel(s) > 1 + for i = numel(s):-1:1 + o(i) = str2obj(s(i)); + end + o = reshape(o, size(s)); + return +end + +if ismember('Data', fieldnames(s)) + o = feval(s.classname, s.Data); +else + o = feval(s.classname); +end + +for name = fieldnames(s)' + if strcmp(name{1}, 'classname') || strcmp(name{1}, 'Data') + continue; + end + o.(name{1}) = s.(name{1}); +end + +end \ No newline at end of file diff --git a/src/utility/mat2jl.m b/src/utility/mat2jl.m new file mode 100644 index 0000000..dd30f7e --- /dev/null +++ b/src/utility/mat2jl.m @@ -0,0 +1,26 @@ +function obj = mat2jl(obj) +% Recursively convert an object into something that can be read by Julia (GlueKit) + +if isobject(obj) && ~strcmp(class(obj), "string") + name = class(obj); + orig = warning('off', 'MATLAB:structOnObject'); + obj = arrayfun(@struct, obj); + warning(orig); + for i = 1:numel(obj) + obj(i).classname = name; + end +end + +if isstruct(obj) + fns = fieldnames(obj); + for i = 1:numel(obj) + for fn = fns' + obj(i).(fn{1}) = mat2jl(obj(i).(fn{1})); + end + end +elseif iscell(obj) + obj = cellfun(@mat2jl, obj, 'UniformOutput', false); +end + +end + diff --git a/src/utility/sparse/SparseArray.m b/src/utility/sparse/SparseArray.m index 03064fd..e6de5a8 100644 --- a/src/utility/sparse/SparseArray.m +++ b/src/utility/sparse/SparseArray.m @@ -1107,6 +1107,11 @@ function disp(a) return; end + function jl = mat2jl(a) + jl = struct('classname', 'SparseArray'); + jl.sz = mat2jl(a.sz); + jl.var = mat2jl(a.var); + end end methods (Static) From 379c41769137271103d97561c1d0b33036b287f3 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 27 Sep 2023 14:30:16 +0200 Subject: [PATCH 242/245] Minor improvements --- src/mps/UniformMps.m | 2 +- src/sparse/SparseTensor.m | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index feb1d6b..3546490 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -95,7 +95,7 @@ if iscell(varargin{1}), mps.AL = varargin{1}; else, mps.AL = varargin(1); end if iscell(varargin{2}), mps.AR = varargin{2}; else, mps.AR = varargin(2); end if iscell(varargin{3}), mps.C = varargin{3}; else, mps.C = varargin(3); end - if nargout == 4 + if nargin == 4 if iscell(varargin{4}) mps.AC = varargin{4}; else diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index 6298790..f51f20f 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -183,6 +183,9 @@ if any(todo) I = find(~todo, 1); + if isempty(I) + error('unable to deduce spaces automatically'); + end E = one(spaces{i}(I)); if isdual(spaces{i}(I)) E = conj(E); From 12eef422ed22e3e4b3959cb4b56798db71f3c469 Mon Sep 17 00:00:00 2001 From: Lander Burgelman <39218680+leburgel@users.noreply.github.com> Date: Thu, 8 Feb 2024 14:17:57 +0100 Subject: [PATCH 243/245] Docs update (#23) * Have a go at a docs update, start with symmetry sectors docs * Update spaces docs * Update kernels and tensors docs * Update tensor docs, set up doc stubs for other modules. * Relax `linsolve` test in case of stagnation * Address review comments --- docs/src/img/Fmove.svg | 327 ++++++++--------- docs/src/img/Rmove.svg | 190 +++++----- docs/src/img/fusiontensor.svg | 53 +-- docs/src/img/ipe/Fmove.pdf | Bin 47348 -> 48833 bytes docs/src/img/ipe/conv.sh | 5 + docs/src/index.rst | 7 +- docs/src/lib/algorithms.rst | 50 +++ docs/src/lib/caches.rst | 2 +- docs/src/lib/environments.rst | 12 + docs/src/lib/models.rst | 39 ++ docs/src/lib/mps.rst | 30 ++ docs/src/lib/sparse.rst | 12 + docs/src/lib/tensors.rst | 67 +++- docs/src/lib/utility.rst | 39 +- docs/src/man/algorithms.rst | 4 + docs/src/man/symmetries.rst | 2 + docs/src/man/tensor.rst | 6 +- src/algorithms/Dmrg.m | 81 ++++- src/algorithms/Expand.m | 90 ++++- src/algorithms/IDmrg.m | 70 +++- src/algorithms/IDmrg2.m | 77 +++- src/algorithms/QPAnsatz.m | 44 ++- src/algorithms/Vomps.m | 94 ++++- src/algorithms/Vumps.m | 102 +++++- src/algorithms/eigsolvers/Arnoldi.m | 61 +++- src/algorithms/eigsolvers/KrylovSchur.m | 51 ++- src/caches/DLL.m | 31 +- src/caches/GetMD5/GetMD5.m | 4 +- src/caches/GetMD5/GetMD5_helper.m | 2 +- src/caches/GetMD5/InstallMex.m | 10 +- src/caches/GetMD5/uTest_GetMD5.m | 4 +- src/caches/LRU.m | 47 ++- src/environments/FiniteEnvironment.m | 17 +- src/models/fermionoperators/c_min.m | 11 + src/models/fermionoperators/c_number.m | 6 + src/models/fermionoperators/c_plus.m | 11 + src/models/quantum1dHeisenberg.m | 29 +- src/models/quantum1dHubbard.m | 21 +- src/models/quantum1dIsing.m | 24 ++ src/models/spinoperators/sigma_exchange.m | 14 + src/models/spinoperators/sigma_min.m | 14 + src/models/spinoperators/sigma_plus.m | 14 + src/models/statmech2dIsing.m | 20 ++ src/mps/FiniteMpo.m | 21 +- src/mps/FiniteMps.m | 15 +- src/mps/InfJMpo.m | 6 +- src/mps/InfMpo.m | 9 + src/mps/InfQP.m | 30 +- src/mps/MpoTensor.m | 27 +- src/mps/MpsTensor.m | 56 ++- src/mps/PepsSandwich.m | 12 + src/mps/PepsTensor.m | 20 +- src/mps/UniformMps.m | 195 +++++----- src/sparse/SparseTensor.m | 4 + src/tensors/AbstractTensor.m | 100 +++--- src/tensors/FusionTree.m | 121 +++---- src/tensors/Tensor.m | 380 +++++++++++--------- src/tensors/charges/AbstractCharge.m | 57 +-- src/tensors/charges/BraidingStyle.m | 37 +- src/tensors/charges/FusionStyle.m | 30 +- src/tensors/charges/GtPattern.m | 33 +- src/tensors/charges/O2.m | 27 +- src/tensors/charges/ProductCharge.m | 112 +++--- src/tensors/charges/SU2.m | 10 +- src/tensors/charges/SUN.m | 10 +- src/tensors/charges/U1.m | 6 +- src/tensors/charges/Z1.m | 2 +- src/tensors/charges/Z2.m | 7 +- src/tensors/charges/ZN.m | 14 +- src/tensors/charges/fSU2.m | 8 +- src/tensors/charges/fU1.m | 8 +- src/tensors/charges/fZ2.m | 4 +- src/tensors/kernels/AbelianBlock.m | 8 +- src/tensors/kernels/AbstractBlock.m | 206 ++++++----- src/tensors/kernels/MatrixBlock.m | 18 +- src/tensors/kernels/TrivialBlock.m | 26 +- src/tensors/spaces/AbstractSpace.m | 118 +++--- src/tensors/spaces/Arrow.m | 6 +- src/tensors/spaces/CartesianSpace.m | 76 ++-- src/tensors/spaces/ComplexSpace.m | 58 +-- src/tensors/spaces/GradedSpace.m | 64 ++-- src/tensors/spaces/SU2Space.m | 13 +- src/tensors/spaces/SumSpace.m | 20 +- src/tensors/spaces/U1Space.m | 13 +- src/tensors/spaces/Z2Space.m | 13 +- src/tensors/spaces/fZ2Space.m | 13 +- src/utility/Options.m | 15 +- src/utility/Verbosity.m | 9 + src/utility/between.m | 1 + src/utility/colors.m | 1 + src/utility/dim2str.m | 1 + src/utility/diracdelta.m | 1 + src/utility/indices/contractinds.m | 2 +- src/utility/indices/mod1.m | 8 +- src/utility/indices/next.m | 4 +- src/utility/indices/prev.m | 4 +- src/utility/indices/rankrange.m | 3 +- src/utility/indices/traceinds.m | 4 + src/utility/indices/treeindsequence.m | 4 +- src/utility/indices/unique1.m | 2 +- src/utility/linalg/contract.m | 12 +- src/utility/linalg/eigsolve.m | 48 ++- src/utility/linalg/isapprox.m | 19 + src/utility/linalg/isisometry.m | 14 +- src/utility/linalg/leftnull.m | 13 +- src/utility/linalg/leftorth.m | 32 +- src/utility/linalg/rightnull.m | 16 +- src/utility/linalg/rightorth.m | 32 +- src/utility/linalg/tensorprod.m | 20 +- src/utility/linalg/tensortrace.m | 10 +- src/utility/memsize.m | 5 + src/utility/mod1.m | 9 - src/utility/permutations/invperm.m | 6 +- src/utility/permutations/isperm.m | 7 +- src/utility/permutations/perm2swap.m | 8 +- src/utility/randc.m | 8 +- src/utility/randnc.m | 8 +- src/utility/simulsort.m | 6 +- src/utility/simulsortrows.m | 15 +- src/utility/simulunique.m | 2 +- src/utility/sparse/SparseArray.m | 152 ++++---- src/utility/swapvars.m | 2 +- src/utility/time2str.m | 6 +- src/utility/validations/mustBeEqualLength.m | 11 +- src/utility/validations/mustBeSorted.m | 11 +- src/utility/validations/mustBeUnique.m | 12 +- src/utility/wigner/Wigner3j.m | 13 +- src/utility/wigner/Wigner6j.m | 10 +- test/TestSolvers.m | 4 +- 129 files changed, 2927 insertions(+), 1450 deletions(-) create mode 100755 docs/src/img/ipe/conv.sh create mode 100644 docs/src/lib/algorithms.rst create mode 100644 docs/src/lib/environments.rst create mode 100644 docs/src/lib/models.rst create mode 100644 docs/src/lib/mps.rst create mode 100644 docs/src/lib/sparse.rst create mode 100644 docs/src/man/algorithms.rst delete mode 100644 src/utility/mod1.m diff --git a/docs/src/img/Fmove.svg b/docs/src/img/Fmove.svg index a346b90..b995591 100644 --- a/docs/src/img/Fmove.svg +++ b/docs/src/img/Fmove.svg @@ -1,5 +1,5 @@ - + @@ -9,293 +9,252 @@ - + - - - - - - - + - + - + - - - - - - - - - - - - - - - - - + + - - + + - + - + - + - + - + + + + + + + - + - + - + - + - - - - - - - - - - - - - - - - + - + - + - - - - + - - - - - + + - - + + - - + + - + - - - - - - - - - - - - - - + + - + - - - - - - - + - - - - - - - - + + - - + + - - + + - - + + + + + + + + + + + - + + + + + + + - + - + + - + + - + + - - - - - - - - - - - - - - + + + - + - + - + - + - - - - - - - - - - - - - - - - - - + + - + - - + + + + - - - - - + - - + - + + + - + - - - - - + - + - + - - - + + + - + - + - + + + + - - - - + + + + + + + + + + + + + + + + + - + + - + + + + + + + + + + + + + + + + + - - - - + + + - + + diff --git a/docs/src/img/Rmove.svg b/docs/src/img/Rmove.svg index fd76809..bc1d285 100644 --- a/docs/src/img/Rmove.svg +++ b/docs/src/img/Rmove.svg @@ -1,5 +1,5 @@ - + @@ -8,168 +8,168 @@ - - - - + - + - + + + + - + - - - - + - + - + - + - + - - - - - - - - - - + - - - - - + + - + - + + + + - + - - - - + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - + - + + - + + + - + + - - - - - - - - - - - + + + + + + + + + + + + - + + + - + - - - - - - - - - - + + + + + + + + + + + + - + + - + - + - + - + - + - - + + - + - + - + - + + - + + - + diff --git a/docs/src/img/fusiontensor.svg b/docs/src/img/fusiontensor.svg index 3fdeadd..89fa76c 100644 --- a/docs/src/img/fusiontensor.svg +++ b/docs/src/img/fusiontensor.svg @@ -1,5 +1,5 @@ - + @@ -9,44 +9,51 @@ + + + - - - - - - + + + + + + - + - + + - + + + - - - - - - - - - - - - + + + + + + + + + + + + + - + diff --git a/docs/src/img/ipe/Fmove.pdf b/docs/src/img/ipe/Fmove.pdf index 8752498720850af231fb6cd81f3ac63fe1bb13cf..cf14649983dd9f4a3791a0baaf0ccf122f52336e 100644 GIT binary patch delta 9148 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 on tensors, there are 3 equivalent ways of describing a tensor object: +We start with the definition of a tensor. According to the `Wikipedia page `_ on tensors, there are 3 equivalent ways of describing a tensor object: * As a multidimensional array. diff --git a/src/algorithms/Dmrg.m b/src/algorithms/Dmrg.m index 6c6b107..0bf88b5 100644 --- a/src/algorithms/Dmrg.m +++ b/src/algorithms/Dmrg.m @@ -1,5 +1,51 @@ classdef Dmrg - % Density Matrix Renormalisation Group algorithm for marix product states. + % `Density Matrix Renormalisation Group algorithm `_ for marix product states. + % + % Properties + % ---------- + % tol : :class:`double` + % tolerance for convergence criterion, defaults to :code:`1e-10`. + % + % miniter : :class:`int` + % minimum number of iteration, defaults to 5. + % + % maxiter : :class:`int` + % maximum number of iteration, defaults to 100. + % + % verbosity : :class:`.Verbosity` + % verbosity level of the algorithm, defaults to :code:`Verbosity.iter`. + % + % doplot : :class:`logical` + % plot progress, defaults to :code:`false`. + % + % which : :class:`char` + % eigenvalue selector (passed as the :code:`sigma` argument to :func:`.eigsolve`), + % defaults to :code:`'largestabs'`. + % + % dynamical_tols : :class:`logical` + % indicate whether or not to use a dynamical tolerance scaling for the algorithm's + % subroutines based on the current error measure, defaults to :code:`false` + % + % tol_min : :class:`double` + % smallest allowed convergence tolerance for soubroutines, defaults to :code:`1e-12`. + % + % tol_max : :class:`double` + % highest allowed convergence tolerance for soubroutines, defaults to :code:`1e-10`. + % + % eigs_tolfactor : :class:`double` + % relative scaling factor for determining the convergence tolerance of the eigensolver + % subroutine based on the current error measure, defaults to :code:`1e-6` + % + % sweepstyle : :class:`char` + % sweep style indicating how to sweep through the MPS at each iteration, options are: + % + % - :code:`'f2f'`: (default) front-to-front, sweeping from site 1 to the end and back. + % - :code:`'b2b'`: back-to-back, sweeping from site N to the start and back. + % - :code:`'m2m'`: mid-to-mid, sweeping from the middle site to both ends and back. + % + % alg_eigs : :class:`.KrylovSchur` or :class:`.Arnoldi` + % algorithm used for the local eigsolver updates, defaults to + % :code:`KrylovSchur('MaxIter', 100, 'KrylovDim', 20)`. %% Options properties @@ -22,7 +68,7 @@ saveMethod = 'full' name = 'DMRG' - alg_eigs = KrylovSchur('MaxIter', 100, 'KrylovDim', 20) % TODO: Arnoldi has issues for DMRG specifically, need to figure out why + alg_eigs = KrylovSchur('MaxIter', 100, 'KrylovDim', 20) end properties (Access = private) @@ -49,6 +95,37 @@ end function [mps, envs, eta] = fixedpoint(alg, mpo, mps, envs) + % Find the fixed point MPS of a finite MPO, given an initial guess. + % + % Usage + % ----- + % :code:`[mps, envs, eta] = fixedpoint(alg, mpo, mps, envs)` + % + % Arguments + % --------- + % alg : :class:`.Dmrg` + % DMRG algorithm. + % + % mpo : :class:`.FiniteMpo` + % matrix product operator. + % + % mps : :class:`.FiniteMps` + % initial guess for MPS fixed point. + % + % envs : :class:`.FiniteEnvironment` + % initial guess for the environments. + % + % Returns + % ------- + % mps : :class:`.FiniteMps` + % MPS fixed point. + % + % envs : :class:`.FiniteEnvironment` + % corresponding environments. + % + % eta : :class:`double` + % final error measure at convergence. + arguments alg mpo diff --git a/src/algorithms/Expand.m b/src/algorithms/Expand.m index 2d19685..70056fb 100644 --- a/src/algorithms/Expand.m +++ b/src/algorithms/Expand.m @@ -1,5 +1,70 @@ classdef Expand % Bond expansion algorithm for uniform matrix product states. + % + % Properties + % ---------- + % bondsmethod : :class:`char` + % bond expansion method, options are: + % + % - :code:`'off'`: no bond expansion. + % - :code:`'factor'`: (default) multiply the bond dimsion in each sector by a fixed + % factor + % - :code:`'explicit'`: manually provide bond dimension expansion. + % - :code:`'extrapolate'`: extrapolate the bond dimension in each sector according to + % a pre-defined exponential distribution. + % - :code:`'twosite'`: expand bond dimension according to a truncated two-site update. + % + % chargesmethod : :class:`char` + % charge expansion method, options are: + % + % - :code:`'off'`: (default) no charge expansion. + % - :code:`'fusionproduct'`: expand virtual charges according to the fusion product of + % each previous virtual space with the corresponding physical space. + % - :code:`'twosite'`: expand virtual charges according to a truncated two-site + % update. + % + % schmidtcut : :class:`double` + % cut in singular values used in two-site update, defaults to :code:`1e-5`. + % + % notrunc : :class:`logical` + % disable truncation such that the bond dimension is only grown, defaults to + % :code:`false`. + % + % noisefactor : :class:`double` + % noise factor applied to expanded MPS entries in order to improve stability, defaults + % to :code:`1e-3`. + % + % which : :class:`char` + % eigenvalue selector used in two-site update routine (passed as the :code:`sigma` + % argument to :func:`.eigsolve`), defaults to :code:`'largestabs'`. + % + % minbond : :class:`int` + % minimal bond dimension in for each charge, defaults to :code:`1` + % + % maxbond : :class:`int` + % maximal bond dimension for each charge, defaults to :code:`1e9`. + % + % tolbond : :class:`double` + % tolerance on expanded bond dimension compared to their current values, defaults to + % :code:`0.2`. + % + % bondfactor : :class:`double` + % expansion factor used for the :code:`'factor'` bond expansion method, defaults to + % :code:`1.2`. + % + % cutfactor : :class:`double` + % cut factor used in bond dimension extrapolation for the :code:`'extrapolate'` bond + % expansion method, defaults to :code:`1`. + % + % explicitbonds : :class:`int` + % vector of integers indicating the bond dimsension to add/subtract for each charge, + % defaults to :code:`[]`. + % + % mincharges : :class:`int` + % minimal number of charges in eevery virtual space, defaults to :code:`2`. + % + % finalize : :class:`function_handle` + % optional finalization. %% Options properties @@ -48,7 +113,30 @@ end function [mps2, flag] = changebonds(alg, mpo, mps1) - % Change sectors and bond dimensions of mps virtual spaces. + % Change charges and bond dimensions of MPS virtual spaces. + % + % Usage + % ----- + % :code:`[mps2, flag] = changebonds(alg, mpo, mps1)` + % + % Arguments + % --------- + % alg : :class:`.Expand` + % bond expansion algorithm. + % + % mpo : :class:`.InfMpo` + % matrix product operator. + % + % mps1 : :class:`.UniformMps` + % MPS to be expanded. + % + % Returns + % ------- + % mps2 : :class:`.UniformMps` + % expanded MPS. + % + % flag : :class:`struct` + % explain. % canonicalize before starting for d = 1:depth(mps1) diff --git a/src/algorithms/IDmrg.m b/src/algorithms/IDmrg.m index e4d7863..12e5d65 100644 --- a/src/algorithms/IDmrg.m +++ b/src/algorithms/IDmrg.m @@ -1,5 +1,42 @@ classdef IDmrg - % Infinite Density Matrix Renormalization Group algorithm + % `Infinite Density Matrix Renormalization Group algorithm `_. + % + % Properties + % ---------- + % tol : :class:`double` + % tolerance for convergence criterion, defaults to :code:`1e-10`. + % + % miniter : :class:`int` + % minimum number of iteration, defaults to 5. + % + % maxiter : :class:`int` + % maximum number of iteration, defaults to 100. + % + % verbosity : :class:`.Verbosity` + % verbosity level of the algorithm, defaults to :code:`Verbosity.iter`. + % + % which : :class:`char` + % eigenvalue selector (passed as the :code:`sigma` argument to :func:`.eigsolve`), + % defaults to :code:`'largestabs'`. + % + % dynamical_tols : :class:`logical` + % indicate whether or not to use a dynamical tolerance scaling for the algorithm's + % subroutines based on the current error measure, defaults to :code:`false`. + % + % tol_min : :class:`double` + % smallest allowed convergence tolerance for soubroutines, defaults to :code:`1e-12`. + % + % tol_max : :class:`double` + % highest allowed convergence tolerance for soubroutines, defaults to :code:`1e-6`. + % + % eigs_tolfactor : :class:`double` + % relative scaling factor for determining the convergence tolerance of the local + % update solver subroutine based on the current error measure, defaults to + % :code:`1e-4`. + % + % alg_eigs : :class:`.KrylovSchur` or :class:`.Arnoldi` + % algorithm used for the eigsolver subroutines, defaults to + % :code:`Arnoldi('MaxIter', 100, 'KrylovDim', 20)`. properties tol = 1e-10 @@ -40,6 +77,37 @@ end function [mps, lambda, GL, GR] = fixedpoint(alg, mpo, mps) + % Find the fixed point MPS of an infinite MPO, given an initial guess. + % + % Usage + % ----- + % :code:`[mps, lambda, GL, GR] = fixedpoint(alg, mpo, mps)` + % + % Arguments + % --------- + % alg : :class:`.IDmrg` + % IDMRG algorithm. + % + % mpo : :class:`.InfMpo` + % matrix product operator. + % + % mps : :class:`.UniformMps` + % initial guess for MPS fixed point. + % + % Returns + % ------- + % mps : :class:`.UniformMps` + % MPS fixed point. + % + % lambda : :class:`double` + % eigenvalue. + % + % GL : :class:`cell` of :class:`.MpsTensor` + % left environment tensors. + % + % GR : :class:`cell` of :class:`.MpsTensor` + % right environment tensors. + if period(mpo) ~= period(mps) error('idmrg:argerror', ... 'periodicity of mpo (%d) should be equal to that of the mps (%d)', ... diff --git a/src/algorithms/IDmrg2.m b/src/algorithms/IDmrg2.m index ba29758..b6b51cf 100644 --- a/src/algorithms/IDmrg2.m +++ b/src/algorithms/IDmrg2.m @@ -1,5 +1,49 @@ classdef IDmrg2 - % Infinite Density Matrix Renormalization Group algorithm with 2-site updates. + % `Infinite Density Matrix Renormalization Group algorithm with 2-site updates `_. + % + % Properties + % ---------- + % tol : :class:`double` + % tolerance for convergence criterion, defaults to :code:`1e-10`. + % + % miniter : :class:`int` + % minimum number of iteration, defaults to 5. + % + % maxiter : :class:`int` + % maximum number of iteration, defaults to 100. + % + % verbosity : :class:`.Verbosity` + % verbosity level of the algorithm, defaults to :code:`Verbosity.iter`. + % + % doplot : :class:`logical` + % plot progress, defaults to :code:`false`. + % + % which : :class:`char` + % eigenvalue selector (passed as the :code:`sigma` argument to :func:`.eigsolve`), + % defaults to :code:`'largestabs'`. + % + % dynamical_tols : :class:`logical` + % indicate whether or not to use a dynamical tolerance scaling for the algorithm's + % subroutines based on the current error measure, defaults to :code:`false`. + % + % tol_min : :class:`double` + % smallest allowed convergence tolerance for soubroutines, defaults to :code:`1e-12`. + % + % tol_max : :class:`double` + % highest allowed convergence tolerance for soubroutines, defaults to :code:`1e-6`. + % + % eigs_tolfactor : :class:`double` + % relative scaling factor for determining the convergence tolerance of the local + % update solver subroutine based on the current error measure, defaults to + % :code:`1e-7`. + % + % trunc : :class:`struct` + % truncation method for local 2-site update, see :meth:`.Tensor.tsvd` for details on + % truncation options. + % + % alg_eigs : :class:`.KrylovSchur` or :class:`.Arnoldi` + % algorithm used for the eigsolver subroutines, defaults to + % :code:`Arnoldi('MaxIter', 100, 'KrylovDim', 20)`. properties tol = 1e-10 @@ -50,6 +94,37 @@ end function [mps, lambda, GL, GR] = fixedpoint(alg, mpo, mps) + % Find the fixed point MPS of an infinite MPO, given an initial guess. + % + % Usage + % ----- + % :code:`[mps, lambda, GL, GR] = fixedpoint(alg, mpo, mps)` + % + % Arguments + % --------- + % alg : :class:`.IDmrg2` + % IDMRG2 algorithm. + % + % mpo : :class:`.InfMpo` + % matrix product operator. + % + % mps : :class:`.UniformMps` + % initial guess for MPS fixed point. + % + % Returns + % ------- + % mps : :class:`.UniformMps` + % MPS fixed point. + % + % lambda : :class:`double` + % eigenvalue. + % + % GL : :class:`cell` of :class:`.MpsTensor` + % left environment tensors. + % + % GR : :class:`cell` of :class:`.MpsTensor` + % right environment tensors. + if period(mpo) ~= period(mps) error('idmrg2:argerror', ... 'periodicity of mpo (%d) should be equal to that of the mps (%d)', ... diff --git a/src/algorithms/QPAnsatz.m b/src/algorithms/QPAnsatz.m index 5a59c98..056cc0f 100644 --- a/src/algorithms/QPAnsatz.m +++ b/src/algorithms/QPAnsatz.m @@ -1,5 +1,22 @@ classdef QPAnsatz - % Quasi-Particle excitation ansatz + % `Quasi-Particle excitation ansatz `_. + % + % Properties + % ---------- + % alg_eigs : :class:`.KrylovSchur` or :class:`.Arnoldi` + % algorithm used for the eigsolver subroutines, defaults to + % :code:`Arnoldi('MaxIter', 100, 'KrylovDim', 30, 'Tol', 1e-8)`. + % + % alg_environments : :class:`.struct` + % algorithm used for the environment subroutines (see :meth:`.AbstractTensor.linsolve` + % for details), defaults to :code:`struct('Tol', 1e-10, 'Algorithm', 'bicgstabl')`. + % + % howmany : :class:`int` + % number of excitations to compute. + % + % which : :class:`char` + % eigenvalue selector (passed as the :code:`sigma` argument to :func:`.eigsolve`), + % defaults to :code:`'largestabs'`. properties alg_eigs = Arnoldi('MaxIter', 100, 'KrylovDim', 30, 'Tol', 1e-8, 'Verbosity', Verbosity.diagnostics) @@ -29,6 +46,31 @@ end function [qp, mu] = excitations(alg, mpo, qp) + % Find excitations + % + % Usage + % ----- + % :code:`[qp, mu] = excitations(alg, mpo, qp)` + % + % Arguments + % --------- + % alg : :class:`.QPAnsatz` + % Quasi-particle ansatz algorithm. + % + % mpo : :class:`.InfMpo` + % matrix product operator. + % + % mps : :class:`.UniformMps` + % initial guess for MPS fixed point. + % + % Returns + % ------- + % qp : :class:`.InfQP` + % vector of quasiparticle states. + % + % mu : :class:`double` + % vector of corresponding eigenvalues. + if period(mpo) ~= period(qp) error('QPAnsatz:argerror', ... 'periodicity of mpo (%d) should be equal to that of the mps (%d)', ... diff --git a/src/algorithms/Vomps.m b/src/algorithms/Vomps.m index 01fe23e..9880ec8 100644 --- a/src/algorithms/Vomps.m +++ b/src/algorithms/Vomps.m @@ -1,6 +1,64 @@ classdef Vomps - % Fixed point algorithm for maximizing overlap. - + % `Fixed point algorithm for maximizing overlap `_. + % + % Properties + % ---------- + % tol : :class:`double` + % tolerance for convergence criterion, defaults to :code:`1e-10`. + % + % miniter : :class:`int` + % minimum number of iteration, defaults to 5. + % + % maxiter : :class:`int` + % maximum number of iteration, defaults to 100. + % + % verbosity : :class:`.Verbosity` + % verbosity level of the algorithm, defaults to :code:`Verbosity.iter`. + % + % which : :class:`char` + % eigenvalue selector (passed as the :code:`sigma` argument to :func:`.eigsolve`), + % defaults to :code:`'largestabs'`. + % + % dynamical_tols : :class:`logical` + % indicate whether or not to use a dynamical tolerance scaling for the algorithm's + % subroutines based on the current error measure, defaults to :code:`true`. + % + % tol_min : :class:`double` + % smallest allowed convergence tolerance for soubroutines, defaults to :code:`1e-12`. + % + % tol_max : :class:`double` + % highest allowed convergence tolerance for soubroutines, defaults to :code:`1e-6`. + % + % eigs_tolfactor : :class:`double` + % relative scaling factor for determining the convergence tolerance of the local + % update solver subroutine based on the current error measure, defaults to + % :code:`1e-4`. + % + % canonical_tolfactor : :class:`double` + % relative scaling factor for determining the convergence tolerance of the + % canonicalization subroutine based on the current error measure, defaults to + % :code:`1e-8`. + % + % environments_tolfactor : :class:`double` + % relative scaling factor for determining the convergence tolerance of the environment + % solver subroutine based on the current error measure, defaults to :code:`1e-4`. + % + % multiAC : :class:`char` + % execution style for the local `AC` updates for a multi-site unit cell, options are: + % + % - :code:`'parallel'`: (default) update all `AC` tensors simultaneously. + % - :code:`'sequential'`: update one `AC` tensor at a time, sweeping through the unit + % cell. + % + % dynamical_multiAC : :class:`logical` + % automatically switch from :code:`'sequential'` to :code:`'parallel'` if the error + % measure becomes small enough, defaults to :code:`false`. + % + % tol_multiAC : :class:`char` + % tolerance for automatically switching from :code:`'sequential'` to + % :code:`'parallel'` if the error measure falls below this value, defaults to + % :code:`Inf`. + %% Options properties tol = 1e-5 @@ -51,6 +109,38 @@ end function [mps2, GL, GR] = approximate(alg, mpo, mps1, mps2) + % Approximate the product of an MPS and an MPO as an MPS. + % + % Usage + % ----- + % :code:`[mps2, GL, GR] = approximate(alg, mpo, mps1, mps2)` + % + % Arguments + % --------- + % alg : :class:`.Vumps` + % VUMPS algorithm. + % + % mpo : :class:`.InfMpo` + % matrix product operator. + % + % mps1 : :class:`.UniformMps` + % MPS to which the MPO is applied. + % + % mps2 : :class:`.UniformMps` + % initial guess for MPS approximation. + % + % Returns + % ------- + % mps2 : :class:`.UniformMps` + % MPS approximation, such that :code:`mps2` :math:`\approx` + % :code:`mpo * mps1`. + % + % GL : :class:`cell` of :class:`.MpsTensor` + % left environment tensors. + % + % GR : :class:`cell` of :class:`.MpsTensor` + % right environment tensors. + if period(mpo) ~= period(mps1) || period(mpo) ~= period(mps2) error('vumps:argerror', ... 'periodicitys should match: mpo (%d), mps1 (%d), mps2(%d)', ... diff --git a/src/algorithms/Vumps.m b/src/algorithms/Vumps.m index 4d0010f..a936b22 100644 --- a/src/algorithms/Vumps.m +++ b/src/algorithms/Vumps.m @@ -1,6 +1,71 @@ classdef Vumps < handle - % Variational fixed point algorithm for uniform matrix product states. - + % `Variational fixed point algorithm for uniform matrix product states `_. + % + % Properties + % ---------- + % tol : :class:`double` + % tolerance for convergence criterion, defaults to :code:`1e-10`. + % + % miniter : :class:`int` + % minimum number of iteration, defaults to :code:`5`. + % + % maxiter : :class:`int` + % maximum number of iteration, defaults to code:`100`. + % + % verbosity : :class:`.Verbosity` + % verbosity level of the algorithm, defaults to :code:`Verbosity.iter`. + % + % doplot : :class:`logical` + % plot progress, defaults to :code:`false`. + % + % which : :class:`char` + % eigenvalue selector (passed as the :code:`sigma` argument to :func:`.eigsolve`), + % defaults to :code:`'largestabs'`. + % + % dynamical_tols : :class:`logical` + % indicate whether or not to use a dynamical tolerance scaling for the algorithm's + % subroutines based on the current error measure, defaults to :code:`true`. + % + % tol_min : :class:`double` + % smallest allowed convergence tolerance for soubroutines, defaults to :code:`1e-12`. + % + % tol_max : :class:`double` + % highest allowed convergence tolerance for soubroutines, defaults to :code:`1e-10`. + % + % eigs_tolfactor : :class:`double` + % relative scaling factor for determining the convergence tolerance of the local + % update solver subroutine based on the current error measure, defaults to + % :code:`1e-6`. + % + % canonical_tolfactor : :class:`double` + % relative scaling factor for determining the convergence tolerance of the + % canonicalization subroutine based on the current error measure, defaults to + % :code:`1e-8`. + % + % environments_tolfactor : :class:`double` + % relative scaling factor for determining the convergence tolerance of the environment + % solver subroutine based on the current error measure, defaults to :code:`1e-6`. + % + % multiAC : :class:`char` + % execution style for the local `AC` updates for a multi-site unit cell, options are: + % + % - :code:`'parallel'`: (default) update all `AC` tensors simultaneously. + % - :code:`'sequential'`: update one `AC` tensor at a time, sweeping through the unit + % cell. + % + % dynamical_multiAC : :class:`logical` + % automatically switch from :code:`'sequential'` to :code:`'parallel'` if the error + % measure becomes small enough, defaults to :code:`false`. + % + % tol_multiAC : :class:`char` + % tolerance for automatically switching from :code:`'sequential'` to + % :code:`'parallel'` if the error measure falls below this value, defaults to + % :code:`Inf`. + % + % alg_eigs : :class:`.KrylovSchur` or :class:`.Arnoldi` + % algorithm used for the eigsolver subroutines, defaults to + % :code:`Arnoldi('MaxIter', 100, 'KrylovDim', 20)`. + %% Options properties tol = 1e-10 @@ -73,6 +138,39 @@ end function [mps, lambda, GL, GR, eta] = fixedpoint(alg, mpo, mps) + % Find the fixed point MPS of an infinite MPO, given an initial guess. + % + % Usage + % ----- + % :code:`[mps, lambda, GL, GR, eta] = fixedpoint(alg, mpo, mps)` + % + % Arguments + % --------- + % alg : :class:`.Vumps` + % VUMPS algorithm. + % + % mpo : :class:`.InfMpo` + % matrix product operator. + % + % mps : :class:`.UniformMps` + % initial guess for MPS fixed point. + % + % Returns + % ------- + % mps : :class:`.UniformMps` + % MPS fixed point. + % + % lambda : :class:`double` + % eigenvalue. + % + % GL : :class:`cell` of :class:`.MpsTensor` + % left environment tensors. + % + % GR : :class:`cell` of :class:`.MpsTensor` + % right environment tensors. + % + % eta : :class:`double` + % final error measure at convergence. if period(mpo) ~= period(mps) error('vumps:argerror', ... diff --git a/src/algorithms/eigsolvers/Arnoldi.m b/src/algorithms/eigsolvers/Arnoldi.m index 2902bb7..e04f6eb 100644 --- a/src/algorithms/eigsolvers/Arnoldi.m +++ b/src/algorithms/eigsolvers/Arnoldi.m @@ -1,14 +1,37 @@ classdef Arnoldi % Arnoldi Krylov algorithm for linear algebra problems. + % + % Properties + % ---------- + % tol : :class:`double` + % convergence tolerance, defaults to :code:`1e-10`. + % + % maxiter : :class:`int` + % maximum number of iterations, defaults to :code:`100`. + % + % krylovdim : :class:`int` + % Krylov subspace dimension, defaults to :code:`20`. + % + % deflatedim : :class:`int` + % number of Krylov vectors to keep when deflating. + % + % reorth : :class:`int` + % reorthogonalize basis if larger than this number, defaults to :code:`20`. + % + % nobuild : :class:`int` + % frequency of convergence check when building, defaults to :code:`3`. + % + % verbosity : :class:`.Verbosity` + % display information, defaults to :code:`Verbosity.warn`. properties - tol = 1e-10 % convergence tolerance - maxiter = 100 % maximum iterations - krylovdim = 20 % Krylov subspace dimension - deflatedim % number of Krylov vectors to keep when deflating - reorth = 20 % reorthogonalize basis if larger than this number - nobuild = 3 % frequency of convergence check when building - verbosity = Verbosity.warn % display information + tol = 1e-10 + maxiter = 100 + krylovdim = 20 + deflatedim + reorth = 20 + nobuild = 3 + verbosity = Verbosity.warn end methods @@ -31,26 +54,27 @@ % Usage % ----- % :code:`[V, D, flag] = eigsolve(A, v, howmany, sigma)` + % % :code:`D = eigsolve(A, v, ...)` % % Arguments % --------- - % A : matrix or function_handle + % A : :class:`matrix` or :class:`function_handle` % A square matrix. % A function handle which implements one of the following, depending on sigma: % - % - A \ x, if `sigma` is 0 or 'smallestabs' - % - (A - sigma * I) \ x, if sigma is a nonzero scalar - % - A * x, for all other cases + % - :code:`A \ x`, if `sigma` is 0 or 'smallestabs' + % - :code:`(A - sigma * I) \ x`, if sigma is a nonzero scalar + % - :code:`A * x`, for all other cases % - % v : vector + % v : :class:`vector` % initial guess for the eigenvector. % - % howmany : int + % howmany : :class:`int` % amount of eigenvalues and eigenvectors that should be computed. By default % this is 1, and this should not be larger than the total dimension of A. % - % sigma : `char` or numeric + % sigma : :class:`char` or numeric % selector for the eigenvalues, should be either one of the following: % % - 'largestabs', 'lm': default, eigenvalues of largest magnitude @@ -59,17 +83,20 @@ % - 'smallestabs', 'sm': default, eigenvalues of smallest magnitude % - 'smallestreal', 'sr': eigenvalues with smallest real part % - 'smallestimag', 'si': eigenvalues with smallest imaginary part. + % - numeric : eigenvalues closest to sigma. % % Returns % ------- - % V : (1, howmany) array + % V : (1, howmany) :class:`vector` % vector of eigenvectors. % - % D : numeric + % D : :class:`numeric` % vector of eigenvalues if only a single output argument is asked, diagonal % matrix of eigenvalues otherwise. % - % flag : int + % flag : :class:`int` + % convergence info flag: + % % - flag = 0: all eigenvalues are converged. % - flag = 1: invariant subspace was found and the algorithm was aborted. % - flag = 2: algorithm did not converge after maximum number of iterations. diff --git a/src/algorithms/eigsolvers/KrylovSchur.m b/src/algorithms/eigsolvers/KrylovSchur.m index bce97ee..c1a4581 100644 --- a/src/algorithms/eigsolvers/KrylovSchur.m +++ b/src/algorithms/eigsolvers/KrylovSchur.m @@ -1,11 +1,25 @@ classdef KrylovSchur % KrylovSchur wrapper for Matlab implementation of eigs + % + % Properties + % ---------- + % tol : :class:`double` + % convergence tolerance, defaults to :code:`1e-10`. + % + % maxiter : :class:`int` + % maximum number of iterations, defaults to :code:`100`. + % + % krylovdim : :class:`int` + % Krylov subspace dimension, defaults to :code:`20`. + % + % verbosity : :class:`.Verbosity` + % display information, defaults to :code:`Verbosity.warn`. properties - tol = 1e-10 % convergence tolerance - maxiter = 100 % maximum iterations - krylovdim = 20 % Krylov subspace dimension - verbosity = Verbosity.warn % display information + tol = 1e-10 + maxiter = 100 + krylovdim = 20 + verbosity = Verbosity.warn end methods @@ -28,26 +42,27 @@ % Usage % ----- % :code:`[V, D, flag] = eigsolve(A, v, howmany, sigma, kwargs)` + % % :code:`D = eigsolve(A, v, ...)` % % Arguments % --------- - % A : matrix or function_handle + % A : :class:`matrix` or :class:`function_handle` % A square matrix. % A function handle which implements one of the following, depending on sigma: % - % - A \ x, if `sigma` is 0 or 'smallestabs' - % - (A - sigma * I) \ x, if sigma is a nonzero scalar - % - A * x, for all other cases + % - :code:`A \ x`, if :code:`sigma` is 0 or 'smallestabs' + % - :code:(A - sigma * I) \ x`, if sigma is a nonzero scalar + % - :code:A * x`, for all other cases % - % v : vector + % v : :class:`vector` % initial guess for the eigenvector. % - % howmany : int + % howmany : :class:`int` % amount of eigenvalues and eigenvectors that should be computed. By default % this is 1, and this should not be larger than the total dimension of A. % - % sigma : `char` or numeric + % sigma : :class:`char` or :class:`numeric` % selector for the eigenvalues, should be either one of the following: % % - 'largestabs', 'lm': default, eigenvalues of largest magnitude @@ -56,28 +71,26 @@ % - 'smallestabs', 'sm': default, eigenvalues of smallest magnitude % - 'smallestreal', 'sr': eigenvalues with smallest real part % - 'smallestimag', 'si': eigenvalues with smallest imaginary part. - % - 'bothendsreal', 'be': both ends, with howmany/2 values with largest and - % smallest real part respectively. - % - 'bothendsimag', 'li': both ends, with howmany/2 values with largest and - % smallest imaginary part respectively. % - numeric : eigenvalues closest to sigma. % % Keyword Arguments % ----------------- - % IsSymmetric : logical + % IsSymmetric : :class:`logical` % flag to speed up the algorithm if the operator is symmetric, false by % default. % % Returns % ------- - % V : (1, howmany) array + % V : (1, howmany) :class:`vector` % vector of eigenvectors. % - % D : numeric + % D : :class:`numeric` % vector of eigenvalues if only a single output argument is asked, diagonal % matrix of eigenvalues otherwise. % - % flag : int + % flag : :class:`int` + % convergence info flag: + % % if flag = 0 then all eigenvalues are converged, otherwise not. arguments diff --git a/src/caches/DLL.m b/src/caches/DLL.m index 0c3a775..b562d76 100644 --- a/src/caches/DLL.m +++ b/src/caches/DLL.m @@ -4,10 +4,15 @@ % % Based on the work by Richard Lange (2022). Least-Recently Used (LRU) Cache % (https://www.mathworks.com/matlabcentral/fileexchange/68836-least-recently-used-lru-cache), - % MATLAB Central File Exchange. Retrieved June 18, 2022. + % MATLAB Central File Exchange. Retrieved June 18, 2022. + % + % Properties + % ---------- + % val : :class:`any` + % data stored in this element properties - val % data stored in this element + val end properties (Access = private) @@ -22,12 +27,12 @@ % % Arguments % --------- - % val : any + % val : :class:`any` % data stored in this element. % % Returns % ------- - % dll : :class:`DLL` + % dll : :class:`.DLL` % data wrapped in a doubly-linked list format. obj.val = val; @@ -41,12 +46,12 @@ % % Arguments % --------- - % obj : :class:`DLL` + % obj : :class:`.DLL` % object to remove from the list. % % Returns % ------- - % obj : :class:`DLL` + % obj : :class:`.DLL` % removed object, with detached links. obj.prev.next = obj.next; @@ -60,15 +65,15 @@ % % Arguments % --------- - % obj : :class:`DLL` + % obj : :class:`.DLL` % list to append to. % - % other : :class:`DLL` + % other : :class:`.DLL` % object to append. % % Returns % ------- - % obj : :class:`DLL` + % obj : :class:`.DLL` % updated list. other.next = obj.next; @@ -88,12 +93,12 @@ % % Arguments % --------- - % obj : :class`DLL` + % obj : :class`.DLL` % current element in the list. % % Returns % ------- - % other : :class`DLL` + % other : :class`.DLL` % next element in the list. other = obj.next; @@ -110,12 +115,12 @@ % % Arguments % --------- - % obj : :class`DLL` + % obj : :class`.DLL` % current element in the list. % % Returns % ------- - % other : :class`DLL` + % other : :class`.DLL` % previous element in the list. other = obj.prev; diff --git a/src/caches/GetMD5/GetMD5.m b/src/caches/GetMD5/GetMD5.m index 993afd1..2da3a08 100644 --- a/src/caches/GetMD5/GetMD5.m +++ b/src/caches/GetMD5/GetMD5.m @@ -22,7 +22,7 @@ function GetMD5(varargin) % - 'Array' : Include the class and size information of `data` in the MD5 sum. This can be % applied for (nested) structs, objects, cells and sparse arrays also. % -% Format : char +% Format : :code:`char` % Format of the output, default value is 'hex'. % % - 'hex' : (1, 32) lowercase hexadecimal char. @@ -38,7 +38,7 @@ function GetMD5(varargin) % % Notes % ----- -% For sparse arrays, function handles, java and user-defined objects :func:`GetMD5_helper` +% For sparse arrays, function handles, java and user-defined objects :func:`.GetMD5_helper` % is called to convert into a data format that can be handled. % % The C-Mex-file is compiled automatically when this function is called for the first time. diff --git a/src/caches/GetMD5/GetMD5_helper.m b/src/caches/GetMD5/GetMD5_helper.m index b0083b6..a4da9c7 100644 --- a/src/caches/GetMD5/GetMD5_helper.m +++ b/src/caches/GetMD5/GetMD5_helper.m @@ -1,5 +1,5 @@ function S = GetMD5_helper(V) -% GetMD5_helper: Convert non-elementary array types for GetMD5 +% Convert non-elementary array types for GetMD5 % The C-Mex function GetMD5 calls this function to obtain meaningful unique data % for function handles, java or user-defined objects and sparse arrays. The % applied processing can depend on the needs of the users, therefore it is diff --git a/src/caches/GetMD5/InstallMex.m b/src/caches/GetMD5/InstallMex.m index f360e59..dd3d139 100644 --- a/src/caches/GetMD5/InstallMex.m +++ b/src/caches/GetMD5/InstallMex.m @@ -1,5 +1,5 @@ function Ok = InstallMex(SourceFile, varargin) -% INSTALLMEX - Compile and install Mex file +% Compile and install Mex file % The C, C++ or FORTRAN mex file is compiled and additional installation % routines are started. Advanced users can call MEX() manually instead, but some % beginners are overwhelmed by instructions for a compilation sometimes. @@ -52,9 +52,13 @@ % -------- % % Compile func1.c with LAPACK libraries: -% InstallMex('func1', {'libmwlapack.lib', 'libmwblas.lib'}) +% +% :code:`InstallMex('func1', {'libmwlapack.lib', 'libmwblas.lib'})` +% % Compile func2.cpp, enable debugging and call a test function: -% InstallMex('func2.cpp', '-debug', 'Test_func2'); +% +% :code:`InstallMex('func2.cpp', '-debug', 'Test_func2');` +% % These commands can be appended after the help section of an M-file, when the % compilation should be started automatically, if the compiled MEX is not found. % diff --git a/src/caches/GetMD5/uTest_GetMD5.m b/src/caches/GetMD5/uTest_GetMD5.m index 2313b32..da1f286 100644 --- a/src/caches/GetMD5/uTest_GetMD5.m +++ b/src/caches/GetMD5/uTest_GetMD5.m @@ -10,8 +10,8 @@ function uTest_GetMD5(doSpeed) % Arguments % --------- % doSpeed -% Optional logical flag to trigger time consuming speed tests. Defaults to :code:`true.If -% no speed test is defined, this is ignored. +% Optional logical flag to trigger time consuming speed tests. Defaults to :code:`true`. +% If no speed test is defined, this is ignored. % % Note % ---- diff --git a/src/caches/LRU.m b/src/caches/LRU.m index bf985c8..8ccc726 100644 --- a/src/caches/LRU.m +++ b/src/caches/LRU.m @@ -1,14 +1,31 @@ classdef LRU < handle - % LRU a least-recently-used cache. Stores data up to a preset memory limit, then removes + % A least-recently-used cache. Stores data up to a preset memory limit, then removes % the least-recently-used elements to free up space for additional data. + % + % Properties + % ---------- + % sentinel : :class:`.DLL` + % sentinel of DLL, +sentinel is MRU, -sentinel is LRU + % + % map : :class:`containers.Map` + % map of key --> dll + % + % itemlimit : :class:`int` + % maximum size of cache in number of items + % + % memlimit : :class:`double` + % maximum size of cache in bytes + % + % mem : :class:`double` + % current memory usage in bytes. properties (Access = private) - sentinel % sentinel of DLL, +sentinel is MRU, -sentinel is LRU - map % map of key --> dll - itemlimit = Inf % maximum size of cache in number of items - memlimit = 20 * 2^30 % maximum size of cache in bytes - mem = 0; % current memory usage in bytes. + sentinel + map + itemlimit = Inf + memlimit = 20 * 2^30 + mem = 0; end methods function cache = LRU(itemlimit, memlimit) @@ -16,15 +33,15 @@ % % Arguments % --------- - % itemlimit : int + % itemlimit : :class:`int` % maximum size of cache in number of items. % - % memlimit : numeric + % memlimit : :class:`double` % maximum size of cache in number of bytes. % % Returns % ------- - % cache : :class:`LRU` + % cache : :class:`.LRU` % empty LRU cache. % Initialize data @@ -45,15 +62,15 @@ % % Arguments % --------- - % cache : :class:`LRU` + % cache : :class:`.LRU` % data cache. % - % key : :class:`uint8` + % key : :class:`.uint8` % data key. % % Returns % ------- - % val : any + % val : :class:`any` % value that is stored with a key, or empty if key not in cache. if isKey(cache.map, key) @@ -73,18 +90,18 @@ % % Arguments % --------- - % cache : :class:`LRU` + % cache : :class:`.LRU` % data cache. % % key : :class:`uint8` % data key. % - % val : any + % val : :class:`any` % data value. % % Returns % ------- - % cache : :class:`LRU` + % cache : :class:`.LRU` % updated cache. % remove previously stored data diff --git a/src/environments/FiniteEnvironment.m b/src/environments/FiniteEnvironment.m index 9304f35..71e7f72 100644 --- a/src/environments/FiniteEnvironment.m +++ b/src/environments/FiniteEnvironment.m @@ -1,6 +1,17 @@ classdef FiniteEnvironment - %FINITEENVIRONMENT Summary of this class goes here - % Detailed explanation goes here + % Data structure for managing the environments in finite MPS algorithms. + % + % Properties + % ---------- + % GL : :class:`cell` of :class:`.MpsTensor` + % environment tensors corresponding to the left-gauged part of the MPS. + % + % GR : :class:`cell` of :class:`.MpsTensor` + % environment tensors corresponding to the right-gauged part of the MPS. + % + % Todo + % ---- + % Document properties GL @@ -9,8 +20,6 @@ methods function envs = FiniteEnvironment(varargin) - %FINITEENVIRONMENT Construct an instance of this class - % Detailed explanation goes here if nargin == 0, return; end if nargin == 2 envs.GL = varargin{1}; diff --git a/src/models/fermionoperators/c_min.m b/src/models/fermionoperators/c_min.m index 2e04c81..4851bda 100644 --- a/src/models/fermionoperators/c_min.m +++ b/src/models/fermionoperators/c_min.m @@ -1,4 +1,15 @@ function c = c_min(kwargs) +% Fermionic annihilation operator. +% +% Keyword arguments +% ----------------- +% 'Side' : :class:`char` +% side, 'left or 'right'. +% +% Returns +% c : :class:`.Tensor` +% annihilation operator represented as a 3-leg tensor with :math:`fZ_2` symmetry. + arguments kwargs.side = 'left' end diff --git a/src/models/fermionoperators/c_number.m b/src/models/fermionoperators/c_number.m index b6e1a28..03a1edc 100644 --- a/src/models/fermionoperators/c_number.m +++ b/src/models/fermionoperators/c_number.m @@ -1,4 +1,10 @@ function n = c_number() +% Fermionic number operator. +% +% Returns +% ------- +% n : :class:`.Tensor` +% number operator represented as a 2-leg tensor with :math:`fZ_2` symmetry. pspace = fZ2Space([0 1], [1 1], false); diff --git a/src/models/fermionoperators/c_plus.m b/src/models/fermionoperators/c_plus.m index 795df05..28a8cc8 100644 --- a/src/models/fermionoperators/c_plus.m +++ b/src/models/fermionoperators/c_plus.m @@ -1,4 +1,15 @@ function c_dagger = c_plus(kwargs) +% Fermionic creation operator. +% +% Keyword arguments +% ----------------- +% 'Side' : :class:`char` +% side, 'left or 'right'. +% +% Returns +% c_dagger : :class:`.Tensor` +% creation operator represented as a 3-leg tensor with :math:`fZ_2` symmetry. + arguments kwargs.side = 'left' end diff --git a/src/models/quantum1dHeisenberg.m b/src/models/quantum1dHeisenberg.m index b8a6dd8..e2ee882 100644 --- a/src/models/quantum1dHeisenberg.m +++ b/src/models/quantum1dHeisenberg.m @@ -1,10 +1,37 @@ function mpo = quantum1dHeisenberg(kwargs) +% Hamiltonian for the 1D Heisenberg model. +% +% .. math:: +% H = J \sum_{\langle ij \rangle} \vec{S}_i \cdot \vec{S}_j + h \sum_{i} S_i^z +% +% Keyword arguments +% ----------------- +% 'Spin' : :class:`double` +% halfinteger or integer spin label, defaults to :code:`1`. +% +% 'J' : :class:`double` +% exchange coupling, defaults to :code:`1`. +% +% 'h' : :class:`double` +% magnetic field, defaults to :code:`0`. +% +% 'L' : :class:`int` +% system size, defaults to :code:`Inf`. +% +% 'Symmetry' : :class:`char` +% symmetry group ('U1' or 'SU2'), defaults to :code:`'SU2'`. +% +% Returns +% ------- +% mpo : :class:`.InfJMpo` +% Heisenberg Hamiltonian as a Jordan block MPO. + arguments kwargs.Spin = 1 kwargs.J = 1 kwargs.h = 0 kwargs.L = Inf % size of system - kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'U1', 'SU2'})} = 'Z1' + kwargs.Symmetry {mustBeMember(kwargs.Symmetry, {'Z1', 'U1', 'SU2'})} = 'SU2' end J = kwargs.J; diff --git a/src/models/quantum1dHubbard.m b/src/models/quantum1dHubbard.m index a7c8cf1..85a2bac 100644 --- a/src/models/quantum1dHubbard.m +++ b/src/models/quantum1dHubbard.m @@ -1,10 +1,29 @@ function mpo = quantum1dHubbard(u, mu, kwargs) % Hamiltonian for the 1D Hubbard model. % -% `math`:-\sum (c^+_i c_j + c^+_j c_i) + u\sum (1 - 2n_{up}) * (1-2n_{down}) - mu\sum (n_{up} + n_{down}): +% .. math:: +% H = -\sum_{\langle ij \rangle} (c^+_i c_j + c^+_j c_i) + u \sum_i (1 - 2n_i^{\uparrow}) \cdot (1-2n_i^{\downarrow}) - \mu \sum_i (n_i^{\uparrow} + n_i^{\downarrow}) % % Arguments % --------- +% u : :class:`double` +% interaction strength. +% +% mu : :class:`double` +% chemical potential. +% +% Keyword arguments +% ----------------- +% 'Filling' : :class:`double` +% rational filling factor. +% +% 'Symmetry' : :class:`char` +% symmetry group, defaults to :code:`'fZ2xSU2xU1'`. +% +% Returns +% ------- +% mpo : :class:`.InfJMpo` +% Hubbard Hamiltonian as a Jordan block MPO. arguments u diff --git a/src/models/quantum1dIsing.m b/src/models/quantum1dIsing.m index 3c06b59..3c83a8c 100644 --- a/src/models/quantum1dIsing.m +++ b/src/models/quantum1dIsing.m @@ -1,4 +1,28 @@ function mpo = quantum1dIsing(kwargs) +% Hamiltonian for the 1D transverse-field Ising model. +% +% .. math:: +% H = -J \left(\sum_{\langle ij \rangle} S_i^x S_j^x + h \sum_{i} S_i^z \right). +% +% Keyword arguments +% ----------------- +% 'J' : :class:`double` +% :math:`ZZ` coupling, defaults to :code:`1`. +% +% 'h' : :class:`double` +% relative transverse field strength, defaults to :code:`1`. +% +% 'L' : :class:`int` +% system size, defaults to :code:`Inf`. +% +% 'Symmetry' : :class:`char` +% symmetry group ('Z1', 'Z2' or 'fZ2'), defaults to :code:`'Z1'`. +% +% Returns +% ------- +% mpo : :class:`.InfJMpo` +% Ising Hamiltonian as a Jordan block MPO. + arguments kwargs.J = 1 kwargs.h = 1 diff --git a/src/models/spinoperators/sigma_exchange.m b/src/models/spinoperators/sigma_exchange.m index 80150e6..2ca4b3c 100644 --- a/src/models/spinoperators/sigma_exchange.m +++ b/src/models/spinoperators/sigma_exchange.m @@ -1,4 +1,18 @@ function S = sigma_exchange(spin, symmetry) +% Spin exchange operator. +% +% Arguments +% --------- +% spin : :class:`double` +% halfinteger or integer spin label, defaults to :code:`1/2`. +% +% symmetry : :class:`char` +% symmetry group ('Z1' or 'SU2'), defaults to :code:`'SU2'`. +% +% Returns +% S : :class:`.Tensor` +% two-site exchange interaction represented as a 4-leg tensor. + arguments spin = 1/2 symmetry = 'Z1' diff --git a/src/models/spinoperators/sigma_min.m b/src/models/spinoperators/sigma_min.m index eb2a59a..87aaa23 100644 --- a/src/models/spinoperators/sigma_min.m +++ b/src/models/spinoperators/sigma_min.m @@ -1,4 +1,18 @@ function S = sigma_min(spin, symmetry) +% Spin lowering operator. +% +% Arguments +% --------- +% spin : :class:`double` +% halfinteger or integer spin label, defaults to :code:`1/2`. +% +% symmetry : :class:`char` +% symmetry group ('Z1' or 'U1'), defaults to :code:`'Z1'`. +% +% Returns +% S : :class:`.Tensor` +% lowering operator represented as a 3-leg tensor. + arguments spin = 1/2 symmetry = 'Z1' diff --git a/src/models/spinoperators/sigma_plus.m b/src/models/spinoperators/sigma_plus.m index e7d907c..55c5617 100644 --- a/src/models/spinoperators/sigma_plus.m +++ b/src/models/spinoperators/sigma_plus.m @@ -1,4 +1,18 @@ function S = sigma_plus(spin, symmetry) +% Spin raising operator. +% +% Arguments +% --------- +% spin : :class:`double` +% halfinteger or integer spin label, defaults to :code:`1/2`. +% +% symmetry : :class:`char` +% symmetry group ('Z1' or 'U1'), defaults to :code:`'Z1'`. +% +% Returns +% S : :class:`.Tensor` +% raising operator represented as a 3-leg tensor. + arguments spin = 1/2 symmetry = 'Z1' diff --git a/src/models/statmech2dIsing.m b/src/models/statmech2dIsing.m index 58470ca..ee0f13c 100644 --- a/src/models/statmech2dIsing.m +++ b/src/models/statmech2dIsing.m @@ -1,4 +1,24 @@ function O = statmech2dIsing(kwargs) +% MPO encoding the transfer matrix of the partition function of the 2D classical Ising model +% +% .. math:: +% \mathcal{Z} = \sum_{\{s\}} \prod_{\langle ij \rangle} \exp \left( \beta s_i s_j \right). +% +% Keyword arguments +% ----------------- +% 'beta' : :class:`double` +% inverse temperature. +% +% 'L' : :class:`int` +% system size, defaults to :code:`Inf`. +% +% 'Symmetry' : :class:`char` +% symmetry group ('Z1', 'Z2'), defaults to :code:`'Z1'`. +% +% Returns +% ------- +% mpo : :class:`.InfMpo` or :class:`.Finite` +% MPO transfer matrix of the Ising partition function. arguments kwargs.beta = log(1 + sqrt(2)) / 2; diff --git a/src/mps/FiniteMpo.m b/src/mps/FiniteMpo.m index a1b870f..ce18dca 100644 --- a/src/mps/FiniteMpo.m +++ b/src/mps/FiniteMpo.m @@ -1,5 +1,20 @@ classdef FiniteMpo - % Finite Matrix product operators + % Finite matrix product operator. + % + % Properties + % ---------- + % L : :class:`.MpsTensor` + % left end tensor. + % + % O : :class:`cell` of :class:`.MpoTensor` or :class:`.PepsSandwich` + % bulk MPO tensors. + % + % R : :class:`.MpsTensor` + % right end tensor. + % + % Todo + % ---- + % Document. properties L MpsTensor @@ -60,7 +75,7 @@ end function v = initialize_fixedpoint(mpo) - % Initialize a dense tensor for the fixedpoint of a :class:`FiniteMPO`. + % Initialize a dense tensor for the fixedpoint of a :class:`.FiniteMPO`. N = prod(cellfun(@(x) size(x, 4), mpo.O)); for i = N:-1:1 @@ -100,7 +115,7 @@ end function T = transfermatrix(mpo, mps1, mps2, sites) - arguments + arguments mpo mps1 mps2 = mps1 diff --git a/src/mps/FiniteMps.m b/src/mps/FiniteMps.m index 46d4754..1eeaeb2 100644 --- a/src/mps/FiniteMps.m +++ b/src/mps/FiniteMps.m @@ -1,5 +1,18 @@ classdef FiniteMps - % Finite Matrix product states + % Finite matrix product state. + % + % Properties + % ---------- + % A : :class:`cell` of :class:`.MpsTensor` + % set of tensors that define a finite MPS. + % + % center : :class:`int` + % location of center gauge, such that every tensor to the left (right) of + % :code:`center` is in left (right) gauge. + % + % Todo + % ---- + % Document. properties A (1,:) cell diff --git a/src/mps/InfJMpo.m b/src/mps/InfJMpo.m index 935efa6..06b820c 100644 --- a/src/mps/InfJMpo.m +++ b/src/mps/InfJMpo.m @@ -1,5 +1,9 @@ classdef InfJMpo < InfMpo - % Infinite Mpo with a Jordan block structure + % Infinite translation invariant matrix product operator with a Jordan block structure. + % + % Todo + % ---- + % Document. methods function mpo = InfJMpo(varargin) diff --git a/src/mps/InfMpo.m b/src/mps/InfMpo.m index 7286073..b31579d 100644 --- a/src/mps/InfMpo.m +++ b/src/mps/InfMpo.m @@ -1,5 +1,14 @@ classdef InfMpo % Infinite translation invariant matrix product operator. + % + % Properties + % ---------- + % O : :class:`cell` of :class:`.MpoTensor` or :class:`.PepsSandwich` + % cell of MPO tensors in translation invariant unit cell. + % + % Todo + % ---- + % Document properties O diff --git a/src/mps/InfQP.m b/src/mps/InfQP.m index 170bf57..918ab25 100644 --- a/src/mps/InfQP.m +++ b/src/mps/InfQP.m @@ -1,5 +1,9 @@ classdef InfQP % Infinite Quasi-Particle states + % + % Todo + % ---- + % Document. %% Properties @@ -240,24 +244,24 @@ end function rho = fixedpoint(qp, type, w) - % compute the fixed point of the transfer matrix of an mps. + % compute the fixed point of the transfer matrix of a quasi-particle state. % % Usage % ----- - % :code:`rho = fixedpoint(mps, type, w)` + % :code:`rho = fixedpoint(qp, type, w)` % % Arguments % --------- - % mps : :class:`UniformMps` - % input state. + % mps : :class:`.InfQP` + % input quasi-particle state. % - % type : char + % type : :class:`char` % specification of the type of transfer matrix: % general format: sprintf(%c_%c%c, side, top, bot) where side is 'l' or 'r' to % determine which fixedpoint, and top and bot are 'L' or 'R' to specify % whether to use AL or AR in the transfer matrix. % - % w : integer + % w : :class:`int` % position within the mps unitcell of the fixed point. arguments @@ -287,16 +291,16 @@ % % Arguments % --------- - % qp : :class:`InfQP` + % qp : :class:`.InfQP` % input quasi-particle state. % - % type : 'real' or 'complex' + % type : :class:`char`, 'real' or 'complex' % optionally specify if complex entries should be seen as 1 or 2 parameters. % Defaults to 'complex', with complex parameters. % % Returns % ------- - % v : numeric + % v : :class:`numeric` % real or complex vector containing the parameters of the quasi-particle % state. @@ -313,20 +317,20 @@ % % Arguments % --------- - % v : numeric + % v : :class:`numeric` % real or complex vector containing the parameters of the quasi-particle % state. % - % qp : :class:`InfQP` + % qp : :class:`.InfQP` % input quasi-particle state. % - % type : 'real' or 'complex' + % type : :class:`char`, 'real' or 'complex' % optionally specify if complex entries should be seen as 1 or 2 parameters. % Defaults to 'complex', with complex parameters. % % Returns % ------- - % qp : :class:`InfQP` + % qp : :class:`.InfQP` % output quasi-particle state, filled with the parameters. arguments diff --git a/src/mps/MpoTensor.m b/src/mps/MpoTensor.m index 0c99587..c90f8db 100644 --- a/src/mps/MpoTensor.m +++ b/src/mps/MpoTensor.m @@ -1,15 +1,22 @@ classdef (InferiorClasses = {?Tensor, ?MpsTensor, ?SparseTensor}) MpoTensor < AbstractTensor - % Matrix product operator building block - % This object represents the MPO tensor at a single site as the sum of rank (2,2) - % (sparse) tensors and some scalars, which will be implicitly used as unit tensors. + % Matrix product operator building block. + % + % This object represents the MPO tensor at a single site as the sum of rank (2,2) + % (sparse) tensors and some scalars, which will be implicitly used as unit tensors. + % + % .. code-block:: % % 4 - % ^ + % v % | - % 1 ->-- O -->- 3 + % 1 -<-- O --<- 3 % | - % ^ + % v % 2 + % + % Todo + % ---- + % Document. properties tensors = [] @@ -440,7 +447,7 @@ function disp(O) end function local_operators = decompose_local_operator(H, kwargs) - % convert a tensor into a product of local operators. + % Convert a tensor into a product of local operators. % % Usage % ----- @@ -448,14 +455,14 @@ function disp(O) % % Arguments % --------- - % H : :class:`AbstractTensor` + % H : :class:`.AbstractTensor` % tensor representing a local operator on N sites. % % Keyword Arguments % ----------------- - % 'Trunc' : cell + % 'Trunc' : :class:`cell` % optional truncation method for the decomposition. See also - % :meth:`Tensor.tsvd` + % :meth:`.Tensor.tsvd` arguments H kwargs.Trunc = {'TruncBelow', 1e-14} diff --git a/src/mps/MpsTensor.m b/src/mps/MpsTensor.m index 8385343..0c340ea 100644 --- a/src/mps/MpsTensor.m +++ b/src/mps/MpsTensor.m @@ -1,5 +1,20 @@ classdef (InferiorClasses = {?Tensor, ?SparseTensor}) MpsTensor < AbstractTensor - % Generic mps tensor objects that have a notion of virtual, physical and auxiliary legs. + % Generic MPS tensor objects that have a notion of virtual, physical and auxiliary legs. + % + % Properties + % ---------- + % var : :class:`.Tensor` or :class:`.SparseTensor` + % tensor data of the MPS tensor. + % + % plegs : :class:`int` + % number of physical legs. + % + % alegs : :class:`int` + % number of auxiliary legs. + % + % Todo + % ---- + % Document all methods. properties var @@ -109,7 +124,13 @@ end function tdst = insert_onespace(tsrc, varargin) - % insert a trivial space at position i. + % Insert a trivial space at position :code:`i`, corresponding to an additional + % auxiliary leg. + % + % See Also + % -------- + % :meth:`.Tensor.insert_onespace` + tdst = MpsTensor(insert_onespace(tsrc.var, varargin{:}), tsrc.alegs + 1); end end @@ -137,17 +158,17 @@ end function s = pspace(A) - % The physical space of an :class:`MpsTensor`. + % The physical space of an :class:`.MpsTensor`. s = space(A, 1 + (1:A.plegs)); end function s = leftvspace(A) - % The left virtual space of an :class:`MpsTensor`. + % The left virtual space of an :class:`.MpsTensor`. s = space(A.var(1), 1); end function s = rightvspace(A) - % The right virtual space of an :class:`MpsTensor`. + % The right virtual space of an :class:`.MpsTensor`. s = space(A.var(1), nspaces(A.var(1)) - A.alegs); end end @@ -326,16 +347,17 @@ % Usage % ----- % :code:`t = ctranspose(t)` + % % :code:`t = t'` % % Arguments % --------- - % t : :class:`Tensor` + % t : :class:`.MpsTensor` % input tensor. % % Returns % ------- - % t : :class:`Tensor` + % t : :class:`.MpsTensor` % adjoint tensor. for i = 1:numel(t) @@ -592,7 +614,7 @@ function disp(t) % % Arguments % --------- - % t : :class:`Tensor` + % t : :class:`.MpsTensor` % input tensor. % % type : 'real' or 'complex' @@ -601,7 +623,7 @@ function disp(t) % % Returns % ------- - % v : numeric + % v : :class:`numeric` % real or complex vector containing the parameters of the tensor. arguments @@ -617,19 +639,19 @@ function disp(t) % % Arguments % --------- - % v : numeric + % v : :class:`numeric` % real or complex vector containing the parameters of the tensor. % - % t : :class:`Tensor` + % t : :class:`.MpsTensor` % input tensor. % - % type : 'real' or 'complex' + % type : :class:`char`, 'real' or 'complex' % optionally specify if complex entries should be seen as 1 or 2 parameters. % Defaults to 'complex', with complex parameters. % % Returns % ------- - % t : :class:`Tensor` + % t : :class:`.MpsTensor` % output tensor, filled with the parameters. arguments @@ -654,14 +676,14 @@ function disp(t) % % Arguments % --------- - % H : :class:`AbstractTensor` - % tensor representing a local operator on N sites. + % psi : :class:`.AbstractTensor` + % tensor representing a local state on N sites. % % Keyword Arguments % ----------------- - % 'Trunc' : cell + % 'Trunc' : :class:`cell` % optional truncation method for the decomposition. See also - % :meth:`Tensor.tsvd` + % :meth:`.Tensor.tsvd` arguments psi kwargs.Trunc = {'TruncBelow', 1e-14} diff --git a/src/mps/PepsSandwich.m b/src/mps/PepsSandwich.m index 8ef343d..0db3ebd 100644 --- a/src/mps/PepsSandwich.m +++ b/src/mps/PepsSandwich.m @@ -1,6 +1,18 @@ classdef (InferiorClasses = {?Tensor, ?MpsTensor, ?SparseTensor}) PepsSandwich % Data structure representing a pair of PEPS tensors in an overlap, which behave as an % MPO tensor. + % + % Properties + % ---------- + % top : :class:`.PepsTensor` + % top-layer PEPS tensor, usually interpreted as the 'bra' in the overlap. + % + % bot : :class:`.PepsTensor` + % bottom-layer PEPS tensor, usually interpreted as the 'ket' in the overlap. + % + % Todo + % ---- + % Document. properties top PepsTensor diff --git a/src/mps/PepsTensor.m b/src/mps/PepsTensor.m index 3f49ca4..ad21039 100644 --- a/src/mps/PepsTensor.m +++ b/src/mps/PepsTensor.m @@ -1,8 +1,11 @@ classdef PepsTensor - % Generic PEPS tensor object that hos a notion of virtual and physical legs. - % This object represents the PEPS tensor at a single site as a rank (1, 4) tensor, - % where the physical index lies in the codomain and the virtual indices lie in the - % domain. + % Generic PEPS tensor object that has a notion of virtual and physical legs. + % + % This object represents the PEPS tensor at a single site as a rank (1, 4) tensor, + % where the physical index lies in the codomain and the virtual indices lie in the + % domain. + % + % .. code-block:: % % 5 1 % | / @@ -13,6 +16,15 @@ % ^ % | % 3 + % + % Properties + % ---------- + % var : :class:`.Tensor` + % PEPS tensor data. + % + % Todo + % ---- + % Document. properties var end diff --git a/src/mps/UniformMps.m b/src/mps/UniformMps.m index 3546490..7ab6db4 100644 --- a/src/mps/UniformMps.m +++ b/src/mps/UniformMps.m @@ -1,22 +1,28 @@ classdef UniformMps - % UniformMps - Implementation of infinite translation invariant MPS + % Implementation of infinite translation invariant MPS % % The center gauge is defined to have: - % :math:`AL_w * C_w = AC_w = C_{w-1} * AR_w` + % + % .. math:: + % AL_w \cdot C_w = AC_w = C_{w-1} \cdot AR_w % % Properties % ---------- - % AL : :class:`MpsTensor` + % AL : :class:`.MpsTensor` % left-gauged mps tensors. % - % AR : :class:`MpsTensor` + % AR : :class:`.MpsTensor` % right-gauged mps tensors. % - % C : :class:`Tensor` + % C : :class:`.Tensor` % center gauge transform. % - % AC : :class:`MpsTensor` + % AC : :class:`.MpsTensor` % center-gauged mps tensors. + % + % Todo + % ---- + % Document all methods. properties AL (1,:) cell @@ -37,18 +43,18 @@ % % Arguments % --------- - % A : :class:`cell` of :class:`MpsTensor` + % A : :class:`cell` of :class:`.MpsTensor` % set of tensors per site that define an MPS to be gauged. % - % AL, AR, AC : :class:`cell` of :class:`MpsTensor` + % AL, AR, AC : :class:`cell` of :class:`.MpsTensor` % set of gauged MpsTensors. % - % C : :class:`cell` of :class:`Tensor` + % C : :class:`cell` of :class:`.Tensor` % gauge tensor. % % Returns % ------- - % mps : :class:`UniformMps` + % mps : :class:`.UniformMps` % gauged uniform MPS. narginchk(0, 4); @@ -121,12 +127,12 @@ % fun : :class:`function_handle` % function to initialize the tensor. % - % Repeating Aruguments - % -------------------- - % pspaces : :class:`AbstractSpace` + % Repeating Arguments + % ------------------- + % pspaces : :class:`.AbstractSpace` % physical spaces for each site. % - % vspaces : :class:`AbstractSpace` + % vspaces : :class:`.AbstractSpace` % virtual spaces between each site. (entry `i` corresponds to left of site % `i`.) @@ -161,7 +167,7 @@ % % See Also % -------- - % :meth:`UniformMps.new` + % :meth:`.UniformMps.new` arguments (Repeating) pspaces @@ -176,12 +182,12 @@ %% Properties methods function p = period(mps) - % period over which the mps is translation invariant. + % Period over which the mps is translation invariant. p = length(mps(1).AL); end function d = depth(mps) - % amount of lines in a multi-line mps. + % Number of lines in a multi-line mps. d = size(mps, 1); end @@ -204,19 +210,19 @@ end function s = leftvspace(mps, w) - % return the virtual space to the left of site w. + % Return the virtual space to the left of site `w`. if nargin == 1 || isempty(w), w = 1:period(mps); end s = arrayfun(@leftvspace, mps.AL{w}); end function s = pspace(mps, w) - % return the physical space at site w. + % Return the physical space at site `w`. if nargin == 1 || isempty(w), w = 1:period(mps); end s = pspace(mps.AL{w}); end function s = rightvspace(mps, w) - % return the virtual space to the right of site w. + % Return the virtual space to the right of site `w`. if nargin == 1 || isempty(w), w = 1:period(mps); end s = arrayfun(@rightvspace, mps.AL{w}); end @@ -238,34 +244,35 @@ % % Arguments % --------- - % mps : :class:`UniformMps` + % mps : :class:`.UniformMps` % input mps, from which AL or AR is used as the state, and optionally C as an % initial guess for the gauge. % % Keyword Arguments % ----------------- - % Tol : numeric + % Tol : :class:`numeric` % tolerance for the algorithm. % - % MaxIter : integer - % maximum amount of iterations. + % MaxIter : :class:`integer` + % maximum number of iterations. % - % Method : char + % Method : :class:`char` % algorithm used for decomposition. Must be 'polar', 'qr' or 'qrpos'. % - % Verbosity : :class:`Verbosity` + % Verbosity : :class:`.Verbosity` % level of output. % - % DiagC : logical + % DiagC : :class:`logical` % flag to indicate if `C` needs to be diagonalized. % - % ComputeAC : logical + % ComputeAC : :class:`logical` % flag to indicate if `AC` needs to be computed. % - % Order : 'lr' or 'rl' + % Order : :class:`char`, 'lr' or 'rl' % order of gauge fixing: - % 'lr' uses AL as input tensors, first leftorth, then rightorth. - % 'rl' uses AR as input tensors, first rightorth, then leftorth. + % + % - 'lr' uses AL as input tensors, first :code:`leftorth`, then :class:`rightorth`. + % - 'rl' uses AR as input tensors, first :class:`rightorth`, then :code:`leftorth`. arguments mps @@ -364,7 +371,7 @@ end function mps = diagonalizeC(mps) - % gauge transform an mps such that C is diagonal. + % Gauge transform an mps such that C is diagonal. for i = 1:depth(mps) for w = 1:period(mps(i)) @@ -396,7 +403,7 @@ end function mps = normalize(mps) - % normalize an mps state. + % Normalize an mps state. mps.C = arrayfun(@normalize, mps.C); mps.AC = arrayfun(@normalize, mps.AC); @@ -422,25 +429,25 @@ % % Arguments % --------- - % mps1 : :class:`UniformMps` + % mps1 : :class:`.UniformMps` % input mps for top layer. % - % mps2 : :class:`UniformMps` + % mps2 : :class:`.UniformMps` % input mps for bottom layer, by default equal to the top. % - % sites : integer + % sites : :class:`int` % optionally slice the unit cell of the mps and only define the transfer % matrix for this slice. % % Keyword Arguments % ----------------- - % Type : char + % Type : :class:`char` % 'LL', 'LR', 'RL', 'RR' to determine if the top or bottom respectively are AL % or AR. % % Returns % ------- - % T : FiniteMpo + % T : :class:`.FiniteMpo` % transfer matrix of an mps, acting to the left. arguments @@ -475,16 +482,16 @@ % % Arguments % --------- - % mps : :class:`UniformMps` + % mps : :class:`.UniformMps` % input state. % - % type : char + % type : :class:`char` % specification of the type of transfer matrix: - % general format: sprintf(%c_%c%c, side, top, bot) where side is 'l' or 'r' to + % general format: :code:`sprintf(%c_%c%c, side, top, bot)` where side is 'l' or 'r' to % determine which fixedpoint, and top and bot are 'L' or 'R' to specify % whether to use AL or AR in the transfer matrix. % - % w : integer + % w : :class:`int` % position within the mps unitcell of the fixed point. arguments @@ -533,30 +540,31 @@ % % Arguments % --------- - % mps1 : :class:`UniformMps` + % mps1 : :class:`.UniformMps` % input mps for top layer. % - % mps2 : :class:`UniformMps` + % mps2 : :class:`.UniformMps` % input mps for bottom layer. Default value equal to `mps1`. % - % howmany : integer + % howmany : :class:`int` % number of eigenvectors and eigenvalues to compute. % - % which : 'char' + % which : :class:`char` % type of eigenvectors to target. % % Keyword Arguments % ----------------- % eigopts - % see keyword arguments for :meth:`eigs`. + % see keyword arguments for :func:`.eigsolve`. % - % Verbosity : integer + % Verbosity : :class:`int` % detail level for output. % - % Type : 'char' - % type of transfer matrix to construct. + % Type : :class:`char` + % type of transfer matrix to construct, see + % :meth:`.UniformMps.transfermatrix`. % - % Charge : :class:`AbstractCharge` + % Charge : :class:`.AbstractCharge` % charge of eigenvectors to target. arguments @@ -597,6 +605,8 @@ end function f = fidelity(mps1, mps2, kwargs) + % Compute the fidelity between two uniform MPSs. + arguments mps1 mps2 @@ -612,6 +622,8 @@ end function E = expectation_value(mps1, O, mps2) + % Compute the expectation value of an operator. + arguments mps1 O @@ -646,6 +658,8 @@ end function E = local_expectation_value(mps, O, offset) + % Compute the expectation value of a local operator. + arguments mps O @@ -667,6 +681,13 @@ end function [svals, charges] = schmidt_values(mps, w) + % Compute the Schmidt values and corresponding charges for an entanglement cut + % to the right of site :code:`w`. + % + % Usage + % ----- + % :code:`[svals, charges] = schmidt_values(mps, w)` + arguments mps w = 1 @@ -677,6 +698,8 @@ end function plot_entanglementspectrum(mps, d, w, ax, kwargs) + % Plot the entanglement spectrum of a uniform MPS. + arguments mps d = 1:depth(mps) @@ -754,18 +777,18 @@ function plot_entanglementspectrum(mps, d, w, ax, kwargs) % % Arguments % --------- - % mps : :class:`UniformMps` + % mps : :class:`.UniformMps` % input mps. % - % charge : :class:`AbstractCharge` + % charge : :class:`.AbstractCharge` % charge sector for correlation length to target. % % Returns % ------- - % xi : numeric + % xi : :class:`numeric` % correlation length in the given charge sector. % - % theta : numeric + % theta : :class:`numeric` % angle of the corresponding oscillation period. arguments @@ -799,32 +822,32 @@ function plot_entanglementspectrum(mps, d, w, ax, kwargs) % % Arguments % --------- - % mps : :class:`UniformMps` + % mps : :class:`.UniformMps` % input mps. % - % charge : :class:`AbstractCharge` + % charge : :class:`.AbstractCharge` % charge sector for correlation length to target. % % Keyword Arguments % ----------------- - % HowMany : int + % HowMany : :class:`int` % amount of transfer matrix eigenvalues to compute. % - % Angle : numeric + % Angle : :class:`numeric` % angle in radians around which the gap should be computed. % - % AngleTol : numeric + % AngleTol : :class:`numeric` % tolerance in radians for angles to be considered equal. % % Returns % ------- - % epsilon : numeric + % epsilon : :class:`numeric` % inverse correlation length in the given charge sector. % - % delta : numeric + % delta : :class:`numeric` % refinement parameter. % - % spectrum : numeric + % spectrum : :class:`numeric` % computed partial transfer matrix spectrum. arguments mps @@ -859,6 +882,8 @@ function plot_entanglementspectrum(mps, d, w, ax, kwargs) end function S = entanglement_entropy(mps, w) + % Compute the entanglement entropy of a uniform MPS for a cut to the right of + % site :code:`w` arguments mps w = 1 @@ -873,7 +898,9 @@ function plot_entanglementspectrum(mps, d, w, ax, kwargs) end function S = renyi_entropy(mps, n, w) - arguments + % Compute the :code:`n`-th Renyi entropy of a uniform MPS for a cut to the right + % of site :code:`w` + arguments mps n w = 1 @@ -889,6 +916,8 @@ function plot_entanglementspectrum(mps, d, w, ax, kwargs) end function out = truncate(mps, trunc) + % Truncate a uniform MPS according to the options specified in :code:`trunc` + % (see :meth:`.Tensor.tsvd` for details on the truncation options). arguments mps trunc.TruncDim @@ -911,39 +940,16 @@ function plot_entanglementspectrum(mps, d, w, ax, kwargs) end end - S = EntanglementEntropy(mps, loc); - S = RenyiEntropy(mps,n, loc); - E = ExpectationValue(mps, W, GL, GR) - rho = LeftFixedPoint(mps1, mps2, w, choice) - rho = RightFixedPoint(mps1, mps2, w, choice) - sf=StaticStructureFactor(mps,S,k) - - out = Block(mps, opts) - out = Split(mps, varargin) - %[out, lambda] = Truncate(mps, control, opts) - - [f, rho] = Fidelity(mps1, mps2, tol) - - mps = Conj(mps) - - mps = mtimes(mps, lambda) - - mps = ShiftUnitCell(mps,dd,dw) - out = Rotate180(mps) - out = Transpose(mps) - - mps = SendToGpu(mps) - mps = GetFromGpu(mps) - - [mps, xi] = Retract(mps, eta, alpha) - n = Inner(x, eta, xi) - - end %% Subroutines methods (Access = protected) function [AL, CL, lambda, eta] = uniform_leftorth(mps, CL, kwargs) + % Bring a uniform MPS into left canonical form. + % + % Usage + % ----- + % :code:`[AL, CL, lambda, eta] = uniform_leftorth(mps, CL, kwargs)` arguments mps CL = {} @@ -1047,6 +1053,11 @@ function plot_entanglementspectrum(mps, d, w, ax, kwargs) end function [AR, CR, lambda, eta] = uniform_rightorth(mps, CR, kwargs) + % Bring a uniform MPS into right canonical form. + % + % Usage + % ----- + % :code:`[AR, CR, lambda, eta] = uniform_rightorth(mps, CR, kwargs)` arguments mps CR = mps.C diff --git a/src/sparse/SparseTensor.m b/src/sparse/SparseTensor.m index f51f20f..b9b41b4 100644 --- a/src/sparse/SparseTensor.m +++ b/src/sparse/SparseTensor.m @@ -1,5 +1,9 @@ classdef (InferiorClasses = {?Tensor}) SparseTensor < AbstractTensor % Class for multi-dimensional sparse objects. + % + % Todo + % ---- + % Document properties, behavior and methods. %#ok<*PROPLC> diff --git a/src/tensors/AbstractTensor.m b/src/tensors/AbstractTensor.m index 9680142..8929afc 100644 --- a/src/tensors/AbstractTensor.m +++ b/src/tensors/AbstractTensor.m @@ -1,35 +1,40 @@ classdef AbstractTensor - + % Abstract base class for representing tensors. + % + % See Also + % -------- + % :class:`.Tensor`, :class:`.SparseTensor`, :class:`.MpsTensor` and + % :class:`.MpoTensor` methods function varargout = linsolve(A, b, x0, M1, M2, options) - % Find a solution for a linear system `A(x) = b` or `A * x = b`. + % Find a solution for a linear system :code:`A(x) = b` or :code:`A * x = b`. % % Arguments % --------- - % A : operator + % A : :class:`.AbstractTensor` or :class:`function_handle` % either a function handle implementing or an object that supports % right multiplication. % - % b : :class:`Tensor` + % b : :class:`.AbstractTensor` % right-hand side of the equation, interpreted as vector. % - % x0 : :class:`Tensor` + % x0 : :class:`.AbstractTensor` % optional initial guess for the solution. % - % M1, M2 : operator - % preconditioner M = M1 or M = M1 * M2 to effectively solve the system A * - % inv(M) * y = b with y = M * x. - % M is either a function handle implementing or an object that supports - % left division. + % M1, M2 : :class:`.AbstractTensor` or :class:`function_handle` + % preconditioner :code:`M = M1` or :code:`M = M1 * M2` to effectively solve + % the system :code:`A * inv(M) * y = b` with :code:`y = M * x`. + % :code:`M` is either a function handle implementing or an object that + % supports left division. % % Keyword Arguments % ----------------- - % Tol : numeric + % Tol : :class:`numeric` % specifies the tolerance of the method, by default this is the square root of % eps. % - % Algorithm : char + % Algorithm : :class:`char` % specifies the algorithm used. Can be either one of the following: % % - 'bicgstab' @@ -37,13 +42,13 @@ % - 'gmres' % - 'pcg' % - % MaxIter : int + % MaxIter : :class:`int` % Maximum number of iterations. % - % Restart : int + % Restart : :class:`int` % For 'gmres', amount of iterations after which to restart. % - % Verbosity : int + % Verbosity : :class:`int` % Level of output information, by default nothing is printed if `flag` is % returned, otherwise only warnings are given. % @@ -53,10 +58,10 @@ % % Returns % ------- - % x : :class:`Tensor` + % x : :class:`.AbstractTensor` % solution vector. % - % flag : int + % flag : :class:`int` % a convergence flag: % % - 0 : linsolve converged to the desired tolerance. @@ -65,13 +70,13 @@ % - 3 : linsolve stagnated. % - 4 : one of the scalar quantities calculated became too large or too small. % - % relres : numeric + % relres : :class:`numeric` % relative residual, norm(b - A * x) / norm(b). % - % iter : int + % iter : :class:`int` % iteration number at which x was computed. % - % resvec : numeric + % resvec : :class:`numeric` % vector of estimated residual norms at each part of the iteration. arguments @@ -159,28 +164,29 @@ % Usage % ----- % :code:`[V, D, flag] = eigsolve(A, x0, howmany, sigma, kwargs)` + % % :code:`D = eigsolve(A, x0, ...)` % % Arguments % --------- - % A : :class:`Tensor` or function_handle + % A : :class:`.AbstractTensor` or :class:`function_handle` % A square tensormap interpreted as matrix. % A function handle which implements one of the following, depending on sigma: % - % - A \ x, if `sigma` is 0 or 'smallestabs' - % - (A - sigma * I) \ x, if sigma is a nonzero scalar - % - A * x, for all other cases + % - :code:`A \ x`, if `sigma` is 0 or 'smallestabs' + % - :code:`(A - sigma * I) \ x`, if sigma is a nonzero scalar + % - :code:`A * x`, for all other cases % - % x0 : :class:`Tensor` - % initial guess for the eigenvector. If A is a :class:`Tensor`, this defaults - % to a random complex :class:`Tensor`, for function handles this is a required + % x0 : :class:`.AbstractTensor` + % initial guess for the eigenvector. If A is a :class:`.Tensor`, this defaults + % to a random complex :class:`.Tensor`, for function handles this is a required % argument. % - % howmany : int + % howmany : :class:`int` % amount of eigenvalues and eigenvectors that should be computed. By default % this is 1, and this should not be larger than the total dimension of A. % - % sigma : `char` or numeric + % sigma : :class:`char` or :class:`numeric` % selector for the eigenvalues, should be either one of the following: % % - 'largestabs', 'lm': default, eigenvalues of largest magnitude @@ -197,28 +203,28 @@ % % Keyword Arguments % ----------------- - % Tol : numeric + % Tol : :class:`numeric` % tolerance of the algorithm. % - % Algorithm : char + % Algorithm : :class:`char` % choice of eigensolver algorithm. Currently there is a choice between the use % of Matlab's buitin `eigs` specified by the identifiers 'eigs' or - % 'KrylovSchur', or the use of a custom Arnolid algorithm specified by + % 'KrylovSchur', or the use of a custom Arnoldi algorithm specified by % the identifier 'Arnoldi'. % - % MaxIter : int + % MaxIter : :class:`int` % maximum number of iterations, 100 by default. % - % KrylovDim : int + % KrylovDim : :class:`int` % number of vectors kept in the Krylov subspace. % - % IsSymmetric : logical + % IsSymmetric : :class:`logical` % flag to speed up the algorithm if the operator is symmetric, false by % default. % - % Verbosity : int - % Level of output information, by default nothing is printed if `flag` is - % returned, otherwise only warnings are given. + % Verbosity : :class:`int` + % Level of output information, by default nothing is printed if :code:`flag` + % is returned, otherwise only warnings are given. % % - 0 : no information % - 1 : information at failure @@ -227,15 +233,15 @@ % % Returns % ------- - % V : (1, howmany) :class:`Tensor` + % V : (1, :code:`howmany`) :class:`.AbstractTensor` % vector of eigenvectors. % - % D : numeric + % D : :class:`numeric` % vector of eigenvalues if only a single output argument is asked, diagonal % matrix of eigenvalues otherwise. % - % flag : int - % if flag = 0 then all eigenvalues are converged, otherwise not. + % flag : :class:`int` + % if :code:`flag = 0` then all eigenvalues are converged, otherwise not. arguments A @@ -411,11 +417,11 @@ function disp(t, details) % % Arguments % --------- - % A, B : :class:`Tensor` + % A, B : :class:`.AbstractTensor` % % Returns % ------- - % d : numeric + % d : :class:`numeric` % Euclidean distance, defined as the norm of the distance. n = max(ndims(A), ndims(B)); @@ -438,14 +444,14 @@ function disp(t, details) % % Arguments % --------- - % H : :class:`AbstractTensor` + % H : :class:`.AbstractTensor` % tensor representing a local operator on N sites. % % Keyword Arguments % ----------------- - % 'Trunc' : cell + % 'Trunc' : :class:`cell` % optional truncation method for the decomposition. See also - % :meth:`Tensor.tsvd` + % :meth:`.Tensor.tsvd` arguments H kwargs.Trunc = {'TruncBelow', 1e-12} diff --git a/src/tensors/FusionTree.m b/src/tensors/FusionTree.m index 91cf0dc..6a2943b 100644 --- a/src/tensors/FusionTree.m +++ b/src/tensors/FusionTree.m @@ -3,16 +3,16 @@ % % Properties % ---------- - % charges : :class:`AbstractCharge` + % charges : (:, :) :class:`.AbstractCharge` % labels for the edges of the fusion trees % - % vertices : int + % vertices : (:, :) :class:`int` % labels for the vertices of the fusion trees % - % isdual : logical + % isdual : (1, :) :class:`logical` % indicator of duality transform on the external edges. % - % rank : int + % rank : (1, 2) :class:`int` % amount of splitting tree and fusion tree legs. properties @@ -37,18 +37,18 @@ % % Arguments % --------- - % charges : :class:`AbstractCharge` + % charges : (:, :) :class:`.AbstractCharge` % array of charges, where each row represents an allowed fusion channel. % - % vertices : int = [] + % vertices (:, :) : :class:`.int` % Array of vertex labels. Must be empty or have the same amount of % rows as `charges`. This is optional if the charges have a fusion style % which is multiplicity-free. % - % isdual : logical + % isdual : (1, :) :class:`logical` % mask that shows the presence of duality transformations. % - % rank : int + % rank : (1, 2) :class:`int` % number of splitting and fusing legs. if nargin == 0 @@ -88,15 +88,15 @@ % % Arguments % --------- - % rank : int + % rank : (1, 2) :class:`int` % number of legs for the splitting and fusion tree. % % Repeating Arguments % ------------------- - % charges : :class:`AbstractCharge` + % charges : (1, :) :class:`.AbstractCharge` % set of charges for a given leg % - % isdual : logical + % isdual : :class:`logical` % presence of a duality transform arguments @@ -238,23 +238,23 @@ % % Arguments % --------- - % f : :class:`FusionTree` - % tree to swap. + % f : :class:`.FusionTree` + % tree to swap % - % i : int - % `i` and `i+1` are the braided strands. + % i : :class:`int` + % :code:`i` and :code:`i+1` are the braided strands % - % inv : logical = false - % flag to indicate whether to perform an overcrossing (false) or an - % undercrossing (true). + % inv : :class:`logical` = :code:`false` + % flag to indicate whether to perform an overcrossing (:code:`false`) or an + % undercrossing (:code:`true`) % % Returns % ------- - % c : sparse double - % matrix of coefficients that transform input to output trees. - % f(i) --> c(i,j) * f(j) + % c : (:, :) :class:`sparse` + % matrix of coefficients that transform input to output trees: + % :code:`f(i) --> c(i,j) * f(j)` % - % f : :class:`FusionTree` + % f : :class:`.FusionTree` % braided trees in canonical form. if nargin < 3, inv = false; end @@ -410,16 +410,16 @@ % % Arguments % --------- - % f : :class:`FusionTree` + % f : :class:`.FusionTree` % tree to bend. % % Returns % ------- - % c : sparse double + % c : (:, :) :class:`sparse` % matrix of coefficients that transform input to output trees. - % f(i) --> c(i,j) * f(j) + % :code:`f(i) --> c(i,j) * f(j)` % - % f : :class:`FusionTree` + % f : :class:`.FusionTree` % bent trees in canonical form. f = flip(f); @@ -434,16 +434,16 @@ % % Arguments % --------- - % f : :class:`FusionTree` + % f : :class:`.FusionTree` % tree to bend. % % Returns % ------- - % c : sparse double + % c : (:, :) :class:`sparse` % matrix of coefficients that transform input to output trees. - % `f(i) --> c(i,j) * f(j)` + % :code:`f(i) --> c(i,j) * f(j)` % - % f : :class:`FusionTree` + % f : :class:`.FusionTree` % bent trees in canonical form. if ~hasmultiplicity(fusionstyle(f)) @@ -544,30 +544,31 @@ function [c, f] = braid(f, p, lvl, rank) % Compute the coefficients that bring a braided tree into canonical form. - % This is done by reducing the braid into a composition of elementary swaps - % on neighbouring strands. + % + % This is done by reducing the braid into a composition of elementary swaps + % on neighbouring strands. % % Arguments % --------- - % f : :class:`FusionTree` + % f : :class:`.FusionTree` % tree to braid. % - % p : int + % p : (:, :) :class:`int` % permutation indices. % - % lvl : int + % lvl : (:, :) :class:`int` % height of the strands, indicating over- and undercrossings. % - % rank : int + % rank : :class:`int` (1, 2) % final number of splitting and fusing legs. % % Returns % ------- - % c : sparse double + % c : (:, :) :class:`sparse` % matrix of coefficients that transform input to output trees. - % f(i) --> c(i,j) * f(j) + % :code:`f(i) --> c(i,j) * f(j)` % - % f : :class:`FusionTree` + % f : :class:`.FusionTree` % braided trees in canonical form. % % Todo @@ -717,19 +718,19 @@ % f : :class:`FusionTree` % tree to permute. % - % p : int + % p : (1, :) :class:`int` % permutation indices. % - % r : int + % r : (1, 2) :class:`int` % final number of splitting and fusing legs. % % Returns % ------- - % c : sparse double + % c : (:, :) :class:`sparse` % matrix of coefficients that transform input to output trees. - % `f(i) --> c(i,j) * f(j)` + % :code:`f(i) --> c(i,j) * f(j)` % - % f : :class:`FusionTree` + % f : :class:`.FusionTree` % permuted trees in canonical form. arguments @@ -749,19 +750,19 @@ % % Arguments % --------- - % f : :class:`FusionTree` + % f : :class:`.FusionTree` % tree to repartition. % - % newrank : int + % newrank : (1, 2) :class:`int` % new rank of the fusion tree. % % Returns % ------- - % c : sparse double + % c : (:, :) :class:`sparse` % matrix of coefficients that transform input to output trees. - % `f(i) --> c(i,j) * f(j)` + % :code:`f(i) --> c(i,j) * f(j)` % - % f : :class:`FusionTree` + % f : :class:`.FusionTree` % repartitioned trees in canonical form. arguments @@ -799,22 +800,22 @@ % % Arguments % --------- - % f : :class:`FusionTree` + % f : :class:`.FusionTree` % tree to repartition. % - % i : int or logical + % i : :class:`int` or :class:`logical` % indices of legs to twist. % - % inv : logical + % inv : :class:`logical` % flag to determine inverse twisting. % % Returns % ------- - % c : sparse double + % c : (:, :) :class:`sparse` % matrix of coefficients that transform input to output trees. - % `f(i) --> c(i,j) * f(j)` + % :code:`f(i) --> c(i,j) * f(j)` % - % f : :class:`FusionTree` + % f : :class:`.FusionTree` % twisted trees in canonical form. arguments @@ -1138,11 +1139,11 @@ function displayNonScalarObject(f) % % Arguments % --------- - % f : :class:`FusionTree` + % f : :class:`.FusionTree` % % Returns % ------- - % A : double + % A : :class:`double` % array representation of a fusion tree. assert(issymmetric(braidingstyle(f)), ... @@ -1176,13 +1177,13 @@ function displayNonScalarObject(f) % % Arguments % --------- - % f : :class:`FusionTree` + % f : :class:`.FusionTree` % an array of fusion trees to convert. % % Returns % ------- - % C : cell - % a cell array containing the array representations of f. + % C : :class:`cell` + % a cell array containing the array representations of :code:`f`. if fusionstyle(f) == FusionStyle.Unique C = num2cell(ones(size(f))); diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index 99da0c0..b9d1d98 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -1,5 +1,20 @@ classdef Tensor < AbstractTensor - % Tensor - Base implementation of a dense tensor array with optional symmetries. + % Base implementation of a dense tensor array with optional symmetries. + % + % Properties + % ---------- + % codomain + % codomain vector space represented as tensor product space. + % + % domain + % codomain vector space represented as tensor product space. + % + % var + % block sparse representation of tensor data. + % + % Todo + % ---- + % Document all methods. properties codomain @@ -23,10 +38,10 @@ % % Arguments % --------- - % array : numeric + % array : :class:`numeric` % numeric input array to convert to a :class:`Tensor` % - % codomain, domain : :class:`AbstractSpace` + % codomain, domain : (1, :) :class:`.AbstractSpace` % spaces that define the structure of the output tensor. % % Returns @@ -109,21 +124,21 @@ % % Arguments % --------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % input tensor to fill into. % - % matrices : cell or numeric + % matrices : :class:`cell` or :class:`numeric` % list of matrices or single matrix to fill with. % % fun : :class:`function_handle` % function of signature :code:`fun(dims, charge)` to fill with. % - % charges : :class:`AbstractCharge` + % charges : :class:`.AbstractCharge` % optional list of charges to identify the matrix blocks. % % Returns % ------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % filled tensor. arguments @@ -160,10 +175,10 @@ % % Arguments % --------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % input tensor to fill into. % - % tensors : cell or numeric + % tensors : :class:`cell` or :class:`numeric` % list of tensors or single tensor to fill with. % % fun : :class:`function_handle` @@ -171,7 +186,7 @@ % % Returns % ------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % filled tensor. arguments @@ -208,7 +223,7 @@ % % Repeating Arguments % ------------------- - % tensors : :class:`Tensor` + % tensors : :class:`.Tensor` % input tensors used to copy legs. % % indices : int @@ -216,22 +231,22 @@ % % Keyword Arguments % ----------------- - % Rank : (1, 2) int + % Rank : (1, 2) :class:`int´ % rank of the output tensor, by default this is :code:`[nspaces(t) 0]`. % - % Conj : logical + % Conj : :class:`logical` % flag to indicate whether the space should be equal to the input space, or % fit onto the input space. This can be either an array of size(tensors), or a % scalar, in which case it applies to all tensors. % - % Mode : 'tensor' or 'matrix' + % Mode : :class:`char`, 'tensor' or 'matrix' % method of filling the tensor data. By default this is matrix, where the % function should be of signature :code:`fun(dims, charge)`, for 'tensor' this % should be of signature :code:`fun(dims, tree)`. % % Returns % ------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % output tensor. % % Examples @@ -300,39 +315,39 @@ % function of signature :code:`fun(dims, id)` where id is determined by Mode. % If this is left empty, the tensor data will be uninitialized. % - % dims : int + % dims : :class:`int` % list of dimensions for non-symmetric tensors. % - % arrows : logical + % arrows : :class:`logical` % optional list of arrows for tensor legs. % - % tensor : :class:`Tensor` + % tensor : :class:`.Tensor` % input tensor to copy structure. % % Repeating Arguments % ------------------- - % charges : cell + % charges : :class:`cell` % list of charges for each tensor index. % - % degeneracies : cell + % degeneracies : :class:`cell` % list of degeneracies for each tensor index. % - % arrow : logical + % arrow : :class:`logical` % arrow for each tensor index. % % Keyword Arguments % ----------------- - % Rank : int (1, 2) - % rank of the constructed tensor. By default this is [nspaces(t) 0]. + % Rank : (1, 2) :class:`int` + % rank of the constructed tensor. By default this is :code:`[nspaces(t) 0]`. % - % Mode : 'matrix' or 'tensor' + % Mode : :class:`char`, 'matrix' or 'tensor' % method of filling the resulting tensor. When this is 'matrix' (default), % the function signature is :code:`fun(dims, charge)`, while for 'tensor' the % signature should be :code:`fun(dims, tree)`. % % Returns % ------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % output tensor. arguments @@ -495,7 +510,19 @@ end function tdst = insert_onespace(tsrc, i, dual) - % insert a trivial space at position i. + % Insert a trivial space at position :code:`i`. + % + % Arguments + % --------- + % tsrc : :class:`.Tensor` + % input tensor. + % + % i : :class:`int` + % position at which to insert trivial space, defaults to the last index. + % + % dual : :class:`logical` + % indicate whether or not to dualize the trivial space, defaults to + % :code:`false`. arguments tsrc i = nspaces(tsrc) + 1 @@ -516,7 +543,7 @@ end function tdst = embed(tsrc, tdst) - % embed a tensor in a different tensor. + % Embed a tensor in a different tensor. assert(isequal(rank(tsrc), rank(tdst)), 'tensors:argerror', ... 'tensors must have the same rank'); @@ -553,12 +580,12 @@ % % Arguments % --------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % input tensor. % % Returns % ------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % conjugate tensor. for i = 1:numel(t) @@ -573,16 +600,17 @@ % Usage % ----- % :code:`t = ctranspose(t)` + % % :code:`t = t'` % % Arguments % --------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % input tensor. % % Returns % ------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % adjoint tensor. for i = 1:numel(t) @@ -600,12 +628,12 @@ % % Arguments % --------- - % t1, t2 : :class:`Tensor` + % t1, t2 : :class:`.Tensor` % tensors of equal structure. % % Returns % ------- - % d : double + % d : :class:`double` % scalar dot product of the two tensors. assert(isequal(size(t1), size(t2)), 'tensors:dimerror', ... @@ -631,12 +659,12 @@ % % Arguments % --------- - % t1, t2 : :class:`Tensor` or numeric + % t1, t2 : :class:`.Tensor` or :class:`numeric` % input tensors, scalars are interpreted as scalar * eye. % % Returns % ------- - % t1 : :class:`Tensor` + % t1 : :class:`.Tensor` % output tensor if isnumeric(t1), t1 = t1 + (-t2); return; end @@ -672,12 +700,12 @@ % % Arguments % --------- - % t1, t2 : :class:`Tensor` or numeric + % t1, t2 : :class:`.Tensor` or :class:`numeric` % input tensor or scalar. % % Returns % ------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % output tensor. t = inv(t1) * t2; @@ -694,12 +722,12 @@ % % Arguments % --------- - % t1, t2 : :class:`Tensor` or numeric + % t1, t2 : :class:`.Tensor` or :class:`numeric` % input tensor or scalar. % % Returns % ------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % output tensor. t = t1 * inv(t2); @@ -716,12 +744,12 @@ % % Arguments % --------- - % A, B : :class:`Tensor` - % input tensors, satisfying A.domain = B.codomain. + % A, B : :class:`.Tensor` + % input tensors, satisfying :code:`A.domain == B.codomain`. % % Returns % ------- - % C : :class:`Tensor` + % C : :class:`.Tensor` % output tensor. if isscalar(A) || isscalar(B) @@ -793,10 +821,10 @@ % % Arguments % --------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % input tensor, considered as a matrix from domain to codomain. % - % p : 1, 2, inf or 'fro' + % p : 1, 2, 'inf' or 'fro' % type of norm to compute % % Returns @@ -883,18 +911,18 @@ % % Arguments % --------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % input tensor. % - % p : (1, :) int + % p : (1, :) :class:`int` % permutation vector, by default a trivial permutation. % - % r : (1, 2) int + % r : (1, 2) :class:`int` % rank of the output tensor, by default equal to the rank of the input tensor. % % Returns % ------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % permuted tensor with desired rank. arguments @@ -949,10 +977,11 @@ % % Arguments % --------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % input tensor. % - % r : (1, 2) int + % r : (1, 2) :class:`int` + % rank of the output tensor, by default equal to :code:`[nspaces(t) 0]`. % % Returns % ------- @@ -982,10 +1011,10 @@ % % Arguments % --------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % input tensor. - % uminus - % a : numeric + % + % a : :class:`numeric` % input scalar. % % Returns @@ -1047,20 +1076,20 @@ % % Arguments % --------- - % A, B : :class:`Tensor` - % input tensors, must satisfy space(A, dimA) = conj(space(B, dimB)). + % A, B : :class:`.Tensor` + % input tensors, must satisfy :code:`space(A, dimA) == conj(space(B, dimB))`. % - % dimA, dimB : (1, :) int + % dimA, dimB : (1, :) :class:`int` % selected indices to contract. % % Keyword Arguments % ----------------- - % NumDimensionsA : int + % NumDimensionsA : :class:`int` % number of spaces of A, to satisfy builtin tensorprod syntax. % % Returns % ------- - % C : :class:`Tensor` or numeric + % C : :class:`.Tensor` or :class:`numeric` % output tensor, with the uncontracted spaces of A as codomain, and the % uncontracted spaces of B as domain, or output scalar, if no uncontracted % spaces remain. @@ -1221,20 +1250,20 @@ % Usage % ----- % :code:`t = times(t, a)` - % :code:`t uminus= t .* a` + % % :code:`t = a .* t` % % Arguments % --------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % input tensor. % - % a : numeric + % a : :class:`numeric` % input scalar. % % Returns % ------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % output tensor. if isscalar(A) && ~isscalar(B) @@ -1289,12 +1318,12 @@ % % Arguments % --------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % input tensor, considered as a matrix from domain to codomain. % % Returns % ------- - % tr : double + % tr : :class:`double` % matrix trace of the tensor. tr = 0; @@ -1314,22 +1343,23 @@ % Usage % ----- % :code:`t = transpose(t, p, rank)` + % % :code:`t = t.'` % % Arguments % --------- - % t : :class:`Tensor` + % t : :class:`:Tensor` % input tensor. % - % p : (1, :) int + % p : (1, :) :class:`int` % permutation vector, which must be cyclic. By default this is no permutation. % - % r : (1, 2) int + % r : (1, 2) :class:`int` % rank of the output tensor, by default equal to the rank of the input tensor. % % Returns % ------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % transposed output tensor. if nargin < 2 p = circshift(1:nspaces(t), length(t.domain)); @@ -1349,18 +1379,18 @@ % % Arguments % --------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % input tensor. % - % i : (1, :) int or logical + % i : (1, :) :class:`int` or :class:`logical` % indices to twist. % - % inv : logical + % inv : :class:`logical` % flag to indicate inverse twisting. % % Returns % ------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % twisted tensor with desired rank. arguments @@ -1403,18 +1433,18 @@ % % Arguments % --------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % input tensor. % - % i : (1, :) int or logical + % i : (1, :) :class:`int` or :class:`logical` % indices to twist. % - % inv : logical + % inv : :class:`logical` % flag to indicate inverse twisting. % % Returns % ------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % twisted tensor with desired rank. arguments t @@ -1455,19 +1485,19 @@ % % Arguments % --------- - % A : :class:`Tensor` + % A : :class:`.Tensor` % square input tensor. % % Returns % ------- - % D : (:,:) :class:`Tensor` + % D : (:, :) :class:`.Tensor` % diagonal matrix of eigenvalues. % - % V : (1,:) :class:`Tensor` - % row vector of right eigenvectors such that A * V = V * D. + % V : (1, :) :class:`.Tensor` + % row vector of right eigenvectors such that :code:`A * V = V * D`. % - % W : (1,:) :class:`Tensor` - % row vector of left eigenvectors such that W' * A = D * W'. + % W : (1, :) :class:`Tensor` + % row vector of left eigenvectors such that :code:`W' * A = D * W'`. assert(isequal(A.codomain, A.domain), 'tensors:ArgumentError', ... 'Input should be square.'); @@ -1526,30 +1556,30 @@ % % Arguments % --------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % input tensor to factorize. % - % p1, p2 : int + % p1, p2 : :class:`int` % partition of left and right indices, by default this is the partition of the % input tensor. % - % alg : char or string + % alg : :class:`char` or :class:`string` % selection of algorithms for the decomposition: % - % - 'qr' produces an upper triangular remainder R - % - 'qrpos' corrects the diagonal elements of R to be positive. - % - 'ql' produces a lower triangular remainder R - % - 'qlpos' corrects the diagonal elements of R to be positive. - % - 'polar' produces a Hermitian and positive semidefinite R. - % - 'svd' uses a singular value decomposition. + % - :code:`'qr'` produces an upper triangular remainder R + % - :code:`'qrpos'` corrects the diagonal elements of R to be positive. + % - :code:`'ql'` produces a lower triangular remainder R + % - :code:`'qlpos'` corrects the diagonal elements of R to be positive. + % - :code:`'polar'` produces a Hermitian and positive semidefinite R. + % - :code:`'svd'` uses a singular value decomposition. % % Returns % ------- - % Q : :class:`Tensor` - % Orthonormal basis tensor + % Q : :class:`.Tensor` + % orthonormal basis tensor. % - % R : :class:`Tensor` - % Remainder tensor, depends on selected algorithm. + % R : :class:`.Tensor` + % remainder tensor, depends on selected algorithm. arguments t @@ -1607,29 +1637,29 @@ % % Arguments % --------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % input tensor to factorize. % - % p1, p2 : int + % p1, p2 : :class:`int` % partition of left and right indices, by default this is the partition of the % input tensor. % - % alg : char or string + % alg : :class:`char` or :class:`string` % selection of algorithms for the decomposition: % - % - 'rq' produces an upper triangular remainder R - % - 'rqpos' corrects the diagonal elements of R to be positive. - % - 'lq' produces a lower triangular remainder R - % - 'lqpos' corrects the diagonal elements of R to be positive. - % - 'polar' produces a Hermitian and positive semidefinite R. - % - 'svd' uses a singular value decomposition. + % - :code:`'rq'` produces an upper triangular remainder R + % - :code:`'rqpos'` corrects the diagonal elements of R to be positive. + % - :code:`'lq'` produces a lower triangular remainder R + % - :code:`'lqpos'` corrects the diagonal elements of R to be positive. + % - :code:`'polar'` produces a Hermitian and positive semidefinite R. + % - :code:`'svd'` uses a singular value decomposition. % % Returns % ------- - % R : :class:`Tensor` + % R : :class:`.Tensor` % Remainder tensor, depends on selected algorithm. % - % Q : :class:`Tensor` + % Q : :class:`.Tensor` % Orthonormal basis tensor. arguments @@ -1683,22 +1713,22 @@ % % Arguments % --------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % input tensor to compute the nullspace. % - % p1, p2 : int + % p1, p2 : :class:`int` % partition of left and right indices, by default this is the partition of the % input tensor. % - % alg : char or string + % alg : :class:`char` or :class:`string` % selection of algorithms for the nullspace: % - % - 'svd' - % - 'qr' + % - :code:`'svd'` + % - :code:`'qr'` % % Returns % ------- - % N : :class:`Tensor` + % N : :class:`.Tensor` % orthogonal basis for the left nullspace. arguments @@ -1753,22 +1783,22 @@ % % Arguments % --------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % input tensor to compute the nullspace. % - % p1, p2 : int + % p1, p2 : :class:`int` % partition of left and right indices, by default this is the partition of the % input tensor. % - % alg : char or string + % alg : :class:`char` or :class:`string` % selection of algorithms for the nullspace: % - % - 'svd' - % - 'lq' + % - :code:`'svd'` + % - :code:`'lq'` % % Returns % ------- - % N : :class:`Tensor` + % N : :class:`.Tensor` % orthogonal basis for the right nullspace. arguments @@ -1846,41 +1876,43 @@ % % Usage % ----- - % [U, S, V] = tsvd(t, p1, p2) - % [U, S, V, eta] = tsvd(t, p1, p2, trunc, tol) - % S = tsvd(t, ...) + % :code:`[U, S, V] = tsvd(t, p1, p2)` + % + % :code:`[U, S, V, eta] = tsvd(t, p1, p2, trunc, tol)` + % + % :code:`S = tsvd(t, ...)` % % Arguments % --------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % input tensor. % - % p1, p2 : int + % p1, p2 : :class:`int` % partition of left and right indices, by default this is the partition of the % input tensor. % % Keyword Arguments % ----------------- - % TruncDim : int + % TruncDim : :class:`int` % truncate such that the dim of S is not larger than this value for any given % charge. % - % TruncTotalDim : int + % TruncTotalDim : :class:`int` % truncate such that the total dim of S is not larger than this value. % - % TruncBelow : numeric + % TruncBelow : :class:`numeric` % truncate such that there are no singular values below this value. % - % TruncSpace : :class:`AbstractSpace` + % TruncSpace : :class:`.AbstractSpace` % truncate such that the space of S is smaller than this value. % % Returns % ------- - % U, S, V : :class:`Tensor` + % U, S, V : :class:`.Tensor` % left isometry U, non-negative diagonal S and right isometry V that satisfy % :code:`U * S * V = tpermute(t, [p1 p2], [length(p1) length(p2)])`. % - % eta : numeric + % eta : :class:`numeric` % truncation error. arguments @@ -1999,12 +2031,12 @@ % % Arguments % --------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % input tensor. % % Returns % ------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % output tensor. assert(isequal(t.codomain, t.domain), 'tensors:ArgumentError', ... @@ -2018,16 +2050,17 @@ end function t = inv(t) - % Compute the matrix inverse of a square tensor, such that t * inv(t) = I. + % Compute the matrix inverse of a square tensor, such that + % :code:`t * inv(t) = I`. % % Arguments % --------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % input tensor. % % Returns % ------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % output tensor. assert(isisometric(t.codomain, t.domain), 'tensors:ArgumentError', ... @@ -2051,22 +2084,22 @@ % % Usage % ----- - % :class:`A = x^Y` + % :code:`A = x^Y` % - % :class:`A = X^y` + % :code:`A = X^y` % % Arguments % --------- - % X, Y : :class:`Tensor` - % Square input tensor. + % X, Y : :class:`.Tensor` + % square input tensor. % - % x, y : numeric - % Input scalars. + % x, y : :class:`numeric` + % input scalars. % % Returns % ------- - % A : :class:`Tensor` - % Output tensor. + % A : :class:`.Tensor` + % output tensor. % tensor to a scalar power if isnumeric(Y) && isscalar(Y) @@ -2100,22 +2133,23 @@ function [A, resnorm] = sqrtm(A) % Compute the principal square root of a square tensor. This is the unique root - % for which every eigenvalue has nonnegative real part. If `t` is singular, then - % the result may not exist. + % for which every eigenvalue has nonnegative real part. If :code:`t` is + % singular, then the result may not exist. % % Arguments % --------- - % A : :class:`Tensor` + % A : :class:`.Tensor` % input tensor. % % Returns % ------- - % X : :class:`Tensor` - % principal square root of the input, which has X^2 = A. + % X : :class:`.Tensor` + % principal square root of the input, which has :code:`X^2 = A`. % - % resnorm : numeric - % the relative residual, norm(A - X^2, 1) / norm(A, 1). If this argument is - % returned, no warning is printed if exact singularity is detected. + % resnorm : :class:`numeric` + % the relative residual, :code:`norm(A - X^2, 1) / norm(A, 1)`. If this + % argument is returned, no warning is printed if exact singularity is + % detected. assert(isequal(A.codomain, A.domain), 'tensors:ArgumentError', ... 'Input should be square.'); @@ -2145,19 +2179,19 @@ % nonzero complex column vector `z`. % This is equivalent to any of the following conditions: % - % - M is Hermitian and all eigenvalues are real and positive. - % - M is congruent with a diagonal matrix with positive real entries. - % - There exists an invertible B such that `M = B' * B`. + % - `M` is Hermitian and all eigenvalues are real and positive. + % - `M` is congruent with a diagonal matrix with positive real entries. + % - There exists an invertible `B` such that `M = B' * B`. % % Arguments % --------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % input tensor. % % Returns % ------- - % bool : logical - % true if `t` is positive definite. + % bool : :class:`logical` + % true if :code:`t` is positive definite. mblocks = matrixblocks(t); for i = 1:length(mblocks) @@ -2175,22 +2209,22 @@ % % Arguments % --------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % input tensor. % - % side : char + % side : :class:`char` % either 'left', 'right' or 'both' (default). % % Keyword Arguments % ----------------- - % AbsTol, RelTol : numeric - % `norm(M * M' - eye(size(M))) < max(AbsTol, RelTol * norm(M))`. - % By default `AbsTol = 0` and `RelTol = eps`. + % AbsTol, RelTol : :class:`numeric` + % :code:`norm(t * t' - eye(size(t))) < max(AbsTol, RelTol * norm(t))`. + % By default :code:`AbsTol = 0` and :code:`RelTol = eps`. % % Returns % ------- - % bool : logical - % true if t is isometric. + % bool : :class:`logical` + % true if :code:`t` is isometric. arguments t @@ -2271,7 +2305,7 @@ % % Arguments % --------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % input tensor. % % p : 1, 2, inf or 'fro' @@ -2300,16 +2334,16 @@ % % Arguments % --------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % input tensor. % - % type : 'real' or 'complex' + % type : :class:`char`, 'real' or 'complex' % optionally specify if complex entries should be seen as 1 or 2 parameters. % Defaults to 'complex', with complex parameters. % % Returns % ------- - % v : numeric + % v : :class:`numeric` % real or complex vector containing the parameters of the tensor. arguments @@ -2325,19 +2359,19 @@ % % Arguments % --------- - % v : numeric + % v : :class:`numeric` % real or complex vector containing the parameters of the tensor. % - % t : :class:`Tensor` + % t : :class:`.Tensor` % input tensor. % - % type : 'real' or 'complex' + % type : :class:`char`, 'real' or 'complex' % optionally specify if complex entries should be seen as 1 or 2 parameters. % Defaults to 'complex', with complex parameters. % % Returns % ------- - % t : :class:`Tensor` + % t : :class:`.Tensor` % output tensor, filled with the parameters. arguments @@ -2366,11 +2400,11 @@ % % Arguments % --------- - % t : Tensor + % t : :class:`Tensor` % % Returns % ------- - % a : double + % a : :class:`double` if isa(t.var, 'TrivialBlock') blocks = tensorblocks(t); diff --git a/src/tensors/charges/AbstractCharge.m b/src/tensors/charges/AbstractCharge.m index 49a0685..9f97052 100644 --- a/src/tensors/charges/AbstractCharge.m +++ b/src/tensors/charges/AbstractCharge.m @@ -1,5 +1,5 @@ classdef (Abstract) AbstractCharge - % AbstractCharge - Abstract base class for objects in a fusion category. + % Abstract base class for objects in a fusion category. %% Required categorical data. methods @@ -28,13 +28,13 @@ % % Arguments % -------- - % a : :class:`AbstractCharge` + % a : :class:`.AbstractCharge` % input charge % % Returns % ------- - % abar : :class:`AbstractCharge` - % conjugate charge suche that :code:`one(a)` is an element of :code:`a * abar` + % abar : :class:`.AbstractCharge` + % conjugate charge such that :code:`one(a)` is an element of :code:`a * abar` error('AbstractCharge:requiredMethod', ... 'Error. \nMethod must be overloaded.') end @@ -69,7 +69,7 @@ % % Returns % ------- - % c : :class:`.AbstractCharge` (1, \*) + % c : (1, :) :class:`.AbstractCharge` % the unique elements in the decomposition of the tensor product of ``a`` and % ``b`` error('AbstractCharge:requiredMethod', ... @@ -109,7 +109,7 @@ % % Returns % ------- - % F : :class:`double` (\*, \*, \*, \*) + % F : (:, :, :, :) :class:`double` % recoupling coefficients error('AbstractCharge:requiredMethod', ... 'Error. \nMethod must be overloaded.') @@ -177,7 +177,7 @@ % % Returns % ------- - % C : :class:`double` (\*, \*, \*, \*) + % C : (:, :, :, :) :class:`double` % fusion tensor error('AbstractCharge:optionalMethod', ... 'Error. \nMethod must be overloaded.') @@ -211,7 +211,7 @@ % % Returns % ------- - % R : :class:`double` (\*, \*) + % R : (:, :) :class:`double` % braiding coefficients error('AbstractCharge:optionalMethod', ... 'Error. \nMethod must be overloaded.') @@ -224,7 +224,7 @@ % Compute the fusion to splitting coefficient. % % .. todo:: - % Add diagram? + % Add diagram. % % Arguments % --------- @@ -246,7 +246,7 @@ % Compute the splitting to fusion coefficient. % % .. todo:: - % Add diagram? + % Add diagram. % % Arguments % --------- @@ -303,7 +303,7 @@ % Create a matrix-representation of an arrowflip. % % .. todo:: - % Add diagram or definition? + % Add diagram and proper definition. % % Arguments % --------- @@ -311,7 +311,7 @@ % % Returns % ------- - % F : :class:`double` (\*, \*) + % F : (:, :) :class:`double` % matrix-representation of an arrowflip F = conj(sqrt(qdim(a)) .* fusiontensor(conj(a), a, one(a))); end @@ -320,7 +320,7 @@ % Compute the full recoupling matrix from ``e`` to ``f``. % % .. todo:: - % Add proper definition? + % Add proper definition. % % Usage % ----- @@ -333,14 +333,14 @@ % charges being fused % d : :class:`.AbstractCharge` % total charges - % e : :class:`.AbstractCharge` (1, \*) + % e : (1, :) :class:`.AbstractCharge` % intermediate charges before recoupling - % f : :class:`.AbstractCharge` (1, \*) + % f : (1, :) :class:`.AbstractCharge` % intermediate charge after recoupling % % Returns % ------- - % F : :class:`double` (\*, \*, \*, \*) + % F : (:, :, :, :) :class:`double` % recoupling matrix between all allowed channels if a.fusionstyle == FusionStyle.Unique if nargin < 5, e = a * b; end @@ -414,7 +414,7 @@ % Compute the coefficient obtained by twisting a charge. % % .. todo:: - % Add diagram/definition? + % Add diagram and proper definition. % % Arguments % --------- @@ -449,19 +449,21 @@ %% Utility functions methods function [d, N] = prod(a, dim) - % Total fusion product of charges. - % - % .. todo:: - % Complete docstring + % Compute the total fusion product of array of charges along a given axis. % % Arguments % --------- - % a, b, c, ... : :class:`.AbstractCharge` + % a : :class:`.AbstractCharge` + % input array of charges + % dim : :class:`int` + % array dimension along which to take the fusion product, defaults to first + % non-trivial axis % % Returns % ------- % c : :class:`.AbstractCharge` - % total fusion product determined by subsequently multiplying input charges + % array of total fusion products determined by subsequently multiplying input + % charges along the given direction arguments a dim = find(size(a) ~= 1, 1) @@ -550,11 +552,12 @@ % Cumulative fusion product of elements. % % .. todo:: - % Complete docstring + % Complete docstring. % % Usage % ----- - % :code:`Y = cumprod(X)` computes the cumulative fusion product along the + % :code:`Y = cumprod(X)` + % computes the cumulative fusion product along the % columns of X. % % For ``FusionStyle.Unique``, ``Y`` has the same size as ``X``, which can be @@ -564,7 +567,7 @@ % % Arguments % --------- - % a: :class:`.AbstractCharge` (\*, \*) + % a: (:, :) :class:`.AbstractCharge` % % Returns % ------- @@ -663,7 +666,7 @@ % Create all combinations of vectors. % % .. todo:: - % Complete docstring + % Complete docstring. % % Usage % ----- diff --git a/src/tensors/charges/BraidingStyle.m b/src/tensors/charges/BraidingStyle.m index 5aed581..6956ee4 100644 --- a/src/tensors/charges/BraidingStyle.m +++ b/src/tensors/charges/BraidingStyle.m @@ -1,12 +1,13 @@ classdef BraidingStyle - % BraidingStyle - The braiding behaviour of charges. - % This represents the possibilities for the braiding of two charges. + % The braiding behaviour of charges. % - % Abelian - Trivial braiding with trivial twist. - % Bosonic - Symmetric braiding with trivial twist. - % Fermionic - Symmetric braiding with non-trivial twist. - % Anyonic - Arbitrary braiding and twist. - % None - No braiding defined. + % Enumeration class that encodes the possibilities for the braiding of two charges: + % + % - :code:`Abelian`: Trivial braiding with trivial twist. + % - :code:`Bosonic`: Symmetric braiding with trivial twist. + % - :code:`Fermionic`: Symmetric braiding with non-trivial twist. + % - :code:`Anyonic`: Arbitrary braiding and twist. + % - :code:`None`: No braiding defined. enumeration Abelian @@ -18,15 +19,37 @@ methods function bool = istwistless(style) + % Determine whether a given braiding style has a trivial twist. + % + % Returns :code:`true` for :code:`BraidingStyle.Abelian` and + % :code:`BraidingStyle.Bosonic` and :code:`false` for all other styles. bool = style == BraidingStyle.Abelian || style == BraidingStyle.Bosonic; end function bool = issymmetric(style) + % Determine whether a given braiding style is symmetric. + % + % Returns :code:`true` for all styles except :code:`BraidingStyle.Anyonic` and + % :code:`BraidingStyle.None`. bool = style == BraidingStyle.Abelian || style == BraidingStyle.Bosonic || ... style == BraidingStyle.Fermionic; end function c = and(a, b) + % Determine the braiding style for a direct product of charges. This effectively + % boils down to returning the least specific style. + % + % Arguments + % --------- + % style1 : :class:`.BraidingStyle` + % fusion style of first charge in direct product + % style2 : :class:`.BraidingStyle` + % fusion style of second charge in direct product + % + % Returns + % ------- + % style : :class:`.BraidingStyle` + % fusion style of direct product charge if any([a b] == BraidingStyle.None) c = BraidingStyle.None; diff --git a/src/tensors/charges/FusionStyle.m b/src/tensors/charges/FusionStyle.m index 6d7bc92..2ec3b82 100644 --- a/src/tensors/charges/FusionStyle.m +++ b/src/tensors/charges/FusionStyle.m @@ -1,10 +1,12 @@ classdef FusionStyle < uint8 - % FusionStyle - The fusion product behaviour of charges. - % This represents the possibilities for the decomposition of the fusion product of two charges. + % The fusion product behaviour of charges. % - % Unique - Single unique output. - % Simple - Multiple unique outputs. - % Generic - Multiple outputs. + % Enumeration class that encodes the possible behavior for the decomposition of the + % fusion product of two charges. + % + % - :code:`Unique`: Single unique output. + % - :code:`Simple`: Multiple unique outputs. + % - :code:`Generic`: Multiple outputs. enumeration Unique (0) @@ -14,12 +16,28 @@ methods function bool = hasmultiplicity(style) + % Determine whether a given fusionstyle admits fusion multiplicities. + % + % Returns :code:`true` for :code:`FusionStyle.Generic` and :code:`false` for all + % other styles. bool = style == FusionStyle.Generic; end function style = and(style1, style2) - % Determine the fusionstyle for a direct product of charges. This effectively + % Determine the fusion style for a direct product of charges. This effectively % boils down to returning the least specific style. + % + % Arguments + % --------- + % style1 : :class:`.FusionStyle` + % fusion style of first charge in direct product + % style2 : :class:`.FusionStyle` + % fusion style of second charge in direct product + % + % Returns + % ------- + % style : :class:`.FusionStyle` + % fusion style of direct product charge if style1 == FusionStyle.Generic || style2 == FusionStyle.Generic style = FusionStyle.Generic; diff --git a/src/tensors/charges/GtPattern.m b/src/tensors/charges/GtPattern.m index 6fd8724..537fbe5 100644 --- a/src/tensors/charges/GtPattern.m +++ b/src/tensors/charges/GtPattern.m @@ -1,5 +1,6 @@ classdef GtPattern - % Object that represents a pattern devised by Gelfand and Tsetlin. + % Object that represents a pattern devised by Gelfand and Tsetlin, used for enumerating + % :math:`\mathrm{SU}(N)` basis states. % % :: % @@ -9,18 +10,18 @@ % | m_{1,2} m_{2,2} | % \ m_{1,1} / % - % These consist of triangular arrangements of integers M_ij with the + % These consist of triangular arrangements of integers :math:`M_{ij}` with the % following property: % % .. math:: % % m_{k,l} \geq m_{k,l-1} \geq m_{k+1,l} \quad \text{for} \quad 1 <= k < l <= N % - % They will be represented using arrays of size N x N, where the elements + % They will be represented using arrays of size :math:`N \times N`, where the elements % outside of the triangular region are assumed to be zero. - properties % (Access = private) + properties M double N end @@ -115,17 +116,21 @@ % Usage % ----- % :code:`m = get(pat, k, l)` - % gets the element specified by m_kl. + % gets the element specified by :math:`M_{kl}`. + % % :code:`m = get(pat, ks, l)` - % gets a row vector of elements in the l'th row. + % gets a row vector of elements in the :code:`l`'th row. + % % :code:`m = get(pat, k, ls)` - % gets a col vector of elements in the k'th column. + % gets a column vector of elements in the :code:`k`'th column. % assert(isscalar(p)); m = p.M(k + bitshift(((l + 1 + p.N) .* (p.N - l)), -1)); end function p = set(p, k, l, val) + % Set the value of :math:`M_{kl}` to :code:`val`. + % assert(isscalar(p)); assert(checkbounds(p, k, l)); p.M(k + bitshift(((l + 1 + p.N) * (p.N - l)), -1)) = val; @@ -212,7 +217,7 @@ function disp(p) % Usage % ----- % :code:`sigma = rowsum(pat, l)` - % computes :math:`\sigma_l = \sum_{k=1}^l m_{k, l}`. + % computes :math:`\sigma_l = \sum_{k=1}^l m_{k, l}`. sigma = sum(get(p, 1:l, l)); end @@ -223,8 +228,8 @@ function disp(p) % Usage % ----- % :code:`w = pWeight(pat)` - % computes the pattern weight :math:`W(\text{pat}) = (w_1 w_2 ... w_N)` where - % :math:`w_i = \sigma_i - \sigma_{i-1}`. + % computes the pattern weight :math:`W(\text{pat}) = (w_1 w_2 ... w_N)` where + % :math:`w_i = \sigma_i - \sigma_{i-1}`. % % Note % ---- @@ -232,7 +237,7 @@ function disp(p) % % See Also % -------- - % :meth:`GtPattern.zWeight` + % :meth:`.GtPattern.zWeight` M = p.M; ctr = 0; @@ -252,12 +257,12 @@ function disp(p) % Usage % ----- % :code:`w = zWeight(pat)` - % computes the z-weight :math:`L(\text{pat}) = (l_1 l_2 ... l_N-1)` where - % :math:`l_i = \sigma_l - 1/2(\sigma_{l+1} + \sigma_{l-1})`. + % computes the z-weight :math:`L(\text{pat}) = (l_1 l_2 ... l_N-1)` where + % :math:`l_i = \sigma_l - 1/2(\sigma_{l+1} + \sigma_{l-1})`. % % Note % ---- - % This is a generalization of the m-quantum number for angular momentum. + % This is a generalization of the :math:`m`-quantum number for angular momentum. sigma = [0 arrayfun(@(l) rowsum(p, l), 1:p.N)]; w = sigma(2:end-1) - (sigma(1:end-2) + sigma(3:end))/2; diff --git a/src/tensors/charges/O2.m b/src/tensors/charges/O2.m index cd15446..9595439 100644 --- a/src/tensors/charges/O2.m +++ b/src/tensors/charges/O2.m @@ -1,16 +1,25 @@ classdef O2 < AbstractCharge - % O2 - Irreducible representations of O(2). - % This class represents the representations of O(2), also known as the - % semi-direct product of U(1) and charge conjugation. + % Irreducible representations of :math:`\mathrm{O}(2)`. % - % The representations are labeled using (j, s), indicating the behaviour - % of U(1) and charge conjugation respectively. This leads to two - % 1-dimensional representations (0, 0) and (0, 1), and for any - % non-negative j a two-dimensional representation (j, 2). + % This class represents the representations of :math:`\mathrm{O}(2)`, also known as the + % semi-direct product of :math:`\mathrm{U}(1)` and charge conjugation. + % + % The representations are labeled using :math:`(j, s)`, indicating the behaviour + % of :math:`\mathrm{U}(1)` and charge conjugation respectively. This leads to two + % 1-dimensional representations :math:`(0, 0)` and :math:`(0, 1)`, and for any + % non-negative :math:`j` a two-dimensional representation :math:`(j, 2)`. + % + % Properties + % ---------- + % j : :class:`uint8` + % :math:`\mathrm{U}(1)` label + % + % s : :class:`uint8` + % indicator for type of representation properties - j uint8 % (uint8) U1 label. - s uint8 % (uint8) indicator for type of representation. + j uint8 + s uint8 end methods diff --git a/src/tensors/charges/ProductCharge.m b/src/tensors/charges/ProductCharge.m index 5f228c7..e44c4c5 100644 --- a/src/tensors/charges/ProductCharge.m +++ b/src/tensors/charges/ProductCharge.m @@ -11,14 +11,19 @@ % % Usage % ----- - % charges = ProductCharge(charges1, charges2, ...) - % creates an (array of) charges that are representations of the direct product - % group group1 x group2 x ... + % :code:`charges = ProductCharge(charges1, charges2, ...)` creates an (array of) + % charges that are representations of the direct product group + % :math:`G_1 \otimes G_2 \otimes \dots`. % % Arguments % --------- - % charges1, charges2, ... : AbstractCharge - % charges of the separate groups. + % charges1, charges2, ... : :class:`.AbstractCharge` + % charges of the separate groups + % + % Returns + % ------- + % prodcharge : :class:`.ProductCharge` + % resulting product charge arguments (Repeating) charges @@ -36,7 +41,7 @@ end function a = cat(dim, varargin) - % Concatenate charges. + % Concatenate charges along a given axis. mask = cellfun(@isempty, varargin); firstnonempty = find(~mask, 1); if isempty(firstnonempty) @@ -96,7 +101,21 @@ end function [d, N] = prod(a, dim) - % Total fusion product of charges. + % Compute the total fusion product of array of charges along a given axis. + % + % Arguments + % --------- + % a : :class:`.ProductCharge` + % input array of charges + % dim : :class:`int` + % array dimension along which to take the fusion product, defaults to first + % non-trivial axis + % + % Returns + % ------- + % c : :class:`.ProductCharge` + % array of total fusion products determined by subsequently multiplying input + % charges along the given direction arguments a @@ -181,53 +200,53 @@ end end - function varargout = subsref(prodcharge, s) + function varargout = subsref(prodcharges, s) % Overload indexing. % % Usage % ----- - % charges_slice = charges(i1, i2, ...) - % extracts elements out of the charge array. + % :code:`charges_slice = prodcharges(i1, i2, ...)` + % extracts elements out of the charge array. % - % product_slice = charges{i} - % separate out the direct product factors. + % :code:`product_slice = prodcharges{i}` + % separates out the direct product factors. % % Arguments % --------- - % charges : ProductCharge - % array of charges. + % prodcharges : :class:`.ProductCharge` + % array of product charges % - % s : substruct - % structure containing indexing data. + % s : :class:`substruct` + % structure containing indexing data % % Returns % ------- - % charges_slice : ProductCharge - % sliced array of product charges. + % charges_slice : :class:`.ProductCharge` + % sliced array of product charges % - % product_slice : AbstractCharge - % array of factor charges. + % product_slice : :class:`.AbstractCharge` + % array of factor charges switch s(1).type case '.' - [varargout{1:nargout}] = builtin('subsref', prodcharge, s); + [varargout{1:nargout}] = builtin('subsref', prodcharges, s); case '()' - for i = 1:numel(prodcharge.charges) - prodcharge.charges{i} = prodcharge.charges{i}(s(1).subs{:}); + for i = 1:numel(prodcharges.charges) + prodcharges.charges{i} = prodcharges.charges{i}(s(1).subs{:}); end if length(s) == 1 - varargout = {prodcharge}; + varargout = {prodcharges}; return end - [varargout{1:nargout}] = subsref(prodcharge, s(2:end)); + [varargout{1:nargout}] = subsref(prodcharges, s(2:end)); case '{}' assert(length(s) == 1); assert(length(s(1).subs) == 1); assert(nargout == length(s(1).subs{1})); - varargout(1:nargout) = prodcharge.charges(s(1).subs{:}); + varargout(1:nargout) = prodcharges.charges(s(1).subs{:}); otherwise error('Undefined behaviour'); @@ -239,33 +258,36 @@ % % Usage % ----- + % % :code:`a = subsasgn(a, substruct('()', subs), b)` % % :code:`a(subs{:}) = b` - % assign array slices. + % + % Assign array slices. % % :code:`a = subsasgn(a, substruct('{}', subs), c)` % % :code:`a{i} = c` - % assign to a factor slice. + % + % Assign to a factor slice. % % Arguments % --------- - % a : :class:`ProductCharge` + % a : :class:`.ProductCharge` % array of charges to assign to. % % s : :class:`struct` % structure containing indexing data. % - % b : :class:`ProductCharge` + % b : :class:`.ProductCharge` % slice to assign. % - % c : :class:`AbstractCharge` + % c : :class:`.AbstractCharge` % factor to assign. % % Returns % ------- - % a : ProductCharge + % a : :class:`.ProductCharge` % assigned array switch s(1).type @@ -380,29 +402,9 @@ function F = Fmatrix(a, b, c, d, e, f) % Compute the full recoupling matrix from ``e`` to ``f``. % - % .. todo:: - % Add proper definition? - % - % Usage - % ----- - % :code:`F = Fmatrix(a, b, c, d, e, f)` computes the matrix between all allowed - % channels. - % - % Arguments - % --------- - % a, b, c : :class:`.AbstractCharge` - % charges being fused - % d : :class:`.AbstractCharge` - % total charges - % e : :class:`.AbstractCharge` (1, \*) - % intermediate charges before recoupling - % f : :class:`.AbstractCharge` (1, \*) - % intermediate charge after recoupling - % - % Returns - % ------- - % F : :class:`double` (\*, \*, \*, \*) - % recoupling matrix between all allowed channels + % See also + % -------- + % :meth:`.AbstractCharge.Fmatrix` if a.fusionstyle == FusionStyle.Unique if nargin < 5, e = a * b; end if nargin < 6, f = b * c; end diff --git a/src/tensors/charges/SU2.m b/src/tensors/charges/SU2.m index dd54954..5727c8d 100644 --- a/src/tensors/charges/SU2.m +++ b/src/tensors/charges/SU2.m @@ -1,10 +1,10 @@ classdef SU2 < AbstractCharge & uint8 - % SU2 - Irreducible representations of SU2. - % This class represents the representations of SU2, represented using uint8, such that - % the representative is equal to the quantum dimension. The use of uint8 limits the - % maximum to 255. The spin label can be recovered with spin = (j - 1) / 2. + % Irreducible representations of :math:`\mathrm{SU}(2)`. % - % See also AbstractCharge + % This class represents the representations of :math:`\mathrm{SU}(2)`, represented using + % :code:`uint8`, such that the representative is equal to the quantum dimension. The use + % of :math:`uint8` limits the maximum dimension to 255. The spin label can be recovered + % as :math:`s = (j - 1) / 2`. methods function style = braidingstyle(~) diff --git a/src/tensors/charges/SUN.m b/src/tensors/charges/SUN.m index 6e888ab..4fbae36 100644 --- a/src/tensors/charges/SUN.m +++ b/src/tensors/charges/SUN.m @@ -1,5 +1,13 @@ classdef SUN < AbstractCharge - % Irreducible representations of the special unitary group SU(N). + % Irreducible representations of the special unitary group :math:`\mathrm{SU}(N)`. + % + % .. todo:: + % Explain irrep labeling and give some references. + % + % Properties + % ---------- + % I : (1, :) :class:`uint8` + % integer vector representation label properties I (1,:) uint8 diff --git a/src/tensors/charges/U1.m b/src/tensors/charges/U1.m index 58e2918..e09758e 100644 --- a/src/tensors/charges/U1.m +++ b/src/tensors/charges/U1.m @@ -1,8 +1,8 @@ classdef U1 < AbstractCharge & int16 - % U1 - Irreducible representations of U(1). - % This class represents the representations of U(1), labeled using integers. + % Irreducible representations of :math:`\mathrm{U}(1)`. % - % See also AbstractCharge + % This class represents the representations of :math:`\mathrm{U}(1)`, labeled using + % integers where fusion is given by addition. methods function charge = U1(varargin) diff --git a/src/tensors/charges/Z1.m b/src/tensors/charges/Z1.m index 0629776..930cd7e 100644 --- a/src/tensors/charges/Z1.m +++ b/src/tensors/charges/Z1.m @@ -1,5 +1,5 @@ classdef Z1 < AbstractCharge - % Trivial charges. + % Trivial charge. methods function A = Asymbol(~, ~, ~) diff --git a/src/tensors/charges/Z2.m b/src/tensors/charges/Z2.m index baf5e36..2da8e96 100644 --- a/src/tensors/charges/Z2.m +++ b/src/tensors/charges/Z2.m @@ -2,8 +2,8 @@ % Irreducible representations of :math:`Z_2`. % % This class implements the trivial or sign representations of :math:`Z_2`, represented - % using {:code:`false`, :code:`true`} where multiplication is given by - % :math:`\mathrm{XOR}`, giving the multiplication table: + % using {:code:`false`, :code:`true`} where fusion is given by :math:`\mathrm{XOR}`, + % giving the multiplication table: % % .. list-table:: % @@ -17,9 +17,6 @@ % - :code:`true` % - :code:`false` % - % See Also - % -------- - % :class:`AbstractCharge` methods function A = Asymbol(a, b, c) diff --git a/src/tensors/charges/ZN.m b/src/tensors/charges/ZN.m index 88daf6d..43cbfbe 100644 --- a/src/tensors/charges/ZN.m +++ b/src/tensors/charges/ZN.m @@ -1,8 +1,14 @@ classdef ZN < AbstractCharge & uint8 - % ZN - Irreducible representations of ZN. - % This class represents representations of the cyclic group of order N. - % - % See also AbstractCharge, Z2, Z3, Z4 + % Irreducible representations of :math:`Z_N`. + % + % This class implements the representations of the cyclic group of order :math:`N`, + % represented using unteger labels where fusion is given by addition modulo + % :math:`N`. + % + % Properties + % ---------- + % N : :class:`uint8` + % integer representation label properties N (1,1) uint8 = uint8(1) diff --git a/src/tensors/charges/fSU2.m b/src/tensors/charges/fSU2.m index 3aaca94..c2398d8 100644 --- a/src/tensors/charges/fSU2.m +++ b/src/tensors/charges/fSU2.m @@ -1,7 +1,9 @@ classdef fSU2 < SU2 - % Fermionic spin charges. - % This is equivalent to representations of SU2 x fZ2, but restricted to only allow - % for the combinations of integer with trivial and halfinteger with fermion charges. + % Fermionic :math:`\mathrm{SU}(2)` charges, used to represent fermionspin. + % + % This is equivalent to representations of :math:`\mathrm{SU}(2) \otimes fZ_2`, but + % restricted to only allow for the combinations of integer with trivial and halfinteger + % with fermion charges. methods function style = braidingstyle(~) diff --git a/src/tensors/charges/fU1.m b/src/tensors/charges/fU1.m index e40fde3..fc2a9b5 100644 --- a/src/tensors/charges/fU1.m +++ b/src/tensors/charges/fU1.m @@ -1,7 +1,9 @@ classdef fU1 < U1 - % Fermionic U1 charges. - % This is equivalent to representations of U1 x fZ2, but restricted to only allow - % for the combinations of even with trivial and odd with fermion charges. + % Fermionic :math:`\mathrm{U}(1)` charges. + % + % This is equivalent to representations of :math:`\mathrm{U}(1) \otimes fZ_2`, but + % restricted to only allow for the combinations of even with trivial and odd with + % fermion charges. methods function style = braidingstyle(~) diff --git a/src/tensors/charges/fZ2.m b/src/tensors/charges/fZ2.m index 901acff..f6955b0 100644 --- a/src/tensors/charges/fZ2.m +++ b/src/tensors/charges/fZ2.m @@ -1,7 +1,5 @@ classdef fZ2 < Z2 - % Fermionic charges. - % - % See also AbstractCharge + % Fermionic charges, implemented as a graded :math:`Z_2` symmetry. methods function style = braidingstyle(~) diff --git a/src/tensors/kernels/AbelianBlock.m b/src/tensors/kernels/AbelianBlock.m index 38d3b40..ca595c7 100644 --- a/src/tensors/kernels/AbelianBlock.m +++ b/src/tensors/kernels/AbelianBlock.m @@ -1,7 +1,9 @@ classdef AbelianBlock < MatrixBlock - %ABELIANBLOCK Summary of this class goes here - % Detailed explanation goes here - + % Structure for storing symmetric tensor data for an Abelian symmetry. + % + % This represents the blocks in the block-diagonal decomposition of a tensor defined + % over graded vector spaces corresponding to an Abelian symmetry, allowing for a more + % efficient multiplication. methods function Y = axpby(a, X, b, Y, p, map) diff --git a/src/tensors/kernels/AbstractBlock.m b/src/tensors/kernels/AbstractBlock.m index ad459fb..d433725 100644 --- a/src/tensors/kernels/AbstractBlock.m +++ b/src/tensors/kernels/AbstractBlock.m @@ -1,5 +1,6 @@ classdef (Abstract) AbstractBlock % Abstract structure for storing tensor data. + % % This represents the blocks in the block-diagonal decomposition of a general tensor. %#ok<*INUSD> @@ -13,19 +14,19 @@ % % Arguments % --------- - % fun : function_handle + % fun : :class:`function_handle` % initialising function for the tensor data, with signature - % `data = fun(dims)` where dims is a row vector of dimensions. + % :code:`data = fun(dims)` where dims is a row vector of dimensions. % - % codomain : AbstractSpace + % codomain : :class:`.AbstractSpace` % vector of vector spaces that form the codomain. % - % domain : AbstractSpace + % domain : :class:`.AbstractSpace` % vector of vector spaces that form the domain. % % Returns % ------- - % X : AbstractBlock + % X : :class:`.AbstractBlock` % tensor data. if isa(codomain, 'CartesianSpace') || isa(codomain, 'ComplexSpace') || ... @@ -68,103 +69,119 @@ %% Required methods methods function Y = axpby(a, X, b, Y, p, map) - % (Abstract) Compute ```Y = permute(X, p) .* a + Y .* b```. + % Compute :code:`Y = permute(X, p) .* a + Y .* b`. % This method is the computationally critical method of this class, thus has - % special cases for scalar multiplication (a == 0), addition (nargin == 4), and - % various optimizations when a == 1, b == 0 | b == 1. Additionally, this method - % should not be called directly as it should not perform any error checks. + % special cases for scalar multiplication (:code:`a == 0`), addition + % (:code:`nargin == 4`), and various optimizations when :code:`a == 1`, + % :code:`b == 0 || b == 1`. Additionally, this method should not be called + % directly as it should not perform any error checks. % % Arguments % --------- - % a : double + % a : :class:`double` % scalar to multiply with X. % - % X : :class:`AbstractBlock` + % X : :class:`.AbstractBlock` % list of source blocks. % - % b : double + % b : :class:`double` % scalar to multiply with Y. % - % Y : :class:`AbstractBlock` + % Y : :class:`.AbstractBlock` % list of destination blocks. % - % p : int + % p : :class:`int` % permutation vector for X. % - % map : (sparse) double + % map : (sparse) :class:`double` % coefficient matrix for permuting X. % % Returns % ------- - % Y : :class:`AbstractBlock` - % Result of computing Y = permute(X, p) .* a + Y .* b. - + % Y : :class:`.AbstractBlock` + % Result of computing :code:`Y = permute(X, p) .* a + Y .* b`. + % + % Note + % ---- + % This is an abstract method that should be overloaded for each subtype. error('This method should be overloaded.'); end function [mblocks, mcharges] = matrixblocks(b) - % (Abstract) Extract a list of coupled matrix blocks. + % Extract a list of coupled matrix blocks. % % Arguments % --------- - % b : AbstractBlock + % b : :class:`.AbstractBlock` % list of input data. % % Returns % ------- - % mblocks : cell + % mblocks : :class:`cell` % list of non-zero coupled matrix blocks, sorted according to its charge. % - % mcharges : AbstractCharge + % mcharges : :class:`.AbstractCharge` % list of coupled charges. + % + % Note + % ---- + % This is an abstract method that should be overloaded for each subtype. error('This method should be overloaded.'); end function C = mul(C, A, B, a, b) - % (Abstract) Compute ```C = (A .* a) * (B .* b)```. + % Compute :code:`C = (A .* a) * (B .* b)`. % Compute the matrix product of two source tensor structures, and store the % result in the destination tensor structure. This method should not perform any % error checks. % % Arguments % --------- - % C : AbstractBlock + % C : :class:`.AbstractBlock` % location to store the result % - % A : AbstractBlock + % A : :class:`.AbstractBlock` % first matrix factor % - % B : AbstractBlock + % B : :class:`.AbstractBlock` % second matrix factor % - % a : double = 1 + % a : :class:`double` = 1 % first scalar factor % - % b : double = 1 + % b : :class:`double` = 1 % second scalar factor % % Returns % ------- - % C : AbstractBlock + % C : :class:`.AbstractBlock` % Result of computing C = (A .* a) * (B .* b) + % + % Note + % ---- + % This is an abstract method that should be overloaded for each subtype. error('This method should be overloaded.'); end function tblocks = tensorblocks(b) - % (Abstract) Extract a list of uncoupled tensor blocks. + % Extract a list of uncoupled tensor blocks. % % Arguments % --------- - % b : AbstractBlock + % b : :class:`.AbstractBlock` % list of input data. % % Returns % ------- - % tblocks : cell + % tblocks : :class:`cell` % list of non-zero uncoupled tensor blocks, sorted according to the coupled % charge and then in column-major order according to the uncoupled charges. + % + % Note + % ---- + % This is an abstract method that should be overloaded for each subtype. error('This method should be overloaded.'); end @@ -174,103 +191,106 @@ %% Optional methods methods function Y = axpy(a, X, Y, p, map) - % Compute ```Y = permute(X, p) .* a + Y```. + % Compute :code:`Y = permute(X, p) .* a + Y`. % This method is a convenience method that automatically falls back on - % ```Y = axpby(a, X, 1, Y, p, map)```, but can be overloaded if the additional + % :code:`Y = axpby(a, X, 1, Y, p, map)`, but can be overloaded if the additional % efficiency is desired. % % Arguments % --------- - % a : double + % a : :class:`double` % scalar to multiply with X. % - % X : AbstractBlock + % X : :class:`.AbstractBlock` % list of source blocks. % - % Y : AbstractBlock + % Y : :class:`.AbstractBlock` % list of destination blocks. % - % p : int + % p : :class:`int` % permutation vector for X. % - % map : (sparse) double + % map : (sparse) :class:`double` % coefficient matrix for permuting X. % % Returns % ------- - % Y : AbstractBlock - % Result of computing Y = permute(X, p) .* a + Y. + % Y : :class:`.AbstractBlock` + % Result of computing :code:`Y = permute(X, p) .* a + Y`. Y = axpby(a, X, 1, Y, p, map); end function Y = minus(X, Y) - % Subtraction of X and Y. + % Subtraction of :code:`X` and :code`Y`. % % Usage % ----- - % Y = minus(X, Y) - % Y = X - Y + % :code:`Y = minus(X, Y)` + % + % :code:`Y = X - Y` % % Arguments % --------- - % X : AbstractBlock + % X : :class:`.AbstractBlock` % first list of input data. % - % Y : AbstractBlock + % Y : :class:`.AbstractBlock` % second list of input data. % % Returns % ------- - % Y : AbstractBlock - % list of output matrices. + % Y : :class:`.AbstractBlock` + % list of output data. Y = axpby(1, X, -1, Y); end function Y = plus(X, Y) - % Addition of X and Y. + % Addition of :code:`X` and :code:`Y`. % % Usage % ----- - % Y = plus(X, Y) - % Y = X + Y + % :code:`Y = plus(X, Y)` + % + % :code:`Y = X + Y` % % Arguments % --------- - % X : AbstractBlock + % X : :class:`.AbstractBlock` % first list of input data. % - % Y : AbstractBlock + % Y : :class:`.AbstractBlock` % second list of input data. % % Returns % ------- - % Y : MatrixBlock + % Y : :class:`.AbstractBlock` % list of output data. Y = axpby(1, X, 1, Y); end function Y = times(Y, a) - % Scalar multiplication of Y and a. + % Scalar multiplication of :code:`Y` and :code:`a`. % % Usage % ----- - % Y = times(Y, a) - % Y = Y .* a + % :code:`Y = times(Y, a)` + % + % :code:`Y = Y .* a` % % Arguments % --------- - % Y : AbstractBlock + % Y : :class:`.AbstractBlock` % list of input data. % - % a : double + % a : :class:`double` % scalar factor. % % Returns % ------- - % Y : AbstractBlock + % Y : :class:`.AbstractBlock` % list of output data. Y = axpby(0, [], a, Y); @@ -281,20 +301,20 @@ % % Usage % ----- - % Y = rdivide(Y, a) - % Y = Y ./ a + % :code:`Y = rdivide(Y, a)` + % :code:`Y = Y ./ a` % % Arguments % --------- - % Y : AbstractBlock + % Y : :class:`.AbstractBlock` % list of input data. % - % a : double + % a : :class:`double` % scalar factor. % % Returns % ------- - % Y : AbstractBlock + % Y : :class:`.AbstractBlock` % list of output data. Y = axpby(0, [], 1/a, Y); @@ -305,17 +325,18 @@ % % Usage % ----- - % A = uplus(A) - % A = +A + % :code:`A = uplus(A)` + % + % :code:`A = +A` % % Arguments % --------- - % A : AbstractBlock + % A : :class:`.AbstractBlock` % list of input data. % % Returns % ------- - % A : AbstractBlock + % A : :class:`.AbstractBlock` % list of output data. end @@ -325,18 +346,19 @@ % % Usage % ----- - % A = uminus(A) - % A = -A + % :code:`A = uminus(A)` + % + % :code:`A = -A` % % Arguments % --------- - % A : MatrixBlock - % list of input matrices. + % A : :class:`.AbstractBlock` + % list of input data. % % Returns % ------- - % A : MatrixBlock - % list of output matrices. + % A : :class:`.AbstractBlock` + % list of output data. Y = Y .* (-1); end @@ -346,22 +368,44 @@ % % Arguments % --------- - % X : :class:`AbstractBlock` + % X : :class:`.AbstractBlock` % input block. % % Returns % ------- - % type : char + % type : :class:`char` % the scalar type of the data, which is one of the following: % - % - 'single' or 'double' - % - 'logical' - % - 'int8', 'int16', 'int32' or 'int64' - % - 'uint8', 'uint16', 'uint32' or 'uint64' + % - :class:`single` or :class:`double` + % - :class:`logical` + % - :class:`int8`, :class:`int16`, :class:`int32` or :class:`int64` + % - :class:`uint8`, :class:`uint16`, :class:`uint32` or :class:`uint64` error('tensors:AbstractMethod', 'This method should be overloaded.'); end + function X = ctranspose(X) + % Adjoint of a tensor. + % + % Usage + % ----- + % :code:`X = ctranspose(X)` + % + % :code:`X = X'` + % + % Arguments + % --------- + % X : :class:`.AbstractBlock` + % list of input data. + % + % Returns + % ------- + % X : :class:`.AbstractBlock` + % list of adjoint output data. + + end + + function bool = iszero(X) bool = isempty(X.var); end diff --git a/src/tensors/kernels/MatrixBlock.m b/src/tensors/kernels/MatrixBlock.m index fe70412..ef3f45b 100644 --- a/src/tensors/kernels/MatrixBlock.m +++ b/src/tensors/kernels/MatrixBlock.m @@ -1,6 +1,11 @@ classdef MatrixBlock < AbstractBlock - %MATRIXBLOCK Summary of this class goes here - % Detailed explanation goes here + % Structure for storing symmetric tensor data. + % + % This represents the blocks in the block-diagonal decomposition of a tensor defined + % over graded vector spaces. + % + % .. todo:: + % Document properties and behavior. %#ok<*INUSD> properties @@ -615,17 +620,18 @@ % % Usage % ----- - % X = ctranspose(X) - % X = X' + % :code:`X = ctranspose(X)` + % + % :code:`X = X'` % % Arguments % --------- - % X : :class:`MatrixBlock` + % X : :class:`.MatrixBlock` % list of input matrices. % % Returns % ------- - % X : :class:`MatrixBlock` + % X : :class:`.MatrixBlock` % list of adjoint output matrices. X.rank = fliplr(X.rank); diff --git a/src/tensors/kernels/TrivialBlock.m b/src/tensors/kernels/TrivialBlock.m index 76c3dcb..a3498f3 100644 --- a/src/tensors/kernels/TrivialBlock.m +++ b/src/tensors/kernels/TrivialBlock.m @@ -1,5 +1,8 @@ classdef TrivialBlock < AbstractBlock - % TrivialBlock - Data structure for tensors without symmetry. + % Data structure for tensors without symmetry. + % + % .. todo:: + % Document properties and behavior. properties var @@ -18,7 +21,7 @@ function Y = axpby(a, X, b, Y, p, ~) - %% Special case 1: scalar multiplication + %%% Special case 1: scalar multiplication if a == 0 if b == 1 return; @@ -117,25 +120,6 @@ end function Y = rdivide(Y, a) - % Scalar division of Y and a. - % - % Usage - % ----- - % Y = rdivide(Y, a) - % Y = Y ./ a - % - % Arguments - % --------- - % Y : MatrixBlock - % list of input matrices. - % - % a : double - % scalar factor. - % - % Returns - % ------- - % Y : MatrixBlock - % list of output matrices. if a == 1, return; end if a == -1, Y = -Y; return; end diff --git a/src/tensors/spaces/AbstractSpace.m b/src/tensors/spaces/AbstractSpace.m index 499b15d..6973626 100644 --- a/src/tensors/spaces/AbstractSpace.m +++ b/src/tensors/spaces/AbstractSpace.m @@ -1,9 +1,17 @@ classdef (Abstract) AbstractSpace % Abstract structure of a tensor index. + % + % Properties + % ---------- + % dimensions : :class:`int` or :class:`struct` + % Specification of the internal dimensions + % + % dual : :class:`logical` + % Flag to indicate if the space is a dual space - properties %(Access = protected) - dimensions % Specification of the internal dimensions - dual (1,1) logical = false % Flag to indicate if the space is a dual space + properties + dimensions + dual (1,1) logical = false end %% Constructors @@ -13,15 +21,15 @@ % % Repeating Arguments % ------------------- - % dimensions : int or struct + % dimensions : :class:`int` or :class:`struct` % a variable which represents the internal dimension of the space. % - % isdual : logical + % isdual : :class:`logical` % a variable which indicates if a space is dual. % % Returns % ------- - % spaces : :class:`AbstractSpace` + % spaces : :class:`.AbstractSpace` % array of spaces. arguments (Repeating) @@ -45,16 +53,20 @@ % % Repeating Arguments % ------------------- - % dimensions : int or struct + % dimensions : :class:`int` or :class:`struct` % a variable which represents the internal dimension of the space. % - % dual : logical + % dual : :class:`logical` % a variable which indicates if a space is dual. % % Returns % ------- - % spaces : :class:`AbstractSpace` + % spaces : :class:`.AbstractSpace` % array of spaces. + % + % Note + % ---- + % This abstract constructor should be overloaded for every concrete subtype. error('tensors:AbstractMethod', 'This method should be overloaded.'); end @@ -73,12 +85,12 @@ % % Arguments % --------- - % spaces : (1, :) :class:`AbstractSpace` + % spaces : (1, :) :class:`.AbstractSpace` % input spaces. % % Returns % ------- - % c : (:, :) :class:`AbstractCharge` + % c : (:, :) :class:`.AbstractCharge` % list of charge combinations, where each row is a combination. error('tensors:AbstractMethod', 'This method should be overloaded.'); @@ -89,12 +101,12 @@ % % Arguments % --------- - % spaces : (1, :) :class:`AbstractSpace` + % spaces : (1, :) :class:`.AbstractSpace` % input spaces. % % Returns % ------- - % d : (:, :) int + % d : (:, :) :class:`int` % list of degeneracy combinations, where each row is an entry. error('tensors:AbstractMethod', 'This method should be overloaded.'); @@ -105,12 +117,12 @@ % % Arguments % --------- - % spaces : (1, :) :class:`AbstractSpace` + % spaces : (1, :) :class:`.AbstractSpace` % input spaces. % % Returns % ------- - % d : (1, :) numeric + % d : (1, :) :class:`double` % total dimension of each of the input spaces. error('tensors:AbstractMethod', 'This method should be overloaded.'); @@ -125,12 +137,12 @@ % % Arguments % --------- - % codomain, domain : :class:`GradedSpace` + % codomain, domain : :class:`.GradedSpace` % input spaces. % % Returns % ------- - % trees : :class:`FusionTree` + % trees : :class:`.FusionTree` % list of all allowed fusion trees. rank = [length(codomain) length(domain)]; @@ -150,12 +162,12 @@ % % Arguments % --------- - % codomain, domain : (1, :) :class:`AbstractSpace` + % codomain, domain : (1, :) :class:`.AbstractSpace` % input spaces. % % Returns % ------- - % style : :class:`BraidingStyle` + % style : :class:`.BraidingStyle` % braiding style of the internal structure. error('tensors:AbstractMethod', 'This method should be overloaded.'); @@ -166,12 +178,12 @@ % % Arguments % --------- - % codomain, domain : (1, :) :class:`AbstractSpace` + % codomain, domain : (1, :) :class:`.AbstractSpace` % input spaces. % % Returns % ------- - % style : :class:`FusionStyle` + % style : :class:`.FusionStyle` % fusion style of the internal structure. error('tensors:AbstractMethod', 'This method should be overloaded.'); @@ -186,12 +198,12 @@ % % Arguments % --------- - % spaces : (1, :) :class:`AbstractSpace` + % spaces : (1, :) :class:`.AbstractSpace` % input spaces. % % Returns % ------- - % spaces : (1, :) :class:`AbstractSpace` + % spaces : (1, :) :class:`.AbstractSpace` % dual spaces. for i = 1:length(spaces) @@ -205,12 +217,12 @@ % % Arguments % --------- - % spaces : (1, :) :class:`AbstractSpace` + % spaces : (1, :) :class:`.AbstractSpace` % input product space. % % Returns % ------- - % spaces : (1, :) :class:`AbstractSpace` + % spaces : (1, :) :class:`.AbstractSpace` % dual product space. spaces = conj(spaces(length(spaces):-1:1)); @@ -221,12 +233,12 @@ % % Arguments % --------- - % space1, space2 : (1,1) :class:`AbstractSpace` + % space1, space2 : (1, 1) :class:`.AbstractSpace` % input spaces. % % Returns % ------- - % space : (1,1) :class:`AbstractSpace` + % space : (1, 1) :class:`.AbstractSpace` % fused space. error('tensors:AbstractMethod', 'This method should be overloaded.'); @@ -237,12 +249,12 @@ % % Arguments % --------- - % space1, space2 : (1,1) :class:`AbstractSpace` + % space1, space2 : (1, 1) :class:`.AbstractSpace` % input spaces. % % Returns % ------- - % space : (1,1) :class:`AbstractSpace` + % space : (1, 1) :class:`.AbstractSpace` % direct sum space. @@ -254,12 +266,12 @@ % % Arguments % --------- - % spaces : (1, :) :class:`AbstractSpace` - % Array of input spaces. + % spaces : (1, :) :class:`.AbstractSpace` + % array of input spaces. % % Returns % ------- - % space : (1, 1) :class:`AbstractSpace` + % space : (1, 1) :class:`.AbstractSpace` % Fused space which is isomorphic to the input product space. % TODO this is probably faster by fusing from both ends to the middle. @@ -274,12 +286,12 @@ % % Arguments % --------- - % spaces : (1, :) :class:`AbstractSpace` - % Array of input spaces. + % spaces : (1, :) :class:`.AbstractSpace` + % array of input spaces. % % Returns % ------- - % space : (1, 1) :class:`AbstractSpace` + % space : (1, 1) :class:`.AbstractSpace` % direct sum space. space = spaces(1); for i = 2:length(spaces) @@ -288,7 +300,23 @@ end function spaces = insertone(spaces, i, dual) - % insert a trivial space at position i. + % Insert a trivial space at a given position. + % + % Arguments + % --------- + % spaces : (1, :) :class:`.AbstractSpace` + % array of input spaces. + % + % i : :class:`int` + % position at which to insert trivial space, defaults to the end. + % + % dual : :class:`logical` + % indicate if trivial space should be dualized, defaults to :code:`false`. + % + % Returns + % ------- + % spaces : (1, :) :class:`.AbstractSpace` + % array of output spaces. arguments spaces i = length(spaces) + 1 @@ -307,12 +335,12 @@ % % Arguments % --------- - % spaces1, spaces2 : (1, :) :class:`AbstractSpace` + % spaces1, spaces2 : (1, :) :class:`.AbstractSpace` % input spaces, either of equal size or scalar. % % Returns % ------- - % bools : (1, :) logical + % bools : (1, :) :class:`logical` % flags that indicate if the element spaces are equal. error('tensors:AbstractMethod', 'This method should be overloaded.'); @@ -344,16 +372,16 @@ % % Usage % ----- - % bool = isequal(spaces{:}) + % :code:`bool = isequal(spaces{:})` % % Repeating Arguments % ------------------- - % spaces : (1,:) :class:`AbstractSpace` + % spaces : (1, :) :class:`.AbstractSpace` % input spaces to compare. % % Returns % ------- - % bool : (1,1) logical + % bool : (1, 1) :class:`logical` % true if all inputs are equal. arguments (Repeating) @@ -376,17 +404,17 @@ function hashable = GetMD5_helper(spaces) % Helper function for hash algorithm. This converts the space object to a data - % structure which can be processed by :func:`GetMD5`. + % structure which can be processed by :func:`.GetMD5`. % % Arguments % --------- - % spaces : :class:`AbstractSpace` + % spaces : :class:`.AbstractSpace` % input spaces. % % Returns % ------- - % hashable : cell - % data which can be accepted by :func:`GetMD5`. + % hashable : :class:`cell` + % data which can be accepted by :func:`.GetMD5`. hashable = {spaces.dimensions, spaces.dual}; end diff --git a/src/tensors/spaces/Arrow.m b/src/tensors/spaces/Arrow.m index 7159648..d867e97 100644 --- a/src/tensors/spaces/Arrow.m +++ b/src/tensors/spaces/Arrow.m @@ -1,6 +1,8 @@ classdef Arrow < logical -% Arrow - Direction of tensor leg. -% Enumeration class with directions. +% Enumeration class reprenting the possible directions of a tensor leg: +% +% - :code:`in`: Incoming tensor leg, encoded as a logical :code:`true` +% - :code:`out`: Outgoing tensor leg, encoded as a logical :code:`false` enumeration in (true) diff --git a/src/tensors/spaces/CartesianSpace.m b/src/tensors/spaces/CartesianSpace.m index 66a3f5d..eb4adc5 100644 --- a/src/tensors/spaces/CartesianSpace.m +++ b/src/tensors/spaces/CartesianSpace.m @@ -8,7 +8,7 @@ % % Repeating Arguments % ------------------- - % dimensions : (1, 1) int + % dimensions : (1, 1) :class:`int` % dimension of the vector space. % % ~ : any @@ -16,7 +16,7 @@ % % Returns % ------- - % spaces : :class:`CartesianSpace` + % spaces : :class:`.CartesianSpace` % array of cartesian spaces. arguments (Repeating) @@ -42,22 +42,24 @@ % % Usage % ----- - % spaces = CartesianSpace.new(dimensions, ~, ...) + % :code:`spaces = CartesianSpace.new(dims)` % - % spaces = CartesianSpace.new(dims) + % :code:`spaces = CartesianSpace.new(dimensions, ~, ...)` + % + % Arguments + % --------- + % dims : (1, :) :class:`int` + % vector of dimensions of all spaces. % % Repeating Arguments % ------------------- - % dimensions : struct + % dimensions : :class:`struct` % a variable which represents the internal dimension of the space. % - % dims : (1, :) int - % vector of dimensions of all spaces. - % % Returns % ------- - % spaces : :class:`CartesianSpace` - % array of spaces. + % spaces : (1, :) :class:`.CartesianSpace` + % array of cartesian vector spaces. if isstruct(varargin{1}) assert(mod(nargin, 2) == 0) @@ -89,13 +91,13 @@ % % Arguments % --------- - % spaces : (1, :) :class:`CartesianSpace` + % spaces : (1, :) :class:`.CartesianSpace` % input spaces. % % Returns % ------- - % c : [] - % empty result. + % c : (1, :) :class:`.Z1` + % array of trivial spaces of corresponding length. c = repmat(Z1, length(spaces), 1); end @@ -105,12 +107,12 @@ % % Arguments % --------- - % spaces : (1, :) :class:`CartesianSpace` + % spaces : (1, :) :class:`.CartesianSpace` % input spaces. % % Returns % ------- - % d : (1, :) int + % d : (1, :) :class:`int` % list of degeneracy combinations, with 1 element. d = [spaces.dimensions]; @@ -121,12 +123,12 @@ % % Arguments % --------- - % spaces : (1, :) :class:`CartesianSpace` + % spaces : (1, :) :class:`.CartesianSpace` % input spaces. % % Returns % ------- - % d : (1, :) int + % d : (1, :) :class:`int` % total dimension of each of the input spaces. d = [spaces.dimensions]; @@ -137,13 +139,13 @@ % % Arguments % --------- - % codomain, domain : (1, :) :class:`CartesianSpace` + % codomain, domain : (1, :) :class:`.CartesianSpace` % input spaces. % % Returns % ------- - % style : :class:`BraidingStyle` - % Trivial braiding style + % style : :class:`.BraidingStyle` + % trivial braiding style, :code:`BraidingStyle.Abelian`. style = BraidingStyle.Abelian; end @@ -153,13 +155,13 @@ % % Arguments % --------- - % codomain, domain : (1, :) :class:`CartesianSpace` + % codomain, domain : (1, :) :class:`.CartesianSpace` % input spaces. % % Returns % ------- - % style : :class:`FusionStyle` - % fusion style of the internal structure. + % style : :class:`.FusionStyle` + % fusion style of the internal structure, :code:`FusionStyle.Unique`. style = FusionStyle.Unique; end @@ -187,12 +189,12 @@ % % Arguments % --------- - % spaces : (1, :) :class:`CartesianSpace` + % spaces : (1, :) :class:`.CartesianSpace` % input spaces. % % Returns % ------- - % spaces : (1, :) :class:`CartesianSpace` + % spaces : (1, :) :class:`.CartesianSpace` % dual spaces. end @@ -201,12 +203,12 @@ % % Arguments % --------- - % space1, space2 : (1, 1) :class:`CartesianSpace` + % space1, space2 : (1, 1) :class:`.CartesianSpace` % input spaces. % % Returns % ------- - % space : (1, 1) :class:`CartesianSpace` + % space : (1, 1) :class:`.CartesianSpace` % fused space. space.dimensions = space.dimensions * space2.dimensions; @@ -217,13 +219,13 @@ % % Arguments % --------- - % spaces : (1, :) :class:`CartesianSpace` - % Array of input spaces. + % spaces : (1, :) :class:`.CartesianSpace` + % array of input spaces. % % Returns % ------- - % space : (1, 1) :class:`CartesianSpace` - % Fused space which is isomorphic to the input product space. + % space : (1, 1) :class:`.CartesianSpace` + % fused space which is isomorphic to the input product space. space = CartesianSpace(prod(dims(spaces)), []); end @@ -246,12 +248,12 @@ % % Arguments % --------- - % spaces1, spaces2 : (1, :) :class:`CartesianSpace` + % spaces1, spaces2 : (1, :) :class:`.CartesianSpace` % input spaces, either of equal size or scalar. % % Returns % ------- - % bools : (1, :) logical + % bools : (1, :) :class:`logical` % flags that indicate if the element spaces are equal. bools = [spaces1.dimensions] == [spaces2.dimensions]; @@ -269,17 +271,17 @@ function hashable = GetMD5_helper(spaces) % Helper function for hash algorithm. This converts the space object to a data - % structure which can be processed by :func:`GetMD5`. + % structure which can be processed by :func:`.GetMD5`. % % Arguments % --------- - % spaces : (1, :) :class:`CartesianSpace` + % spaces : (1, :) :class:`.CartesianSpace` % input spaces. % % Returns % ------- - % hashable : (1, :) int - % data which can be accepted by :func:`GetMD5`. + % hashable : (1, :) :class:`int` + % data which can be accepted by :func:`.GetMD5`. hashable = [spaces.dimensions]; end diff --git a/src/tensors/spaces/ComplexSpace.m b/src/tensors/spaces/ComplexSpace.m index 41afb1c..6f4a7c9 100644 --- a/src/tensors/spaces/ComplexSpace.m +++ b/src/tensors/spaces/ComplexSpace.m @@ -8,15 +8,15 @@ % % Repeating Arguments % ------------------- - % dimensions : (1, 1) int + % dimensions : (1, 1) :class:`int` % dimension of the vector space. % - % isdual : (1, 1) logical + % isdual : (1, 1) :class:`logical` % flag to denote dual spaces. % % Returns % ------- - % spaces : :class:`ComplexSpace` + % spaces : (1, :) :class:`.ComplexSpace` % array of complex spaces. arguments (Repeating) @@ -41,15 +41,15 @@ % % Repeating Arguments % ------------------- - % dimensions : int or struct + % dimensions : :class:`int` or :class:`struct` % a variable which represents the internal dimension of the space. % - % isdual : logical + % isdual : :class:`logical` % a variable which indicates if a space is dual. % % Returns % ------- - % spaces : :class:`ComplexSpace` + % spaces : :class:`.ComplexSpace` % array of spaces. arguments (Repeating) @@ -80,13 +80,13 @@ % % Arguments % --------- - % spaces : (1, :) :class:`CartesianSpace` + % spaces : (1, :) :class:`.ComplexSpace` % input spaces. % % Returns % ------- - % c : [] - % empty result. + % c : (1, :) :class:`.Z1` + % array of trivial spaces of corresponding length. c = repmat(Z1, length(spaces), 1); end @@ -96,12 +96,12 @@ % % Arguments % --------- - % spaces : (1, :) :class:`ComplexSpace` + % spaces : (1, :) :class:`.ComplexSpace` % input spaces. % % Returns % ------- - % d : (1, :) int + % d : (1, :) :class:`int` % list of degeneracy combinations, with 1 element. d = [spaces.dimensions]; @@ -112,12 +112,12 @@ % % Arguments % --------- - % spaces : (1, :) :class:`ComplexSpace` + % spaces : (1, :) :class:`.ComplexSpace` % input spaces. % % Returns % ------- - % d : (1, :) int + % d : (1, :) :class:`int` % total dimension of each of the input spaces. d = [spaces.dimensions]; @@ -128,13 +128,13 @@ % % Arguments % --------- - % codomain, domain : (1, :) :class:`ComplexSpace` + % codomain, domain : (1, :) :class:`.ComplexSpace` % input spaces. % % Returns % ------- - % style : :class:`BraidingStyle` - % Trivial braiding style + % style : :class:`.BraidingStyle` + % trivial braiding style, :code:`BraidingStyle.Abelian`. style = BraidingStyle.Abelian; end @@ -144,13 +144,13 @@ % % Arguments % --------- - % codomain, domain : (1, :) :class:`ComplexSpace` + % codomain, domain : (1, :) :class:`.ComplexSpace` % input spaces. % % Returns % ------- - % style : :class:`FusionStyle` - % fusion style of the internal structure. + % style : :class:`.FusionStyle` + % fusion style of the internal structure, :code:`FusionStyle.Unique`. style = FusionStyle.Unique; end @@ -168,12 +168,12 @@ % % Arguments % --------- - % space1, space2 : (1, 1) :class:`ComplexSpace` + % space1, space2 : (1, 1) :class:`.ComplexSpace` % input spaces. % % Returns % ------- - % space : (1, 1) :class:`ComplexSpace` + % space : (1, 1) :class:`.ComplexSpace` % fused space. space.dimensions = space.dimensions * space2.dimensions; @@ -185,12 +185,12 @@ % % Arguments % --------- - % spaces : (1, :) :class:`ComplexSpace` + % spaces : (1, :) :class:`.ComplexSpace` % Array of input spaces. % % Returns % ------- - % space : (1, 1) :class:`ComplexSpace` + % space : (1, 1) :class:`.ComplexSpace` % Fused space which is isomorphic to the input product space. arguments spaces @@ -227,12 +227,12 @@ % % Arguments % --------- - % spaces1, spaces2 : (1, :) :class:`ComplexSpace` + % spaces1, spaces2 : (1, :) :class:`.ComplexSpace` % input spaces, either of equal size or scalar. % % Returns % ------- - % bools : (1, :) logical + % bools : (1, :) :class:`logical` % flags that indicate if the element spaces are equal. bools = [spaces1.dimensions] == [spaces2.dimensions] & ... @@ -250,17 +250,17 @@ function hashable = GetMD5_helper(spaces) % Helper function for hash algorithm. This converts the space object to a data - % structure which can be processed by :func:`GetMD5`. + % structure which can be processed by :func:`.GetMD5`. % % Arguments % --------- - % spaces : (1, :) :class:`ComplexSpace` + % spaces : (1, :) :class:`.ComplexSpace` % input spaces. % % Returns % ------- - % hashable : (1, :) int - % data which can be accepted by :func:`GetMD5`. + % hashable : (1, :) :class:`int` + % data which can be accepted by :func:`.GetMD5`. hashable = [spaces.dimensions spaces.isdual]; end diff --git a/src/tensors/spaces/GradedSpace.m b/src/tensors/spaces/GradedSpace.m index 64d965a..358282c 100644 --- a/src/tensors/spaces/GradedSpace.m +++ b/src/tensors/spaces/GradedSpace.m @@ -8,17 +8,17 @@ % % Repeating Arguments % ------------------- - % dimensions : (1, 1) struct - % internal structure of the vector space, with fields 'charges' and - % 'degeneracies'. + % dimensions : (1, 1) :class:`struct` + % internal structure of the vector space, with fields :code:`charges` and + % :code:`degeneracies`. % - % dual : (1, 1) logical + % dual : (1, 1) :class:`logical` % flag to denote dual spaces. % % Returns % ------- - % spaces : :class:`GradedSpace` - % array of cartesian spaces. + % spaces : :class:`.GradedSpace` + % array of graded spaces. arguments (Repeating) dimensions (1, 1) struct @@ -84,28 +84,28 @@ % % Usage % ----- - % spaces = GradedSpace.new(charges, degeneracies, dual, ...) + % :code:`spaces = GradedSpace.new(charges, degeneracies, dual, ...)` % - % spaces = GradedSpace.new(dimensions, dual, ...) + % :code:`spaces = GradedSpace.new(dimensions, dual, ...)` % % Repeating Arguments % ------------------- - % dimensions : struct + % dimensions : :class:`struct` % a variable which represents the internal dimension of the space. % - % charges : :class:`AbstractCharge` + % charges : (1, :) :class:`.AbstractCharge` % charges for the internal structure of the space. % - % degeneracies : int + % degeneracies : (1, :) :class:`int` % degeneracies for the internal structure of the space. % - % dual : logical + % dual : :class:`logical` % a variable which indicates if a space is dual. % % Returns % ------- - % spaces : :class:`GradedSpace` - % array of spaces. + % spaces : :class:`.GradedSpace` + % array of graded spaces. if isstruct(varargin{1}) % default assert(mod(nargin, 2) == 0); @@ -140,13 +140,14 @@ % % Arguments % --------- - % spaces : (1, :) :class:`GradedSpace` + % spaces : (1, :) :class:`.GradedSpace` % input spaces. % % Returns % ------- - % c : (:, :) :class:`AbstractCharge` - % list of charge combinations, where each row is an entry. + % c : (:, :) :class:`.AbstractCharge` + % list of charge combinations, where each row represents a possible + % combination. if isdual(spaces(1)) c = conj(spaces(1).dimensions.charges); @@ -168,13 +169,14 @@ % % Arguments % --------- - % spaces : (1, :) :class:`GradedSpace` + % spaces : (1, :) :class:`.GradedSpace` % input spaces. % % Returns % ------- - % d : (:, :) int - % list of degeneracy combinations, where each row is an entry. + % d : (:, :) :class:`int` + % list of degeneracy combinations, where each row represents a possible + % combination. d = spaces(1).dimensions.degeneracies; for i = 2:length(spaces) @@ -187,12 +189,12 @@ % % Arguments % --------- - % spaces : (1, :) :class:`GradedSpace` + % spaces : (1, :) :class:`.GradedSpace` % input spaces. % % Returns % ------- - % d : (1, :) int + % d : (1, :) :class:`int` % total dimension of each of the input spaces. d = zeros(size(spaces)); @@ -208,12 +210,12 @@ % % Arguments % --------- - % space1, space2 : (1, 1) :class:`GradedSpace` + % space1, space2 : (1, 1) :class:`.GradedSpace` % input spaces. % % Returns % ------- - % space : (1, 1) :class:`GradedSpace` + % space : (1, 1) :class:`.GradedSpace` % fused space. space = prod([space1, space2]); @@ -224,17 +226,19 @@ % % Arguments % --------- - % spaces : (1, :) :class:`GradedSpace` - % Array of input spaces. + % spaces : (1, :) :class:`.GradedSpace` + % array of input spaces. % % Returns % ------- - % space : (1, 1) :class:`GradedSpace` - % Fused space which is isomorphic to the input product space. + % space : (1, 1) :class:`.GradedSpace` + % fused space which is isomorphic to the input product space. + arguments spaces isdual = false end + if fusionstyle(spaces) == FusionStyle.Unique c = prod(charges(spaces), 1); d = prod(degeneracies(spaces), 1); @@ -269,12 +273,12 @@ % % Arguments % --------- - % spaces1, spaces2 : (1, :) :class:`GradedSpace` + % spaces1, spaces2 : (1, :) :class:`.GradedSpace` % input spaces, either of equal size or scalar. % % Returns % ------- - % bools : (1, :) logical + % bools : (1, :) :class:`logical` % flags that indicate if the element spaces are equal. if isempty(spaces1) && isempty(spaces2) diff --git a/src/tensors/spaces/SU2Space.m b/src/tensors/spaces/SU2Space.m index 53fd3fb..55cf784 100644 --- a/src/tensors/spaces/SU2Space.m +++ b/src/tensors/spaces/SU2Space.m @@ -1,5 +1,16 @@ function V = SU2Space(charges, bonds, isdual) -% Convenience constructor for SU2-graded spaces. +% Convenience constructor for :math:`\mathrm{SU}(2)`-graded spaces. +% +% Arguments +% -------- +% charges : (1, :) :class:`int`-like +% vector of :math:`\mathrm{SU}(2)` charge labels. +% +% bonds : (1, :) :class:`int` +% degeneracy of each charge. +% +% isdual : :class:`logical` +% indicate if the space is dual. arguments charges bonds diff --git a/src/tensors/spaces/SumSpace.m b/src/tensors/spaces/SumSpace.m index 456551d..1f50ff1 100644 --- a/src/tensors/spaces/SumSpace.m +++ b/src/tensors/spaces/SumSpace.m @@ -1,10 +1,20 @@ classdef (InferiorClasses = {?GradedSpace, ?CartesianSpace, ?ComplexSpace}) SumSpace < AbstractSpace - % Direct product structure for a tensor index. + % Direct sum structure for a tensor index. %% Constructors methods function spaces = SumSpace(subspaces) - % Construct an array of vector spaces. + % Construct a direct sum of vector spaces array of vector spaces. + % + % Repeating Arguments + % ------------------- + % subspace : :class:`.AbstractSpace` + % individual subspaces of sum space. + % + % Returns + % ------- + % spaces : :class:`.SumSpace` + % array of spaces representing a direct sum of vector spaces. arguments (Repeating) subspaces @@ -16,7 +26,7 @@ dual = cell(size(subspaces)); for i = 1:length(subspaces) dual{i} = isdual(subspaces{i}(1)); - assert(all(isdual(subspaces{i}) == dual{i}), 'Direct product structure should have all dual or all regular spaces.'); + assert(all(isdual(subspaces{i}) == dual{i}), 'Direct sum structure should have all dual or all regular spaces.'); end args = [subspaces; dual]; end @@ -57,12 +67,12 @@ % % Arguments % --------- - % space1, space2 : (1,1) :class:`AbstractSpace` + % space1, space2 : (1, 1) :class:`.SumSpace` % input spaces. % % Returns % ------- - % space : (1,1) :class:`AbstractSpace` + % space : (1, 1) :class:`.SumSpace` % fused space. arguments space1 SumSpace diff --git a/src/tensors/spaces/U1Space.m b/src/tensors/spaces/U1Space.m index 48dbfb8..334d850 100644 --- a/src/tensors/spaces/U1Space.m +++ b/src/tensors/spaces/U1Space.m @@ -1,5 +1,16 @@ function V = U1Space(charges, bonds, isdual) -% Convenience constructor for U1-graded spaces. +% Convenience constructor for :math:`\mathrm{U}(1)`-graded spaces. +% +% Arguments +% -------- +% charges : (1, :) :class:`int`-like +% vector of :math:`\mathrm{U}(1)` charge labels. +% +% bonds : (1, :) :class:`int` +% degeneracy of each charge. +% +% isdual : :class:`logical` +% indicate if the space is dual. arguments charges bonds diff --git a/src/tensors/spaces/Z2Space.m b/src/tensors/spaces/Z2Space.m index 4a6feee..cb522c3 100644 --- a/src/tensors/spaces/Z2Space.m +++ b/src/tensors/spaces/Z2Space.m @@ -1,5 +1,16 @@ function V = Z2Space(charges, bonds, isdual) -% Convenience constructor for Z2-graded spaces. +% Convenience constructor for :math:`Z_2`-graded spaces. +% +% Arguments +% -------- +% charges : (1, :) :class:`logical`-like +% vector of :math:`Z_2` charge labels. +% +% bonds : (1, :) :class:`int` +% degeneracy of each charge. +% +% isdual : :class:`logical` +% indicate if the space is dual. arguments charges bonds diff --git a/src/tensors/spaces/fZ2Space.m b/src/tensors/spaces/fZ2Space.m index 0572a07..ceeb035 100644 --- a/src/tensors/spaces/fZ2Space.m +++ b/src/tensors/spaces/fZ2Space.m @@ -1,5 +1,16 @@ function V = fZ2Space(charges, bonds, isdual) -% Convenience constructor for fZ2-graded spaces. +% Convenience constructor for :math:`fZ_2`-graded spaces. +% +% Arguments +% -------- +% charges : (1, :) :class:`logical`-like +% vector of :math:`fZ_2` charge labels. +% +% bonds : (1, :) :class:`int` +% degeneracy of each charge. +% +% isdual : :class:`logical` +% indicate if the space is dual. arguments charges bonds diff --git a/src/utility/Options.m b/src/utility/Options.m index 753dc5c..39c12b6 100644 --- a/src/utility/Options.m +++ b/src/utility/Options.m @@ -1,9 +1,15 @@ classdef Options % Various package settings. - methods (Static) function bool = CacheEnabled(bool) + % Enable cache. + % + % Usage + % ----- + % :code:`bool = Options.CacheEnabled(bool)` sets the cache enabling to :code:`bool`. + % + % :code:`bool = Options.CacheEnabled(bool)` returns the current cache enabling. persistent isenabled if nargin > 0 isenabled = bool; @@ -15,6 +21,13 @@ end function bool = Debug(bool) + % Enable cache. + % + % Usage + % ----- + % :code:`bool = Options.Debug(bool)` sets the debug mode to :code:`bool`. + % + % :code:`bool = Options.Debug(bool)` returns the current debug mode. persistent dodebug if nargin > 0 dodebug = bool; diff --git a/src/utility/Verbosity.m b/src/utility/Verbosity.m index fb0d412..35e972a 100644 --- a/src/utility/Verbosity.m +++ b/src/utility/Verbosity.m @@ -1,5 +1,14 @@ classdef Verbosity < uint8 % Verbosity level enumeration class. + % + % Levels: + % + % - off (0): No information + % - warn (1): Information on failure + % - conv (2): Convergence information + % - iter (3): Information about each iteration + % - detail (4): Detailed information about each iteration + % - diagnostics (255): All possible information enumeration off (0) % No information diff --git a/src/utility/between.m b/src/utility/between.m index 944f40b..9425644 100644 --- a/src/utility/between.m +++ b/src/utility/between.m @@ -1,4 +1,5 @@ function x = between(x1, x, x2) +% Restrict :code:`x` to lie between :code:`x1` and :code:`x2` assert(x1 <= x2, 'range', 'x1 should be smaller than or equal to x2'); if x < x1 diff --git a/src/utility/colors.m b/src/utility/colors.m index 37d5628..446de07 100644 --- a/src/utility/colors.m +++ b/src/utility/colors.m @@ -1,4 +1,5 @@ function y = colors(i) +% Periodically cycle through standard Matlab colors. y=[0 0.4470 0.7410 0.8500 0.3250 0.0980 diff --git a/src/utility/dim2str.m b/src/utility/dim2str.m index a403bc7..4d1b693 100644 --- a/src/utility/dim2str.m +++ b/src/utility/dim2str.m @@ -1,4 +1,5 @@ function s = dim2str(sz) +% Convert size array to string output. s = regexprep(mat2str(sz), {'\[', '\]', '\s+'}, {'', '', 'x'}); diff --git a/src/utility/diracdelta.m b/src/utility/diracdelta.m index 2ed8d80..93f40b2 100644 --- a/src/utility/diracdelta.m +++ b/src/utility/diracdelta.m @@ -1,4 +1,5 @@ function d = diracdelta(sz) +% Construct delta tensor of given size. Note that all dimensions must have the same length. assert(all(sz == sz(1))) d = zeros(sz); diff --git a/src/utility/indices/contractinds.m b/src/utility/indices/contractinds.m index 14ab24e..726e387 100644 --- a/src/utility/indices/contractinds.m +++ b/src/utility/indices/contractinds.m @@ -2,7 +2,7 @@ % Find the contracted dimensions. % % :code:`[dimA, dimB] = contractinds(ia, ib)` -% locates the repeated indices. +% locates the repeated indices in two vectors of integers. ind = find(ia(:) == ib).' - 1; dimA = mod(ind, length(ia)) + 1; diff --git a/src/utility/indices/mod1.m b/src/utility/indices/mod1.m index 3c0207a..2b86986 100644 --- a/src/utility/indices/mod1.m +++ b/src/utility/indices/mod1.m @@ -1,17 +1,17 @@ function m = mod1(x, y) -% Modulus after division, counting from 1:y. +% Modulus after division, counting from :code:`1:y`. % % Arguments % --------- -% x : int +% x : :class:`int` % numerator. % -% y : int +% y : :class:`int` % divisor. % % Returns % ------- -% m : int +% m : :class:`int` % remainder after division, where a value of 0 is replaced with y. m = mod(x-1, y) + 1; diff --git a/src/utility/indices/next.m b/src/utility/indices/next.m index 60bbaba..cfa0afb 100644 --- a/src/utility/indices/next.m +++ b/src/utility/indices/next.m @@ -4,11 +4,11 @@ % Usage % ----- % :code:`j = next(i, total)` -% gives the :math:`i + 1`'th index, but loops back to 1 when :math:`j > \text{total}`. +% gives the :math:`i + 1`'th index, but loops back to 1 when :math:`j > \text{total}`. % % See Also % -------- -% :func:`prev` +% :func:`.prev` j = mod(i, total) + 1; diff --git a/src/utility/indices/prev.m b/src/utility/indices/prev.m index ed2ef71..3489dd7 100644 --- a/src/utility/indices/prev.m +++ b/src/utility/indices/prev.m @@ -4,11 +4,11 @@ % Usage % ----- % :code:`j = prev(i, total)` -% gives the :math:`i - 1`'th index, but loops back to total when :math:`j < 1`. +% gives the :math:`i - 1`'th index, but loops back to total when :math:`j < 1`. % % See Also % -------- -% :func:`next` +% :func:`.next` j = mod(i - 2, total) + 1; diff --git a/src/utility/indices/rankrange.m b/src/utility/indices/rankrange.m index 8751470..153bc01 100644 --- a/src/utility/indices/rankrange.m +++ b/src/utility/indices/rankrange.m @@ -1,6 +1,5 @@ function r = rankrange(rank) -%RANKRANGE Summary of this function goes here -% Detailed explanation goes here +% Convert tensor rank into a contiguous range of dimensions. r = [1:rank(1) rank(1) + (rank(2):-1:1)]; diff --git a/src/utility/indices/traceinds.m b/src/utility/indices/traceinds.m index 8340e66..6d75ffe 100644 --- a/src/utility/indices/traceinds.m +++ b/src/utility/indices/traceinds.m @@ -1,4 +1,8 @@ function [dimA1, dimA2] = traceinds(ia) +% Find the traced dimensions. +% +% :code:`[dimA1, dimA2] = traceinds(ia)` +% locates the repeated indices in a vectors of integers. ind = find(ia(:) == ia & ~tril(true(length(ia)))).' - 1; dimA1 = mod(ind, length(ia)) + 1; diff --git a/src/utility/indices/treeindsequence.m b/src/utility/indices/treeindsequence.m index 791e91a..d570059 100644 --- a/src/utility/indices/treeindsequence.m +++ b/src/utility/indices/treeindsequence.m @@ -7,12 +7,12 @@ % % Arguments % --------- -% n : int +% n : :class:`int` % number of external edges % % Returns % ------- -% t : int +% t : :class:`int` % total number of edges % % Example diff --git a/src/utility/indices/unique1.m b/src/utility/indices/unique1.m index d1e6b03..f5cb5d8 100644 --- a/src/utility/indices/unique1.m +++ b/src/utility/indices/unique1.m @@ -4,7 +4,7 @@ % Usage % ----- % :code:`inds = unique1(inds)` -% deletes all elements that appear more than once. +% deletes all elements that appear more than once. inds = inds(sum(inds(:) == inds) == 1); diff --git a/src/utility/linalg/contract.m b/src/utility/linalg/contract.m index 7a3c1b3..8da895a 100644 --- a/src/utility/linalg/contract.m +++ b/src/utility/linalg/contract.m @@ -11,23 +11,23 @@ % % Repeating Arguments % ------------------- -% tensors : :class:`Tensor` +% tensors : :class:`.Tensor` % list of tensors that constitute the vertices of the network. % -% indices : int -% list of indices that define the links and contraction order, using ncon-like syntax. +% indices : (1, :) :class:`int` +% list of indices that define the links and contraction order, using `ncon-like syntax `_. % % Keyword Arguments % ----------------- -% Conj : (1, :) logical +% Conj : (1, :) :class:`logical` % optional list to flag that tensors should be conjugated. % -% Rank : (1, 2) int +% Rank : (1, 2) :class:`int` % optionally specify the rank of the resulting tensor. % % Returns % ------- -% C : :class:`Tensor` or numeric +% C : :class:`.Tensor` or :class:`numeric` % result of the tensor network contraction. % TODO contraction order checker, order specifier. diff --git a/src/utility/linalg/eigsolve.m b/src/utility/linalg/eigsolve.m index 860486c..9c1ae78 100644 --- a/src/utility/linalg/eigsolve.m +++ b/src/utility/linalg/eigsolve.m @@ -4,28 +4,29 @@ % Usage % ----- % :code:`[V, D, flag] = eigsolve(A, v, howmany, sigma, kwargs)` +% % :code:`D = eigsolve(A, v, ...)` % % Arguments % --------- -% A : matrix or function_handle +% A : :class:`matrix` or :class:`function_handle` % A square matrix. % A function handle which implements one of the following, depending on sigma: % -% - A \ x, if `sigma` is 0 or 'smallestabs' -% - (A - sigma * I) \ x, if sigma is a nonzero scalar -% - A * x, for all other cases +% - :code:`A \ x`, if `sigma` is 0 or 'smallestabs' +% - :code:`(A - sigma * I) \ x`, if sigma is a nonzero scalar +% - :code:`A * x`, for all other cases % -% v : vector -% initial guess for the eigenvector. If A is a :class:`Tensor`, this defaults -% to a random complex :class:`Tensor`, for function handles this is a required +% v : :class:`vector` +% initial guess for the eigenvector. If A is a :class:`.Tensor`, this defaults +% to a random complex :class:`.Tensor`, for function handles this is a required % argument. % -% howmany : int +% howmany : :class:`int` % amount of eigenvalues and eigenvectors that should be computed. By default % this is 1, and this should not be larger than the total dimension of A. % -% sigma : `char` or numeric +% sigma : :class:`char` or :class:`numeric` % selector for the eigenvalues, should be either one of the following: % % - 'largestabs', 'lm': default, eigenvalues of largest magnitude @@ -34,52 +35,43 @@ % - 'smallestabs', 'sm': default, eigenvalues of smallest magnitude % - 'smallestreal', 'sr': eigenvalues with smallest real part % - 'smallestimag', 'si': eigenvalues with smallest imaginary part. -% - 'bothendsreal', 'be': both ends, with howmany/2 values with largest and -% smallest real part respectively. -% - 'bothendsimag', 'li': both ends, with howmany/2 values with largest and -% smallest imaginary part respectively. % - numeric : eigenvalues closest to sigma. % % Keyword Arguments % ----------------- -% Tol : numeric +% Tol : :class:`double` % tolerance of the algorithm. % -% Algorithm : char +% Algorithm : :class:`char` % choice of eigensolver algorithm. Currently there is a choice between the use % of Matlab's buitin `eigs` specified by the identifiers 'eigs' or % 'KrylovSchur', or the use of a custom Arnolid algorithm specified by % the identifier 'Arnoldi'. % -% MaxIter : int +% MaxIter : :class:`int` % maximum number of iterations, 100 by default. % -% KrylovDim : int +% KrylovDim : :class:`int` % number of vectors kept in the Krylov subspace. % -% IsSymmetric : logical +% IsSymmetric : :class:`logical` % flag to speed up the algorithm if the operator is symmetric, false by % default. % -% Verbosity : int +% Verbosity : :class:`.Verbosity` % Level of output information, by default nothing is printed if `flag` is -% returned, otherwise only warnings are given. -% -% - 0 : no information -% - 1 : information at failure -% - 2 : information at convergence -% - 3 : information at every iteration +% returned, otherwise only warnings are given, defaults to :code:`Verbosity.warn`. % % Returns % ------- -% V : (1, howmany) array +% V : (1, howmany) :class:`vector` % vector of eigenvectors. % -% D : numeric +% D : :class:`numeric` % vector of eigenvalues if only a single output argument is asked, diagonal % matrix of eigenvalues otherwise. % -% flag : int +% flag : :class:`int` % if flag = 0 then all eigenvalues are converged, otherwise not. arguments diff --git a/src/utility/linalg/isapprox.m b/src/utility/linalg/isapprox.m index f131a6c..f2de936 100644 --- a/src/utility/linalg/isapprox.m +++ b/src/utility/linalg/isapprox.m @@ -1,4 +1,23 @@ function bool = isapprox(A, B, tol) +% Verify whether two arrays are approximately equal, based on their Euclidean distance. +% +% Arguments +% --------- +% A, B : :class:`numeric` +% input arrays of the same size. +% +% Keyword Arguments +% ----------------- +% AbsTol : :class:`double` +% absolute tolerance. +% +% RelTol : :code:`double` +% relative tolerance. +% +% Returns +% ------- +% bool : :code:`logical` +% true if :code:`A` and :code:`B` are approximately equal. arguments A diff --git a/src/utility/linalg/isisometry.m b/src/utility/linalg/isisometry.m index a4fc99e..f8026a0 100644 --- a/src/utility/linalg/isisometry.m +++ b/src/utility/linalg/isisometry.m @@ -3,24 +3,24 @@ % % Arguments % --------- -% A : numeric +% A : :class:`numeric` % input matrix. % -% side : 'left', 'right' or 'both' -% check if A' * A == I, A * A' == I, or both by default. +% side : :class:`char`, 'left', 'right' or 'both' +% check if :code:`A' * A == I`, :code:`A * A' == I`, or both by default. % % Keyword Arguments % ----------------- -% AbsTol : double +% AbsTol : :class:`double` % absolute tolerance % -% RelTol : double +% RelTol : :code:`double` % relative tolerance % % Returns % ------- -% bool : logical -% true if A is an isometry. +% bool : :code:`logical` +% true if :code:`A` is an isometry. arguments A diff --git a/src/utility/linalg/leftnull.m b/src/utility/linalg/leftnull.m index c547045..483dffa 100644 --- a/src/utility/linalg/leftnull.m +++ b/src/utility/linalg/leftnull.m @@ -1,20 +1,21 @@ function N = leftnull(A, alg, atol) -% Compute the left nullspace of a matrix, such that N' * A = 0. +% Compute the left nullspace of a matrix, such that :code:`N' * A == 0`. % % Arguments % --------- -% A : numeric +% A : :class:`numeric` % input matrix. % -% alg : 'qr' or 'svd' +% alg : :class:`char`, 'qr' or 'svd' % choice of algorithm, default 'svd'. % -% atol : double -% absolute tolerance for the null space, defaults to max(size(A)) * eps(max(svd(A))). +% atol : :class:`double` +% absolute tolerance for the null space, defaults to +% :code:`max(size(A)) * eps(max(svd(A)))`. % % Returns % ------- -% N : numeric +% N : :class:`numeric` % orthonormal basis for the orthogonal complement of the support of the row space of A. arguments diff --git a/src/utility/linalg/leftorth.m b/src/utility/linalg/leftorth.m index ff8fd48..82b00fe 100644 --- a/src/utility/linalg/leftorth.m +++ b/src/utility/linalg/leftorth.m @@ -1,10 +1,36 @@ function [Q, R] = leftorth(A, alg) - - +% Factorize a matrix into an orthonormal basis `Q` and remainder `R`, such that +% :code:`A = Q * R`. +% +% Usage +% ----- +% :code:`[Q, R] = leftorth(A, alg)` +% +% Arguments +% --------- +% A : :class:`numeric` +% input matrix to factorize. +% +% alg : :class:`char` or :class:`string` +% selection of algorithms for the decomposition: +% +% - 'qr' produces an upper triangular remainder R +% - 'qrpos' corrects the diagonal elements of R to be positive. +% - 'ql' produces a lower triangular remainder R +% - 'qlpos' corrects the diagonal elements of R to be positive. +% - 'polar' produces a Hermitian and positive semidefinite R. +% - 'svd' uses a singular value decomposition. +% +% Returns +% ------- +% Q : :class:`numeric` +% orthonormal basis matrix +% +% R : :class:`numeric` +% remainder matrix, depends on selected algorithm. % TODO have a look at https://github.com/iwoodsawyer/factor - switch alg case 'qr' [Q, R] = qr(A, 0); diff --git a/src/utility/linalg/rightnull.m b/src/utility/linalg/rightnull.m index f891242..0ad7412 100644 --- a/src/utility/linalg/rightnull.m +++ b/src/utility/linalg/rightnull.m @@ -1,21 +1,23 @@ function N = rightnull(A, alg, atol) -% Compute the right nullspace of a matrix, such that A * N' = 0. +% Compute the right nullspace of a matrix, such that :code:`A * N' == 0`. % % Arguments % --------- -% A : numeric +% A : :class:`numeric` % input matrix. % -% alg : 'lq' or 'svd' +% alg : :class:`char`, 'lq' or 'svd' % choice of algorithm, default 'svd'. % -% atol : double -% absolute tolerance for the null space, defaults to max(size(A)) * eps(max(svd(A))). +% atol : :class:`double` +% absolute tolerance for the null space, defaults to +% :code:`max(size(A)) * eps(max(svd(A)))`. % % Returns % ------- -% N : numeric -% orthonormal basis for the orthogonal complement of the support of the column space of A. +% N : :class:`numeric` +% orthonormal basis for the orthogonal complement of the support of the column space of +% :code:`A`. arguments A diff --git a/src/utility/linalg/rightorth.m b/src/utility/linalg/rightorth.m index ca2ac50..3e54e2d 100644 --- a/src/utility/linalg/rightorth.m +++ b/src/utility/linalg/rightorth.m @@ -1,7 +1,33 @@ function [R, Q] = rightorth(A, alg) - - - +% Factorize a matrix into an orthonormal basis `Q` and remainder `L`, such that +% :code:`A = L * Q`. +% +% Usage +% ----- +% :code:`[R, Q] = rightorth(A, alg)` +% +% Arguments +% --------- +% A : :class:`numeric` +% input matrix to factorize. +% +% alg : :class:`char` or :class:`string` +% selection of algorithms for the decomposition: +% +% - 'rq' produces an upper triangular remainder R +% - 'rqpos' corrects the diagonal elements of R to be positive. +% - 'lq' produces a lower triangular remainder R +% - 'lqpos' corrects the diagonal elements of R to be positive. +% - 'polar' produces a Hermitian and positive semidefinite R. +% - 'svd' uses a singular value decomposition. +% +% Returns +% ------- +% R : :class:`numeric` +% remainder matrix, depends on selected algorithm. +% +% Q : :class:`numeric` +% orthonormal basis matrix. % TODO have a look at https://github.com/iwoodsawyer/factor diff --git a/src/utility/linalg/tensorprod.m b/src/utility/linalg/tensorprod.m index f76dde3..16f0c3b 100644 --- a/src/utility/linalg/tensorprod.m +++ b/src/utility/linalg/tensorprod.m @@ -4,18 +4,22 @@ % Usage % ----- % :code:`C = tensorprod(A, B, dimA, dimB)` -% returns the tensor product of tensors A and B. The arguments dimA and dimB are -% vectors that specify which dimensions to contract in A and B. The size of the -% output tensor is the size of the uncontracted dimensions of A followed by the size -% of the uncontracted dimensions of B. +% +% returns the tensor product of tensors :code:`A` and :code:`B`. The arguments :code:`dimA` +% and :code:`dimB` are vectors that specify which dimensions to contract in :code:`A` and +% :code:`B`. The size of the +% output tensor is the size of the uncontracted dimensions of :code:`A` followed by the size +% of the uncontracted dimensions of :code:`B`. % % :code:`C = tensorprod(A, B)` -% returns the outer product of tensors A and B. This is equivalent to the previous -% syntax with dimA = dimB = []. +% +% returns the outer product of tensors :code:`A` and :code:`B`. This is equivalent to the +% previous syntax with :code:`dimA = dimB = []`. % % :code:`C = tensorprod(_, NumDimensionsA=ndimsA)` -% optionally specifies the number of dimensions in tensor A in addition to combat the -% removal of trailing singleton dimensions. +% +% optionally specifies the number of dimensions in tensor :code:`A` in addition to combat the +% removal of trailing singleton dimensions. arguments A diff --git a/src/utility/linalg/tensortrace.m b/src/utility/linalg/tensortrace.m index d71cbd9..5fe2727 100644 --- a/src/utility/linalg/tensortrace.m +++ b/src/utility/linalg/tensortrace.m @@ -4,11 +4,13 @@ % Usage % ----- % -% :code:`[C, ic] = tensortrace(A, ia)` -% traces over the indices that appear twice in ia. +% :code:`[C, ic] = tensortrace(A, i1)` +% +% traces over the indices that appear twice in :code:`i1`. % -% :code:`[C, ic] = tensortrace(A, ia, ic)` -% optionally specifies the output indices' order. +% :code:`[C, ic] = tensortrace(A, i1, i2)` +% +% optionally specifies the output indices' order. if isempty(i1) && isempty(i2), C = A; return; end assert(length(i1) == length(i2), 'invalid indices'); diff --git a/src/utility/memsize.m b/src/utility/memsize.m index c266585..ebe1e73 100644 --- a/src/utility/memsize.m +++ b/src/utility/memsize.m @@ -1,4 +1,9 @@ function [bytes, unit] = memsize(in, unit) +% Check memory size of object in prefered unit ('GB', 'MB', 'KB' or 'B'). +% +% Usage +% ----- +% :code:`[bytes, unit] = memsize(in, unit)` if isa(in, 'containers.Map') warning('off', 'MATLAB:structOnObject'); diff --git a/src/utility/mod1.m b/src/utility/mod1.m deleted file mode 100644 index 1e9d522..0000000 --- a/src/utility/mod1.m +++ /dev/null @@ -1,9 +0,0 @@ -function i = mod1(i, N) - - -i = mod(i, N); -i(i == 0) = N; - - -end - diff --git a/src/utility/permutations/invperm.m b/src/utility/permutations/invperm.m index 35a4e49..9922649 100644 --- a/src/utility/permutations/invperm.m +++ b/src/utility/permutations/invperm.m @@ -1,9 +1,11 @@ function invp = invperm(p) -% Compute the inverse permutation. +% Compute the inverse of an integer permutation. +% % Usage % ----- % :code:`invp = invperm(p)` -% computes the permutation that satisfies :code:`invp(p) = 1:length(p)`. +% +% computes the permutation that satisfies :code:`invp(p) = 1:length(p)`. invp(p) = 1:length(p); diff --git a/src/utility/permutations/isperm.m b/src/utility/permutations/isperm.m index 4a1a635..5f6617a 100644 --- a/src/utility/permutations/isperm.m +++ b/src/utility/permutations/isperm.m @@ -1,6 +1,9 @@ function bool = isperm(p) -% isperm - Check if a vector is a permutation. -% bool = isperm(p) +% Check if a vector is a permutation. +% +% Usage +% ----- +% :code:`bool = isperm(p)` bool = all(sort(p) == 1:length(p)); diff --git a/src/utility/permutations/perm2swap.m b/src/utility/permutations/perm2swap.m index 4cd3075..b873c59 100644 --- a/src/utility/permutations/perm2swap.m +++ b/src/utility/permutations/perm2swap.m @@ -3,14 +3,14 @@ % % Arguments % --------- -% p : int +% p : :class:`int` % permutation vector. % % Returns % ------- -% s : int -% list of swaps that compose into the permutation vector, where `i` indicates a swap -% between indices `i` and `i+1`. +% s : :class:`int` +% list of swaps that compose into the permutation vector, where :code:`i` indicates a swap +% between indices :code:`i` and :code:`i+1`. N = length(p); s = []; diff --git a/src/utility/randc.m b/src/utility/randc.m index cb0bbdd..d44c00d 100644 --- a/src/utility/randc.m +++ b/src/utility/randc.m @@ -14,18 +14,18 @@ % % Arguments % --------- -% m, n, p, ... : int +% m, n, p, ... : :class:`int` % integers defining the size of the output array. % % classname : :class:`char` -% datatype of the array, default :class:`double`. +% datatype of the array, default :code:`'double'`. % -% Y : numeric +% Y : :class:`numeric` % create an array of the same class as Y. % % Returns % ------- -% R : numeric +% R : :class:`numeric` % complex pseudorandom values, real and imaginary part distributed from the uniform % distribution. diff --git a/src/utility/randnc.m b/src/utility/randnc.m index 5d884bc..c69d4f0 100644 --- a/src/utility/randnc.m +++ b/src/utility/randnc.m @@ -14,18 +14,18 @@ % % Arguments % --------- -% m, n, p, ... : int +% m, n, p, ... : :class:`int` % integers defining the size of the output array. % % classname : :class:`char` -% datatype of the array, default :class:`double`. +% datatype of the array, default :code:`'double'`. % -% Y : numeric +% Y : :class:`numeric` % create an array of the same class as Y. % % Returns % ------- -% R : numeric +% R : :class:`numeric` % complex pseudorandom values, real and imaginary part distributed from the normal % distribution. diff --git a/src/utility/simulsort.m b/src/utility/simulsort.m index 09b6e89..e99c8b7 100644 --- a/src/utility/simulsort.m +++ b/src/utility/simulsort.m @@ -19,18 +19,18 @@ % % Keyword Arguments % ----------------- -% Dimension : int +% Dimension : :class:`int` % determine the dimension along which to sort. This behaves similarly to SORT, by default: % - for vectors, sorts the elements % - for matrices, sorts each column % - for N-D arrays, sorts along the first non-singleton dimension. % -% Direction : 'ascend' or 'descend' +% Direction : :class:`char`, 'ascend' or 'descend' % specify the sorting direction, defaults to 'ascend'. % % Returns % ------- -% I : int +% I : :class:`int` % permutation vector that brings the input arrays into sorted order. % % array1, array2, ... diff --git a/src/utility/simulsortrows.m b/src/utility/simulsortrows.m index 0be7897..150e89e 100644 --- a/src/utility/simulsortrows.m +++ b/src/utility/simulsortrows.m @@ -1,33 +1,34 @@ function [I, varargout] = simulsortrows(arrays, kwargs) % Simultaneous sorting of several input arrays by row. -% Sorts the rows such that equal rows of array{i} appear sorted by the rows of array{i+1}. +% +% Sorts the rows such that equal rows of array{i} appear sorted by the rows of array{i+1}. % % This is achieved by sorting rows from end to front, making use of the fact that SORTROWS % is stable and thus will not mess up the order of later rows when earlier rows are equal. % % Usage % ----- -% [I, array1, array2, ...] = simulsortrows(array1, array2, ..., kwargs) +% :code:`[I, array1, array2, ...] = simulsortrows(array1, array2, ..., kwargs)` % -% Arguments -% --------- +% Repeating arguments +% ------------------- % array1, array2, ... % arrays of equal size that need to be sorted. These can be of any type that supports % SORTROWS. % % Keyword Arguments % ----------------- -% Col : int +% Col : :class:`int` % vector of indices that specifies the columns used for sorting. % -% Direction : 'ascend' or 'descend' +% Direction : :class:`char`, 'ascend' or 'descend' % specify the sorting direction. You can also specify a different direction for each % column by using a cell array of 'ascend' and 'descend' of the same size as Col, such % that corresponding elements are sorted ascending or descending. % % Returns % ------- -% I : int +% I : :class:`int` % permutation vector that brings the input arrays into rowsorted order. % % array1, array2, ... diff --git a/src/utility/simulunique.m b/src/utility/simulunique.m index 27a2e6a..adf65f6 100644 --- a/src/utility/simulunique.m +++ b/src/utility/simulunique.m @@ -1,5 +1,5 @@ function varargout = simulunique(varargin) -%SIMULUNIQUE Set unique over multiple arrays. +% Set unique over multiple arrays. nlhs = max(1, nargout); diff --git a/src/utility/sparse/SparseArray.m b/src/utility/sparse/SparseArray.m index e6de5a8..0428552 100644 --- a/src/utility/sparse/SparseArray.m +++ b/src/utility/sparse/SparseArray.m @@ -1,7 +1,7 @@ classdef SparseArray % Class for multi-dimensional sparse arrays. % - % Limited to arrays with a total number of elements of at most 2^48-1. + % Limited to arrays with a total number of elements of at most 2^48-1. %% Properties properties (Access = private) @@ -33,11 +33,11 @@ % Empty constructor. % % :code:`a = SparseArray(b)` - % Copies/converts :code:`b` if it is a :class:`SparseArray`, a dense array or + % Copies/converts :code:`b` if it is a :class:`.SparseArray`, a dense array or % a sparse matrix. % % :code:`a = SparseArray(b, sz)` - % Copies/converts :code:`b` if it is a :class:`SparseArray`, a dense array or + % Copies/converts :code:`b` if it is a :class:`.SparseArray`, a dense array or % a sparse matrix, and sets the size of :code:`a` to :code:`sz` % % Example @@ -145,12 +145,12 @@ % % Arguments % --------- - % a : :class:`SparseArray` + % a : :class:`.SparseArray` % input array. % % Returns % ------- - % b : :class:`SparseArray` + % b : :class:`.SparseArray` % output array. a.var = abs(a.var); end @@ -161,16 +161,16 @@ % % Arguments % --------- - % a : :class:`SparseArray` + % a : :class:`.SparseArray` % input array. % - % tol : :class:`float` , optional + % tol : :class:`double` , optional % threshold tolerance for absolute values of entries, defaults to % :code:`1e-15`. % % Returns % ------- - % b : :class:`SparseArray` + % b : :class:`.SparseArray` % sparse array with entries of absolute value below :code:`tol` set to zero. arguments a @@ -187,12 +187,12 @@ % % Arguments % --------- - % a : :class:`SparseArray` + % a : :class:`.SparseArray` % input array. % % Returns % ------- - % b : :class:`SparseArray` + % b : :class:`.SparseArray` % output array. a.var = conj(a.var); end @@ -210,12 +210,12 @@ % % Arguments % --------- - % a : :class:`SparseArray` + % a : :class:`.SparseArray` % input array. % % Returns % ------- - % b : :class:`SparseArray` + % b : :class:`.SparseArray` % output array. assert(ismatrix(a), 'sparse:RankError', 'ctranspose is only defined for 2D sparse arrays.'); a = permute(conj(a), [2, 1]); @@ -292,7 +292,7 @@ function disp(a) % % Arguments % --------- - % a : :class:`SparseArray` + % a : :class:`.SparseArray` % input array % % Returns @@ -318,7 +318,7 @@ function disp(a) % % Arguments % --------- - % a : :class:`SparseArray` + % a : :class:`.SparseArray` % input array. % % Returns @@ -333,7 +333,7 @@ function disp(a) % % Arguments % --------- - % a : :class:`SparseArray` + % a : :class:`.SparseArray` % input array. % g : (1, :) :class:`int` % list of number of contiguous indices to be grouped in each index of the @@ -341,7 +341,7 @@ function disp(a) % % Returns % ------- - % b : :class:`SparseArray` + % b : :class:`.SparseArray` % output array with grouped indices. % % Example @@ -365,12 +365,12 @@ function disp(a) % % Arguments % --------- - % a : :class:`SparseArray` + % a : :class:`.SparseArray` % input array. % % Returns % ------- - % b : :class:`SparseArray` + % b : :class:`.SparseArray` % output array with real entries corresponding to the imaginary part of the % entries of :code:`a`. a.var = imag(a.var); @@ -385,7 +385,7 @@ function disp(a) % % Arguments % --------- - % a : :class:`SparseArray` + % a : :class:`.SparseArray` % input array. % % Returns @@ -400,13 +400,13 @@ function disp(a) % % Arguments % --------- - % a : :class:`SparseArray` + % a : :class:`.SparseArray` % input array. % % Returns % ------- % bool : :class:`logical` - % defaults to :code:`false` for :class:`SparseArray`. + % defaults to :code:`false` for :class:`.SparseArray`. bool = false; end @@ -415,7 +415,7 @@ function disp(a) % % Arguments % --------- - % a : :class:`SparseArray` + % a : :class:`.SparseArray` % input array. % % Returns @@ -435,18 +435,23 @@ function disp(a) function c = ldivide(a, b) % Elementwise left division for sparse arrays. % - % :code:`ldivide(a, b)` is called for the syntax :code:`a .\ b` where :code:`a` - % or :code:`b` is a :class:`SparseArray`. :code:`a`and :code:`b` must have the - % same size, unless one is a scalar. + % Usage + % ----- + % :code:`ldivide(a, b)` + % + % :code:`a .\ b` + % + % where :code:`a` or :code:`b` is a :class:`SparseArray`. :code:`a`and :code:`b` + % must have the same size, unless one is a scalar. % % Arguments % --------- - % a, b : :class:`SparseArray` or :class:`double` + % a, b : :class:`.SparseArray` or :class:`double` % input arrays to be divided. % % Returns % ------- - % c : :class:`SparseArray` + % c : :class:`.SparseArray` % elementwise left division of :code:`b` by :code:`a`. c = rdivide(b, a); end @@ -454,19 +459,24 @@ function disp(a) function c = minus(a, b) % Elementwise subtraction for sparse arrays. % - % :code:`minus(a, b)` is called for the syntax :code:`a - b` where :code:`a` - % or :code:`b` is a :class:`SparseArray`. :code:`a`and :code:`b` must have the - % same size, unless one of the is scalar. Scalar can be subtracted from a sparse - % array of any size, resulting in a dense array. + % Usage + % ----- + % :code:`minus(a, b)` + % + % :code:`a - b` + % + % where :code:`a`or :code:`b` is a :class:`.SparseArray`. :code:`a`and :code:`b` + % must have the same size, unless one of the is scalar. Scalar can be subtracted + % from a sparse array of any size, resulting in a dense array. % % Arguments % --------- - % a, b : :class:`SparseArray` or :class:`double` + % a, b : :class:`.SparseArray` or :class:`double` % intput arrays. % % Returns % ------- - % c : :class:`SparseArray` or :class:`double` + % c : :class:`.SparseArray` or :class:`double` % output array. % % Example @@ -493,14 +503,15 @@ function disp(a) % % Arguments % --------- - % a : :class:`SparseArray` + % a : :class:`.SparseArray` % intput array. + % % b : :class:`double` % scalar to divide by. % % Returns % ------- - % c : :class:`SparseArray` + % c : :class:`.SparseArray` % output array. % % Example @@ -518,8 +529,13 @@ function disp(a) function c = mtimes(a, b) % Matrix multiplication for 2D sparse arrays. % - % :code:`mtimes(a, b)` is called for the syntax :code:`a * b` where :code:`a` - % or :code:`b` is a :class:`SparseArray`. + % Usage + % ----- + % :code:`mtimes(a, b)` + % + % :code:`a * b` + % + % where :code:`a` or :code:`b` is a :class:`.SparseArray`. % % See Also % -------- @@ -622,17 +638,18 @@ function disp(a) % % :code:`a + b` % - % :code:`a` and :code:`b` must have the same size, + % where :code:`a` and :code:`b` must have the same size, % unless one is a scalar. A scalar can be added to a sparse array of any size. % % Arguments % --------- - % a, b : :class:`SparseArray` or :class:`double` + % a, b : :class:`.SparseArray` or :class:`double` % input arrays. % % Returns % ------- - % + % c : :class:`.SparseArray` + % output array. % % Example % ------- @@ -687,18 +704,23 @@ function disp(a) function c = rdivide(a, b) % Elementwise right division for sparse arrays. % - % :code:`rdivide(a, b)` is called for the syntax :code:`a ./ b` where :code:`a` - % or :code:`b` is a :class:`SparseArray`. :code:`a`and :code:`b` must have the - % same size, unless one is a scalar. + % Usage + % ----- + % :code:`rdivide(a, b)` + % + % :code:`a ./ b` + % + % where :code:`a` or :code:`b` is a :class:`.SparseArray`. :code:`a`and + % :code:`b` must have the same size, unless one is a scalar. % % Arguments % --------- - % a, b : :class:`SparseArray` or :class:`double` + % a, b : :class:`.SparseArray` or :class:`double` % input arrays to be divided. % % Returns % ------- - % c : :class:`SparseArray` + % c : :class:`.SparseArray` % elementwise left division of :code:`a` by :code:`b`. % % Example @@ -727,12 +749,12 @@ function disp(a) % % Arguments % --------- - % a : :class:`SparseArray` + % a : :class:`.SparseArray` % input array. % % Returns % ------- - % b : :class:`SparseArray` + % b : :class:`.SparseArray` % output array with real entries corresponding to the real part of the % entries of :code:`a`. a.var = real(a.var); @@ -798,9 +820,10 @@ function disp(a) % % Usage % ----- - % :code:`b = squeeze(a)` - % returns a sparse array :code:`b` with the same elements as :code:`a` but - % with all the singleton dimensions removed. + % :code:`b = squeeze(a)` + % + % returns a sparse array :code:`b` with the same elements as :code:`a` but + % with all the singleton dimensions removed. % % Example % ------- @@ -1006,6 +1029,7 @@ function disp(a) function varargout = svds(a, varargin) % Find a few singular values and vectors. + % % See Also % -------- % `Documentation for builtin Matlab svds `_. @@ -1017,10 +1041,15 @@ function disp(a) function c = times(a, b) % Array multiplication for sparse tensors. % - % :code:`c = times(a, b)` is called for the syntax :code:`a .* b` when :code:`a` - % or :code:`b` is a sparse array. :code:`a` and :code:`b` must have the same - % size, unless one is a scalar. A scalar can be be multiplied by a sparse array - % of any size. + % Usage + % ----- + % :code:`c = times(a, b)` + % + % :code:`a .* b` + % + % where :code:`a` or :code:`b` is a sparse array. :code:`a` and :code:`b` must + % have the same size, unless one is a scalar. A scalar can be be multiplied by + % a sparse array of any size. % % Example % ------- @@ -1054,12 +1083,12 @@ function disp(a) % % Arguments % --------- - % a : :class:`SparseArray` + % a : :class:`.SparseArray` % input array. % % Returns % ------- - % b : :class:`SparseArray` + % b : :class:`.SparseArray` % output array. assert(ismatrix(a), 'sparse:RankError', 'ctranspose is only defined for 2D sparse arrays.'); a = permute(a, [2, 1]); @@ -1076,12 +1105,12 @@ function disp(a) % % Arguments % --------- - % a : :class:`SparseArray` + % a : :class:`.SparseArray` % input array. % % Returns % ------- - % b : :class:`SparseArray` + % b : :class:`.SparseArray` % output array. a.var = -a.var; end @@ -1097,12 +1126,12 @@ function disp(a) % % Arguments % --------- - % a : :class:`SparseArray` + % a : :class:`.SparseArray` % input array. % % Returns % ------- - % b : :class:`SparseArray` + % b : :class:`.SparseArray` % output array. return; end @@ -1128,7 +1157,7 @@ function disp(a) % % Returns % ------- - % a : :class:`SparseArray` + % a : :class:`.SparseArray` % output delta-array. a = SparseArray(repmat(1:inddim, numinds, 1)', 1, repmat(inddim, 1, numinds)); end @@ -1144,6 +1173,7 @@ function disp(a) % --------- % sz : (1, :) :class:`int` % size of the sparse array + % % density : :class:`double` % density of nonzero elements (0 < :code:`density` < 1) a = SparseArray([], [], sz); diff --git a/src/utility/swapvars.m b/src/utility/swapvars.m index 881ff3f..5a53f00 100644 --- a/src/utility/swapvars.m +++ b/src/utility/swapvars.m @@ -4,7 +4,7 @@ % Usage % ----- % :code:`[a, b] = swapvars(a, b)` -% stores the value of a in b, and the value of b in a. +% stores the value of :code:`a` in :code:`b`, and the value of :code:`b` in :code:`a`. end diff --git a/src/utility/time2str.m b/src/utility/time2str.m index 9de752f..500c996 100644 --- a/src/utility/time2str.m +++ b/src/utility/time2str.m @@ -1,5 +1,9 @@ function timeStr = time2str(time, unit, precision) -%TIMESTRING Returns string version of a duration in seconds. +% Returns string version of a duration in seconds. +% +% Usage +% ----- +% :code:`timeStr = time2str(time, unit, precision)` if nargin < 3, precision = 1; end diff --git a/src/utility/validations/mustBeEqualLength.m b/src/utility/validations/mustBeEqualLength.m index 6359ddd..b8ffbde 100644 --- a/src/utility/validations/mustBeEqualLength.m +++ b/src/utility/validations/mustBeEqualLength.m @@ -1,10 +1,11 @@ function mustBeEqualLength(a, b) -% mustBeEqualLength - Validate that the inputs are of equal length. -% mustBeEqualLength(a, b) throws an error if length(a) ~= length(b) +% Validate that the inputs are of equal length. +% +% :code:`mustBeEqualLength(a, b)` throws an error if :code:`length(a) ~= length(b)` % -% Class support: -% All classes that define these methods: -% length +% Note +% ---- +% Supported by all classes that define these methods: :code:`length` if length(a) ~= length(b) throwAsCaller(createValidatorException('TensorTrack:validators:mustBeEqualLength')); diff --git a/src/utility/validations/mustBeSorted.m b/src/utility/validations/mustBeSorted.m index 492014e..d6fec5c 100644 --- a/src/utility/validations/mustBeSorted.m +++ b/src/utility/validations/mustBeSorted.m @@ -1,10 +1,11 @@ function mustBeSorted(A) -% mustBeSorted - Validate that value is sorted. -% mustBeSorted(A) throws an error if A is not sorted. +% Validate that input is sorted. +% +% :code:`mustBeSorted(A)` throws an error if :code:`A` is not sorted. % -% Class support: -% All classes that define these methods: -% issorted +% Note +% ---- +% Supported by all classes that define these methods: :code:`issorted` if ~issorted(A) throwAsCaller(createValidatorException('TensorTrack:validators:mustBeSorted')); diff --git a/src/utility/validations/mustBeUnique.m b/src/utility/validations/mustBeUnique.m index 3a11e51..466d0d0 100644 --- a/src/utility/validations/mustBeUnique.m +++ b/src/utility/validations/mustBeUnique.m @@ -1,11 +1,11 @@ function mustBeUnique(A) -% mustBeUnique - Validate that there are no repeated values. -% mustBeUnique(A) throws an error if A contains repeated values. +% Validate that there are no repeated values. % -% Class support: -% All classes that define these methods: -% unique -% length +% :code:`mustBeUnique(A)` throws an error if :code:`A` contains repeated values. +% +% Note +% ---- +% Supported all classes that define these methods: :code:`unique`, :code:`length` if length(A) ~= length(unique(A)) throwAsCaller(createValidatorException('validators:mustBeUnique')); diff --git a/src/utility/wigner/Wigner3j.m b/src/utility/wigner/Wigner3j.m index 0a49dda..835e3af 100644 --- a/src/utility/wigner/Wigner3j.m +++ b/src/utility/wigner/Wigner3j.m @@ -1,5 +1,5 @@ function wig = Wigner3j(j1,j2,j3,m1,m2,m3,ifs,ifcb) -% Computes the Wigner $3j$ symbols +% Computes the Wigner :math:`3j` symbols % % .. math:: % @@ -49,14 +49,15 @@ % % ifcb % if exists and is true, switches to computing the Clebsch-Gordan coefficients instead of -% the $3j$-symbols (default :code:`false`) +% the :math:`3j`-symbols (default :code:`false`) % % Returns % ------- % W -% the resulting values of the 3j-symbols (:code:`ifcb=false`) or the Clebsch-Gordan -% coefficients (:code:`ifcb=true`) in either numeric double-precision or symbolic form -% (see the input parameter :code:`ifs`); array of the same size as $j_1$. +% the resulting values of the :math:`3j`-symbols (:code:`ifcb=false`) or the +% Clebsch-Gordan coefficients (:code:`ifcb=true`) in either numeric double-precision or +% symbolic form (see the input parameter :code:`ifs`); array of the same size as +% :math:`j_1`. % % % Notes @@ -71,7 +72,7 @@ % d. all the "numeric" algorithms switch automatically to the symbolic computations % as soon as the numeric overflow occurs, thereby improving the accuracy % but slowing down the computations for some big quantum numbers -% e. in cases with at least one of the $j$ quantum numbers <=2, the explicit accurate +% e. in cases with at least one of the :math:`j` quantum numbers <=2, the explicit accurate % equations are applied in all the algorithms ensuring the highest possible accuracy % f. for relatively small quantum numbers (up to ~20) all the algorithms provide % approximately equivalent results with a reasonably high accuracy diff --git a/src/utility/wigner/Wigner6j.m b/src/utility/wigner/Wigner6j.m index a14d7dc..e02f986 100644 --- a/src/utility/wigner/Wigner6j.m +++ b/src/utility/wigner/Wigner6j.m @@ -1,5 +1,5 @@ function wig = Wigner6j(j1,j2,j3,j4,j5,j6,ifs,ifcb) -% Computes the Wigner $$6j$$ symbols +% Computes the Wigner :math:`6j` symbols % % .. math:: % @@ -48,14 +48,14 @@ % % ifcb % if exists and is true, switches to computing the coupling matrix elements instead of the -% $6j$-symbols (default :code:`false`) +% :math:`6j`-symbols (default :code:`false`) % % Returns % ------- % W -% the resulting values of the 3j-symbols (:code:`ifcb=false`) or the Clebsch-Gordan +% the resulting values of the :math:`6j`-symbols (:code:`ifcb=false`) or the coupling matrix elements % coefficients (:code:`ifcb=true`) in either numeric double-precision or symbolic form -% (see the input parameter :code:`ifs`); array of the same size as $j_1$. +% (see the input parameter :code:`ifs`); array of the same size as :math:`j_1`. % % Notes % ----- @@ -69,7 +69,7 @@ % d. all the "numeric" algorithms switch automatically to the symbolic computations % e. as soon as the numeric overflow occurs, thereby improving the accuracy % but slowing down the computations for some big quantum numbers -% f. in cases with at least one of the $j$ quantum numbers <=2, the explicit accurate +% f. in cases with at least one of the :math:`j` quantum numbers <=2, the explicit accurate % equations are applied in all the algorithms ensuring the highest possible accuracy % g. for relatively small quantum numbers (up to ~20) all the algorithms provide % approximately equivalent results with a reasonably high accuracy diff --git a/test/TestSolvers.m b/test/TestSolvers.m index df9fe5e..5483b9a 100644 --- a/test/TestSolvers.m +++ b/test/TestSolvers.m @@ -32,13 +32,13 @@ function linsolve(tc, spaces) [x, flag, relres] = linsolve(A, b, 'Algorithm', alg); tc.assertTrue(isapprox(norm(A * x - b) / norm(b), relres, ... 'AbsTol', tc.tol, 'RelTol', tc.tol)); - tc.assertTrue(flag == 0); + tc.assertTrue(flag == 0 || (flag == 3 && relres < tc.tol)); f = @(x) A * x; [x2, flag, relres] = linsolve(f, b, 'Algorithm', alg); tc.assertTrue(isapprox(norm(f(x2) - b) / norm(b), relres, ... 'AbsTol', tc.tol, 'RelTol', tc.tol)); - tc.assertTrue(flag == 0); + tc.assertTrue(flag == 0 || (flag == 3 && relres < tc.tol)); end end From 9c420101dc81051d2f021c6273a7e9019d27ed70 Mon Sep 17 00:00:00 2001 From: Lander Burgelman <39218680+leburgel@users.noreply.github.com> Date: Thu, 8 Feb 2024 14:51:21 +0100 Subject: [PATCH 244/245] Fix Read The Docs build (#24) * Trying to fix some rtd issues * Try updating sphinx version --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 2cacf55..7927650 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,4 @@ -sphinx==4.5.0 +sphinx==5.0.2 sphinx-rtd-theme==0.5.2 sphinxcontrib-matlabdomain==0.18.0 sphinx-prompt==1.5.0 From 0eeb2fe2c757a5734f4f88a2aca5f50540afd462 Mon Sep 17 00:00:00 2001 From: qmortier Date: Thu, 8 Feb 2024 17:53:01 +0100 Subject: [PATCH 245/245] ctmrg (#22) * ctmrg * Create UniformPeps.m * remove plot tsvd + ctmrg tests * Add CTMRG to docs --------- Co-authored-by: Lander Burgelman <39218680+leburgel@users.noreply.github.com> Co-authored-by: leburgel --- docs/src/index.rst | 2 +- docs/src/lib/algorithms.rst | 9 + docs/src/lib/environments.rst | 3 +- docs/src/lib/mps.rst | 3 + src/algorithms/Ctmrg.m | 369 ++++++++++++++++++++++++++++ src/environments/CtmrgEnvironment.m | 64 +++++ src/mps/PepsSandwich.m | 4 +- src/mps/PepsTensor.m | 5 + src/mps/UniformPeps.m | 163 ++++++++++++ src/tensors/Tensor.m | 18 +- src/utility/plot_schmidtspectrum.m | 59 +++++ test/TestCtmrg.m | 76 ++++++ 12 files changed, 768 insertions(+), 7 deletions(-) create mode 100644 src/algorithms/Ctmrg.m create mode 100644 src/environments/CtmrgEnvironment.m create mode 100644 src/mps/UniformPeps.m create mode 100644 src/utility/plot_schmidtspectrum.m create mode 100644 test/TestCtmrg.m diff --git a/docs/src/index.rst b/docs/src/index.rst index 9a63f69..63e72a7 100644 --- a/docs/src/index.rst +++ b/docs/src/index.rst @@ -35,8 +35,8 @@ Additionally, for tensors which are invariant under general global symmetries, v lib/tensors lib/sparse lib/mps - lib/environments lib/algorithms + lib/environments lib/models lib/utility lib/caches diff --git a/docs/src/lib/algorithms.rst b/docs/src/lib/algorithms.rst index 446c01d..204c96c 100644 --- a/docs/src/lib/algorithms.rst +++ b/docs/src/lib/algorithms.rst @@ -44,6 +44,15 @@ Infinite MPS algorithms :members: changebonds +Infinite PEPS algorithms +------------------------ + +.. autoclass:: src.algorithms.Ctmrg + :no-members: + :members: fixedpoint + + + Eigsolvers ---------- diff --git a/docs/src/lib/environments.rst b/docs/src/lib/environments.rst index cde31f8..696ad17 100644 --- a/docs/src/lib/environments.rst +++ b/docs/src/lib/environments.rst @@ -9,4 +9,5 @@ Environments This section contains the API documentation for the :mod:`.environments` module. .. autoclass:: src.environments.FiniteEnvironment - +.. autoclass:: src.environments.CtmrgEnvironment + :no-members: diff --git a/docs/src/lib/mps.rst b/docs/src/lib/mps.rst index 53e7d7e..569c9d6 100644 --- a/docs/src/lib/mps.rst +++ b/docs/src/lib/mps.rst @@ -28,3 +28,6 @@ Operators .. autoclass:: src.mps.PepsTensor .. autoclass:: src.mps.PepsSandwich :no-members: +.. autoclass:: src.mps.UniformPeps + :no-members: + :members: UniformPeps diff --git a/src/algorithms/Ctmrg.m b/src/algorithms/Ctmrg.m new file mode 100644 index 0000000..2c15eeb --- /dev/null +++ b/src/algorithms/Ctmrg.m @@ -0,0 +1,369 @@ +classdef Ctmrg + % `Corner Transfer Matrix Renormalisation Group algorithm `_ for PEPS. + % + % Properties + % ---------- + % tol : :class:`double` + % tolerance for convergence criterion, defaults to :code:`1e-10`. + % + % miniter : :class:`int` + % minimum number of iteration, defaults to :code:`5`. + % + % maxiter : :class:`int` + % maximum number of iteration, defaults to :code:`100`. + % + % projectortype : :class:`char` + % projector scheme used in the algorithm, currently only supports a single default + % value. + % + % trunc : :class:`struct` + % specification of truncation options, see :meth:`.Tensor.tsvd` for details. + % + % verbosity : :class:`.Verbosity` + % verbosity level of the algorithm, defaults to :code:`Verbosity.iter`. + % + % doplot : :class:`logical` + % plot progress, defaults to :code:`false`. + + %% Options + properties + + tol = 1e-10 + miniter = 5 + maxiter = 100 + + projectortype = 'cornersboris' + trunc + + verbosity = Verbosity.iter + doPlot = false + + doSave = false + saveIterations = 1 + saveMethod = 'full' + name = 'CTMRG' + + end + + methods + function alg = Ctmrg(kwargs) + arguments + kwargs.?Ctmrg + end + + fields = fieldnames(kwargs); + if ~isempty(fields) + for field = fields.' + alg.(field{1}) = kwargs.(field{1}); + end + end + + end + + function [envs, new_norm, err] = fixedpoint(alg, peps_top, peps_bot, envs) + % Find the fixed point CTMRG environment of an infinite PEPS overlap. + % + % Usage + % ----- + % :code:`[envs, new_norm, err] = fixedpoint(alg, peps_top, peps_bot, envs)` + % + % Arguments + % --------- + % alg : :class:`.Ctmrg` + % CTMRG algorithm. + % + % peps_top : :class:`.UniformPeps` + % top-layer uniform PEPS, usually interpreted as the 'ket' in the overlap. + % + % peps_bot : :class:`.UniformPeps` + % bottom-layer uniform PEPS, usually interpreted as the 'bra' in the overlap, + % defaults to the top layer state. + % + % envs : :class:`.CtmrgEnv` + % initial guess for the fixed point CTMRG environment. + % + % Returns + % ------- + % envs : :class:`.CtmrgEnv` + % fixed point CTMRG environment. + % + % new_norm : :class:`double` + % corresponding norm. + % + % err : :class:`double` + % final error measure at convergence. + + arguments + alg + peps_top + peps_bot = peps_top + envs = CtmrgEnvironment(peps_top, peps_bot) + end + + if isempty(alg.trunc) + alg.trunc = struct('TruncSpace', space(envs.corners{1,:,:},1)); + end + + err = Inf; + iter = 1; + + t_total = tic; + disp_init(alg); + + old_norm = contract_ctrmg(alg, peps_top, peps_bot, envs); + + eta1 = 1.0; + while (err > alg.tol && iter <= alg.maxiter) || iter <= alg.miniter + + t_iter = tic; + + eta = 0.0; + for i = 1:4 + if isfield(alg.trunc,'doPlot') && alg.trunc.doPlot + subplot(2,2,i) + end + [envs, eta0] = left_move(alg, peps_top, peps_bot, envs); + eta = max(eta, eta0); + envs = rot90(envs); + peps_top = rot90(peps_top); + peps_bot = rot90(peps_bot); + end + + new_norm = contract_ctrmg(alg, peps_top, peps_bot, envs); + + err = abs(old_norm - new_norm); + deta = abs((eta1 - eta) / eta1); + + if alg.doPlot + plot(alg, iter, envs, err); + end + + disp_iter(alg, iter, err, abs(new_norm), eta, deta, toc(t_iter)); + if alg.doSave && mod(iter, alg.saveIterations) == 0 + save_iteration(alg, envs, err, new_norm, iter, eta, toc(t_total)); + end + + if iter > alg.miniter && err < alg.tol + disp_conv(alg, err, abs(new_norm), eta, deta, toc(t_total)); + return + end + + old_norm = new_norm; + eta1 = eta; + iter = iter + 1; + end + + disp_maxiter(alg, err, abs(new_norm), eta, deta, toc(t_total)); + end + + function [envs, eta] = left_move(alg, peps_top, peps_bot, envs) + + eta = 0.0; + h = height(peps_top); + w = width(peps_top); + + for j = 1:w + + [above_projs, below_projs, etaj] = get_projectors(alg, peps_top, peps_bot, envs, j); + eta = max(eta, etaj); + + %use the projectors to grow the corners/edges + for i = 1:h + + envs.corners{1,prev(i,h),j} = contract(envs.corners{1,prev(i,h),prev(j,w)}, [2 1], ... + envs.edges{1,prev(i,h),j}, [1 3 4 -2], ... + above_projs{prev(i,h)}, [-1 4 3 2], ... + 'Rank', [1,1]); + + envs.edges{4,i,j} = contract( envs.edges{4,i,prev(j,w)}, [7 2 4 1], ... + peps_top.A{i,j}.var, [6 2 8 -2 3], ... + conj(peps_bot.A{i,j}.var), [6 4 9 -3 5], ... + below_projs{prev(i,h)}, [1 3 5 -4], ... + above_projs{i}, [-1 9 8 7], ... + 'Rank', [3,1]); + + envs.corners{4,next(i,h),j} = contract(envs.corners{4,next(i,h),prev(j,w)}, [1 2], ... + envs.edges{3,next(i,h),j}, [-1 3 4 1], ... + below_projs{i}, [2 3 4 -2], ... + 'Rank', [1,1]); + + end + + envs.corners(1,:,j) = cellfun(@(x) x ./ norm(x), envs.corners(1,:,j), 'UniformOutput', false); + envs.edges(4,:,j) = cellfun(@(x) x ./ norm(x), envs.edges(4,:,j), 'UniformOutput', false); + envs.corners(4,:,j) = cellfun(@(x) x ./ norm(x), envs.corners(4,:,j), 'UniformOutput', false); + + end + end + + function [above_projs, below_projs, etaj] = get_projectors(alg, peps_top, peps_bot, envs, j) + + etaj = 0; + h = height(peps_top); + w = width(peps_top); + above_projs = cell(h,1); + below_projs = cell(h,1); + + switch alg.projectortype + + case 'cornersboris' + + for i = 1:h + + %SW corner + Q1 = contract( envs.edges{3, mod1(i+2,h), j}, [-1 5 3 1], ... + envs.corners{4, mod1(i+2,h), prev(j,w)}, [1 2], ... + envs.edges{4, next(i,h), prev(j,w)}, [2 6 4 -6], ... + peps_top.A{next(i,h), j}.var, [7 6 5 -2 -5], ... + conj(peps_bot.A{next(i,h), j}.var), [7 4 3 -3 -4], ... + 'Rank', [3,3]); + %NW corner + Q2 = contract( envs.edges{4, i, prev(j,w)}, [-1 3 5 2], ... + envs.corners{1, prev(i,h), prev(j,w)}, [2 1], ... + envs.edges{1, prev(i,h), j}, [1 4 6 -6], ... + peps_top.A{i, j}.var, [7 3 -2 -5 4], ... + conj(peps_bot.A{i, j}.var), [7 5 -3 -4 6], ... + 'Rank', [3,3]); + + trun = [fieldnames(alg.trunc),struct2cell(alg.trunc)]'; + [U, S, V, etaij] = tsvd(contract(Q1,[-1,-2,-3,3,2,1],Q2,[1,2,3,-4,-5,-6],'Rank',[3,3]), trun{:}); + + etaj = max(etaj, etaij); + isqS = inv(sqrtm(S)); + Q = isqS*U'*Q1; + P = Q2*V'*isqS; + + %norm(contract(Q,[-1,1,2,3],P,[3,2,1,-2],'Rank',[1,1])-Tensor.eye(space(Q,1),space(Q,1))) + %norm(P*Q-Tensor.eye(space(Q,2:4)',space(Q,2:4)')) + + above_projs{i} = Q; + below_projs{i} = P; %should be ok for any arrow + + end + + end + + + end + + function total = contract_ctrmg(alg, peps_top, peps_bot, envs) + + total = 1.0; + h = height(peps_top); + w = width(peps_top); + + for i = 1:h + for j = 1:w + total = total * contract( envs.corners{1,prev(i,h),prev(j,w)}, [9 1], ... + envs.edges{1,prev(i,h),j}, [1 4 7 2], ... + envs.corners{2,prev(i,h),next(j,w)}, [2 3], ... + envs.edges{2,i,next(j,w)}, [3 5 8 17], ... + envs.corners{3,next(i,h),next(j,w)}, [17 16], ... + envs.edges{3,next(i,h),j}, [16 14 15 13], ... + envs.corners{4,next(i,h),prev(j,w)}, [13 12], ... + envs.edges{4,i,prev(j,w)}, [12 10 11 9], ... + peps_top.A{i,j}.var, [6 10 14 5 4], ... + conj(peps_bot.A{i,j}.var), [6 11 15 8 7]); + + total = total * contract( envs.corners{1,prev(i,h),prev(j,w)}, [4 1], ... + envs.corners{2,prev(i,h),j}, [1 2], ... + envs.corners{3,i,j}, [2 3], ... + envs.corners{4,i,prev(j,w)}, [3 4]); + + total = total / contract( envs.corners{1,prev(i,h),prev(j,w)}, [2 1], ... + envs.corners{2,prev(i,h),j}, [1 3], ... + envs.edges{2,i,j}, [3 4 5 6], ... + envs.corners{3,next(i,h),j}, [6 7], ... + envs.corners{4,next(i,h),prev(j,w)}, [7 8], ... + envs.edges{4,i,prev(j,w)}, [8 4 5 2]); + + total = total / contract( envs.corners{1,prev(i,h),prev(j,w)}, [1 3], ... + envs.corners{4,i,prev(j,w)}, [2 1], ... + envs.edges{1,prev(i,h),j}, [3 4 5 6], ... + envs.corners{2,prev(i,h),next(j,w)}, [6 7], ... + envs.corners{3,i,next(j,w)}, [7 8], ... + envs.edges{3,i,j}, [8 4 5 2]); + end + end + + end + + end + + %% Display + methods(Access = private) + + function plot(alg, iter, envs, eta) + if ~alg.doPlot, return; end + persistent axhistory axspectrum + + D = height(envs); + W = width(envs); + + if iter == 1 + figure('Name', 'Ctmrg'); + axhistory = subplot(D + 1, W, 1:W); + axspectrum = gobjects(D, W); + for d = 1:D, for w = 1:W + axspectrum(d, w) = subplot(D + 1, W, w + d * W); + end, end + linkaxes(axspectrum, 'y'); + end + + + if isempty(axhistory.Children) + semilogy(axhistory, iter, eta, '.', 'Color', colors(1), ... + 'DisplayName', 'Errors', 'MarkerSize', 10); + hold on + ylim(axhistory, [alg.tol / 5, max([1 eta])]); + yline(axhistory, alg.tol, '-', 'Convergence', ... + 'LineWidth', 2); + hold off + else + axhistory.Children(end).XData(end+1) = iter; + axhistory.Children(end).YData(end+1) = eta; + end + plot_entanglementspectrum(UniformMps({MpsTensor(envs.edges{1,:,:})}), 1:D, 1:W, axspectrum); + drawnow + end + + function disp_init(alg) + if alg.verbosity < Verbosity.conv, return; end + fprintf('---- CTMRG ----\n'); + end + + function disp_iter(alg, iter, err, norm, eta, deta, t) + if alg.verbosity < Verbosity.iter, return; end + fprintf('iteration: %4d\t\terror: %.2e\t\tnorm: %.10e\t\teta: %.2e\t\tdeta: %.2e\t(%s)\n', ... + iter, err, norm, eta, deta, time2str(t)); + end + + function disp_conv(alg, err, norm, eta, deta, t) + if alg.verbosity < Verbosity.conv, return; end + fprintf('CTMRG converged \t\terror: %.2e\t\tnorm: %.10e\t\teta: %.2e\t\tdeta: %.2e\t(%s)\n', ... + err, norm, eta, deta, time2str(t)); + fprintf('---------------\n'); + end + + function disp_maxiter(alg, err, norm, eta, deta, t) + if alg.verbosity < Verbosity.warn, return; end + fprintf('CTMRG max iterations \terror: %.2e\t\tnorm: %.10e\t\teta: %.2e\t\tdeta: %.2e\t(%s)\n', ... + err, norm, eta, deta, time2str(t)); + fprintf('---------------\n'); + end + + function save_iteration(alg, envs, err, norm, iter, eta, t) + fileName = alg.name; + fileData = struct; + fileData.envs = envs; + fileData.err = err; + fileData.norm = norm; + fileData.iteration = iter; + fileData.eta = eta; + fileData.time = t; + save(fileName, '-struct', 'fileData', '-v7.3'); + end + end + +end + diff --git a/src/environments/CtmrgEnvironment.m b/src/environments/CtmrgEnvironment.m new file mode 100644 index 0000000..18bb2da --- /dev/null +++ b/src/environments/CtmrgEnvironment.m @@ -0,0 +1,64 @@ +classdef CtmrgEnvironment + % Data structure for managing CTMRG environments. + % + % Properties + % ---------- + % corners : :class:`cell` of :class:`.Tensor` + % cell array of corner tensors. + % + % edges : :class:`cell` of :class:`.Tensor` + % cell array of edge tensors. + % + % Todo + % ---- + % Document. + + properties + corners + edges + end + + methods + + function obj = CtmrgEnvironment(varargin) + + if nargin == 0, return; end + + if iscell(varargin{1}) + obj.corners = varargin{1}; + obj.edges = varargin{2}; + end + + obj.corners = cellfun(@(x)x ./ norm(x), obj.corners,'UniformOutput',false); + obj.edges = cellfun(@(x)x ./ norm(x), obj.edges,'UniformOutput',false); + end + + function out = rot90(in) + %rotate unit cell + out = CtmrgEnvironment(flip(permute(in.corners,[1,3,2]),3),flip(permute(in.edges,[1,3,2]),3)); + %relabel corners and edges accordingly + in = out; + for i = 1:4 + out.corners(i,:,:) = in.corners(prev(i,4),:,:); + out.edges(i,:,:) = in.edges(prev(i,4),:,:); + end + + end + + function chi = bond_dimensions(obj) + chi = reshape(cellfun(@(x) dims(x,1),obj.corners(1,:,:)), size(obj.corners,2:3)); + end + + function h = height(obj) + % vertical period over which the peps is translation invariant. + h = height(obj.corners{1,:,:}); + end + + function w = width(obj) + % horizontal period over which the peps is translation invariant. + w = width(obj.corners{1,:,:}); + end + + end +end + diff --git a/src/mps/PepsSandwich.m b/src/mps/PepsSandwich.m index 0db3ebd..015670b 100644 --- a/src/mps/PepsSandwich.m +++ b/src/mps/PepsSandwich.m @@ -5,10 +5,10 @@ % Properties % ---------- % top : :class:`.PepsTensor` - % top-layer PEPS tensor, usually interpreted as the 'bra' in the overlap. + % top-layer PEPS tensor, usually interpreted as the 'ket' in the overlap. % % bot : :class:`.PepsTensor` - % bottom-layer PEPS tensor, usually interpreted as the 'ket' in the overlap. + % bottom-layer PEPS tensor, usually interpreted as the 'bra' in the overlap. % % Todo % ---- diff --git a/src/mps/PepsTensor.m b/src/mps/PepsTensor.m index ad21039..9e24d60 100644 --- a/src/mps/PepsTensor.m +++ b/src/mps/PepsTensor.m @@ -209,6 +209,11 @@ function t = randnc(varargin) t = PepsTensor.new(@randnc, varargin{:}); end + + function t = zeros(varargin) + t = PepsTensor.new(@zeros, varargin{:}); + end + end end diff --git a/src/mps/UniformPeps.m b/src/mps/UniformPeps.m new file mode 100644 index 0000000..dd881bb --- /dev/null +++ b/src/mps/UniformPeps.m @@ -0,0 +1,163 @@ +classdef UniformPeps + % Implementation of infinite translation invariant PEPS + % + % Properties + % ---------- + % A : :class:`cell` of :class:`.PepsTensor` + % cell array of PEPS tensors in 2D unit cell. + % + % Todo + % ---- + % Document. + + properties + A cell + end + + + %% Constructors + methods + function peps = UniformPeps(varargin) + % Usage + % ----- + % :code:`peps = UniformPeps(A)` + % + % Arguments + % --------- + % A : :class:`cell` of :class:`.PepsTensor` + % cell array of PEPS tensors in 2D unit cell. + % + % Returns + % ------- + % peps : :class:`.UniformPeps` + % infinite translation-invariant PEPS. + + if nargin == 0, return; end % default empty constructor + + if nargin == 1 + if isa(varargin{1}, 'UniformPeps') % copy constructor + peps.A = varargin{1}.A; + + elseif isa(varargin{1}, 'Tensor') + peps.A{1} = PepsTensor(varargin{1}); + + elseif isa(varargin{1}, 'PepsTensor') + peps.A{1} = varargin{1}; + + elseif iscell(varargin{1}) + assert(isa(varargin{1}{1},'PepsTensor')) + for i = height(varargin{1}):-1:1 + for j = width(varargin{1}):-1:1 + peps.A{i,j} = varargin{1}{i,j}; + end + end + else + error('Invalid constructor for UniformPeps.') + end + + else + error('Invalid constructor for UniformPeps.') + end + end + end + + %% Properties + methods + function h = height(peps) + % vertical period over which the peps is translation invariant. + h = height(peps.A); + end + + function w = width(peps) + % horizontal period over which the peps is translation invariant. + w = width(peps.A); + end + + function s = westvspace(peps, h, w) + % return the virtual space to the left of site (h,w). + if nargin == 1, h = 1:height(peps); w = 1:width(peps); end + s = cellfun(@westvspace, peps.A(h,w)); + end + + function s = southvspace(peps, h, w) + % return the virtual space to the bottom of site (h,w). + if nargin == 1, h = 1:height(peps); w = 1:width(peps); end + s = cellfun(@southvspace, peps.A(h,w)); + end + + function s = eastvspace(peps, h, w) + % return the virtual space to the right of site (h,w). + if nargin == 1, h = 1:height(peps); w = 1:width(peps); end + s = cellfun(@eastvspace, peps.A(h,w)); + end + + function s = northvspace(peps, h, w) + % return the virtual space to the top of site (h,w). + if nargin == 1, h = 1:height(peps); w = 1:width(peps); end + s = cellfun(@northvspace, peps.A(h,w)); + end + + function s = pspace(peps, h, w) + % return the physical space at site (h,w). + if nargin == 1, h = 1:height(peps); w = 1:width(peps); end + s = cellfun(@pspace, peps.A(h,w)); + end + + function peps = rot90(peps) + peps.A = cellfun(@(x)rot90(x),peps.A.','UniformOutput',false); + end + + function peps = rot270(peps) + peps.A = cellfun(@(x)rot270(x),peps.A.','UniformOutput',false); + end + + function type = underlyingType(peps) + type = underlyingType(peps.A{1,1}); + end + end + + + %% Methods + methods + %build horizontalTransferMatrix + + %build verticalTransferMatrix + + %CtmrgEnvironment + + function ctmrgenv = CtmrgEnvironment(peps_top, peps_bot, varargin) + + h = height(peps_top); + w = width(peps_top); + C = cell(4, h, w); + T = cell(4, h, w); + + if nargin == 2 + vspace = repmat(one(space(peps_top.A{1})), h, w); + elseif all(size(varargin{1})==[1,1]) + vspace = repmat(varargin{1}, h, w); + else + vspace = varargin{1}; + end + + for i = 1:h + for j = 1:w + C{1,i,j} = Tensor.randnc(vspace(i,j), vspace(i,j)); + C{2,i,j} = Tensor.randnc(vspace(i,prev(j,w)), vspace(i,prev(j,w))); + C{3,i,j} = Tensor.randnc(vspace(prev(i,h),prev(j,w)), vspace(prev(i,h),prev(j,w))); + C{4,i,j} = Tensor.randnc(vspace(prev(i,h),j), vspace(prev(i,h),j)); + + T{1,i,j} = Tensor.randnc([vspace(i,prev(j,w)), northvspace(peps_top,next(i,h),j)', northvspace(peps_bot,next(i,h),j)], vspace(i,j)); + T{2,i,j} = Tensor.randnc([vspace(prev(i,h),prev(j,w)), eastvspace(peps_top,i,prev(j,w))', eastvspace(peps_bot,i,prev(j,w))], vspace(i,prev(j,w))); + T{3,i,j} = Tensor.randnc([vspace(prev(i,h),j), southvspace(peps_top,prev(i,h),j)', southvspace(peps_bot,prev(i,h),j)], vspace(prev(i,h),prev(j,w))); + T{4,i,j} = Tensor.randnc([vspace(i,j), westvspace(peps_top,i,next(j,w))', westvspace(peps_bot,i,next(j,w))], vspace(prev(i,h),j)); + end + end + + ctmrgenv = CtmrgEnvironment(C, T); + + end + + end +end + diff --git a/src/tensors/Tensor.m b/src/tensors/Tensor.m index b9d1d98..f1a9e5f 100644 --- a/src/tensors/Tensor.m +++ b/src/tensors/Tensor.m @@ -1986,9 +1986,21 @@ end end if isfield(trunc, 'TruncSpace') - error('TBA'); + truncspace = trunc.TruncSpace; + [b, ind] = ismember(truncspace.dimensions.charges, dims.charges); + assert(all(b),"Truncation space contains charges that S does not.") + assert(all(dims.degeneracies(ind) >= truncspace.dimensions.degeneracies), "Truncation space has degeneracies larger than S.") + dims.degeneracies(ind) = truncspace.dimensions.degeneracies; + dims.degeneracies(setxor(ind,1:numel(dims.degeneracies))) = 0; + for i = 1:length(mblocks) + s = diag(Ss{i}); + eta = eta + sum(s(dims.degeneracies(i) + 1:end).^2 * qdim(dims.charges(i))); + Us{i} = Us{i}(:, 1:dims.degeneracies(i)); + Ss{i} = diag(s(1:dims.degeneracies(i))); + Vs{i} = Vs{i}(1:dims.degeneracies(i), :); + end end - + if ~doTrunc W1 = prod(t.codomain); W2 = prod(t.domain); @@ -2014,7 +2026,7 @@ U.var = fill_matrix_data(U.var, Us, dims.charges); S.var = fill_matrix_data(S.var, Ss, dims.charges); V.var = fill_matrix_data(V.var, Vs, dims.charges); - + if nargout <= 1 U = S; end diff --git a/src/utility/plot_schmidtspectrum.m b/src/utility/plot_schmidtspectrum.m new file mode 100644 index 0000000..c63c911 --- /dev/null +++ b/src/utility/plot_schmidtspectrum.m @@ -0,0 +1,59 @@ +function [ax] = plot_schmidtspectrum(S, ax, kwargs) + +arguments + S + ax = [] + kwargs.SymmetrySort = true + kwargs.ExpandQdim = false + kwargs.Marker = '.' +end + + if isempty(ax) + figure; + ax = gca; + end + + if kwargs.Marker == '.' + kwargs.MarkerSize = 10; + else + kwargs.MarkerSize = 6; + end + + %hold(ax, 'off'); + [svals, charges] = matrixblocks(S); + if kwargs.ExpandQdim + for i = 1:length(svals) + svals{i} = reshape(repmat(svals{i}, 1, qdim(charges(i))), [], 1); + end + end + ctr = 0; + labels = arrayfun(@string, charges, 'UniformOutput', false); + lengths = cellfun(@length, svals); + ticks = cumsum(lengths); + if kwargs.SymmetrySort + for i = 1:length(svals) + semilogy(ax, ctr+(1:lengths(i)), svals{i}, 'Marker', kwargs.Marker, 'MarkerSize', kwargs.MarkerSize, 'Color', colors(i)); + if i == 1, hold(ax, 'on'); end + ctr = ctr + lengths(i); + end + set(ax, 'Xtick', ticks, 'fontsize', 10, ... + 'XtickLabelRotation', 60, 'Xgrid', 'on'); + else + [~, p] = sort(vertcat(svals{:}), 'descend'); + p = invperm(p); + for i = 1:length(svals) + semilogy(ax, p(ctr+(1:lengths(i))), svals{i}, 'Marker', kwargs.Marker, 'MarkerSize', kwargs.MarkerSize, 'Color', colors(i)); + if i == 1, hold(ax, 'on'); end + ctr = ctr + lengths(i); + end + + end + legend(ax, arrayfun(@(x) line([0,1],[0,0],'Color', colors(x), 'Marker', '.', 'MarkerSize', 10, 'linestyle', 'none'),1:numel(labels)), labels, 'Location', 'Best') + set(ax, 'TickLabelInterpreter', 'latex'); + xlim(ax, [1 - 1e-8 ticks(end) + 1e-8]); + hold off + + linkaxes(ax, 'y'); + +end + diff --git a/test/TestCtmrg.m b/test/TestCtmrg.m new file mode 100644 index 0000000..40b312f --- /dev/null +++ b/test/TestCtmrg.m @@ -0,0 +1,76 @@ +classdef TestCtmrg < matlab.unittest.TestCase + % Unit tests for infinite matrix product operators. + + properties (TestParameter) + pspace = struct('fermion', GradedSpace.new(fZ2(0, 1), [1 1], false)) + vspace = struct('fermion', GradedSpace.new(fZ2(0, 1), [1 1], false)) + chispace = struct('fermion', GradedSpace.new(fZ2(0, 1), [10 10], false)) + alg = {Ctmrg('tol', 1e-6)} + %other symms TBA + %larger unit cells TBA + end + + methods (Test, ParameterCombination='sequential') + function testEnvironments(tc, pspace, vspace, chispace) + peps = UniformPeps(PepsTensor.randnc(pspace, vspace, vspace)); + envs = CtmrgEnvironment(peps, peps, chispace); + %check matching spaces TBA + end + + function testConvergence(tc, pspace, vspace, chispace, alg) + maxloops = 10; + i = 0; + err = 1; + while err > alg.tol && i < maxloops + i = i+1; + peps = UniformPeps(PepsTensor.randnc(pspace, vspace, vspace)); + envs = CtmrgEnvironment(peps, peps, chispace); + [envs, norm, err] = fixedpoint(alg, peps, peps, envs); + end + assert(i < maxloops, "Convergence seems to fail for simple random PEPS.") + %check left move TBA + %check norm TBA + end + + function testPWave(tc) + %initialize + pspace = GradedSpace.new(fZ2(0, 1), [1 1], false); + vspace = pspace; + chispace = GradedSpace.new(fZ2(0, 1), [10 10], false); + alg = Ctmrg('tol', 1e-6, 'miniter', 1, 'maxiter', 200); + %fill PEPS as PWave from Gaussian + mblocks{1} = [-0.4084 - 0.5139i -0.1033 + 0.1492i -0.7413 - 1.1574i 0.5924 + 0.6552i ... + -0.0660 - 0.1002i -0.1678 - 0.3237i -0.3739 + 0.9139i 0.6497 + 0.8648i]; + mblocks{2} = [ 0.5454 + 0.6080i -0.3003 - 0.2482i 0.3430 - 0.1734i -0.2851 - 0.2658i ... + -0.4272 + 0.5267i 0.4567 + 0.5106i -0.6708 - 0.1306i 0.2184 - 0.1094i]; + A = PepsTensor.zeros(pspace, vspace, vspace); + peps = UniformPeps(PepsTensor(A.var.fill_matrix(mblocks))); + A = peps.A{1}.var; + %run ctmrg + maxloops = 10; + i = 0; + err = 1; + while err > alg.tol && i < maxloops + i = i+1; + envs = CtmrgEnvironment(peps, peps, chispace); + [envs, norm, err] = fixedpoint(alg, peps, peps, envs); + end + assert(i < maxloops, "Convergence seems to fail for PWave example.") + %local density comparison to Gaussian + O = Tensor(pspace, pspace); + O.var.var{2} = 1; + GL = contract(envs.corners{1,1,1}, [1 -4], envs.edges{4,1,1}, [2 -2 -3 1], envs.corners{4,1,1}, [-1 2]); + GR = contract(envs.corners{2,1,1}, [-1 1], envs.edges{2,1,1}, [1 -2 -3 2], envs.corners{3,1,1}, [2 -4]); + n = contract(GL, 1:4, GR, [4,2,3,1]); + GL = GL/sqrt(n); + GR = GR/sqrt(n); + l = contract(envs.edges{3,1,1}, [-1 5 2 1], GL, [1 4 3 8], envs.edges{1,1,1}, [8 9 10 -4], ... + A, [7 4 5 -2 9], conj(A), [6 3 2 -3 10], O, [6 7]); + lw = contract(envs.edges{3,1,1}, [-1 5 2 1], GL, [1 4 3 8], envs.edges{1,1,1}, [8 9 10 -4], ... + A, [6 4 5 -2 9], conj(A), [6 3 2 -3 10]); + rho = contract(l, [2 3 4 1], GR, [1 3 4 2])/contract(lw, [2 3 4 1], GR, [1 3 4 2]); + assert(abs(rho - 0.178787157813086) < sqrt(alg.tol), "CTMRG yields wrong local density for PWave example.") + end + end +end +