Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ Manifest.toml
.vscode
docs/build/
.DS_Store
CLAUDE.md
46 changes: 30 additions & 16 deletions docs/src/finalizers.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,30 @@ By default this finalization process is as follow:
We calculate the "norm" of the scheme's tensor(s) by taking the trace over the lattice directions.
To keep the numbers in the tensor(s) from diverging, we divide the tensor(s) by this norm.

For TRG this is for example:
```Julia
n = norm(@tensor T[1 2; 2 1])
T /= n
```

At the end of a simulation, the `run!` function returns a vector of these norms. You can take this data to calculate the free energy through the `free_energy(data, β)` function for example.

This finalization is handled through what we call [`Finalizer`](@ref)s.

These [`Finalizer`](@ref)s are a way for the user to calculate all sorts of things throughout a TNR calculation.

!!! note "TRG and the new iterable interface"
[`TRG`](@ref) has been refactored with a new iterable interface via [`Renormalizer`](@ref). Instead of using a `Finalizer`, you can inspect intermediate states directly by iterating:
```julia
renorm = Renormalizer(TRG(; trunc = truncrank(16), maxiter = 25), T)
# Inspect intermediate states during iteration:
for (state, norms) in renorm
# capture intermediate tensors, compute observables, etc.
τ0, c = extract_tau_and_c(state.T; fast = false)
end
# After iteration, access results directly:
T = get_tensor(renorm) # final tensor
f = free_energy(renorm.norms, β) # norms are stored in the renormalizer
```
The `Finalizer` pattern is still used by all other schemes ([`BTRG`](@ref), [`ATRG`](@ref), [`HOTRG`](@ref), [`LoopTNR`](@ref), etc.).

A custom instance of `Finalizer` can be created as:
```Julia
function my_finalization(scheme::TRG)
function my_finalization(scheme::HOTRG)
n = finalize!(scheme) # normalizes the tensor and return said norm
data = calculate_something(scheme)
return n, data # Two Float64s
Expand All @@ -44,11 +53,11 @@ We use this type parameter `E` to correctly allocate a `Vector{E}` in which all
The default [`Finalizer`](@ref) is `default_Finalizer` which normalizes the tensor(s) and stores the norm.
For the impurity methods ([`ImpurityTRG`](@ref) and [`ImpurityHOTRG`](@ref)) the defaults are `ImpurityTRG_Finalizer` and `ImpurityHOTRG_Finalizer` respectively, as these methods usually require us to store more than just one norm per iteration.

[`TRG`](@ref), [`ATRG`](@ref), [`HOTRG`](@ref) and [`BTRG`](@ref) can be normalized by calculating the norm of a 2x2 patch of tensors, which is more computationally expensive but should™ be more stable.
[`ATRG`](@ref), [`HOTRG`](@ref) and [`BTRG`](@ref) can be normalized by calculating the norm of a 2x2 patch of tensors, which is more computationally expensive but should™ be more stable.

TNRKit exports the following pre-built `Finalizer` instances:

- **`two_by_two_Finalizer`** - Normalizes using a 2×2 patch of tensors (more stable but computationally more expensive). Works with [`TRG`](@ref), [`ATRG`](@ref), [`HOTRG`](@ref), and [`BTRG`](@ref).
- **`two_by_two_Finalizer`** - Normalizes using a 2×2 patch of tensors (more stable but computationally more expensive). Works with [`ATRG`](@ref), [`HOTRG`](@ref), and [`BTRG`](@ref).

- **`GSDegeneracy_Finalizer`** - Computes the ground state degeneracy at each TNR step. Returns a `Float64` at each iteration.

Expand All @@ -59,18 +68,23 @@ TNRKit exports the following pre-built `Finalizer` instances:
```julia
using TNRKit

# Default finalization (simple norm)
# Default finalization (simple norm) — using BTRG
T = classical_ising(ising_βc)
scheme = TRG(T)
scheme = BTRG(T)
data = run!(scheme, truncrank(16), maxiter(25))

# Use the two-by-two normalizer (more stable)
T = classical_ising(ising_βc)
scheme = TRG(T)
data = run!(scheme, truncrank(16), maxiter(25); finalizer=two_by_two_Finalizer)
# TRG with the new iterable interface (no Finalizer needed)
renorm = Renormalizer(TRG(; trunc = truncrank(16), maxiter = 25), T)
# Iterate to inspect intermediate states:
for (state, norms) in renorm
τ0, c = extract_tau_and_c(state.T; fast = true)
# ...
end
T_final = get_tensor(renorm)
f = free_energy(renorm.norms, ising_βc)

# Track ground state degeneracy throughout the simulation
T = classical_ising(ising_βc)
scheme = TRG(T)
scheme = BTRG(T)
gsd_data = run!(scheme, truncrank(16), maxiter(25); finalizer=GSDegeneracy_Finalizer)
```
18 changes: 15 additions & 3 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ You can use TNRKit for calculating:
Many common TNR schemes have already been implemented:

**2D square tensor networks**
* [`TRG`](@ref) (Levin and Nave's Tensor Renormalization Group)
* [`TRG`](@ref) (Levin and Nave's Tensor Renormalization Group) — now with an iterable [`Renormalizer`](@ref) interface
* [`BTRG`](@ref) (bond-weighted TRG)
* [`LoopTNR`](@ref) (entanglement filtering + loop optimization)
* [`SLoopTNR`](@ref) (c4 & inversion symmetric LoopTNR)
Expand Down Expand Up @@ -58,11 +58,23 @@ T = classical_ising(ising_βc) # partition function of classical Ising model at
scheme = BTRG(T) # Bond-weighted TRG (excellent choice)
data = run!(scheme, truncrank(16), maxiter(25)) # max bond-dimension of 16, for 25 iterations
```
`data` now contains 26 norms of the tensor, 1 for every time the tensor was normalized. (By default there is a normalization step before the first coarse-graining step wich can be turned off by changing the kwarg `run!(...; finalize_beginning=false)`)

[`TRG`](@ref) has been refactored with a new iterable interface. Create a pure algorithm config and wrap it in a [`Renormalizer`](@ref):
```julia
alg = TRG(; trunc = truncrank(16), maxiter = 25) # algorithm config (kwargs with defaults)
renorm = Renormalizer(alg, T) # iterable RG state machine
# Or iterate manually to inspect intermediate states:
for (state, norms) in renorm
# state is a TRGState holding the current tensor
# norms is the accumulated normalization factors
end
T_final = get_tensor(renorm) # extract the final tensor
```
`norms` (or `renorm.norms`) now contains 26 normalization factors, 1 for every time the tensor was normalized. (The tensor is normalized once at the start, and once after each RG step.)

Using these norms you could, for example, calculate the free energy of the critical classical Ising model:
```Julia
f = free_energy(data, ising_βc) # -2.1096504926141826902647832
f = free_energy(renorm.norms, ising_βc) # -2.1096504926141826902647832
```
You could even compare to the exact value, as calculated by the [Onsager solution](https://en.wikipedia.org/wiki/Ising_model#:~:text=Onsager%27s%20exact%20solution):

Expand Down
41 changes: 24 additions & 17 deletions examples/example.jl
Original file line number Diff line number Diff line change
@@ -1,29 +1,36 @@
using Revise, TensorKit, TNRKit

# criterion to determine convergence
trg_f(steps::Int, data) = abs(log(data[end]) * 2.0^(-steps))

# stop when converged or after 50 steps, whichever comes first
stopping_criterion = convcrit(1.0e-16, trg_f) & maxiter(20)

# choose a TensorKit truncation scheme
trunc = truncrank(16) & trunctol(atol = 1.0e-40)

# initialize the TRG scheme
scheme = TRG(classical_ising(1.0))
# ---- TRG with the new iterable interface ----

# run the TRG scheme (and normalize and store the norm in the beginning (finalize_beginning=true))
data = run!(scheme, trunc, stopping_criterion; finalize_beginning = true)
# or: data = run!(scheme, truncrank(16)), this will default to maxiter(100)
# create a pure algorithm config (kwargs with sensible defaults)
alg = TRG(; trunc = trunc, maxiter = 25)
renorm = Renormalizer(alg, classical_ising(1.0))

# initialize the BTRG scheme
scheme = BTRG(classical_ising(1.0), -0.5)
# iterate manually to inspect intermediate states
for (state, norms) in renorm
τ0, _ = extract_tau_and_c(state.T; fast = true)
# compute observables at each step...
end

# extract results after iteration
T_final = get_tensor(renorm)
f = free_energy(renorm.norms, 1.0)

# ---- BTRG ----

# run the BTRG scheme
# criterion to determine convergence
trg_f(steps::Int, data) = abs(log(data[end]) * 2.0^(-steps))
stopping_criterion = convcrit(1.0e-16, trg_f) & maxiter(20)

# initialize and run the BTRG scheme
scheme = BTRG(classical_ising(1.0), -0.5)
data = run!(scheme, trunc, stopping_criterion)

# initialize the HOTRG scheme
scheme = HOTRG(classical_ising(1.0))
# ---- HOTRG ----

# run the HOTRG scheme
# initialize and run the HOTRG scheme
scheme = HOTRG(classical_ising(1.0))
data = run!(scheme, trunc, stopping_criterion)
6 changes: 6 additions & 0 deletions src/TNRKit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export trivial_convcrit

# schemes
include("schemes/tnrscheme.jl")
include("schemes/renormalizer.jl")
include("schemes/trg.jl")
include("schemes/btrg.jl")
include("schemes/hotrg.jl")
Expand Down Expand Up @@ -61,6 +62,10 @@ include("schemes/symmetric_looptnr.jl")
export classical_ising_inv # Ising model with all legs in codomain

export TNRScheme
export TNRParams
export TRGParams
export Renormalizer
export get_tensor

export TRG
export BTRG
Expand Down Expand Up @@ -91,6 +96,7 @@ export LoopTNR, LoopParameters
export SLoopTNR

export run!
export step!

# models
include("models/ising.jl")
Expand Down
89 changes: 89 additions & 0 deletions src/schemes/renormalizer.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"""
$(TYPEDEF)

Iterable state machine that performs RG coarse-graining steps on network tensors.

# Fields
$(TYPEDFIELDS)

# Iterator
Each iteration yields `(state, norms)`.
- First yield: normalized initial state before any RG step.
- Subsequent yields: state after each RG step + normalization.
- Stops after `alg.maxiter` RG steps have been performed.
"""
mutable struct Renormalizer{A <: TNRParams, S}
"Algorithm configuration (truncation, maxiter, etc.)"
alg::A
"Algorithm-specific state holding all network tensors"
state::S
"Accumulated normalization factors at each step"
norms::Vector{Float64}
"Number of RG steps performed so far"
step::Int
end

"""
get_tensor(r::Renormalizer)

Return the tensor(s) stored in the renormalizer's current state.
"""
function get_tensor end

"""
_renorm_step!(r::Renormalizer)

Perform one RG step for the specific algorithm.
Dispatches on the algorithm type stored in `r.alg`.
Each algorithm must define its own method.
"""
function _renorm_step! end

function Base.iterate(r::Renormalizer)
return ((r.state, r.norms), 0)
end

function Base.iterate(r::Renormalizer, state::Int)
state >= r.alg.maxiter && return nothing
_renorm_step!(r)
r.step = state + 1
return ((r.state, r.norms), state + 1)
end

function Base.show(io::IO, r::Renormalizer)
println(io, "Renormalizer")
println(io, " * algorithm: $(nameof(typeof(r.alg)))")
println(io, " * state: $(nameof(typeof(r.state)))")
println(io, " * step: $(r.step) / $(r.alg.maxiter)")
println(io, " * norms: $(length(r.norms)) entries")
return nothing
end

"""
step!(r::Renormalizer)

Perform one RG coarse-graining step. Wraps `Base.iterate`.
Throws an error if `maxiter` has already been reached.
"""
function step!(r::Renormalizer)
r.step >= r.alg.maxiter && error("maxiter ($(r.alg.maxiter)) reached")
iterate(r, r.step)
return r
end

"""
run!(renorm::Renormalizer; verbosity=1)

Run the RG flow to completion. Returns `(final_state, norms)`.
"""
function run!(renorm::Renormalizer; verbosity = 1)
algname = nameof(typeof(renorm.alg))
LoggingExtras.withlevel(; verbosity) do
@infov 1 "Starting $algname simulation\n"
for (state, norms) in renorm
@infov 2 "norm: $(norms[end])"
end
@infov 1 "Simulation finished after $(renorm.step) RG steps\n"
end
return renorm.state, renorm.norms
end
9 changes: 9 additions & 0 deletions src/schemes/tnrscheme.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ const ImpurityTRG_Finalizer = Finalizer(finalize!, Tuple{Float64, Float64})
const ImpurityHOTRG_Finalizer = Finalizer(finalize!, Tuple{Float64, Float64, Float64, Float64})

# Finalization functions for the various TNR schemes
"""
abstract type TNRParams

Abstract type for pure TNR algorithm descriptors.
These store algorithm parameters (truncation, max iterations, etc.)
but NOT tensor data. Tensors are managed by [`Renormalizer`](@ref).
"""
abstract type TNRParams end

abstract type TNRScheme{E, S} end

function run!(scheme::TNRScheme, trscheme::TruncationStrategy, criterion::stopcrit, finalizer::Finalizer{E}; finalize_beginning = true, verbosity = 1) where {E}
Expand Down
Loading
Loading