# ITensor

## Description

`ITensors.ITensor`

— Type`ITensor`

An ITensor is a tensor whose interface is independent of its memory layout. Therefore it is not necessary to know the ordering of an ITensor's indices, only which indices an ITensor has. Operations like contraction and addition of ITensors automatically handle any memory permutations.

**Examples**

```
julia> i = Index(2, "i")
(dim=2|id=287|"i")
#
# Make an ITensor with random elements:
#
julia> A = randomITensor(i', i)
ITensor ord=2 (dim=2|id=287|"i")' (dim=2|id=287|"i")
NDTensors.Dense{Float64,Array{Float64,1}}
julia> @show A;
A = ITensor ord=2
Dim 1: (dim=2|id=287|"i")'
Dim 2: (dim=2|id=287|"i")
NDTensors.Dense{Float64,Array{Float64,1}}
2×2
0.28358594718392427 1.4342219756446355
1.6620103556283987 -0.40952231269251566
julia> @show inds(A);
inds(A) = ((dim=2|id=287|"i")', (dim=2|id=287|"i"))
#
# Set the i==1, i'==2 element to 1.0:
#
julia> A[i => 1, i' => 2] = 1;
julia> @show A;
A = ITensor ord=2
Dim 1: (dim=2|id=287|"i")'
Dim 2: (dim=2|id=287|"i")
NDTensors.Dense{Float64,Array{Float64,1}}
2×2
0.28358594718392427 1.4342219756446355
1.0 -0.40952231269251566
julia> @show storage(A);
storage(A) = [0.28358594718392427, 1.0, 1.4342219756446355, -0.40952231269251566]
julia> B = randomITensor(i, i');
julia> @show B;
B = ITensor ord=2
Dim 1: (dim=2|id=287|"i")
Dim 2: (dim=2|id=287|"i")'
NDTensors.Dense{Float64,Array{Float64,1}}
2×2
-0.6510816500352691 0.2579101497658179
0.256266641521826 -0.9464735926768166
#
# Can add or subtract ITensors as long as they
# have the same indices, in any order:
#
julia> @show A + B;
A + B = ITensor ord=2
Dim 1: (dim=2|id=287|"i")'
Dim 2: (dim=2|id=287|"i")
NDTensors.Dense{Float64,Array{Float64,1}}
2×2
-0.3674957028513448 1.6904886171664615
1.2579101497658178 -1.3559959053693322
```

## Dense Constructors

`ITensors.ITensor`

— Method```
ITensor([::Type{ElT} = Float64, ]inds)
ITensor([::Type{ElT} = Float64, ]inds::Index...)
```

Construct an ITensor filled with zeros having indices `inds`

and element type `ElT`

. If the element type is not specified, it defaults to `Float64`

.

The storage will have `NDTensors.Dense`

type.

**Examples**

```
i = Index(2,"index_i")
j = Index(4,"index_j")
k = Index(3,"index_k")
A = ITensor(i,j)
B = ITensor(ComplexF64,k,j)
```

`ITensors.ITensor`

— Method```
ITensor([::Type{ElT} = Float64, ]::UndefInitializer, inds)
ITensor([::Type{ElT} = Float64, ]::UndefInitializer, inds::Index...)
```

Construct an ITensor filled with undefined elements having indices `inds`

and element type `ElT`

. If the element type is not specified, it defaults to `Float64`

. One purpose for using this constructor is that initializing the elements in an undefined way is faster than initializing them to a set value such as zero.

The storage will have `NDTensors.Dense`

type.

**Examples**

```
i = Index(2,"index_i")
j = Index(4,"index_j")
k = Index(3,"index_k")
A = ITensor(undef,i,j)
B = ITensor(ComplexF64,undef,k,j)
```

`ITensors.ITensor`

— Method```
ITensor([ElT::Type, ]x::Number, inds)
ITensor([ElT::Type, ]x::Number, inds::Index...)
```

Construct an ITensor with all elements set to `x`

and indices `inds`

.

If `x isa Int`

or `x isa Complex{Int}`

then the elements will be set to `float(x)`

unless specified otherwise by the first input.

The storage will have `NDTensors.Dense`

type.

**Examples**

```
i = Index(2,"index_i"); j = Index(4,"index_j"); k = Index(3,"index_k");
A = ITensor(1.0, i, j)
A = ITensor(1, i, j) # same as above
B = ITensor(2.0+3.0im, j, k)
```

In future versions this may not automatically convert integer inputs with `float`

, and in that case the particular element type should not be relied on.

`ITensors.ITensor`

— Method```
ITensor([ElT::Type, ]A::Array, inds)
ITensor([ElT::Type, ]A::Array, inds::Index...)
itensor([ElT::Type, ]A::Array, inds)
itensor([ElT::Type, ]A::Array, inds::Index...)
```

Construct an ITensor from an Array `A`

and indices `inds`

. The ITensor will be a view of the Array data if possible (if no conversion to a different element type is necessary).

If specified, the ITensor will have element type `ElT`

.

If the element type of `A`

is `Int`

or `Complex{Int}`

and the desired element type isn't specified, it will be converted to `Float64`

or `Complex{Float64}`

automatically. To keep the element type as an integer, specify it explicitly, for example with:

```
i = Index(2, "i")
A = [0 1; 1 0]
T = ITensor(eltype(A), A, i', dag(i))
```

**Examples**

```
i = Index(2,"index_i")
j = Index(2,"index_j")
M = [1. 2;
3 4]
T = ITensor(M, i, j)
T[i => 1, j => 1] = 3.3
M[1, 1] == 3.3
T[i => 1, j => 1] == 3.3
```

In future versions this may not automatically convert `Int`

/`Complex{Int}`

inputs to floating point versions with `float`

(once tensor operations using `Int`

/`Complex{Int}`

are natively as fast as floating point operations), and in that case the particular element type should not be relied on. To avoid extra conversions (and therefore allocations) it is best practice to directly construct with `itensor([0. 1; 1 0], i', dag(i))`

if you want a floating point element type. The conversion is done as a performance optimization since often tensors are passed to BLAS/LAPACK and need to be converted to floating point types compatible with those libraries, but future projects in Julia may allow for efficient operations with more general element types (for example see https://github.com/JuliaLinearAlgebra/Octavian.jl).

`ITensors.randomITensor`

— Method```
randomITensor([::Type{ElT <: Number} = Float64, ]inds)
randomITensor([::Type{ElT <: Number} = Float64, ]inds::Index...)
```

Construct an ITensor with type `ElT`

and indices `inds`

, whose elements are normally distributed random numbers. If the element type is not specified, it defaults to `Float64`

.

**Examples**

```
i = Index(2,"index_i")
j = Index(4,"index_j")
k = Index(3,"index_k")
A = randomITensor(i,j)
B = randomITensor(ComplexF64,undef,k,j)
```

`ITensors.onehot`

— Function```
onehot(ivs...)
setelt(ivs...)
```

Create an ITensor with all zeros except the specified value, which is set to 1.

**Examples**

```
i = Index(2,"i")
A = onehot(i=>2)
# A[i=>2] == 1, all other elements zero
j = Index(3,"j")
B = onehot(i=>1,j=>3)
# B[i=>1,j=>3] == 1, all other element zero
```

## Dense View Constructors

`ITensors.itensor`

— Method`itensor(args...; kwargs...)`

Like the `ITensor`

constructor, but with attempt to make a view of the input data when possible.

## QN BlockSparse Constructors

`ITensors.ITensor`

— Method```
ITensor([::Type{ElT} = Float64, ][flux::QN = QN(), ]inds)
ITensor([::Type{ElT} = Float64, ][flux::QN = QN(), ]inds::Index...)
```

Construct an ITensor with BlockSparse storage filled with `zero(ElT)`

where the nonzero blocks are determined by `flux`

.

If `ElT`

is not specified it defaults to `Float64`

.

If `flux`

is not specified, the ITensor will be empty (it will contain no blocks, and have an undefined flux). The flux will be set by the first element that is set.

**Examples**

```
julia> i
(dim=3|id=212|"i") <Out>
1: QN(0) => 1
2: QN(1) => 2
julia> @show ITensor(QN(0), i', dag(i));
ITensor(QN(0), i', dag(i)) = ITensor ord=2
Dim 1: (dim=3|id=212|"i")' <Out>
1: QN(0) => 1
2: QN(1) => 2
Dim 2: (dim=3|id=212|"i") <In>
1: QN(0) => 1
2: QN(1) => 2
NDTensors.BlockSparse{Float64, Vector{Float64}, 2}
3×3
Block(1, 1)
[1:1, 1:1]
0.0
Block(2, 2)
[2:3, 2:3]
0.0 0.0
0.0 0.0
julia> @show ITensor(QN(1), i', dag(i));
ITensor(QN(1), i', dag(i)) = ITensor ord=2
Dim 1: (dim=3|id=212|"i")' <Out>
1: QN(0) => 1
2: QN(1) => 2
Dim 2: (dim=3|id=212|"i") <In>
1: QN(0) => 1
2: QN(1) => 2
NDTensors.BlockSparse{Float64, Vector{Float64}, 2}
3×3
Block(2, 1)
[2:3, 1:1]
0.0
0.0
julia> @show ITensor(ComplexF64, QN(1), i', dag(i));
ITensor(ComplexF64, QN(1), i', dag(i)) = ITensor ord=2
Dim 1: (dim=3|id=212|"i")' <Out>
1: QN(0) => 1
2: QN(1) => 2
Dim 2: (dim=3|id=212|"i") <In>
1: QN(0) => 1
2: QN(1) => 2
NDTensors.BlockSparse{ComplexF64, Vector{ComplexF64}, 2}
3×3
Block(2, 1)
[2:3, 1:1]
0.0 + 0.0im
0.0 + 0.0im
julia> @show ITensor(undef, QN(1), i', dag(i));
ITensor(undef, QN(1), i', dag(i)) = ITensor ord=2
Dim 1: (dim=3|id=212|"i")' <Out>
1: QN(0) => 1
2: QN(1) => 2
Dim 2: (dim=3|id=212|"i") <In>
1: QN(0) => 1
2: QN(1) => 2
NDTensors.BlockSparse{Float64, Vector{Float64}, 2}
3×3
Block(2, 1)
[2:3, 1:1]
0.0
1.63e-322
```

Construction with undefined flux:

```
julia> A = ITensor(i', dag(i));
julia> @show A;
A = ITensor ord=2
Dim 1: (dim=3|id=212|"i")' <Out>
1: QN(0) => 1
2: QN(1) => 2
Dim 2: (dim=3|id=212|"i") <In>
1: QN(0) => 1
2: QN(1) => 2
NDTensors.EmptyStorage{NDTensors.EmptyNumber, NDTensors.BlockSparse{NDTensors.EmptyNumber, Vector{NDTensors.EmptyNumber}, 2}}
3×3
julia> isnothing(flux(A))
true
julia> A[i' => 1, i => 2] = 2
2
julia> @show A;
A = ITensor ord=2
Dim 1: (dim=3|id=212|"i")' <Out>
1: QN(0) => 1
2: QN(1) => 2
Dim 2: (dim=3|id=212|"i") <In>
1: QN(0) => 1
2: QN(1) => 2
NDTensors.BlockSparse{Int64, Vector{Int64}, 2}
3×3
Block(1, 2)
[1:1, 2:3]
2 0
julia> flux(A)
QN(-1)
```

`ITensors.ITensor`

— Method`ITensor([ElT::Type, ]::AbstractArray, inds; tol = 0)`

Create a block sparse ITensor from the input Array, and collection of QN indices. Zeros are dropped and nonzero blocks are determined from the zero values of the array.

Optionally, you can set a tolerance such that elements less than or equal to the tolerance are dropped.

**Examples**

```
julia> i = Index([QN(0)=>1, QN(1)=>2], "i");
julia> A = [1e-9 0.0 0.0;
0.0 2.0 3.0;
0.0 1e-10 4.0];
julia> @show ITensor(A, i', dag(i); tol = 1e-8);
ITensor(A, i', dag(i); tol = 1.0e-8) = ITensor ord=2
Dim 1: (dim=3|id=468|"i")' <Out>
1: QN(0) => 1
2: QN(1) => 2
Dim 2: (dim=3|id=468|"i") <In>
1: QN(0) => 1
2: QN(1) => 2
NDTensors.BlockSparse{Float64,Array{Float64,1},2}
3×3
Block: (2, 2)
[2:3, 2:3]
2.0 3.0
0.0 4.0
```

`ITensors.ITensor`

— Method```
ITensor([::Type{ElT} = Float64,] ::UndefInitializer, flux::QN, inds)
ITensor([::Type{ElT} = Float64,] ::UndefInitializer, flux::QN, inds::Index...)
```

Construct an ITensor with indices `inds`

and BlockSparse storage with undefined elements of type `ElT`

, where the nonzero (allocated) blocks are determined by the provided QN `flux`

. One purpose for using this constructor is that initializing the elements in an undefined way is faster than initializing them to a set value such as zero.

The storage will have `NDTensors.BlockSparse`

type.

**Examples**

```
i = Index([QN(0)=>1, QN(1)=>2], "i")
A = ITensor(undef,QN(0),i',dag(i))
B = ITensor(Float64,undef,QN(0),i',dag(i))
C = ITensor(ComplexF64,undef,QN(0),i',dag(i))
```

## Diagonal constructors

`ITensors.diagITensor`

— Method```
diagITensor([::Type{ElT} = Float64, ]inds)
diagITensor([::Type{ElT} = Float64, ]inds::Index...)
```

Make a sparse ITensor of element type `ElT`

with only elements along the diagonal stored. Defaults to having `zero(T)`

along the diagonal.

The storage will have `NDTensors.Diag`

type.

`ITensors.diagITensor`

— Method```
diagITensor([ElT::Type, ]v::Vector, inds...)
diagitensor([ElT::Type, ]v::Vector, inds...)
```

Make a sparse ITensor with non-zero elements only along the diagonal. In general, the diagonal elements will be those stored in `v`

and the ITensor will have element type `eltype(v)`

, unless specified explicitly by `ElT`

. The storage will have `NDTensors.Diag`

type.

In the case when `eltype(v) isa Union{Int, Complex{Int}}`

, by default it will be converted to `float(v)`

. Note that this behavior is subject to change in the future.

The version `diagITensor`

will never output an ITensor whose storage data is an alias of the input vector data.

The version `diagitensor`

might output an ITensor whose storage data is an alias of the input vector data in order to minimize operations.

`ITensors.diagITensor`

— Method```
diagITensor([ElT::Type, ]x::Number, inds...)
diagitensor([ElT::Type, ]x::Number, inds...)
```

Make a sparse ITensor with non-zero elements only along the diagonal. In general, the diagonal elements will be set to the value `x`

and the ITensor will have element type `eltype(x)`

, unless specified explicitly by `ElT`

. The storage will have `NDTensors.Diag`

type.

In the case when `x isa Union{Int, Complex{Int}}`

, by default it will be converted to `float(x)`

. Note that this behavior is subject to change in the future.

`ITensors.delta`

— Method```
delta([::Type{ElT} = Float64, ]inds)
delta([::Type{ElT} = Float64, ]inds::Index...)
```

Make a uniform diagonal ITensor with all diagonal elements `one(ElT)`

. Only a single diagonal element is stored.

This function has an alias `δ`

.

## QN Diagonal constructors

`ITensors.diagITensor`

— Method```
diagITensor([::Type{ElT} = Float64, ][flux::QN = QN(), ]is)
diagITensor([::Type{ElT} = Float64, ][flux::QN = QN(), ]is::Index...)
```

Make an ITensor with storage type `NDTensors.DiagBlockSparse`

with elements `zero(ElT)`

. The ITensor only has diagonal blocks consistent with the specified `flux`

.

If the element type is not specified, it defaults to `Float64`

. If theflux is not specified, it defaults to `QN()`

.

`ITensors.delta`

— Method```
delta([::Type{ElT} = Float64, ][flux::QN = QN(), ]is)
delta([::Type{ElT} = Float64, ][flux::QN = QN(), ]is::Index...)
```

Make an ITensor with storage type `NDTensors.DiagBlockSparse`

with uniform elements `one(ElT)`

. The ITensor only has diagonal blocks consistent with the specified `flux`

.

If the element type is not specified, it defaults to `Float64`

. If theflux is not specified, it defaults to `QN()`

.

## Convert to Array

`Core.Array`

— Method```
Array{ElT, N}(T::ITensor, i:Index...)
Array{ElT}(T::ITensor, i:Index...)
Array(T::ITensor, i:Index...)
Matrix{ElT}(T::ITensor, row_i:Index, col_i::Index)
Matrix(T::ITensor, row_i:Index, col_i::Index)
Vector{ElT}(T::ITensor)
Vector(T::ITensor)
```

Given an ITensor `T`

with indices `i...`

, returns an Array with a copy of the ITensor's elements. The order in which the indices are provided indicates the order of the data in the resulting Array.

`NDTensors.array`

— Method`array(T::ITensor, inds...)`

Convert an ITensor `T`

to an Array.

The ordering of the elements in the Array are specified by the input indices `inds`

. This tries to avoid copying of possible (i.e. may return a view of the original data), for example if the ITensor's storage is Dense and the indices are already in the specified ordering so that no permutation is required.

Note that in the future we may return specialized AbstractArray types for certain storage types, for example a `LinearAlgebra.Diagonal`

type for an ITensor with `Diag`

storage. The specific storage type shouldn't be relied upon.

`NDTensors.matrix`

— Method`matrix(T::ITensor, inds...)`

Convert an ITensor `T`

to a Matrix.

The ordering of the elements in the Matrix are specified by the input indices `inds`

. This tries to avoid copying of possible (i.e. may return a view of the original data), for example if the ITensor's storage is Dense and the indices are already in the specified ordering so that no permutation is required.

Note that in the future we may return specialized AbstractArray types for certain storage types, for example a `LinearAlgebra.Diagonal`

type for an ITensor with `Diag`

storage. The specific storage type shouldn't be relied upon.

`NDTensors.vector`

— Method`vector(T::ITensor, inds...)`

Convert an ITensor `T`

to an Vector.

The ordering of the elements in the Array are specified by the input indices `inds`

. This tries to avoid copying of possible (i.e. may return a view of the original data), for example if the ITensor's storage is Dense and the indices are already in the specified ordering so that no permutation is required.

Note that in the future we may return specialized AbstractArray types for certain storage types, for example a `LinearAlgebra.Diagonal`

type for an ITensor with `Diag`

storage. The specific storage type shouldn't be relied upon.

`NDTensors.array`

— Method`array(T::ITensor)`

Given an ITensor `T`

, returns an Array with a copy of the ITensor's elements, or a view in the case the the ITensor's storage is Dense.

The ordering of the elements in the Array, in terms of which Index is treated as the row versus column, depends on the internal layout of the ITensor.

This method is intended for developer use only and not recommended for use in ITensor applications unless you know what you are doing (for example you are certain of the memory ordering of the ITensor because you permuted the indices into a certain order).

`NDTensors.matrix`

— Method`matrix(T::ITensor)`

Given an ITensor `T`

with two indices, returns a Matrix with a copy of the ITensor's elements, or a view in the case the ITensor's storage is Dense.

The ordering of the elements in the Matrix, in terms of which Index is treated as the row versus column, depends on the internal layout of the ITensor.

This method is intended for developer use only and not recommended for use in ITensor applications unless you know what you are doing (for example you are certain of the memory ordering of the ITensor because you permuted the indices into a certain order).

`NDTensors.vector`

— Method`vector(T::ITensor)`

Given an ITensor `T`

with one index, returns a Vector with a copy of the ITensor's elements, or a view in the case the ITensor's storage is Dense.

## Getting and setting elements

`Base.getindex`

— Method`getindex(T::ITensor, ivs...)`

Get the specified element of the ITensor, using a list of `IndexVal`

s or `Pair{<:Index, Int}`

.

**Example**

```
i = Index(2; tags = "i")
A = ITensor(2.0, i, i')
A[i => 1, i' => 2] # 2.0, same as: A[i' => 2, i => 1]
```

`Base.setindex!`

— Method```
setindex!(T::ITensor, x::Number, ivs...)
setindex!(T::ITensor, x::Number, I::Integer...)
setindex!(T::ITensor, x::Number, I::CartesianIndex)
```

Set the specified element of the ITensor, using a list of `Pair{<:Index, Integer}`

(or `IndexVal`

).

If just integers are used, set the specified element of the ITensor using internal Index ordering of the ITensor (only for advanced usage, only use if you know the axact ordering of the indices).

**Example**

```
i = Index(2; tags = "i")
A = ITensor(i, i')
A[i => 1, i' => 2] = 1.0 # same as: A[i' => 2, i => 1] = 1.0
A[1, 2] = 1.0 # same as: A[i => 1, i' => 2] = 1.0
# Some simple slicing is also supported
A[i => 2, i' => :] = [2.0 3.0]
A[2, :] = [2.0 3.0]
```

## Properties

`NDTensors.inds`

— Method`inds(T::ITensor)`

Return the indices of the ITensor as a Tuple.

`NDTensors.ind`

— Method`ind(T::ITensor, i::Int)`

Get the Index of the ITensor along dimension i.

`ITensors.dir`

— Method`dir(A::ITensor, i::Index)`

Return the direction of the Index `i`

in the ITensor `A`

.

## Priming and tagging

`ITensors.prime`

— Method```
prime[!](A::ITensor, plinc::Int = 1; <keyword arguments>) -> ITensor
prime(inds, plinc::Int = 1; <keyword arguments>) -> IndexSet
```

Increase the prime level of the indices of an ITensor or collection of indices.

Optionally, only modify the indices with the specified keyword arguments.

**Arguments**

`tags = nothing`

: if specified, only modify Index`i`

if`hastags(i, tags) == true`

.`plev = nothing`

: if specified, only modify Index`i`

if`hasplev(i, plev) == true`

.

The ITensor functions come in two versions, `f`

and `f!`

. The latter modifies the ITensor in-place. In both versions, the ITensor storage is not modified or copied (so it returns an ITensor with a view of the original storage).

`ITensors.setprime`

— Method```
setprime[!](A::ITensor, plev::Int; <keyword arguments>) -> ITensor
setprime(inds, plev::Int; <keyword arguments>) -> IndexSet
```

Set the prime level of the indices of an ITensor or collection of indices.

Optionally, only modify the indices with the specified keyword arguments.

**Arguments**

`tags = nothing`

: if specified, only modify Index`i`

if`hastags(i, tags) == true`

.`plev = nothing`

: if specified, only modify Index`i`

if`hasplev(i, plev) == true`

.

The ITensor functions come in two versions, `f`

and `f!`

. The latter modifies the ITensor in-place. In both versions, the ITensor storage is not modified or copied (so it returns an ITensor with a view of the original storage).

`ITensors.noprime`

— Method```
noprime[!](A::ITensor; <keyword arguments>) -> ITensor
noprime(inds; <keyword arguments>) -> IndexSet
```

Set the prime level of the indices of an ITensor or collection of indices to zero.

Optionally, only modify the indices with the specified keyword arguments.

**Arguments**

`tags = nothing`

: if specified, only modify Index`i`

if`hastags(i, tags) == true`

.`plev = nothing`

: if specified, only modify Index`i`

if`hasplev(i, plev) == true`

.

The ITensor functions come in two versions, `f`

and `f!`

. The latter modifies the ITensor in-place. In both versions, the ITensor storage is not modified or copied (so it returns an ITensor with a view of the original storage).

`ITensors.mapprime`

— Method```
replaceprime[!](A::ITensor, plold::Int, plnew::Int; <keyword arguments>) -> ITensor
replaceprime[!](A::ITensor, plold => plnew; <keyword arguments>) -> ITensor
mapprime[!](A::ITensor, <arguments>; <keyword arguments>) -> ITensor
replaceprime(inds, plold::Int, plnew::Int; <keyword arguments>)
replaceprime(inds::IndexSet, plold => plnew; <keyword arguments>)
mapprime(inds, <arguments>; <keyword arguments>)
```

Set the prime level of the indices of an ITensor or collection of indices with prime level `plold`

to `plnew`

.

Optionally, only modify the indices with the specified keyword arguments.

**Arguments**

`tags = nothing`

: if specified, only modify Index`i`

if`hastags(i, tags) == true`

.`plev = nothing`

: if specified, only modify Index`i`

if`hasplev(i, plev) == true`

.

`f`

and `f!`

. The latter modifies the ITensor in-place. In both versions, the ITensor storage is not modified or copied (so it returns an ITensor with a view of the original storage).

`ITensors.swapprime`

— Method```
swapprime[!](A::ITensor, pl1::Int, pl2::Int; <keyword arguments>) -> ITensor
swapprime[!](A::ITensor, pl1 => pl2; <keyword arguments>) -> ITensor
swapprime(inds, pl1::Int, pl2::Int; <keyword arguments>)
swapprime(inds, pl1 => pl2; <keyword arguments>)
```

Set the prime level of the indices of an ITensor or collection of indices with prime level `pl1`

to `pl2`

, and those with prime level `pl2`

to `pl1`

.

Optionally, only modify the indices with the specified keyword arguments.

**Arguments**

`tags = nothing`

: if specified, only modify Index`i`

if`hastags(i, tags) == true`

.`plev = nothing`

: if specified, only modify Index`i`

if`hasplev(i, plev) == true`

.

`f`

and `f!`

. The latter modifies the ITensor in-place. In both versions, the ITensor storage is not modified or copied (so it returns an ITensor with a view of the original storage).

`ITensors.addtags`

— Method```
addtags[!](A::ITensor, ts::String; <keyword arguments>) -> ITensor
addtags(inds, ts::String; <keyword arguments>)
```

Add the tags `ts`

to the indices of an ITensor or collection of indices.

Optionally, only modify the indices with the specified keyword arguments.

**Arguments**

`tags = nothing`

: if specified, only modify Index`i`

if`hastags(i, tags) == true`

.`plev = nothing`

: if specified, only modify Index`i`

if`hasplev(i, plev) == true`

.

`f`

and `f!`

. The latter modifies the ITensor in-place. In both versions, the ITensor storage is not modified or copied (so it returns an ITensor with a view of the original storage).

`ITensors.removetags`

— Method```
removetags[!](A::ITensor, ts::String; <keyword arguments>) -> ITensor
removetags(inds, ts::String; <keyword arguments>)
```

Remove the tags `ts`

from the indices of an ITensor or collection of indices.

Optionally, only modify the indices with the specified keyword arguments.

**Arguments**

`tags = nothing`

: if specified, only modify Index`i`

if`hastags(i, tags) == true`

.`plev = nothing`

: if specified, only modify Index`i`

if`hasplev(i, plev) == true`

.

`f`

and `f!`

. The latter modifies the ITensor in-place. In both versions, the ITensor storage is not modified or copied (so it returns an ITensor with a view of the original storage).

`ITensors.replacetags`

— Method```
replacetags[!](A::ITensor, tsold::String, tsnew::String; <keyword arguments>) -> ITensor
replacetags(is::IndexSet, tsold::String, tsnew::String; <keyword arguments>) -> IndexSet
```

Replace the tags `tsold`

with `tsnew`

for the indices of an ITensor.

Optionally, only modify the indices with the specified keyword arguments.

**Arguments**

`tags = nothing`

: if specified, only modify Index`i`

if`hastags(i, tags) == true`

.`plev = nothing`

: if specified, only modify Index`i`

if`hasplev(i, plev) == true`

.

`f`

and `f!`

. The latter modifies the ITensor in-place. In both versions, the ITensor storage is not modified or copied (so it returns an ITensor with a view of the original storage).

`ITensors.settags`

— Method```
settags[!](A::ITensor, ts::String; <keyword arguments>) -> ITensor
settags(is::IndexSet, ts::String; <keyword arguments>) -> IndexSet
```

Set the tags of the indices of an ITensor or IndexSet to `ts`

.

Optionally, only modify the indices with the specified keyword arguments.

**Arguments**

`tags = nothing`

: if specified, only modify Index`i`

if`hastags(i, tags) == true`

.`plev = nothing`

: if specified, only modify Index`i`

if`hasplev(i, plev) == true`

.

`f`

and `f!`

. The latter modifies the ITensor in-place. In both versions, the ITensor storage is not modified or copied (so it returns an ITensor with a view of the original storage).

`ITensors.swaptags`

— Method```
swaptags[!](A::ITensor, ts1::String, ts2::String; <keyword arguments>) -> ITensor
swaptags(is::IndexSet, ts1::String, ts2::String; <keyword arguments>) -> IndexSet
```

Swap the tags `ts1`

with `ts2`

for the indices of an ITensor.

Optionally, only modify the indices with the specified keyword arguments.

**Arguments**

`tags = nothing`

: if specified, only modify Index`i`

if`hastags(i, tags) == true`

.`plev = nothing`

: if specified, only modify Index`i`

if`hasplev(i, plev) == true`

.

`f`

and `f!`

. The latter modifies the ITensor in-place. In both versions, the ITensor storage is not modified or copied (so it returns an ITensor with a view of the original storage).

## Index collections set operations

`ITensors.commoninds`

— Function`commoninds(A, B; kwargs...)`

Return a Vector with indices that are common between the indices of `A`

and `B`

(the set intersection, similar to `Base.intersect`

).

`ITensors.commonind`

— Function`commonind(A, B; kwargs...)`

Return the first `Index`

common between the indices of `A`

and `B`

.

See also `commoninds`

.

`ITensors.uniqueinds`

— Function`uniqueinds(A, B; kwargs...)`

Return Vector with indices that are unique to the set of indices of `A`

and not in `B`

(the set difference, similar to `Base.setdiff`

).

`ITensors.uniqueind`

— Function`uniqueind(A, B; kwargs...)`

Return the first `Index`

unique to the set of indices of `A`

and not in `B`

.

See also `uniqueinds`

.

`ITensors.noncommoninds`

— Function`noncommoninds(A, B; kwargs...)`

Return a Vector with indices that are not common between the indices of `A`

and `B`

(the symmetric set difference, similar to `Base.symdiff`

).

`ITensors.noncommonind`

— Function`noncommonind(A, B; kwargs...)`

Return the first `Index`

not common between the indices of `A`

and `B`

.

See also `noncommoninds`

.

`ITensors.unioninds`

— Function`unioninds(A, B; kwargs...)`

Return a Vector with indices that are the union of the indices of `A`

and `B`

(the set union, similar to `Base.union`

).

`ITensors.unionind`

— Function`unionind(A, B; kwargs...)`

Return the first `Index`

in the union of the indices of `A`

and `B`

.

See also `unioninds`

.

`ITensors.hascommoninds`

— Function```
hascommoninds(A, B; kwargs...)
hascommoninds(B; kwargs...) -> f::Function
```

Check if the ITensors or sets of indices `A`

and `B`

have common indices.

If only one ITensor or set of indices `B`

is passed, return a function `f`

such that `f(A) = hascommoninds(A, B; kwargs...)`

## Index Manipulations

`ITensors.replaceind`

— Method`replaceind[!](A::ITensor, i1::Index, i2::Index) -> ITensor`

Replace the Index `i1`

with the Index `i2`

in the ITensor.

The indices must have the same space (i.e. the same dimension and QNs, if applicable).

`ITensors.replaceinds`

— Method```
replaceinds(A::ITensor, inds1, inds2) -> ITensor
replaceinds!(A::ITensor, inds1, inds2)
```

Replace the Index `inds1[n]`

with the Index `inds2[n]`

in the ITensor, where `n`

runs from `1`

to `length(inds1) == length(inds2)`

.

The indices must have the same space (i.e. the same dimension and QNs, if applicable).

The storage of the ITensor is not modified or copied (the output ITensor is a view of the input ITensor).

`ITensors.swapind`

— Method```
swapind(A::ITensor, i1::Index, i2::Index) -> ITensor
swapind!(A::ITensor, i1::Index, i2::Index)
```

Swap the Index `i1`

with the Index `i2`

in the ITensor.

The indices must have the same space (i.e. the same dimension and QNs, if applicable).

`ITensors.swapinds`

— Method```
swapinds(A::ITensor, inds1, inds2) -> ITensor
swapinds!(A::ITensor, inds1, inds2)
```

Swap the Index `inds1[n]`

with the Index `inds2[n]`

in the ITensor, where `n`

runs from `1`

to `length(inds1) == length(inds2)`

.

The indices must have the same space (i.e. the same dimension and QNs, if applicable).

The storage of the ITensor is not modified or copied (the output ITensor is a view of the input ITensor).

## Math operations

`Base.:*`

— Method```
A::ITensor * B::ITensor
contract(A::ITensor, B::ITensor)
```

Contract ITensors A and B to obtain a new ITensor. This contraction `*`

operator finds all matching indices common to A and B and sums over them, such that the result will have only the unique indices of A and B. To prevent indices from matching, their prime level or tags can be modified such that they no longer compare equal - for more information see the documentation on Index objects.

**Examples**

```
i = Index(2,"index_i"); j = Index(4,"index_j"); k = Index(3,"index_k")
A = randomITensor(i,j)
B = randomITensor(j,k)
C = A * B # contract over Index j
A = randomITensor(i,i')
B = randomITensor(i,i'')
C = A * B # contract over Index i
A = randomITensor(i)
B = randomITensor(j)
C = A * B # outer product of A and B, no contraction
A = randomITensor(i,j,k)
B = randomITensor(k,i,j)
C = A * B # inner product of A and B, all indices contracted
```

`Base.exp`

— Method`exp(A::ITensor, Linds=Rinds', Rinds=inds(A,plev=0); ishermitian = false)`

Compute the exponential of the tensor `A`

by treating it as a matrix $A_{lr}$ with the left index `l`

running over all indices in `Linds`

and `r`

running over all indices in `Rinds`

.

Only accepts index lists `Linds`

,`Rinds`

such that: (1) `length(Linds) + length(Rinds) == length(inds(A))`

(2) `length(Linds) == length(Rinds)`

(3) For each pair of indices `(Linds[n],Rinds[n])`

, `Linds[n]`

and `Rinds[n]`

represent the same Hilbert space (the same QN structure in the QN case, or just the same length in the dense case), and appear in `A`

with opposite directions.

When `ishermitian=true`

the exponential of `Hermitian(A_{lr})`

is computed internally.

## Decompositions

`LinearAlgebra.svd`

— Method`svd(A::ITensor, inds::Index...; <keyword arguments>)`

Singular value decomposition (SVD) of an ITensor `A`

, computed by treating the "left indices" provided collectively as a row index, and the remaining "right indices" as a column index (matricization of a tensor).

The first three return arguments are `U`

, `S`

, and `V`

, such that `A ≈ U * S * V`

.

Whether or not the SVD performs a trunction depends on the keyword arguments provided.

**Examples**

```
i = Index(2)
j = Index(5)
k = Index(2)
A = randomITensor(i, j, k)
U, S, V = svd(A, i, k);
@show norm(A - U * S * V) <= 10 * eps() * norm(A)
# This will truncate the last 2 singular values.
# The norm of the difference with the original tensor
# will be the sqrt root of the sum of the squares of the
# singular values that get truncated.
Utrunc, Strunc, Vtrunc = svd(A, i, k; maxdim=2);
@show norm(A - Utrunc * Strunc * Vtrunc) ≈ sqrt(S[3, 3]^2 + S[4, 4]^2)
# Alternatively we can specify that we want to truncate
# the weights of the singular values up to a certain cutoff,
# so the error will be no larger than the cutoff.
Utrunc2, Strunc2, Vtrunc2 = svd(A, i, k; cutoff=1e-10);
@show norm(A - Utrunc2 * Strunc2 * Vtrunc2) <= 1e-10
```

**Keywords**

`maxdim::Int`

: the maximum number of singular values to keep.`mindim::Int`

: the minimum number of singular values to keep.`cutoff::Float64`

: set the desired truncation error of the SVD, by default defined as the sum of the squares of the smallest singular values.`lefttags::String = "Link,u"`

: set the tags of the Index shared by`U`

and`S`

.`righttags::String = "Link,v"`

: set the tags of the Index shared by`S`

and`V`

.`alg::String = "divide_and_conquer"`

. Options:`"divide_and_conquer"`

- A divide-and-conquer algorithm. LAPACK's gesdd. Fast, but may lead to some innacurate singular values for very ill-conditioned matrices. Also may sometimes fail to converge, leading to errors (in which case "qr_iteration" or "recursive" can be tried).`"qr_iteration"`

- Typically slower but more accurate for very ill-conditioned matrices compared to`"divide_and_conquer"`

. LAPACK's gesvd.`"recursive"`

- ITensor's custom svd. Very reliable, but may be slow if high precision is needed. To get an`svd`

of a matrix`A`

, an eigendecomposition of $A^{\dagger} A$ is used to compute`U`

and then a`qr`

of $A^{\dagger} U$ is used to compute`V`

. This is performed recursively to compute small singular values.

`use_absolute_cutoff::Bool = false`

: set if all probability weights below the`cutoff`

value should be discarded, rather than the sum of discarded weights.`use_relative_cutoff::Bool = true`

: set if the singular values should be normalized for the sake of truncation.

`LinearAlgebra.eigen`

— Method`eigen(A::ITensor[, Linds, Rinds]; <keyword arguments>)`

Eigendecomposition of an ITensor `A`

, computed by treating the "left indices" `Linds`

provided collectively as a row index, and remaining "right indices" `Rinds`

as a column index (matricization of a tensor).

If no indices are provided, pairs of primed and unprimed indices are searched for, with `Linds`

taken to be the primed indices and `Rinds`

taken to be the unprimed indices.

The return arguments are the eigenvalues `D`

and eigenvectors `U`

as tensors, such that `A * U ∼ U * D`

(more precisely they are approximately equal up to proper replacements of indices, see the example for details).

Whether or not `eigen`

performs a trunction depends on the keyword arguments provided. Note that truncation is only well defined for positive semidefinite matrices.

**Arguments**

`maxdim::Int`

: the maximum number of singular values to keep.`mindim::Int`

: the minimum number of singular values to keep.`cutoff::Float64`

: set the desired truncation error of the eigenvalues, by default defined as the sum of the squares of the smallest eigenvalues. For now truncation is only well defined for positive semi-definite eigenspectra.`ishermitian::Bool = false`

: specify if the matrix is Hermitian, in which case a specialized diagonalization routine will be used and it is guaranteed that real eigenvalues will be returned.`plev::Int = 0`

: set the prime level of the Indices of`D`

. Default prime levels are subject to change.`leftplev::Int = plev`

: set the prime level of the Index unique to`D`

. Default prime levels are subject to change.`rightplev::Int = leftplev+1`

: set the prime level of the Index shared by`D`

and`U`

. Default tags are subject to change.`tags::String = "Link,eigen"`

: set the tags of the Indices of`D`

. Default tags are subject to change.`lefttags::String = tags`

: set the tags of the Index unique to`D`

. Default tags are subject to change.`righttags::String = tags`

: set the tags of the Index shared by`D`

and`U`

. Default tags are subject to change.`use_absolute_cutoff::Bool = false`

: set if all probability weights below the`cutoff`

value should be discarded, rather than the sum of discarded weights.`use_relative_cutoff::Bool = true`

: set if the singular values should be normalized for the sake of truncation.

**Examples**

```
i, j, k, l = Index(2, "i"), Index(2, "j"), Index(2, "k"), Index(2, "l")
A = randomITensor(i, j, k, l)
Linds = (i, k)
Rinds = (j, l)
D, U = eigen(A, Linds, Rinds)
dl, dr = uniqueind(D, U), commonind(D, U)
Ul = replaceinds(U, (Rinds..., dr) => (Linds..., dl))
A * U ≈ Ul * D # true
```

`LinearAlgebra.factorize`

— Method`factorize(A::ITensor, Linds::Index...; <keyword arguments>)`

Perform a factorization of `A`

into ITensors `L`

and `R`

such that `A ≈ L * R`

.

**Arguments**

`ortho::String = "left"`

: Choose orthogonality properties of the factorization.`"left"`

: the left factor`L`

is an orthogonal basis such that`L * dag(prime(L, commonind(L,R))) ≈ I`

.`"right"`

: the right factor`R`

forms an orthogonal basis.`"none"`

, neither of the factors form an orthogonal basis, and in general are made as symmetrically as possible (depending on the decomposition used).

`which_decomp::Union{String, Nothing} = nothing`

: choose what kind of decomposition is used.`nothing`

: choose the decomposition automatically based on the other arguments. For example, when`nothing`

is chosen and`ortho = "left"`

or`"right"`

, and a cutoff is provided,`svd`

or`eigen`

is used depending on the provided cutoff (`eigen`

is only used when the cutoff is greater than`1e-12`

, since it has a lower precision). When no truncation is requested`qr`

is used for dense ITensors and`svd`

for block-sparse ITensors (in the future`qr`

will be used also for block-sparse ITensors in this case).`"svd"`

:`L = U`

and`R = S * V`

for`ortho = "left"`

,`L = U * S`

and`R = V`

for`ortho = "right"`

, and`L = U * sqrt.(S)`

and`R = sqrt.(S) * V`

for`ortho = "none"`

. To control which`svd`

algorithm is choose, use the`svd_alg`

keyword argument. See the documentation for`svd`

for the supported algorithms, which are the same as those accepted by the`alg`

keyword argument.`"eigen"`

:`L = U`

and $R = U^{\dagger} A$ where`U`

is determined from the eigendecompositon $A A^{\dagger} = U D U^{\dagger}$ for`ortho = "left"`

(and vice versa for`ortho = "right"`

).`"eigen"`

is not supported for`ortho = "none"`

.`"qr"`

:`L=Q`

and`R`

an upper-triangular matrix when`ortho = "left"`

, and`R = Q`

and`L`

a lower-triangular matrix when`ortho = "right"`

(currently supported for dense ITensors only).

In the future, other decompositions like QR (for block-sparse ITensors), polar, cholesky, LU, etc. are expected to be supported.

For truncation arguments, see: `svd`

## Memory operations

`ITensors.permute`

— Method`permute(T::ITensor, inds...; allow_alias = false)`

Return a new ITensor `T`

with indices permuted according to the input indices `inds`

. The storage of the ITensor is permuted accordingly.

If called with `allow_alias = true`

, it avoids copying data if possible. Therefore, it may return an alias of the input ITensor (an ITensor that shares the same data), such as if the permutation turns out to be trivial.

By default, `allow_alias = false`

, and it never returns an alias of the input ITensor.

**Examples**

```
i = Index(2, "index_i"); j = Index(4, "index_j"); k = Index(3, "index_k");
T = randomITensor(i, j, k)
pT_1 = permute(T, k, i, j)
pT_2 = permute(T, j, i, k)
pT_noalias_1 = permute(T, i, j, k)
pT_noalias_1[1, 1, 1] = 12
T[1, 1, 1] != pT_noalias_1[1, 1, 1]
pT_noalias_2 = permute(T, i, j, k; allow_alias = false)
pT_noalias_2[1, 1, 1] = 12
T[1, 1, 1] != pT_noalias_1[1, 1, 1]
pT_alias = permute(T, i, j, k; allow_alias = true)
pT_alias[1, 1, 1] = 12
T[1, 1, 1] == pT_alias[1, 1, 1]
```