diff --git a/.gitignore b/.gitignore index d2e3e08d..1fdef104 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ Manifest.toml .vscode docs/build/ .DS_Store +CLAUDE.md diff --git a/docs/src/finalizers.md b/docs/src/finalizers.md index a4fa12d3..7963a6c6 100644 --- a/docs/src/finalizers.md +++ b/docs/src/finalizers.md @@ -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 @@ -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. @@ -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) ``` \ No newline at end of file diff --git a/docs/src/index.md b/docs/src/index.md index 0cd019e5..6318c0ae 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -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) @@ -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): diff --git a/examples/example.jl b/examples/example.jl index 17c2bd11..3a705196 100644 --- a/examples/example.jl +++ b/examples/example.jl @@ -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) diff --git a/src/TNRKit.jl b/src/TNRKit.jl index 7bc3a089..78e77dc7 100644 --- a/src/TNRKit.jl +++ b/src/TNRKit.jl @@ -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") @@ -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 @@ -91,6 +96,7 @@ export LoopTNR, LoopParameters export SLoopTNR export run! +export step! # models include("models/ising.jl") diff --git a/src/schemes/renormalizer.jl b/src/schemes/renormalizer.jl new file mode 100644 index 00000000..b5b57131 --- /dev/null +++ b/src/schemes/renormalizer.jl @@ -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 diff --git a/src/schemes/tnrscheme.jl b/src/schemes/tnrscheme.jl index 00f5d365..ee37ac83 100644 --- a/src/schemes/tnrscheme.jl +++ b/src/schemes/tnrscheme.jl @@ -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} diff --git a/src/schemes/trg.jl b/src/schemes/trg.jl index af1c9f43..7268860b 100644 --- a/src/schemes/trg.jl +++ b/src/schemes/trg.jl @@ -17,7 +17,7 @@ The elementary modular parameter `τ₀ ↦ (τ₀ - 1) / (τ₀ + 1)`. - 0: No output - 1: Print information at start and end of the algorithm - 2: Print information at each step - + # Fields $(TYPEDFIELDS) @@ -34,16 +34,106 @@ mutable struct TRG{E, S, TT <: AbstractTensorMap{E, S, 2, 2}} <: TNRScheme{E, S} end end -function step!(scheme::TRG, trunc::TruncationStrategy) - A, B = SVD12(scheme.T, trunc) - Tp = transpose(scheme.T, ((2, 4), (1, 3))) +""" +$(TYPEDEF) + +Parameters for the Tensor Renormalization Group algorithm. + +# Constructors + +All parameters are passed as keyword arguments with sensible defaults: + + $(FUNCTIONNAME)(; trunc=truncrank(16), maxiter=20) + +# Fields + +$(TYPEDFIELDS) + +# References +* [Levin & Nave Phys. Rev. Letters 99(12) (2007)](@cite levin2007) +""" +Base.@kwdef struct TRGParams <: TNRParams + "Truncation strategy for SVD steps" + trunc::TruncationStrategy = truncrank(16) + "Maximum number of RG coarse-graining steps" + maxiter::Int = 20 +end + +# ============================================================================== +# Renormalizer interface (new iterable API) +# ============================================================================== + +function Renormalizer(params::TRGParams, T::TT) where {TT} + scheme = TRG(T) + n = finalize!(scheme) + return Renormalizer{TRGParams, typeof(scheme)}(params, scheme, [n], 0) +end + +function Renormalizer(params::TRGParams, scheme::TRG) + n = finalize!(scheme) + return Renormalizer{TRGParams, typeof(scheme)}(params, scheme, [n], 0) +end + +function get_tensor(r::Renormalizer{<:TRGParams}) + return r.state.T +end + +function _renorm_step!(r::Renormalizer{<:TRGParams}) + T = r.state.T + trunc = r.alg.trunc + + # TRG coarse-graining (Levin & Nave) + A, B = SVD12(T, trunc) + Tp = transpose(T, ((2, 4), (1, 3))) C, D = SVD12(Tp, trunc) - @plansor scheme.T[-1 -2; -3 -4] := D[-2; 1 2] * B[-1; 4 1] * C[4 3; -3] * A[3 2; -4] - return scheme + @plansor T_new[-1 -2; -3 -4] := D[-2; 1 2] * B[-1; 4 1] * C[4 3; -3] * A[3 2; -4] + r.state.T = T_new + + # Trace-based normalization + n = finalize!(r.state) + push!(r.norms, n) + + return r end +# ============================================================================== +# Legacy run! — delegates to the new iterable interface +# ============================================================================== + +function run!( + scheme::TRG, + trscheme::TruncationStrategy, + criterion::stopcrit; + verbosity = 1, + ) + maxit = criterion isa maxiter ? criterion.n : + criterion isa MultipleCrit ? (c for c in criterion.crits if c isa maxiter) |> first |> (c -> c.n) : 100 + params = TRGParams(; trunc = trscheme, maxiter = maxit) + renorm = Renormalizer(params, scheme) + + LoggingExtras.withlevel(; verbosity) do + @infov 1 "Starting simulation\n $(scheme)\n" + for _ in 1:(renorm.alg.maxiter) + step!(renorm) + end + @infov 1 "Simulation finished after $(renorm.step) RG steps\n" + end + return renorm.norms +end + +# ============================================================================== +# Base.show +# ============================================================================== + function Base.show(io::IO, scheme::TRG) println(io, "TRG - Tensor Renormalization Group") println(io, " * T: $(summary(scheme.T))") return nothing end + +function Base.show(io::IO, params::TRGParams) + println(io, "TRGParams") + println(io, " * truncation: $(params.trunc)") + println(io, " * maxiter: $(params.maxiter)") + return nothing +end diff --git a/src/utility/cft.jl b/src/utility/cft.jl index 2cc09fef..93559ccf 100644 --- a/src/utility/cft.jl +++ b/src/utility/cft.jl @@ -67,22 +67,29 @@ function CFTData( end end +""" + _row_transfer_matrix(T::AbstractTensorMap, unitcell::Int) + +Build a row transfer matrix from `unitcell` copies of the tensor `T` +concatenated horizontally with periodic boundary conditions. +""" +function _row_transfer_matrix(T::AbstractTensorMap, unitcell::Int) + indices = [[i, -i, -(i + unitcell), i + 1] for i in 1:unitcell] + indices[end][4] = 1 + Tcontracted = ncon(fill(T, unitcell), indices) + outinds = ntuple(i -> i, unitcell) + ininds = ntuple(i -> unitcell + i, unitcell) + return permute(Tcontracted, (outinds, ininds)) +end + """ Construct the transfer matrix along vertical direction with `unitcell` copies of `T` concatenated horizontally. `τ0` is the modular parameter of a single `T`. """ function _scaling_dimensions(T::TensorMap{E, S, 2, 2}, τ0::Number; unitcell = 1) where {E, S} - indices = [[i, -i, -(i + unitcell), i + 1] for i in 1:unitcell] - indices[end][4] = 1 - - T = ncon(fill(T, unitcell), indices) - # restore leg convention - outinds = Tuple(collect(1:unitcell)) - ininds = Tuple(collect((unitcell + 1):(2unitcell))) - T = permute(T, (outinds, ininds)) - - sv = StructuredVector(eig_vals(T)) + tm = _row_transfer_matrix(T, unitcell) + sv = StructuredVector(eig_vals(tm)) sv = filter(x -> real(x) > 0 && abs(x) > 1.0e-12, sv) isempty(sv) && throw(ArgumentError("No valid eigenvalues found in transfer matrix spectrum.")) diff --git a/src/utility/gs_degeneracy.jl b/src/utility/gs_degeneracy.jl index cffd000f..ed3cc180 100644 --- a/src/utility/gs_degeneracy.jl +++ b/src/utility/gs_degeneracy.jl @@ -1,150 +1,107 @@ -""" - $(SIGNATURES) +function _ground_state_degeneracy(tm::AbstractTensorMap{E, S, N, N}) where {E, S, N} + D, _ = eig_full(tm) + D = D / tr(D) + evs = filter(!iszero, abs.(D.data)) + entropy = -sum(evs .* log.(evs)) + return exp(entropy) +end -Calculates the Ground State Degeneracy (GSD) from the fixed-point tensor of a TNRScheme, -using the eigenvalues of the transfer matrix. The GSD is the exponential of the Shannon entropy. """ -function ground_state_degeneracy(scheme::TNRScheme{E}, unitcell::Int = 1) where {E} - # Construct contraction indices - indices = Vector{NTuple{4, Int}}(undef, unitcell) - for i in 1:unitcell - indices[i] = (i, -i, -(i + unitcell), i + 1) - end - indices[end] = (unitcell, -unitcell, -(unitcell + unitcell), 1) - - # Contract tensors - Ts = fill(scheme.T, unitcell) - T = ncon(Ts, indices) - - # Construct static tuple indices - outinds = ntuple(i -> i, unitcell) - ininds = ntuple(i -> unitcell + i, unitcell) + ground_state_degeneracy(T::AbstractTensorMap, unitcell=1) - T = permute(T, (outinds, ininds)) - - # Compute normalized eigenvalues - D, _ = eig_full(T) - D = D / tr(D) - vals = filter(!iszero, abs.(D.data)) - # Shannon entropy (stable + efficient) - S = 0.0 - for v in vals - ev = abs(v) - if ev > 0 - S -= ev * log(ev) - end - end +Compute the Ground State Degeneracy (GSD) from a single network tensor, +using the eigenvalues of the transfer matrix. The GSD is the exponential +of the Shannon entropy of the normalized eigenvalue spectrum. +""" +function ground_state_degeneracy(T::AbstractTensorMap, unitcell::Int = 1) + tm = _row_transfer_matrix(T, unitcell) + return _ground_state_degeneracy(tm) +end - return exp(S) +""" + ground_state_degeneracy(TA::AbstractTensorMap, TB::AbstractTensorMap) + +Compute the GSD for a checkerboard network (TA, TB) from the 2-column transfer matrix +``` + ┌-┐ ┌-┐ + 1'--A-------B---3' + | | | | + | | | | + | | | | + 2'--B-------A---4' + └-┘ └-┘ +``` +""" +function ground_state_degeneracy(TA::AbstractTensorMap, TB::AbstractTensorMap) + @tensor tm[-1 -2; -3 -4] := TA[-1 1; 3 2] * TB[2 6; 4 -3] * + TB[-2 3; 1 5] * TA[5 4; 6 -4] + return _ground_state_degeneracy(tm) end -function ground_state_degeneracy(scheme::BTRG{E}; unitcell::Int = 1) where {E} - indices = Vector{NTuple{4, Int}}(undef, unitcell) - for i in 1:unitcell - indices[i] = (i, -i, -(i + unitcell), i + 1) - end - indices[end] = (unitcell, -unitcell, -(unitcell + unitcell), 1) +ground_state_degeneracy(scheme::TNRScheme; unitcell::Int = 1) = ground_state_degeneracy(scheme.T, unitcell) + +function ground_state_degeneracy(scheme::BTRG; unitcell::Int = 1) @tensor T_unit[-1 -2; -3 -4] := scheme.T[1 2; -3 -4] * scheme.S1[-2; 2] * scheme.S2[-1; 1] - T = ncon(fill(T_unit, unitcell), indices) - - # Construct static tuple indices - outinds = ntuple(i -> i, unitcell) - ininds = ntuple(i -> unitcell + i, unitcell) - - T = permute(T, (outinds, ininds)) - D, _ = eig_full(T) - D = D / tr(D) - vals = filter(!iszero, abs.(D.data)) - # Shannon entropy (stable + efficient) - S = 0.0 - for v in vals - ev = abs(v) - if ev > 0 - S -= ev * log(ev) - end - end - - return exp(S) + return ground_state_degeneracy(T_unit, unitcell) end -function ground_state_degeneracy(scheme::LoopTNR{E}) where {E} - norm_const = area_term(scheme.TA, scheme.TB) - T1 = scheme.TA / abs(norm_const)^(1 / 4) - T2 = scheme.TB / abs(norm_const)^(1 / 4) - - @tensor T_unit[-1 -2; -3 -4] := T1[-1 1; 3 2] * T2[2 6; 4 -3] * - T2[-2 3; 1 5] * T1[5 4; 6 -4] - D, _ = eig_full(T_unit) - D = D / tr(D) - vals = filter(!iszero, abs.(D.data)) - # Shannon entropy (stable + efficient) - S = 0.0 - for v in vals - ev = abs(v) - if ev > 0 - S -= ev * log(ev) - end - end - - return exp(S) -end +ground_state_degeneracy(scheme::LoopTNR) = ground_state_degeneracy(scheme.TA, scheme.TB) """ -$(SIGNATURES) - -Calculates the Gu-Wen ratio X1 and X2 from the fixed-point tensor of a TNRScheme. -The Gu-Wen ratios are related to the Ground state Degeneracy and the the scaling dimensions. See references. + gu_wen_ratio(T::AbstractTensorMap{E, S, 2, 2}) where {E, S} + +Compute the Gu-Wen ratios (X1, X2) from a single network tensor. +The Gu-Wen ratios are related to the ground state degeneracy and +the scaling dimensions. # References * [Zheng-Cheng Gu & Xiao-Gang Wen. PhysRevB.80.155131](@cite gu2009) * [Satoshi Morita et al. arxiv:2512.03395](@cite morita2025) """ -function gu_wen_ratio(scheme::TNRScheme{E}) where {E} - T_unit = scheme.T - - one_norm = norm(@tensor T_unit[1 2; 2 1]) - two_norm_X1 = norm(@tensor T_unit[1 2; 2 3] * T_unit[3 4; 4 1]) - two_norm_X2 = norm(@tensor T_unit[1 2; 3 4] * T_unit[4 3; 2 1]) - +function gu_wen_ratio(T::AbstractTensorMap{E, S, 2, 2}) where {E, S} + one_norm = norm(@tensor T[1 2; 2 1]) + two_norm_X1 = norm(@tensor T[1 2; 2 3] * T[3 4; 4 1]) + two_norm_X2 = norm(@tensor T[1 2; 3 4] * T[4 3; 2 1]) X1 = (one_norm^2) / (two_norm_X1) X2 = (one_norm^2) / (two_norm_X2) return X1, X2 end -function gu_wen_ratio(scheme::BTRG{E}) where {E} - @tensor T_unit[-1 -2; -3 -4] := scheme.T[1 2; -3 -4] * scheme.S1[-2; 2] * - scheme.S2[-1; 1] - one_norm = norm(@tensor T_unit[1 2; 2 1]) - two_norm_X1 = norm(@tensor T_unit[1 2; 2 3] * T_unit[3 4; 4 1]) - two_norm_X2 = norm(@tensor T_unit[1 2; 3 4] * T_unit[4 3; 2 1]) +""" + gu_wen_ratio(TA::AbstractTensorMap{E, S, 2, 2}, TB::AbstractTensorMap{E, S, 2, 2}) where {E, S} - X1 = (one_norm^2) / (two_norm_X1) - X2 = (one_norm^2) / (two_norm_X2) - return X1, X2 -end -function gu_wen_ratio(scheme::LoopTNR{E}) where {E} - T1 = scheme.TA - T2 = scheme.TB +Compute the Gu-Wen ratios (X1, X2) for a checkerboard network (TA, TB). +""" +function gu_wen_ratio( + TA::AbstractTensorMap{E, S, 2, 2}, TB::AbstractTensorMap{E, S, 2, 2} + ) where {E, S} one_norm = norm( - @tensor opt = true T1[1 2; 3 4] * T2[4 5; 6 1] * - T2[7 3; 2 8] * T1[8 6; 5 7] + @tensor opt = true TA[1 2; 3 4] * TB[4 5; 6 1] * + TB[7 3; 2 8] * TA[8 6; 5 7] ) - two_norm_X1 = norm( - @tensor opt = true T1[1 2; 3 4] * T2[4 5; 6 7] * - T1[7 8; 9 10] * T2[10 11; 12 1] * - T2[13 3; 2 14] * T1[14 6; 5 15] * T2[15 9; 8 16] * T1[16 12; 11 13] + @tensor opt = true TA[1 2; 3 4] * TB[4 5; 6 7] * + TA[7 8; 9 10] * TB[10 11; 12 1] * + TB[13 3; 2 14] * TA[14 6; 5 15] * TB[15 9; 8 16] * TA[16 12; 11 13] ) - two_norm_X2 = norm( - @tensor opt = true T1[1 2; 3 4] * T2[4 5; 6 7] * - T1[7 8; 9 10] * T2[10 11; 12 1] * - T2[13 9; 2 14] * T1[14 12; 5 15] * - T2[15 3; 8 16] * T1[16 6; 11 13] + @tensor opt = true TA[1 2; 3 4] * TB[4 5; 6 7] * + TA[7 8; 9 10] * TB[10 11; 12 1] * + TB[13 9; 2 14] * TA[14 12; 5 15] * + TB[15 3; 8 16] * TA[16 6; 11 13] ) - X1 = (one_norm^2) / (two_norm_X1) X2 = (one_norm^2) / (two_norm_X2) return X1, X2 end + +gu_wen_ratio(scheme::TNRScheme) = gu_wen_ratio(scheme.T) + +function gu_wen_ratio(scheme::BTRG) + @tensor T_unit[-1 -2; -3 -4] := scheme.T[1 2; -3 -4] * scheme.S1[-2; 2] * + scheme.S2[-1; 1] + return gu_wen_ratio(T_unit) +end + +gu_wen_ratio(scheme::LoopTNR) = gu_wen_ratio(scheme.TA, scheme.TB) diff --git a/test/fermions/fermions.jl b/test/fermions/fermions.jl index f434c503..49463b7a 100644 --- a/test/fermions/fermions.jl +++ b/test/fermions/fermions.jl @@ -7,8 +7,8 @@ T = gross_neveu_start(0, 0, 0) # === TRG === @testset "TRG - Gross-Neveu Model" begin - scheme = TRG(T) - data = run!(scheme, truncrank(16), maxiter(25)) + renorm = Renormalizer(TRG(; trunc = truncrank(16), maxiter = 25), T) + _, data = run!(renorm; verbosity = 0) @test free_energy(data, 1.0) ≈ f_bench rtol = 1.0e-3 end @@ -56,7 +56,12 @@ end data_c4vCTM = run!(c4vCTM(T_flipped_C4v), truncrank(8), maxiter(10)) free_energy_c4vCTM = -data_c4vCTM / β - schemes = [TRG, BTRG, HOTRG, ATRG, LoopTNR] + # TRG uses the new iterable interface + renorm = Renormalizer(TRG(; trunc = truncrank(8), maxiter = 10), T_flipped_C4v) + _, data_TRG = run!(renorm; verbosity = 0) + @test free_energy_c4vCTM ≈ free_energy(data_TRG, β; scalefactor = 2.0) rtol = 1.0e-9 + + schemes = [BTRG, HOTRG, ATRG, LoopTNR] for scheme in schemes data = run!(scheme(T_flipped_C4v), truncrank(8), maxiter(10)) scalefactor = scheme ∈ [HOTRG, ATRG] ? 4.0 : 2.0 diff --git a/test/models/models.jl b/test/models/models.jl index 07ef8878..b00c9a7f 100644 --- a/test/models/models.jl +++ b/test/models/models.jl @@ -44,8 +44,8 @@ model_temp_answer_string_3d = [ for (model, temp, answer, description) in model_temp_answer_string_2d @testset "$(description)" begin - scheme = TRG(model) - data = run!(scheme, truncrank(16), maxiter(25)) + renorm = Renormalizer(TRG(; trunc = truncrank(16), maxiter = 25), model) + _, data = run!(renorm; verbosity = 0) @test free_energy(data, temp) ≈ answer rtol = 1.0e-3 end end diff --git a/test/schemes/schemes.jl b/test/schemes/schemes.jl index b428a0a6..c8a7a6b2 100644 --- a/test/schemes/schemes.jl +++ b/test/schemes/schemes.jl @@ -25,11 +25,6 @@ end """ Normalize the tensor, return the normalization factor and elementary modular parameter """ -function tau_finalize!(scheme::TRG) - n = finalize!(scheme) - τ0, c = extract_tau_and_c(scheme.T; fast = false) - return (n, τ0) -end function tau_finalize!(scheme::LoopTNR) n = finalize!(scheme) τ0, c = extract_tau_and_c(scheme.TA, scheme.TB; fast = false) @@ -41,20 +36,8 @@ end @info "Anisotropy: Jx = $(Jx_aniso), Jy = $(Jy_aniso)" @info "TRG anisotropic ising free energy" scheme = TRG(T_aniso) - elt = scalartype(T_aniso) - finalizer = Finalizer(tau_finalize!, Tuple{elt, complex(elt)}) - data = run!(scheme, truncrank(24), maxiter(25), finalizer) - - ns = map(Base.Fix2(getindex, 1), data) - @test free_energy(ns, βc_aniso) ≈ f_aniso_exact rtol = 2.0e-6 - - @info "TRG τ → (τ - 1) / (τ + 1)" - f_trg(τ) = (τ - 1) / (τ + 1) - τs = map(Base.Fix2(getindex, 2), data) - for n in 5:7 - @test τs[n + 1] ≈ f_trg(τs[n]) rtol = 5.0e-2 - @info "* verified for step $(n - 1) → $n" - end + data = run!(scheme, truncrank(24), maxiter(25)) + @test free_energy(data, βc_aniso) ≈ f_aniso_exact rtol = 2.0e-6 @info "TRG anisotropic ising CFT data — shape [1, 1, 0]" scheme = TRG(T_aniso) @@ -88,6 +71,33 @@ end @test X2 ≈ 2.0 rtol = 1.0e-2 end +@testset "TRG - Iterable Interface" begin + @info "TRG iterable: free energy, tau map, CFT in single run" + elt = complex(scalartype(T_aniso)) + params = TRGParams(; trunc = truncrank(24), maxiter = 25) + renorm = Renormalizer(params, T_aniso) + τs = elt[] + T_step10 = nothing + for (state, _) in renorm + τ0, _ = extract_tau_and_c(state.T; fast = false) + push!(τs, τ0) + (renorm.step == 10) && (T_step10 = state.T) + end + + @test free_energy(renorm.norms, βc_aniso) ≈ f_aniso_exact rtol = 2.0e-6 + + @info "TRG τ → (τ - 1) / (τ + 1)" + f_trg(τ) = (τ - 1) / (τ + 1) + for n in 5:7 + @test τs[n + 1] ≈ f_trg(τs[n]) rtol = 5.0e-2 + end + + cft = CFTData(T_step10; shape = [1, 1, 0]) + sd = sort(real(cft.scaling_dimensions)[2:end]; by = abs) + @test sd[1] ≈ ising_cft_exact[1] rtol = 2.0e-3 + @test sd[2] ≈ ising_cft_exact[2] rtol = 2.0e-2 +end + # BTRG @testset "BTRG - Ising Model" begin @info "BTRG ising free energy"