ITensorNetworkAD.jl icon indicating copy to clipboard operation
ITensorNetworkAD.jl copied to clipboard

Define an `ITensorNetwork` struct

Open mtfishman opened this issue 4 years ago • 1 comments

Define an ITensorNetwork struct that stores a collection of ITensors that make up a network as well as metadata about the connectivity of the network (such as an adjacency list or lattice iterator). For example, a PEPS class might be defined as an alias of an ITensorNetwork with a Square lattice:

struct ITensorNetwork{N,Lattice}
  tensors::Array{ITensor,N}
  lattice::Lattice
end

const MPS = ITensorNetwork{CartesianIndex{2},Chain}
const PEPS = ITensorNetwork{CartesianIndex{2},Square}

# This would be a generic, unstructured network (not fully connected, but not with any regular pattern)
const TN = ITensorNetwork{CartesianIndex{2},Unstructured}

# And other lattice definitions like `Tree`, `FullyConnected`, etc.

This could have a generic method for operations like priming the links of a network, which makes use of the lattice object to make it faster to determine the neighbors of a tensor in order to determine which indices to prime.

An example of how this would work in practice is that right now there are two "neighbors" functions which determine the neighbors of a site/node in an ITensor network. One is based on the specialized definition for a HyperCubic lattice: https://github.com/ITensor/ITensorNetworkAD.jl/blob/17d1a44f3d6034abcf233f25e724f748963820a5/src/ITensorNetworks/tensor_networks.jl#L168-L177 and one is based on naively searching for which tensors in the network have shared indices with the specified tensor: https://github.com/ITensor/ITensorNetworkAD.jl/blob/17d1a44f3d6034abcf233f25e724f748963820a5/src/ITensorNetworks/tensor_networks.jl#L627-L639

Then, something like prime(linkinds, tn) would use one or the other neighbors function depending on the type of lattice.

mtfishman avatar Jul 16 '21 14:07 mtfishman

An alternative design is to have a type hierarchy like:

abstract type AbstractITensorNetwork end

struct HyperCubicITensorNetwork{N} <: AbstractITensorNetwork
  tensors::Array{ITensor,N}
end

# The lattice is determined by overloading the `lattice` function
lattice(::HyperCubicITensorNetwork{N}) where {N} = HyperCubic{N}()

const MPS = HyperCubicITensorNetwork{1}
const PEPS = HyperCubicITensorNetwork{2}

struct UnstructuredITensorNetwork <: AbstractITensorNetwork
  tensors::Array{ITensor,N}
end

const TN = UnstructuredITensorNetwork

lattice(::UnstructuredITensorNetwork) = Unstructured()

I'm not sure which design would be better. The second design based on a type hierarchy and function overloading is a bit more flexible because different lattice types could store different types of information about the network connectivity. For example, in practice the UnstructuredITensorNetwork could store an adjacency list that caches the actual network structure based on shared indices, which it could do once when it is constructed and then update if ITensors in the network are modified.

mtfishman avatar Jul 16 '21 19:07 mtfishman