Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement an MPS method initializing the tensors to identity (copy-tensors) #218

Open
wants to merge 12 commits into
base: refactor/ansatz-lattice
Choose a base branch
from

Conversation

Todorbsc
Copy link
Contributor

@Todorbsc Todorbsc commented Oct 9, 2024

Closes #165

@Todorbsc Todorbsc added the enhancement New feature or request label Oct 9, 2024
@Todorbsc Todorbsc self-assigned this Oct 9, 2024
Copy link
Member

@mofeing mofeing left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! But could you clarify a lil bit how it works?

test/MPS_test.jl Outdated
Comment on lines 29 to 50
dispatch_arraysdims = [
([2, 4], [5, 4, 3], [2, 3]),
[(2, 4), (5, 4, 3), (2, 3)],
((2, 4), (5, 4, 3), (2, 3)),
[[2, 4], [5, 4, 3], [2, 3]],
]
for arraysdims in dispatch_arraysdims
ψ = MPS(arraysdims) # Default order (:o, :l, :r)
@test size(tensors(ψ; at=site"1")) == (2, 4)
@test size(tensors(ψ; at=site"2")) == (5, 4, 3)
@test size(tensors(ψ; at=site"3")) == (2, 3)
t1 = tensors(ψ; at=site"1")
@test t1[1, 1] == t1[2, 2] == 1
@test sum(t1) == 2
t2 = tensors(ψ; at=site"2")
@test t2[1, 1, 1] == t2[2, 2, 2] == t2[3, 3, 3] == 1
@test sum(t2) == 3
t3 = tensors(ψ; at=site"3")
@test t3[1, 1] == t3[2, 2] == 1
@test sum(t3) == 2
end

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrap this into a @testset

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 65 to 69
MPS(arraysdims::NTuple{N,<:Vector{Int}}; order=defaultorder(MPS)) where {N} = MPS(collect(arraysdims); order=order)
MPS(arraysdims::Vector{<:Tuple{Vararg{Int}}}; order=defaultorder(MPS)) = MPS(collect.(arraysdims); order=order)
function MPS(arraysdims::NTuple{N,Tuple{Vararg{Int}}}; order=defaultorder(MPS)) where {N}
return MPS(collect(collect.(arraysdims)); order=order)
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would currently just delete these methods for now...

Suggested change
MPS(arraysdims::NTuple{N,<:Vector{Int}}; order=defaultorder(MPS)) where {N} = MPS(collect(arraysdims); order=order)
MPS(arraysdims::Vector{<:Tuple{Vararg{Int}}}; order=defaultorder(MPS)) = MPS(collect.(arraysdims); order=order)
function MPS(arraysdims::NTuple{N,Tuple{Vararg{Int}}}; order=defaultorder(MPS)) where {N}
return MPS(collect(collect.(arraysdims)); order=order)
end

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed them

function MPS(arraysdims::NTuple{N,Tuple{Vararg{Int}}}; order=defaultorder(MPS)) where {N}
return MPS(collect(collect.(arraysdims)); order=order)
end
function MPS(arraysdims::Vector{<:Vector{Int}}; order=defaultorder(MPS))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would use Base.identity, not a constructor since you're not passing arrays.

Suggested change
function MPS(arraysdims::Vector{<:Vector{Int}}; order=defaultorder(MPS))
function Base.identity(::Type{MPS}, arraysdims; order=defaultorder(MPS))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was one possibility along with the one @jofrevalles suggested below of using ones. I replaced the constructor by identity as I think it fits better than ones.

Copy link
Member

@jofrevalles jofrevalles left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Todorbsc

Comment on lines 65 to 88
MPS(arraysdims::NTuple{N,<:Vector{Int}}; order=defaultorder(MPS)) where {N} = MPS(collect(arraysdims); order=order)
MPS(arraysdims::Vector{<:Tuple{Vararg{Int}}}; order=defaultorder(MPS)) = MPS(collect.(arraysdims); order=order)
function MPS(arraysdims::NTuple{N,Tuple{Vararg{Int}}}; order=defaultorder(MPS)) where {N}
return MPS(collect(collect.(arraysdims)); order=order)
end
function MPS(arraysdims::Vector{<:Vector{Int}}; order=defaultorder(MPS))
@assert length(arraysdims[1]) == 2 "First array must have 2 dimensions"
@assert all(==(3) ∘ length, arraysdims[2:(end - 1)]) "All arrays must have 3 dimensions"
@assert length(arraysdims[end]) == 2 "Last array must have 2 dimensions"
issetequal(order, defaultorder(MPS)) ||
throw(ArgumentError("order must be a permutation of $(String.(defaultorder(MPS)))"))

return MPS(
map(arraysdims) do arrdims
mindim = minimum(arrdims)
arr = zeros(ComplexF64, arrdims...)
deltas = ntuple(x -> ntuple(_ -> x, length(arrdims)), mindim)
broadcast(delta -> arr[delta...] = 1.0, deltas)
arr
end;
order=order,
)
end

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not create another constructor for MPS, since that may be misleading to the user who wants to directly create an MPS with some specific arrays. Instead, you could extend the function ones or one similiar, to make that when we call ones(MPS, arraydims ... ) we get this kind of MPS. Something similar to the things we do with rand.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I said above, I agree it is better to not implement another constructor. The option that fits the best for me is Base.identity, check it out :)

Copy link
Member

@mofeing mofeing Oct 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, in general I agree with @jofrevalles but I think ones might be misleading since it could mean you're initializing the MPS to a $\ket{11\dots 1}$ state.

I think identity is better because contracting against another "identity" MPS gives you 1, all the tensors are just COPY-tensors and "identity initialization" is how is known in some packages (it's also known as eye-initialization but eye is how the identity is known on those languages).

@jofrevalles
Copy link
Member

Also note that, as we discussed, we should add a docstring to every new function we make, since we lack a lot of documentation. :)

@Todorbsc
Copy link
Contributor Author

Todorbsc commented Oct 9, 2024

Thanks! But could you clarify a lil bit how it works?

Sure! The point is to create an identity MPS where the tensors will have some user-provided dimensions (arraysdims). Thus, what Base.identity(::Type{MPS}, arraysdims; order) would do is create zero-initialized tensors with the specified dimensions and then assign to the unit those tensor elements that have the same index in all of its dimensions.
For example: In a tensor of shape (3,4,5), the constraint is the lowest dimension of the tensor, i.e. 3, doing the unit assignment to the tensors elements (1,1,1), (2,2,2) and (3,3,3).

@mofeing
Copy link
Member

mofeing commented Oct 9, 2024

Sure! The point is to create an identity MPS where the tensors will have some user-provided dimensions (arraysdims). Thus, what Base.identity(::Type{MPS}, arraysdims; order) would do is create zero-initialized tensors with the specified dimensions and then assign to the unit those tensor elements that have the same index in all of its dimensions. For example: In a tensor of shape (3,4,5), the constraint is the lowest dimension of the tensor, i.e. 3, doing the unit assignment to the tensors elements (1,1,1), (2,2,2) and (3,3,3).

Thanks for clarifying but I think having custom dimensions is not so relevant and can be tedious for the user to generate all the tuples...

IMO it would be better to accept a signature like

Base.identity(::Type{MPS}, n; maxdim=2^(n ÷ 2), physdim=2)

where n is the number of sites and maxdim is the $\chi$ and is the maximum bond dimensions. The size of the virtual dims would then increase exponentially fast until arriving to maxdim.

@Todorbsc
Copy link
Contributor Author

Todorbsc commented Oct 10, 2024

maxdim=2^(n ÷ 2), physdim=2

@mofeing What happens in the case where $\chi$ (maxdim) is reached before the middle of the MPS?
For instance, a 7-site MPS with maxdim=4: the 1st virtual index would be 2-dimensional and the 2nd one would be 4-dimensional (where the maxdim is reached). Would the 3rd one (reaching to the half of the chain) also be 4-dimensional?
This would give the following bond dimensions [2, 4, 4, 4, 4, 2]. Is this right?

@mofeing
Copy link
Member

mofeing commented Oct 10, 2024

@mofeing What happens in the case where χ (maxdim) is reached before the middle of the MPS? For instance, a 7-site MPS with maxdim=4: the 1st virtual index would be 2-dimensional and the 2nd one would be 4-dimensional (where the maxdim is reached). Would the 3rd one (reaching to the half of the chain) also be 4-dimensional? This would give the following bond dimensions [2, 4, 4, 4, 4, 2]. Is this right?

the order or num dims of the tensors is not affected. just the size of the virtual indices is upper bounded. so yeah, [2, 4, 4, 4, 4, 2] is correct.

you can use this code in here to compute the virtual index sizes for an open MPS

χl, χr = let after_mid = i > n ÷ 2, i = (n + 1 - abs(2i - n - 1)) ÷ 2
χl = min(χ, p^(i - 1))
χr = min(χ, p^i)
# swap bond dims after mid and handle midpoint for odd-length MPS
(isodd(n) && i == n ÷ 2 + 1) ? (χl, χl) : (after_mid ? (χr, χl) : (χl, χr))
end

@Todorbsc
Copy link
Contributor Author

@mofeing What happens in the case where χ (maxdim) is reached before the middle of the MPS? For instance, a 7-site MPS with maxdim=4: the 1st virtual index would be 2-dimensional and the 2nd one would be 4-dimensional (where the maxdim is reached). Would the 3rd one (reaching to the half of the chain) also be 4-dimensional? This would give the following bond dimensions [2, 4, 4, 4, 4, 2]. Is this right?

the order or num dims of the tensors is not affected. just the size of the virtual indices is upper bounded. so yeah, [2, 4, 4, 4, 4, 2] is correct.

you can use this code in here to compute the virtual index sizes for an open MPS

χl, χr = let after_mid = i > n ÷ 2, i = (n + 1 - abs(2i - n - 1)) ÷ 2
χl = min(χ, p^(i - 1))
χr = min(χ, p^i)
# swap bond dims after mid and handle midpoint for odd-length MPS
(isodd(n) && i == n ÷ 2 + 1) ? (χl, χl) : (after_mid ? (χr, χl) : (χl, χr))
end

Perfect, thanks!
Actually, I had written my own method for the bond dimensions before reading this, check it out and I can change it if you wish.

@Todorbsc Todorbsc linked an issue Oct 10, 2024 that may be closed by this pull request
@Todorbsc Todorbsc changed the title Implement a MPS constructor that initializes the tensors to identity Implement an MPS method initializing the tensors to identity (copy-tensors) Oct 11, 2024
Copy link
Member

@jofrevalles jofrevalles left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! I would add some empty lines to make the testset clearer, but this is ready to merge soon.

test/MPS_test.jl Outdated Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement identity initialization of Chain
3 participants