From 3d9797e3c9626edc6e5f44e4b4e228e3e4519fdf Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 18 Jun 2026 14:08:31 +0800 Subject: [PATCH 1/7] Refactor TRG to iterable Renormalizer interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TRG is now a pure algorithm struct (@kwdef, kwargs-only) storing trunc and maxiter - New Renormalizer type holds algorithm state (TRGState) and is iterable via Base.iterate - _renorm_step! dispatches on algorithm type, making the interface extensible - step! wraps Base.iterate; run! returns (final_state, norms) - TRGState holds the network tensor; get_tensor extracts it - Finalizer no longer needed for TRG — users inspect intermediate states via iteration - gs_degeneracy.jl refactored: primary methods take tensors directly, scheme wrappers delegate - simple_scheme union updated (TRG removed) - All tests, examples, and docs updated Co-Authored-By: Claude Opus 4.8 --- docs/src/finalizers.md | 46 +++++++---- docs/src/index.md | 18 +++- examples/example.jl | 41 +++++---- src/TNRKit.jl | 5 ++ src/schemes/renormalizer.jl | 90 ++++++++++++++++++++ src/schemes/tnrscheme.jl | 9 ++ src/schemes/trg.jl | 90 ++++++++++++++------ src/utility/finalize.jl | 2 +- src/utility/gs_degeneracy.jl | 156 ++++++++++++++--------------------- test/fermions/fermions.jl | 11 ++- test/models/models.jl | 4 +- test/schemes/schemes.jl | 44 +++++----- 12 files changed, 329 insertions(+), 187 deletions(-) create mode 100644 src/schemes/renormalizer.jl 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..1fc26b8c 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,9 @@ include("schemes/symmetric_looptnr.jl") export classical_ising_inv # Ising model with all legs in codomain export TNRScheme +export TNRAlgorithm +export Renormalizer +export get_tensor export TRG export BTRG @@ -91,6 +95,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..59e1ce13 --- /dev/null +++ b/src/schemes/renormalizer.jl @@ -0,0 +1,90 @@ +""" +$(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 <: TNRAlgorithm, 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. +For a single-tensor scheme like [`TRG`](@ref), returns the central tensor `T`. +""" +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`](@ref). +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..39a815ed 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 TNRAlgorithm + +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 TNRAlgorithm 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..7417c5bc 100644 --- a/src/schemes/trg.jl +++ b/src/schemes/trg.jl @@ -1,23 +1,26 @@ """ $(TYPEDEF) -Tensor Renormalization Group +Tensor Renormalization Group algorithm parameters. + +This is a pure algorithm descriptor — it stores truncation and iteration +parameters but no tensor data. Tensors are managed by [`Renormalizer`](@ref). # Constructors - $(FUNCTIONNAME)(T) -# Running the algorithm - run!(::TRG, trunc::TruncationStrategy, stop::Stopcrit[, finalizer=default_Finalizer, finalize_beginning=true, verbosity=1]) +All parameters are passed as keyword arguments with sensible defaults: + + $(FUNCTIONNAME)(; trunc=truncrank(16), maxiter=20) + +# Usage -Each step rescales the lattice by a (linear) factor of √2, -and rotate the lattice by 45 degrees in counter clockwise direction. -The elementary modular parameter `τ₀ ↦ (τ₀ - 1) / (τ₀ + 1)`. +```julia +alg = TRG() # uses defaults +alg = TRG(; trunc = truncrank(24), maxiter = 25) # full configuration +renorm = Renormalizer(alg, T) +T_final, norms = run!(renorm) +``` -!!! info "verbosity levels" - - 0: No output - - 1: Print information at start and end of the algorithm - - 2: Print information at each step - # Fields $(TYPEDFIELDS) @@ -25,25 +28,64 @@ $(TYPEDFIELDS) # References * [Levin & Nave Phys. Rev. Letters 99(12) (2007)](@cite levin2007) """ -mutable struct TRG{E, S, TT <: AbstractTensorMap{E, S, 2, 2}} <: TNRScheme{E, S} - "central tensor" +Base.@kwdef struct TRG <: TNRAlgorithm + "Truncation strategy for SVD steps" + trunc::TruncationStrategy = truncrank(16) + "Maximum number of RG coarse-graining steps" + maxiter::Int = 20 +end + +""" +$(TYPEDEF) + +Stores the tensor state for one step of a [`TRG`](@ref) renormalization. + +$(TYPEDFIELDS) +""" +mutable struct TRGState{E, S, TT <: AbstractTensorMap{E, S, 2, 2}} + "Central tensor" T::TT +end - function TRG(T::TT) where {E, S, TT <: AbstractTensorMap{E, S, 2, 2}} - return new{E, S, TT}(T) - end +function Renormalizer(alg::TRG, T::TT) where {E, S, TT <: AbstractTensorMap{E, S, 2, 2}} + n = norm(@tensor T[1 2; 2 1]) + T_norm = T / n + state = TRGState{E, S, TT}(T_norm) + return Renormalizer{TRG, TRGState{E, S, TT}}(alg, state, [n], 0) end -function step!(scheme::TRG, trunc::TruncationStrategy) - A, B = SVD12(scheme.T, trunc) - Tp = transpose(scheme.T, ((2, 4), (1, 3))) +""" + _renorm_step!(r::Renormalizer) + +Perform one TRG coarse-graining step followed by trace-based normalization. +Operates on `r` directly to ensure in-place mutation of `r.state.T`. +""" +function _renorm_step!(r::Renormalizer{<:TRG}) + T = r.state.T + trunc = r.alg.trunc + + # TRG coarse-graining + 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 (same logic as finalize! for TRG) + n = norm(@tensor r.state.T[1 2; 2 1]) + r.state.T /= n + push!(r.norms, n) + + return r +end + +function get_tensor(r::Renormalizer{<:TRG}) + return r.state.T end -function Base.show(io::IO, scheme::TRG) +function Base.show(io::IO, alg::TRG) println(io, "TRG - Tensor Renormalization Group") - println(io, " * T: $(summary(scheme.T))") + println(io, " * truncation: $(alg.trunc)") + println(io, " * maxiter: $(alg.maxiter)") return nothing end diff --git a/src/utility/finalize.jl b/src/utility/finalize.jl index ec271ed1..1fc6adc7 100644 --- a/src/utility/finalize.jl +++ b/src/utility/finalize.jl @@ -1,4 +1,4 @@ -const simple_scheme = Union{TRG, ATRG, HOTRG} +const simple_scheme = Union{ATRG, HOTRG} # 1x1 unitcell finalize function finalize!(scheme::simple_scheme) diff --git a/src/utility/gs_degeneracy.jl b/src/utility/gs_degeneracy.jl index cffd000f..f20f4991 100644 --- a/src/utility/gs_degeneracy.jl +++ b/src/utility/gs_degeneracy.jl @@ -1,32 +1,27 @@ """ - $(SIGNATURES) + ground_state_degeneracy(T::AbstractTensorMap, unitcell=1) -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. +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(scheme::TNRScheme{E}, unitcell::Int = 1) where {E} - # Construct contraction indices +function ground_state_degeneracy(T::AbstractTensorMap, unitcell::Int = 1) 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) + Ts = fill(T, unitcell) + Tcontracted = ncon(Ts, indices) - # Construct static tuple indices outinds = ntuple(i -> i, unitcell) ininds = ntuple(i -> unitcell + i, unitcell) + Tcontracted = permute(Tcontracted, (outinds, ininds)) - T = permute(T, (outinds, ininds)) - - # Compute normalized eigenvalues - D, _ = eig_full(T) + D, _ = eig_full(Tcontracted) D = D / tr(D) vals = filter(!iszero, abs.(D.data)) - # Shannon entropy (stable + efficient) S = 0.0 for v in vals ev = abs(v) @@ -34,117 +29,86 @@ function ground_state_degeneracy(scheme::TNRScheme{E}, unitcell::Int = 1) where S -= ev * log(ev) end end - return exp(S) 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) - - @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) -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) +""" + ground_state_degeneracy(TA::AbstractTensorMap, TB::AbstractTensorMap; unitcell=1) +Compute the GSD from a two-site unit cell (TA, TB). Builds an effective +single-site tensor and delegates to the single-tensor method. +""" +function ground_state_degeneracy(TA::AbstractTensorMap, TB::AbstractTensorMap; unitcell::Int = 1) + norm_const = area_term(TA, TB) + T1 = TA / abs(norm_const)^(1 / 4) + T2 = 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] + return ground_state_degeneracy(T_unit, unitcell) +end - 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 +ground_state_degeneracy(scheme::TNRScheme; unitcell::Int = 1) = ground_state_degeneracy(scheme.T, unitcell) - return exp(S) +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] + return ground_state_degeneracy(T_unit, unitcell) end +ground_state_degeneracy(scheme::LoopTNR; unitcell::Int = 2) = ground_state_degeneracy(scheme.TA, scheme.TB; unitcell) + """ -$(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) + +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) + 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, TB::AbstractTensorMap) - 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) from a two-site unit cell (TA, TB). +""" +function gu_wen_ratio(TA::AbstractTensorMap, TB::AbstractTensorMap) 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..baf92593 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) @@ -39,28 +34,27 @@ end # TRG @testset "TRG - Anisotropic Ising Model" begin @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 + elt = complex(scalartype(T_aniso)) + renorm = Renormalizer(TRG(; trunc = truncrank(24), maxiter = 25), 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) - τ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 @info "TRG anisotropic ising CFT data — shape [1, 1, 0]" - scheme = TRG(T_aniso) - run!(scheme, truncrank(24), maxiter(10)) # use fast tau algorithm below - cft = CFTData(scheme; shape = [1, 1, 0]) + cft = CFTData(T_step10; shape = [1, 1, 0]) sd_all = real(cft.scaling_dimensions) cft_sorted = sort(sd_all[2:end]; by = abs) @@ -70,19 +64,19 @@ end @info "TRG anisotropic ising ground state degeneracy" T1 = classical_ising(βc_aniso - 0.01; Jx = Jx_aniso, Jy = Jy_aniso) - scheme = TRG(T1) - run!(scheme, truncrank(16), maxiter(20)) - gsd = ground_state_degeneracy(scheme) - X1, X2 = gu_wen_ratio(scheme) + renorm3 = Renormalizer(TRG(; trunc = truncrank(16), maxiter = 20), T1) + run!(renorm3; verbosity = 0) + gsd = ground_state_degeneracy(get_tensor(renorm3)) + X1, X2 = gu_wen_ratio(get_tensor(renorm3)) @test gsd ≈ 1 rtol = 1.0e-2 @test X1 ≈ 1.0 rtol = 1.0e-2 @test X2 ≈ 1.0 rtol = 1.0e-2 T2 = classical_ising(βc_aniso + 0.01; Jx = Jx_aniso, Jy = Jy_aniso) - scheme = TRG(T2) - run!(scheme, truncrank(16), maxiter(20)) - gsd = ground_state_degeneracy(scheme) - X1, X2 = gu_wen_ratio(scheme) + renorm4 = Renormalizer(TRG(; trunc = truncrank(16), maxiter = 20), T2) + run!(renorm4; verbosity = 0) + gsd = ground_state_degeneracy(get_tensor(renorm4)) + X1, X2 = gu_wen_ratio(get_tensor(renorm4)) @test gsd ≈ 2 rtol = 1.0e-2 @test X1 ≈ 2.0 rtol = 1.0e-2 @test X2 ≈ 2.0 rtol = 1.0e-2 From 2aa9de6961dee4123d6146e601fe02f151624e6e Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 18 Jun 2026 14:29:00 +0800 Subject: [PATCH 2/7] Fix doc refs --- src/schemes/renormalizer.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/schemes/renormalizer.jl b/src/schemes/renormalizer.jl index 59e1ce13..49443271 100644 --- a/src/schemes/renormalizer.jl +++ b/src/schemes/renormalizer.jl @@ -63,7 +63,7 @@ end """ step!(r::Renormalizer) -Perform one RG coarse-graining step. Wraps [`Base.iterate`](@ref). +Perform one RG coarse-graining step. Wraps `Base.iterate`. Throws an error if `maxiter` has already been reached. """ function step!(r::Renormalizer) From e2c9e50eb6a2e2960862d820d6c8c49cf97f2a13 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 18 Jun 2026 19:40:02 +0800 Subject: [PATCH 3/7] Fix GS degeneracy --- src/schemes/renormalizer.jl | 1 - src/utility/cft.jl | 27 ++++++++----- src/utility/gs_degeneracy.jl | 75 ++++++++++++++++-------------------- 3 files changed, 51 insertions(+), 52 deletions(-) diff --git a/src/schemes/renormalizer.jl b/src/schemes/renormalizer.jl index 49443271..f5eb2b0f 100644 --- a/src/schemes/renormalizer.jl +++ b/src/schemes/renormalizer.jl @@ -27,7 +27,6 @@ end get_tensor(r::Renormalizer) Return the tensor(s) stored in the renormalizer's current state. -For a single-tensor scheme like [`TRG`](@ref), returns the central tensor `T`. """ function get_tensor 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 f20f4991..eb911753 100644 --- a/src/utility/gs_degeneracy.jl +++ b/src/utility/gs_degeneracy.jl @@ -1,3 +1,11 @@ +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.(ev)) + return exp(entropy) +end + """ ground_state_degeneracy(T::AbstractTensorMap, unitcell=1) @@ -6,45 +14,28 @@ 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) - 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) - - Ts = fill(T, unitcell) - Tcontracted = ncon(Ts, indices) - - outinds = ntuple(i -> i, unitcell) - ininds = ntuple(i -> unitcell + i, unitcell) - Tcontracted = permute(Tcontracted, (outinds, ininds)) - - D, _ = eig_full(Tcontracted) - D = D / tr(D) - vals = filter(!iszero, abs.(D.data)) - S = 0.0 - for v in vals - ev = abs(v) - if ev > 0 - S -= ev * log(ev) - end - end - return exp(S) + tm = _row_transfer_matrix(T, unitcell) + return _ground_state_degeneracy(tm) end """ - ground_state_degeneracy(TA::AbstractTensorMap, TB::AbstractTensorMap; unitcell=1) - -Compute the GSD from a two-site unit cell (TA, TB). Builds an effective -single-site tensor and delegates to the single-tensor method. + 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; unitcell::Int = 1) - norm_const = area_term(TA, TB) - T1 = TA / abs(norm_const)^(1 / 4) - T2 = 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] - return ground_state_degeneracy(T_unit, unitcell) +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 ground_state_degeneracy(scheme::TNRScheme; unitcell::Int = 1) = ground_state_degeneracy(scheme.T, unitcell) @@ -55,10 +46,10 @@ function ground_state_degeneracy(scheme::BTRG; unitcell::Int = 1) return ground_state_degeneracy(T_unit, unitcell) end -ground_state_degeneracy(scheme::LoopTNR; unitcell::Int = 2) = ground_state_degeneracy(scheme.TA, scheme.TB; unitcell) +ground_state_degeneracy(scheme::LoopTNR) = ground_state_degeneracy(scheme.TA, scheme.TB) """ - gu_wen_ratio(T::AbstractTensorMap) + 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 @@ -68,7 +59,7 @@ the scaling dimensions. * [Zheng-Cheng Gu & Xiao-Gang Wen. PhysRevB.80.155131](@cite gu2009) * [Satoshi Morita et al. arxiv:2512.03395](@cite morita2025) """ -function gu_wen_ratio(T::AbstractTensorMap) +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]) @@ -78,11 +69,13 @@ function gu_wen_ratio(T::AbstractTensorMap) end """ - gu_wen_ratio(TA::AbstractTensorMap, TB::AbstractTensorMap) + gu_wen_ratio(TA::AbstractTensorMap{E, S, 2, 2}, TB::AbstractTensorMap{E, S, 2, 2}) where {E, S} -Compute the Gu-Wen ratios (X1, X2) from a two-site unit cell (TA, TB). +Compute the Gu-Wen ratios (X1, X2) for a checkerboard network (TA, TB). """ -function gu_wen_ratio(TA::AbstractTensorMap, TB::AbstractTensorMap) +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 TA[1 2; 3 4] * TB[4 5; 6 1] * TB[7 3; 2 8] * TA[8 6; 5 7] From eceba81e9a3de4e67cdad8763ed1aa930e7474e9 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 18 Jun 2026 20:50:21 +0800 Subject: [PATCH 4/7] Fix variable name typo --- src/utility/gs_degeneracy.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utility/gs_degeneracy.jl b/src/utility/gs_degeneracy.jl index eb911753..ed3cc180 100644 --- a/src/utility/gs_degeneracy.jl +++ b/src/utility/gs_degeneracy.jl @@ -2,7 +2,7 @@ function _ground_state_degeneracy(tm::AbstractTensorMap{E, S, N, N}) where {E, S D, _ = eig_full(tm) D = D / tr(D) evs = filter(!iszero, abs.(D.data)) - entropy = -sum(evs .* log.(ev)) + entropy = -sum(evs .* log.(evs)) return exp(entropy) end From 962ed737800599d04f3ea6b96e0a2ac219a2aa9d Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Fri, 19 Jun 2026 16:31:23 +0800 Subject: [PATCH 5/7] Simplify TRGState type parameters --- src/schemes/trg.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/schemes/trg.jl b/src/schemes/trg.jl index 7417c5bc..49bd22f7 100644 --- a/src/schemes/trg.jl +++ b/src/schemes/trg.jl @@ -42,16 +42,16 @@ Stores the tensor state for one step of a [`TRG`](@ref) renormalization. $(TYPEDFIELDS) """ -mutable struct TRGState{E, S, TT <: AbstractTensorMap{E, S, 2, 2}} +mutable struct TRGState{TT <: AbstractTensorMap{<:Any, <:Any, 2, 2}} "Central tensor" T::TT end -function Renormalizer(alg::TRG, T::TT) where {E, S, TT <: AbstractTensorMap{E, S, 2, 2}} +function Renormalizer(alg::TRG, T::TT) where {TT} n = norm(@tensor T[1 2; 2 1]) T_norm = T / n - state = TRGState{E, S, TT}(T_norm) - return Renormalizer{TRG, TRGState{E, S, TT}}(alg, state, [n], 0) + state = TRGState{TT}(T_norm) + return Renormalizer{TRG, TRGState{TT}}(alg, state, [n], 0) end """ From 31d509dee62ca1bf98c5e12c2301e7d50bb90fbf Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Sat, 20 Jun 2026 00:36:45 +0800 Subject: [PATCH 6/7] Add non-breaking iterable Renormalizer interface for TRG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename TNRAlgorithm → TNRParams (pure parameter descriptors) - New TRGParams <: TNRParams with @kwdef defaults (trunc, maxiter) - Old TRG{E,S,TT} <: TNRScheme{E,S} restored as state type - Renormalizer wraps TRGParams + TRG for iterable RG stepping - Legacy run!(::TRG, trunc, criterion) delegates to new interface - Add iterable TRG test set alongside restored old-style tests - Finalizer support removed from TRG run!; use iterable for per-step data Co-Authored-By: Claude Opus 4.8 --- .gitignore | 1 + src/TNRKit.jl | 3 +- src/schemes/renormalizer.jl | 2 +- src/schemes/tnrscheme.jl | 4 +- src/schemes/trg.jl | 132 ++++++++++++++++++++++++------------ src/utility/finalize.jl | 2 +- test/schemes/schemes.jl | 68 ++++++++++++------- 7 files changed, 139 insertions(+), 73 deletions(-) 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/src/TNRKit.jl b/src/TNRKit.jl index 1fc26b8c..78e77dc7 100644 --- a/src/TNRKit.jl +++ b/src/TNRKit.jl @@ -62,7 +62,8 @@ include("schemes/symmetric_looptnr.jl") export classical_ising_inv # Ising model with all legs in codomain export TNRScheme -export TNRAlgorithm +export TNRParams +export TRGParams export Renormalizer export get_tensor diff --git a/src/schemes/renormalizer.jl b/src/schemes/renormalizer.jl index f5eb2b0f..b5b57131 100644 --- a/src/schemes/renormalizer.jl +++ b/src/schemes/renormalizer.jl @@ -12,7 +12,7 @@ Each iteration yields `(state, norms)`. - Subsequent yields: state after each RG step + normalization. - Stops after `alg.maxiter` RG steps have been performed. """ -mutable struct Renormalizer{A <: TNRAlgorithm, S} +mutable struct Renormalizer{A <: TNRParams, S} "Algorithm configuration (truncation, maxiter, etc.)" alg::A "Algorithm-specific state holding all network tensors" diff --git a/src/schemes/tnrscheme.jl b/src/schemes/tnrscheme.jl index 39a815ed..ee37ac83 100644 --- a/src/schemes/tnrscheme.jl +++ b/src/schemes/tnrscheme.jl @@ -29,13 +29,13 @@ const ImpurityHOTRG_Finalizer = Finalizer(finalize!, Tuple{Float64, Float64, Flo # Finalization functions for the various TNR schemes """ - abstract type TNRAlgorithm + 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 TNRAlgorithm end +abstract type TNRParams end abstract type TNRScheme{E, S} end diff --git a/src/schemes/trg.jl b/src/schemes/trg.jl index 49bd22f7..c1977f38 100644 --- a/src/schemes/trg.jl +++ b/src/schemes/trg.jl @@ -1,10 +1,43 @@ """ $(TYPEDEF) -Tensor Renormalization Group algorithm parameters. +Tensor Renormalization Group -This is a pure algorithm descriptor — it stores truncation and iteration -parameters but no tensor data. Tensors are managed by [`Renormalizer`](@ref). +# Constructors + $(FUNCTIONNAME)(T) + +# Running the algorithm + run!(::TRG, trunc::TruncationStrategy, stop::Stopcrit[, finalizer=default_Finalizer, finalize_beginning=true, verbosity=1]) + +Each step rescales the lattice by a (linear) factor of √2, +and rotate the lattice by 45 degrees in counter clockwise direction. +The elementary modular parameter `τ₀ ↦ (τ₀ - 1) / (τ₀ + 1)`. + +!!! info "verbosity levels" + - 0: No output + - 1: Print information at start and end of the algorithm + - 2: Print information at each step + +# Fields + +$(TYPEDFIELDS) + +# References +* [Levin & Nave Phys. Rev. Letters 99(12) (2007)](@cite levin2007) +""" +mutable struct TRG{E, S, TT <: AbstractTensorMap{E, S, 2, 2}} <: TNRScheme{E, S} + "central tensor" + T::TT + + function TRG(T::TT) where {E, S, TT <: AbstractTensorMap{E, S, 2, 2}} + return new{E, S, TT}(T) + end +end + +""" +$(TYPEDEF) + +Parameters for the Tensor Renormalization Group algorithm. # Constructors @@ -12,15 +45,6 @@ All parameters are passed as keyword arguments with sensible defaults: $(FUNCTIONNAME)(; trunc=truncrank(16), maxiter=20) -# Usage - -```julia -alg = TRG() # uses defaults -alg = TRG(; trunc = truncrank(24), maxiter = 25) # full configuration -renorm = Renormalizer(alg, T) -T_final, norms = run!(renorm) -``` - # Fields $(TYPEDFIELDS) @@ -28,64 +52,88 @@ $(TYPEDFIELDS) # References * [Levin & Nave Phys. Rev. Letters 99(12) (2007)](@cite levin2007) """ -Base.@kwdef struct TRG <: TNRAlgorithm +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 -""" -$(TYPEDEF) +# ============================================================================== +# Renormalizer interface (new iterable API) +# ============================================================================== -Stores the tensor state for one step of a [`TRG`](@ref) renormalization. - -$(TYPEDFIELDS) -""" -mutable struct TRGState{TT <: AbstractTensorMap{<:Any, <:Any, 2, 2}} - "Central tensor" - T::TT +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(alg::TRG, T::TT) where {TT} - n = norm(@tensor T[1 2; 2 1]) - T_norm = T / n - state = TRGState{TT}(T_norm) - return Renormalizer{TRG, TRGState{TT}}(alg, state, [n], 0) +function Renormalizer(params::TRGParams, scheme::TRG) + n = finalize!(scheme) + return Renormalizer{TRGParams, typeof(scheme)}(params, scheme, [n], 0) end -""" - _renorm_step!(r::Renormalizer) +function get_tensor(r::Renormalizer{<:TRGParams}) + return r.state.T +end -Perform one TRG coarse-graining step followed by trace-based normalization. -Operates on `r` directly to ensure in-place mutation of `r.state.T`. -""" -function _renorm_step!(r::Renormalizer{<:TRG}) +function _renorm_step!(r::Renormalizer{<:TRGParams}) T = r.state.T trunc = r.alg.trunc - # TRG coarse-graining + # TRG coarse-graining (Levin & Nave) A, B = SVD12(T, trunc) Tp = transpose(T, ((2, 4), (1, 3))) C, D = SVD12(Tp, trunc) @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 (same logic as finalize! for TRG) - n = norm(@tensor r.state.T[1 2; 2 1]) - r.state.T /= n + # Trace-based normalization + n = finalize!(r.state) push!(r.norms, n) return r end -function get_tensor(r::Renormalizer{<:TRG}) - return r.state.T +# ============================================================================== +# 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 -function Base.show(io::IO, alg::TRG) +# ============================================================================== +# Base.show +# ============================================================================== + +function Base.show(io::IO, scheme::TRG) println(io, "TRG - Tensor Renormalization Group") - println(io, " * truncation: $(alg.trunc)") - println(io, " * maxiter: $(alg.maxiter)") + 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/finalize.jl b/src/utility/finalize.jl index 1fc6adc7..ec271ed1 100644 --- a/src/utility/finalize.jl +++ b/src/utility/finalize.jl @@ -1,4 +1,4 @@ -const simple_scheme = Union{ATRG, HOTRG} +const simple_scheme = Union{TRG, ATRG, HOTRG} # 1x1 unitcell finalize function finalize!(scheme::simple_scheme) diff --git a/test/schemes/schemes.jl b/test/schemes/schemes.jl index baf92593..c8a7a6b2 100644 --- a/test/schemes/schemes.jl +++ b/test/schemes/schemes.jl @@ -34,27 +34,16 @@ end # TRG @testset "TRG - Anisotropic Ising Model" begin @info "Anisotropy: Jx = $(Jx_aniso), Jy = $(Jy_aniso)" - elt = complex(scalartype(T_aniso)) - renorm = Renormalizer(TRG(; trunc = truncrank(24), maxiter = 25), 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 - @info "* verified for step $(n - 1) → $n" - end + @info "TRG anisotropic ising free energy" + scheme = TRG(T_aniso) + 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) + run!(scheme, truncrank(24), maxiter(10)) # use fast tau algorithm below - cft = CFTData(T_step10; shape = [1, 1, 0]) + cft = CFTData(scheme; shape = [1, 1, 0]) sd_all = real(cft.scaling_dimensions) cft_sorted = sort(sd_all[2:end]; by = abs) @@ -64,24 +53,51 @@ end @info "TRG anisotropic ising ground state degeneracy" T1 = classical_ising(βc_aniso - 0.01; Jx = Jx_aniso, Jy = Jy_aniso) - renorm3 = Renormalizer(TRG(; trunc = truncrank(16), maxiter = 20), T1) - run!(renorm3; verbosity = 0) - gsd = ground_state_degeneracy(get_tensor(renorm3)) - X1, X2 = gu_wen_ratio(get_tensor(renorm3)) + scheme = TRG(T1) + run!(scheme, truncrank(16), maxiter(20)) + gsd = ground_state_degeneracy(scheme) + X1, X2 = gu_wen_ratio(scheme) @test gsd ≈ 1 rtol = 1.0e-2 @test X1 ≈ 1.0 rtol = 1.0e-2 @test X2 ≈ 1.0 rtol = 1.0e-2 T2 = classical_ising(βc_aniso + 0.01; Jx = Jx_aniso, Jy = Jy_aniso) - renorm4 = Renormalizer(TRG(; trunc = truncrank(16), maxiter = 20), T2) - run!(renorm4; verbosity = 0) - gsd = ground_state_degeneracy(get_tensor(renorm4)) - X1, X2 = gu_wen_ratio(get_tensor(renorm4)) + scheme = TRG(T2) + run!(scheme, truncrank(16), maxiter(20)) + gsd = ground_state_degeneracy(scheme) + X1, X2 = gu_wen_ratio(scheme) @test gsd ≈ 2 rtol = 1.0e-2 @test X1 ≈ 2.0 rtol = 1.0e-2 @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" From 7650a3b10653a92d8bf4f1d4352566106ebc079b Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Sat, 20 Jun 2026 00:39:07 +0800 Subject: [PATCH 7/7] Fix formatting --- src/schemes/trg.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/schemes/trg.jl b/src/schemes/trg.jl index c1977f38..7268860b 100644 --- a/src/schemes/trg.jl +++ b/src/schemes/trg.jl @@ -107,7 +107,7 @@ function run!( 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 + 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)