# Upgrade guide

## Upgrading from ITensors.jl 0.1 to 0.2

The main breaking changes in ITensor.jl v0.2 involve changes to the `ITensor`

, `IndexSet`

, and `IndexVal`

types. Most user code should be fine, but see below for more details.

In addition, we have moved development of NDTensors.jl into ITensors.jl to simplify the development process until NDTensors is more stable and can be a standalone package. Again, see below for more details.

For a more comprehensive list of changes, see the change log and the commit history on Github.

If you have issues upgrading, please reach out by raising an issue on Github or asking a question on the ITensor support forum.

Also make sure to run your code with `julia --depwarn=yes`

to see warnings about function names and interfaces that have been deprecated and will be removed in v0.3 of ITensors.jl (these are not listed here).

## Major design changes: changes to the `ITensor`

, `IndexSet`

, and `IndexVal`

types

### Changes to the ITensor type

#### Removal of tensor order type parameter

The tensor order type paramater has been removed from the `ITensor`

type, so you can no longer write `ITensor{3}`

to specify an order 3 ITensor (PR #591). Code that uses the ITensor order type parameter will now lead to the following error:

```
julia> i = Index(2)
(dim=2|id=588)
julia> ITensor{2}(i', i)
ERROR: TypeError: in Type{...} expression, expected UnionAll, got Type{ITensor}
Stacktrace:
[1] top-level scope
@ REPL[27]:1
```

Simply remove the type parameter:

```
julia> ITensor(i', i)
ITensor ord=2 (dim=2|id=913)' (dim=2|id=913)
ITensors.NDTensors.EmptyStorage{ITensors.NDTensors.EmptyNumber, ITensors.NDTensors.Dense{ITensors.NDTensors.EmptyNumber, Vector{ITensors.NDTensors.EmptyNumber}}}
```

Pro tip: from the command line, you can replace all examples like that with:

`find . -type f -iname "*.jl" -exec sed -i 's/ITensor{.*}/ITensor/g' "{}" +`

Of course, make sure to back up your code before running this!

Additionally, a common code pattern may be using the type parameter for dispatch:

```
using ITensors
function mynorm(A::ITensor{N}) where {N}
return norm(A)^N
end
function mynorm(A::ITensor{1})
return norm(A)
end
function mynorm(A::ITensor{2})
return norm(A)^2
end
```

Instead, you can use an if-statement:

```
function mynormN(A::ITensor)
return norm(A)^order(A)
end
function mynorm1(A::ITensor)
return norm(A)
end
function mynorm2(A::ITensor)
return norm(A)^2
end
function mynorm(A::ITensor)
return if order(A) == 1
mynorm1(A)
elseif order(A) == 2
mynorm2(A)
else
return mynormN(A)
end
end
```

Alternatively, you can use the `Order`

type to dispatch on the ITensor order as follows:

```
function mynorm(::Order{N}, A::ITensor) where {N}
return norm(A)^N
end
function mynorm(::Order{1}, A::ITensor)
return norm(A)
end
function mynorm(::Order{2}, A::ITensor)
return norm(A)^2
end
function mynorm(A::ITensor)
return mynorm(Order(A), A)
end
```

`Order(A::ITensor)`

returns the order of the ITensor (like `order(A::ITensor)`

), however as a type that can be dispatched on. Note that it is not type stable, so there will be a small runtime overhead for doing this.

#### Change to storage type of Index collection in ITensor

ITensors now store a `Tuple`

of `Index`

instead of an `IndexSet`

(PR #626). Therefore, calling `inds`

on an ITensor will now just return a `Tuple`

:

```
julia> i = Index(2)
(dim=2|id=770)
julia> j = Index(3)
(dim=3|id=272)
julia> A = randomITensor(i, j)
ITensor ord=2 (dim=2|id=770) (dim=3|id=272)
ITensors.NDTensors.Dense{Float64, Vector{Float64}}
julia> inds(A)
((dim=2|id=770), (dim=3|id=272))
```

while before it returned an `IndexSet`

(in fact, the `IndexSet`

type has been removed, see below for details). In general, this should not affect user code, since a `Tuple`

of `Index`

should have all of the same functions defined for it that `IndexSet`

did. If you find this is not the case, please raise an issue on Github or on the ITensor support forum.

#### ITensor type now directly wraps a Tensor

The ITensor type no longer has separate field `inds`

and `store`

, just a single field `tensor`

(PR #626). In general you should not be accessing the fields directly, instead you should be using the functions `inds(A::ITensor)`

and `storage(A::ITensor)`

, so this should not affect most code. However, in case you have code like:

```
i = Index(2)
A = randomITensor(i)
A.inds
```

this will error in v0.2 with:

```
julia> A.inds
ERROR: type ITensor has no field inds
Stacktrace:
[1] getproperty(x::ITensor, f::Symbol)
@ Base ./Base.jl:33
[2] top-level scope
@ REPL[43]:1
```

and you should change it to:

`inds(A)`

### Changes to the ITensor constructors

#### Plain ITensor constructors now return ITensors with `EmptyStorage`

storage

`ITensor`

constructors from collections of `Index`

, such as `ITensor(i, j, k)`

, now return an `ITensor`

with `EmptyStorage`

(previously called `Empty`

) storage instead of `Dense`

or `BlockSparse`

storage filled with 0 values. Most operations should still work that worked previously, but please contact us if there are issues (PR #641).

For example:

```
julia> i = Index(2)
(dim=2|id=346)
julia> A = ITensor(i', dag(i))
ITensor ord=2 (dim=2|id=346)' (dim=2|id=346)
ITensors.NDTensors.EmptyStorage{ITensors.NDTensors.EmptyNumber, ITensors.NDTensors.Dense{ITensors.NDTensors.EmptyNumber, Vector{ITensors.NDTensors.EmptyNumber}}}
julia> A' * A
ITensor ord=2 (dim=2|id=346)'' (dim=2|id=346)
ITensors.NDTensors.EmptyStorage{ITensors.NDTensors.EmptyNumber, ITensors.NDTensors.Dense{ITensors.NDTensors.EmptyNumber, Vector{ITensors.NDTensors.EmptyNumber}}}
```

so now contracting two `EmptyStorage`

ITensors returns another `EmptyStorage`

ITensor. You can allocate the storage by setting elements of the ITensor:

```
julia> A[i' => 1, i => 1] = 0.0
0.0
julia> @show A;
A = ITensor ord=2
Dim 1: (dim=2|id=346)'
Dim 2: (dim=2|id=346)
ITensors.NDTensors.Dense{Float64, Vector{Float64}}
2×2
0.0 0.0
0.0 0.0
```

Additionally, it will take on the element type of the first value set:

```
julia> A = ITensor(i', dag(i))
ITensor ord=2 (dim=2|id=346)' (dim=2|id=346)
ITensors.NDTensors.EmptyStorage{ITensors.NDTensors.EmptyNumber, ITensors.NDTensors.Dense{ITensors.NDTensors.EmptyNumber, Vector{ITensors.NDTensors.EmptyNumber}}}
julia> A[i' => 1, i => 1] = 1.0 + 0.0im
1.0 + 0.0im
julia> @show A;
A = ITensor ord=2
Dim 1: (dim=2|id=346)'
Dim 2: (dim=2|id=346)
ITensors.NDTensors.Dense{ComplexF64, Vector{ComplexF64}}
2×2
1.0 + 0.0im 0.0 + 0.0im
0.0 + 0.0im 0.0 + 0.0im
```

If you have issues upgrading, please let us know.

#### Slight change to automatic conversion of element type when constructing ITensor from Array

`ITensor`

constructors from `Array`

now only convert to floating point for `Array{Int}`

and `Array{Complex{Int}}`

. That same conversion is added for QN ITensor constructors to be consistent with non-QN versions (PR #620). Previously it tried to convert arrays of any element type to the closest floating point type with Julia's `float`

function. This should not affect most user code.

### Changes to the IndexSet type

The `IndexSet`

type has been removed in favor of Julia's `Tuple`

and `Vector`

types (PR #626). `ITensor`

s now contain a `Tuple`

of `Index`

, while set operations like `commoninds`

that used to return `IndexSet`

now return a `Vector`

of `Index`

:

```
julia> i = Index(2)
(dim=2|id=320)
julia> A = randomITensor(i', i)
ITensor ord=2 (dim=2|id=320)' (dim=2|id=320)
ITensors.NDTensors.Dense{Float64, Vector{Float64}}
julia> inds(A) # Previously returned IndexSet, now returns Tuple
((dim=2|id=320)', (dim=2|id=320))
julia> commoninds(A', A) # Previously returned IndexSet, now returns Vector
1-element Vector{Index{Int64}}:
(dim=2|id=320)'
```

To help with upgrading code, `IndexSet{IndexT}`

has been redefined as a type alias for `Vector{IndexT<:Index}`

(which is subject to change to some other collection of indices, and likely will be removed in ITensors v0.3). Therefore it no longer has a type parameter for the number of indices, similar to the change to the `ITensor`

type. If you were using the plain `IndexSet`

type, code should generally still work properly. However, if you were using the type parameters of `IndexSet`

, such as:

```
function myorder2(is::IndexSet{N}) where {N}
return N^2
end
```

then you will need to remove the type parameter and rewrite your code generically to accept `Tuple`

or `Vector`

, such as:

```
function myorder2(is)
return length(is)^2
end
```

In general you should be able to just remove usages of `IndexSet`

in your code, and can just use `Tuple`

or `Vector`

of `Index`

instead, such as change `is = IndexSet(i, j, k)`

to `is = (i, j, k)`

or `is = [i, j, k]`

. Priming, tagging, and set operations now work generically on those types. If you see issues with upgrading your code, please let us know.

### Changes to the IndexVal type

Similar to the removal of `IndexSet`

, we have also removed the `IndexVal`

type (PR #665). Now, all use cases of `IndexVal`

can be replaced by using Julia's `Pair`

type, for example instead of:

```
i = Index(2)
IndexVal(i, 2)
```

use:

```
i = Index(2)
i => 2
# Or:
Pair(i, 2)
```

Note that we have made `IndexVal{IndexT}`

an alias for `Pair{IndexT,Int}`

, so code using `IndexVal`

such as `IndexVal(i, 2)`

should generally still work. However, we encourage users to change from `IndexVal(i, 2)`

to `i => 2`

.

## NDTensors.jl package now being developed internally within ITensors.jl

The `NDTensors`

module has been moved into the `ITensors`

package, so `ITensors`

no longer depends on the standalone `NDTensors`

package. This should only effect users who were using both `NDTensors`

and `ITensors`

seperately. If you want to use the latest `NDTensors`

library, you should do `using ITensors.NDTensors`

instead of `using NDTensors`

, and will need to install `ITensors`

with `using Pkg; Pkg.add("ITensors")`

in order to use the latest versions of `NDTensors`

. Note the current `NDTensors.jl`

package will still exist, but for now developmentof `NDTensors`

will occur within `ITensors.jl`

(PR #650).

## Miscellaneous breaking changes

`state`

function renamed `val`

, `state`

given a new more general definition

Rename the `state`

functions currently defined for various site types to `val`

for mapping a string name for an index to an index value (used in ITensor indexing and MPS construction). `state`

functions now return single-index ITensors representing various single-site states (PR #664). So now to get an Index value from a string, you use:

```
N = 10
s = siteinds("S=1/2", N)
val(s[1], "Up") == 1
val(s[1], "Dn") == 2
```

`state`

now returns an ITensor corresponding to the state with that value as the only nonzero element:

```
julia> @show state(s[1], "Up");
state(s[1], "Up") = ITensor ord=1
Dim 1: (dim=2|id=597|"S=1/2,Site,n=1")
ITensors.NDTensors.Dense{Float64, Vector{Float64}}
2-element
1.0
0.0
julia> @show state(s[1], "Dn");
state(s[1], "Dn") = ITensor ord=1
Dim 1: (dim=2|id=597|"S=1/2,Site,n=1")
ITensors.NDTensors.Dense{Float64, Vector{Float64}}
2-element
0.0
1.0
```

which allows for more general states to be defined, such as:

```
julia> @show state(s[1], "X+");
state(s[1], "X+") = ITensor ord=1
Dim 1: (dim=2|id=597|"S=1/2,Site,n=1")
ITensors.NDTensors.Dense{Float64, Vector{Float64}}
2-element
0.7071067811865475
0.7071067811865475
julia> @show state(s[1], "X-");
state(s[1], "X-") = ITensor ord=1
Dim 1: (dim=2|id=597|"S=1/2,Site,n=1")
ITensors.NDTensors.Dense{Float64, Vector{Float64}}
2-element
0.7071067811865475
-0.7071067811865475
```

which will be used for making more general MPS product states.

This should not affect end users in general, besides ones who had customized the previous `state`

function, such as with overloads like:

```
ITensors.state(::SiteType"My_S=1/2", ::StateName"Up") = 1
ITensors.state(::SiteType"My_S=1/2", ::StateName"Dn") = 2
```

which should be changed now to:

```
ITensors.val(::SiteType"My_S=1/2", ::StateName"Up") = 1
ITensors.val(::SiteType"My_S=1/2", ::StateName"Dn") = 2
```

`"Qubit"`

site type QN convention change

The QN convention of the `"Qubit"`

site type is changed to track the total number of 1 bits instead of the net number of 1 bits vs 0 bits (i.e. change the QN from +1/-1 to 0/1) (PR #676).

```
julia> s = siteinds("Qubit", 4; conserve_number=true)
4-element Vector{Index{Vector{Pair{QN, Int64}}}}:
(dim=2|id=925|"Qubit,Site,n=1") <Out>
1: QN("Number",0) => 1
2: QN("Number",1) => 1
(dim=2|id=799|"Qubit,Site,n=2") <Out>
1: QN("Number",0) => 1
2: QN("Number",1) => 1
(dim=2|id=8|"Qubit,Site,n=3") <Out>
1: QN("Number",0) => 1
2: QN("Number",1) => 1
(dim=2|id=385|"Qubit,Site,n=4") <Out>
1: QN("Number",0) => 1
2: QN("Number",1) => 1
```

Before it was +1/-1 like `"S=1/2"`

:

```
julia> s = siteinds("S=1/2", 4; conserve_sz=true)
4-element Vector{Index{Vector{Pair{QN, Int64}}}}:
(dim=2|id=364|"S=1/2,Site,n=1") <Out>
1: QN("Sz",1) => 1
2: QN("Sz",-1) => 1
(dim=2|id=823|"S=1/2,Site,n=2") <Out>
1: QN("Sz",1) => 1
2: QN("Sz",-1) => 1
(dim=2|id=295|"S=1/2,Site,n=3") <Out>
1: QN("Sz",1) => 1
2: QN("Sz",-1) => 1
(dim=2|id=810|"S=1/2,Site,n=4") <Out>
1: QN("Sz",1) => 1
2: QN("Sz",-1) => 1
```

This shouldn't affect end users in general. The new convention is a bit more intuitive since the quantum number can be thought of as counting the total number of 1 bits in the state, though the conventions can be mapped to each other with a constant.

`maxlinkdim`

for MPS/MPO with no indices

`maxlinkdim(::MPS/MPO)`

returns a minimum of `1`

(previously it returned 0 for MPS/MPO without and link indices) (PR #663).