ITensors.jl

repository·main·Indexed 20 days ago

https://github.com/itensor/itensors.jl

A Julia library for creating efficient tensor network algorithms. It provides a flexible interface for tensor operations where indices are first-class objects, supporting construction, contraction, SVD, and element-wise operations. The library includes tools for optimizing contraction sequences via TensorOperations.jl and provides the ITensorVisualizationCore for visualization. Note that MPS and MPO functionality is located in the separate ITensorMPS.jl library.

Tokens
15.5K
Snippets
71
Records
87
Agent score
71%

What's inside ITensors.jl

  1. What is an ITensor?

    main
    An ITensor is a tensor whose interface is independent of its memory layout. ITensor indices are objects that carry extra information and are designed to 'recognize' each other (i.e., they can be compared for equality).
  2. What is the Expose module and when to use it

    main

    The Expose module is designed to unwrap complex types to facilitate generic programming for type-based functions. It is primarily used to intercept workflows and prevent unexpected behavior that can occur when functions interact with arbitrary type wrappers.

    Use Expose when you are developing generic algorithms that need to access the underlying data or structure of wrapped types without being blocked by the wrapper itself.

  3. Supported GPU backends for ITensor

    main

    ITensor uses package extensions to support various GPU backends. The following backends are supported:

    • CUDA.jl: For NVIDIA GPUs.
    • cuTENSOR.jl: An extension of CUDA.jl that provides accelerated binary tensor contractions. If loaded, ITensors with CuArray data use cuTENSOR; otherwise, they fall back to cuBLAS via matrix multiplication.
    • Metal.jl: For Apple GPUs.
    • AMDGPU.jl: For AMD GPUs.

    Important Considerations:

    • Dense vs. Block Sparse: Currently, only dense tensor operations are well supported. Block sparse operations (common with QN conservation) are experimental and may be slower on GPU than on CPU.
    • Matrix Decompositions: Operations like svd, eigen, and qr may fall back to the CPU if the specific GPU backend lacks native support. CUDA generally has good support, while Metal and AMD have more limited support.
    • Precision: Single precision (Float32) is generally fastest on GPUs. Metal does not support Float64.
  4. Combine multiple indices into one using a combiner

    main

    To merge multiple indices into a single index (where the new dimension is the product of the original dimensions), use the combiner function. This is done by contracting the original ITensor with a 'combiner' ITensor.

    1. Create a combiner: C = combiner(i, k; tags="...").
    2. Contract: CT = C * T.
    3. To undo the process (uncombine), contract with the conjugate: UT = dag(C) * CT.

    You can retrieve the new combined index using combinedind(C).

    # Setup indices
    i = Index(4,"i"); j = Index(3,"j"); k = Index(2,"k")
    T = random_itensor(i,j,k)
    
    # Create and apply combiner
    C = combiner(i, k; tags="c")
    CT = C * T
    
    # Access the new index
    ci = combinedind(C)
    @show inds(CT)
    
    # Uncombine
    UT = dag(C) * CT
    @show inds(UT)
  5. Understand Index properties: IDs, Tags, and Prime Levels

    main

    An Index object is more than just a dimension; it carries metadata used for matching and identification during tensor operations.

    • Identity (ID): Every Index has a unique, immutable id. Two Index objects are considered equal (==) if they share the same ID (e.g., a copy of an index).
    • Tags: An Index can have up to four tag strings (e.g., Index(2, "Site")). You can check for the presence of a tag using hastags(index, "tag_name"). Indices with different tags are not equal, even if they have the same dimension.
    • Prime Levels: Using prime(i) creates a new index with a different primelevel. Even if all other properties (dimension, tags, ID) are identical, indices with different prime levels are not equal.

    Note: For two indices to be considered equal in tensor operations, they must match in ID, tags, and prime level.

    using ITensors
    let
      i = Index(3)
      @show dim(i)     # dimension
      @show id(i)      # unique identifier
    
      # Tags
      j = Index(5,"j")
      s = Index(2,"n=1,Site")
      @show hastags(s,"Site") # true
    
      # Prime levels
      i1 = prime(i)
      @show i1 == i    # false (prime levels differ)
    end
  6. Handle EmptyStorage in ITensor constructors

    main

    In v0.2, plain ITensor constructors (e.g., ITensor(i, j, k)) return an ITensor with EmptyStorage instead of Dense or BlockSparse storage filled with zeros.

    Behavioral Notes

    • Contracting two EmptyStorage ITensors results in another EmptyStorage ITensor.
    • To allocate actual storage, set an element: A[i' => 1, i => 1] = 0.0.
    • The ITensor will adopt the element type of the first value you set (e.g., setting a ComplexF64 value will convert the storage to ComplexF64).
    i = Index(2)
    A = ITensor(i', dag(i)) # Returns EmptyStorage
    
    # Allocate storage by setting a value
    A[i' => 1, i => 1] = 1.0 + 0.0im
    # A is now Dense{ComplexF64, ...}
  7. How to contribute code to ITensor.jl

    main

    To contribute to ITensor.jl, follow these steps:

    1. Discuss major changes: If you are planning a significant contribution (more than a few lines of code), contact the maintainers at support@itensor.org to discuss your design before implementation.
    2. Fork and Branch: Fork the ITensors.jl repository and create a new branch for your changes.
    3. Format Code: ITensor requires specific code formatting. You can format your changes using JuliaFormatter.jl or use pre-commit hooks.
    4. Run Tests: Ensure your changes do not break existing functionality by running the unit tests.
    5. Submit Pull Request: Push your branch to your fork and open a Pull Request (PR) on GitHub. If you are asked to merge your own PR, use the Squash and Merge option.
    # To format code using JuliaFormatter
    using JuliaFormatter
    format(".")
    
    # To run all unit tests
    # Navigate to the test/ folder and run:
    julia runtests.jl
    
    # To run an individual test script from a Julia REPL:
    include("itensor.jl")
  8. Access ITensor indices and storage

    main

    The ITensor type now directly wraps a tensor and no longer has separate .inds and .store fields.

    Task: Update Field Access Do not access A.inds or A.store directly. Instead, use the following functions:

    • Use inds(A) to get the indices (now returns a Tuple of Index instead of an IndexSet).
    • Use storage(A) to access the underlying storage.

    Example of the change:

    # Instead of:
    A.inds
    
    # Use:
    inds(A)
    i = Index(2)
    j = Index(3)
    A = random_itensor(i, j)
    
    # Returns a Tuple of Indices
    inds(A) # ((dim=2|id=770), (dim=3|id=272))
  9. Enable Package Compilation for ITensors

    main

    To use the ITensors.compile() function, you must install PackageCompiler.jl and include it in your code alongside ITensors. This is necessary because compile() relies on running MPS/MPO functionality as example code for Julia to compile.

    using Pkg; Pkg.add("PackageCompiler")
    
    using PackageCompiler
    using ITensors
  10. Save and load ITensors using HDF5

    main

    You can persist ITensors to disk using the HDF5 format. This requires the HDF5.jl package (install via using Pkg; Pkg.add("HDF5")).

    Writing to HDF5 Use h5open in write mode ("w") and call write(f, "key", ITensor).

    Reading from HDF5 Use h5open in read mode ("r") and call read(f, "key", ITensor). Note that you must explicitly pass the ITensor type to the read function so Julia knows how to interpret the data.

    using ITensors, HDF5
    
    # Writing
    i = Index(2)
    T = random_itensor(i)
    f = h5open("myfile.h5", "w") do f
        write(f, "T", T)
    end
    
    # Reading
    T_loaded = h5open("myfile.h5", "r") do f
        read(f, "T", ITensor)
    end
  11. Read and write ITensors to HDF5 files

    main

    You can persist ITensors and other ITensors types to HDF5 files using the HDF5.jl library. Use write(f, "name", object) to save and read(f, "name", Type) to load. You can also apply compression during the write process using the compress keyword argument.

    Note that the HDF5 format used by ITensors.jl is designed for interoperability with the C++ version of ITensor.

    using ITensors, HDF5
    i = Index(2)
    T = random_itensor(i)
    
    # Writing to HDF5 with optional compression
    f = h5open("myfile.h5","w") do f
        write(f,"T",T; compress=3)
    end
    
    # Reading from HDF5
    T2 = h5open("myfile.h5","r") do f
        read(f,"T",ITensor)
    end
    
    T == T2