From b08ea3634b786180360e0012521155218a5b64c2 Mon Sep 17 00:00:00 2001 From: sotashimozono Date: Mon, 14 Sep 2026 13:26:53 +0000 Subject: [PATCH 1/3] Three relations the issues asked for, and the collision one of them names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #156. `CFTEntanglementSlope` carries no `L`, so nothing tells a caller how large `ℓ` may be, and out of range it returns a number rather than refusing: at ℓ near L the complement is a few sites, purity alone caps S, and no `ncuts` describes that. `CFTEntanglementChordSlope` states the same law against the chord, which is in domain over the whole chain and reduces to the present one as ℓ/L → 0. MEASURED on exact free-fermion ground states of the critical uniform chain, block at an open end, so c/6 = 0.08333: L against ln ℓ against ln[chord] 64 0.03495 0.08941 128 0.03332 0.08663 256 0.03308 0.08502 The chord column halves its error each time L doubles and the other does not move, which is what separates a finite-size correction from a wrong law. The test asserts that ratio rather than a threshold, since a threshold cannot tell them apart. #158. `x_m` and `ψ` are what a ground state yields, through the correlation function at criticality against DISTANCE, and no relation read either. `CriticalCorrelationDecay` and `ActivatedCriticalCorrelation` do. Distance is a third scale by the same argument that already separates L from ξ in the neighbouring relations. Anchored on the golden-mean value -2x_m = -(3-√5)/2, which the relation recovers exactly, and on the issue's measured slopes stated in units of their own published error: the average probe sits 1.0σ from the exact value and the typical one 3σ, so the assertion that they are not interchangeable probes is made on the numbers rather than asserted. #157. Documentation only, and the half that does not reach into another repository. `InfiniteRandomness`'s warning about `d` now names the sibling lookup that requires the value it warns against, so the hazard is greppable from the side that triggers it. Naming the two variables apart is the other option and is a breaking change in the atlas, so it stays the issue's. Co-Authored-By: Claude Opus 5 (1M context) --- Project.toml | 2 +- src/relations/entanglement.jl | 33 +++++++++++ src/relations/quantity_links.jl | 3 +- src/relations/scaling.jl | 57 +++++++++++++++++++ src/structure/scaling_dimensions.jl | 8 +++ test/relations/test_entanglement.jl | 85 +++++++++++++++++++++++++++++ test/relations/test_interface.jl | 6 +- test/relations/test_scaling.jl | 46 ++++++++++++++++ 8 files changed, 235 insertions(+), 5 deletions(-) diff --git a/Project.toml b/Project.toml index edad342..db5c6f4 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "AbstractQAtlas" uuid = "dcea2817-62f8-4a75-b498-1b50a9ed1e4d" -version = "0.7.12" +version = "0.7.13" authors = ["sota shimozono "] [deps] diff --git a/src/relations/entanglement.jl b/src/relations/entanglement.jl index b3a4220..4e9045d 100644 --- a/src/relations/entanglement.jl +++ b/src/relations/entanglement.jl @@ -68,6 +68,39 @@ derivative, hence [`also_constrains`](@ref). `ncuts` has no default: @relation :entanglement CFTEntanglementSlope(dS_dlogℓ, c::CentralCharge, ncuts) = dS_dlogℓ - ncuts * c / 6 +""" + CFTEntanglementChordSlope <: AbstractRelation + +The same slope on a FINITE chain, taken against the chord rather than the +distance (Calabrese & Cardy, [CalabreseCardy2004](@cite)): + +`dS/d(ln[(L/π) sin(πℓ/L)]) = ncuts · c/6`. + +[`CFTEntanglementSlope`](@ref) has no `L` and so no way to say how large `ℓ` may +be, and the answer degrades smoothly rather than refusing: at `ℓ → L` the block's +complement is a few sites and purity alone caps `S`, so the quantity has stopped +being a bulk block entropy and no `ncuts` is right for it. MEASURED on exact +free-fermion ground states of the critical uniform chain, block at an open end, +`ncuts = 1`, so `c/6 = 0.08333`: + +| L | against `ln ℓ`, whole chain | against `ln[chord]`, whole chain | +|---|---|---| +| 64 | 0.03495 | 0.08941 | +| 128 | 0.03332 | 0.08663 | +| 256 | 0.03308 | 0.08502 | + +The chord column halves its error each time `L` doubles; the other column does not +move, because it is not a finite-size correction but a different law. Restricted +to `ℓ ≤ L/4` the two agree, which is the regime [`CFTEntanglementSlope`](@ref) is +for and does not state. + +Reduces to it as `ℓ/L → 0`, the chord tending to `ℓ`, so this is the general form +and that one the limit. Variables: `dS_dlogchord` (caller-computed slope against +`ln[(L/π) sin(πℓ/L)]`), `c`, `ncuts`. +""" +@relation :entanglement CFTEntanglementChordSlope(dS_dlogchord, c::CentralCharge, ncuts) = + dS_dlogchord - ncuts * c / 6 + """ InfiniteRandomnessEntanglementSlope <: AbstractRelation diff --git a/src/relations/quantity_links.jl b/src/relations/quantity_links.jl index 41f21ad..ea95642 100644 --- a/src/relations/quantity_links.jl +++ b/src/relations/quantity_links.jl @@ -27,7 +27,8 @@ also_constrains(::ParticleNumberResponse) = (GrandPotential,) # grand-canonical also_constrains(::StaticFromDynamicalStructureFactor) = (DynamicalStructureFactor,) # Sq = ∫S(q,ω)dω/2π (supplied) also_constrains(::ChernFromBerryCurvature) = (BerryCurvature,) # topology: C = ∫Ω d²k/2π (supplied integral) also_constrains(::CFTEntanglementInfinite) = (VonNeumannEntropy,) # the L→∞ sibling -also_constrains(::CFTEntanglementSlope) = (VonNeumannEntropy,) # entanglement: dS/d(ln ℓ) (supplied derivative) +also_constrains(::CFTEntanglementSlope) = (VonNeumannEntropy,) +also_constrains(::CFTEntanglementChordSlope) = (VonNeumannEntropy,) # the finite-chain twin also_constrains(::CFTEntanglementPBC) = (VonNeumannEntropy,) # entanglement: S(ℓ) on a ring also_constrains(::CFTEntanglementOBC) = (VonNeumannEntropy,) # entanglement: S(ℓ) at an open end function also_constrains(::OffCriticalEntanglementSaturation) diff --git a/src/relations/scaling.jl b/src/relations/scaling.jl index 6a73455..f30dbe3 100644 --- a/src/relations/scaling.jl +++ b/src/relations/scaling.jl @@ -182,6 +182,63 @@ magnetization, both in §4.1 and both stated for free boundary conditions. @relation :scaling ActivatedFiniteSizeScaling(dloglogO_dlogL, ψ::ActivatedExponent) = dloglogO_dlogL - ψ +""" + CriticalCorrelationDecay <: AbstractRelation + +The AVERAGE correlation at criticality against DISTANCE +([IgloiMonthus2005](@cite), §A.3): + +`⟨C(r)⟩ ∼ r^{-2 x_m}`, so `d ln⟨C⟩/d ln r = -2 x_m`. + +A third scale, and a third statement. [`ActivatedFiniteSizeScaling`](@ref) is +about the system size `L` and [`ActivatedDynamicalScaling`](@ref) about the +correlation length `ξ`, and the argument that keeps those apart, that `ξ` +diverges at criticality and constrains nothing there while `L` is the only scale +left, applies again: the distance `r` inside a large system at `δ = 0` is neither. + +This is the clean probe of the two. MEASURED on exact free-fermion ground states +of the critical random chain, pairs at the centre, `r = 2…128`, fitted with the +correction term left open rather than chosen: + +| L | samples | no correction | with `1/ln r` | with `r^{-1/2}` | +|---|---|---|---|---| +| 256 | 800 | -0.3774(186) | -0.4148(326) | -0.4813(633) | +| 512 | 500 | -0.4029(204) | -0.4123(362) | -0.4101(673) | +| 1024 | 200 | -0.4062(304) | -0.3935(523) | -0.3438(1018) | + +against `-2 x_m = -(3 - √5)/2 = -0.38197`: consistent at every `L`, with a +correction coefficient small and not keeping its sign, so no correction is needed. +[`ActivatedCriticalCorrelation`](@ref), the typical one, is not that clean. + +Variables: `dlogC_dlogr` (caller-computed slope of `ln⟨C⟩` against `ln r`), `x_m`. +""" +@relation :scaling CriticalCorrelationDecay(dlogC_dlogr, x_m::ScalingDimension) = + dlogC_dlogr + 2 * x_m + +""" + ActivatedCriticalCorrelation <: AbstractRelation + +The TYPICAL correlation at criticality against distance, which is a stretched +exponential rather than a power ([IgloiMonthus2005](@cite), §A.3): + +`exp⟨ln|C(r)|⟩ ∼ exp(-a r^ψ)`, so `d ln(-⟨ln|C|⟩)/d ln r = ψ`. + +The same `ψ` as [`ActivatedFiniteSizeScaling`](@ref) and +[`ActivatedDynamicalScaling`](@ref), read off a third scale. The typical and the +average correlation are different probes of the fixed point rather than two routes +to one number, which is why both relations exist. + +Worse conditioned than [`CriticalCorrelationDecay`](@ref), and the docstring says +so because the numbers do: the same chains put `ψ` near `1/2` only once a +correction is allowed, 0.47 to 0.56 across three correction forms, with the +uncorrected slope about 3σ high and not moving toward `1/2` with `L`. + +Variables: `dloglogC_dlogr` (caller-computed slope of `ln(-⟨ln|C|⟩)` against +`ln r`), `ψ`. +""" +@relation :scaling ActivatedCriticalCorrelation(dloglogC_dlogr, ψ::ActivatedExponent) = + dloglogC_dlogr - ψ + """ TypicalCorrelationLength <: AbstractRelation diff --git a/src/structure/scaling_dimensions.jl b/src/structure/scaling_dimensions.jl index 6eda7ff..ed73101 100644 --- a/src/structure/scaling_dimensions.jl +++ b/src/structure/scaling_dimensions.jl @@ -157,6 +157,14 @@ exponent is derived by [`critical_exponents`](@ref). at construction rather than one per call, and at an infinite-randomness fixed point there is no finite `d + z` to confuse it with anyway. + The hazard is not hypothetical and it has an address. For this same class, + QAtlas reads the effective central charge as + `fetch(Universality{:IsingSDRG}, CentralCharge(); d = 2)`, and `2` is the only + value that lookup takes, because there `d` is the 1+1D CFT's dimension. A + consumer who carries that `d` straight here is the case above. The two are + different variables wearing one letter, and nothing at either call site says + so, which is tracked as QAtlasHub/AbstractQAtlas.jl#157. + Arguments are promoted to a common type; pass `Rational`s where the values are rational. `ψ ≤ 0` is refused rather than accepted: it names a conventional fixed point, which is [`ScalingDimensions`](@ref)'s job, and it would divide by diff --git a/test/relations/test_entanglement.jl b/test/relations/test_entanglement.jl index d82fad2..874e399 100644 --- a/test/relations/test_entanglement.jl +++ b/test/relations/test_entanglement.jl @@ -503,6 +503,91 @@ end @test abs(wrong(64) - c₁_source) > 0.1 end +@testset "the chord slope is in domain over the whole chain and ln ℓ is not" begin + # `CFTEntanglementSlope` carries no `L`, so nothing tells a caller how large `ℓ` + # may be, and out of range it returns a number rather than refusing. Exactly on + # the ED ground state of the critical uniform chain, block at an open end. + function covariance(N) + A = zeros(2N, 2N) + for j in 1:N + A[2j - 1, 2j] = -2.0 + end + for j in 1:(N - 1) + A[2j, 2j + 1] = -2.0 + end + A = A - transpose(A) + F = svd(A) + Γ = F.U * F.Vt + return (Γ .- transpose(Γ)) ./ 2 + end + function ed_entropy(Γ, sites) + idx = vcat(([2j - 1, 2j] for j in sites)...) + ν = eigvals(Hermitian(im .* Γ[idx, idx])) + return free_fermion_entanglement_entropy([ + (1 + real(x)) / 2 for x in ν if real(x) > 0 + ]) + end + function fitslope(xs, ys) + x̄, ȳ = sum(xs) / length(xs), sum(ys) / length(ys) + return sum((xs .- x̄) .* (ys .- ȳ)) / sum((xs .- x̄) .^ 2) + end + c, ncuts = 1 / 2, 1 + + function slopes(N) + Γ = covariance(N) + ls = filter( + l -> 2 <= l <= N - 2, + unique( + round.( + Int, + N .* [0.03, 0.05, 0.08, 0.12, 0.19, 0.3, 0.45, 0.6, 0.75, 0.88, 0.95], + ), + ), + ) + ys = [ed_entropy(Γ, 1:l) for l in ls] + chord = [(N / π) * sin(π * l / N) for l in ls] + near = filter(l -> l <= N ÷ 4, ls) + yn = [ed_entropy(Γ, 1:l) for l in near] + return ( + plain_full=fitslope(log.(ls), ys), + chord_full=fitslope(log.(chord), ys), + plain_near=fitslope(log.(near), yn), + ) + end + s64, s128 = slopes(64), slopes(128) + + # In the regime the present relation is for, it is right. + @test isapprox(s128.plain_near, ncuts * c / 6; atol=0.005) + + # Over the whole chain it is not, and by a lot: the block's complement is a few + # sites, purity caps `S`, and no `ncuts` describes that. + @test abs(s128.plain_full - ncuts * c / 6) > 0.04 + + # The chord form is in domain over the same whole chain. + @test isapprox(s128.chord_full, ncuts * c / 6; atol=0.01) + + # And the two errors are different KINDS. The chord's halves when L doubles, so it + # is a finite-size correction; the other does not move, so it is a wrong law. A + # threshold alone could not tell those apart. + e64, e128 = abs(s64.chord_full - c / 6), abs(s128.chord_full - c / 6) + @test e128 < 0.6 * e64 + p64, p128 = abs(s64.plain_full - c / 6), abs(s128.plain_full - c / 6) + @test p128 > 0.9 * p64 + + # The relation itself: the chord slope reads the central charge back. + @test isapprox( + solve( + CFTEntanglementChordSlope(), Val(:c); dS_dlogchord=s128.chord_full, ncuts=ncuts + ), + c; + atol=0.06, + ) + # It is the general form and `CFTEntanglementSlope` the ℓ ≪ L limit, so on one + # number they agree exactly; what differs is which number the data gives them. + @test residual(CFTEntanglementChordSlope(); dS_dlogchord=c / 6, c=c, ncuts=1) == + residual(CFTEntanglementSlope(); dS_dlogℓ=c / 6, c=c, ncuts=1) +end + @testset "every route from an entropy to c returns the same c" begin # `related_quantities(CentralCharge)` carries five distinct edges to # VonNeumannEntropy, so one measurement can be read for `c` five ways. Each was diff --git a/test/relations/test_interface.jl b/test/relations/test_interface.jl index 3cc5065..edb2a90 100644 --- a/test/relations/test_interface.jl +++ b/test/relations/test_interface.jl @@ -18,9 +18,9 @@ AbstractQAtlas.domain(::_NonAffineDemo) = :test_only # domain that has no line here shows up as a mismatch in this number alone. # Universal-only: the model-specific relations (spin glass, Drude mobility, # single-band Hall) live in QAtlas. - @test length(rels) == 167 + @test length(rels) == 170 @test allunique(typeof.(rels)) - @test length(all_relations(; domain=:scaling)) == 28 # +ActivatedDynamicalScaling, ActivatedFiniteSizeScaling, +14 Appendix-A scaling types, +WeinribHalperinExponent, +QuantumHyperscaling; +TypicalCorrelationLength, GriffithsExponentDivergence, GriffithsSusceptibility, GriffithsSpecificHeat, ActivatedMomentGrowth + @test length(all_relations(; domain=:scaling)) == 30 # +ActivatedDynamicalScaling, ActivatedFiniteSizeScaling, +14 Appendix-A scaling types, +WeinribHalperinExponent, +QuantumHyperscaling; +TypicalCorrelationLength, GriffithsExponentDivergence, GriffithsSusceptibility, GriffithsSpecificHeat, ActivatedMomentGrowth; +CriticalCorrelationDecay, ActivatedCriticalCorrelation (#158) @test length(all_relations(; domain=:thermodynamic)) == 15 @test length(all_relations(; domain=:fundamental)) == 9 # +GrandPotentialLegendre, ParticleNumberResponse (grand-canonical); +ElectricCurrentResponse (j = −∂H/∂A) @test length(all_relations(; domain=:topology)) == 3 @@ -31,7 +31,7 @@ AbstractQAtlas.domain(::_NonAffineDemo) = :test_only @test length(all_relations(; domain=:holographic)) == 1 # BekensteinEntropyBound — the one non-quantum universal bound @test length(all_relations(; domain=:ensemble)) == 2 @test length(all_relations(; domain=:fluctuation)) == 5 # +TypicalBelowAverage, +AnnealedFreeEnergyBound - @test length(all_relations(; domain=:entanglement)) == 32 # +InfiniteRandomnessEntanglementSlope, CFTEntanglementPBC/OBC, OffCriticalEntanglementSaturation, HalvedChainEntropyDifference, CFTEntanglementInfinite, InfiniteRandomnessEntanglementPBC + @test length(all_relations(; domain=:entanglement)) == 33 # +InfiniteRandomnessEntanglementSlope, CFTEntanglementPBC/OBC, OffCriticalEntanglementSaturation, HalvedChainEntropyDifference, CFTEntanglementInfinite, InfiniteRandomnessEntanglementPBC; +CFTEntanglementChordSlope (#156) @test length(all_relations(; domain=:wick)) == 2 @test length(all_relations(; domain=:cft)) == 4 @test isempty(all_relations(; domain=:spinglass)) # model-specific — lives in QAtlas now diff --git a/test/relations/test_scaling.jl b/test/relations/test_scaling.jl index 07f8be8..889b8e8 100644 --- a/test/relations/test_scaling.jl +++ b/test/relations/test_scaling.jl @@ -637,3 +637,49 @@ end SpecificHeatExponent, CorrelationLengthExponent, SpatialDimension, DynamicalExponent ) end + +@testset "the two critical correlation probes are separate statements" begin + # `x_m` and `ψ` are what a ground state actually yields, through the correlation + # function at criticality as a function of DISTANCE. That is a third scale: not + # `L`, which `ActivatedFiniteSizeScaling` is about, and not `ξ`, which diverges + # at criticality and constrains nothing there. + x_m = (3 - sqrt(5)) / 4 # the golden-mean IRFP value, IgloiMonthus2005 + ψ = 1 / 2 + + # Exact on a pure power law: the relation IS the slope, so this pins the + # arithmetic and the sign, and the anchor is the literature value. + @test solve(CriticalCorrelationDecay(), Val(:x_m); dlogC_dlogr=-2 * x_m) ≈ x_m + @test solve(CriticalCorrelationDecay(), Val(:dlogC_dlogr); x_m=x_m) ≈ -2 * x_m + @test check(CriticalCorrelationDecay(); dlogC_dlogr=-0.38197, x_m=x_m, atol=1e-5) + @test !check(CriticalCorrelationDecay(); dlogC_dlogr=+2 * x_m, x_m=x_m, atol=1e-9) + + @test solve(ActivatedCriticalCorrelation(), Val(:ψ); dloglogC_dlogr=ψ) ≈ ψ + @test check(ActivatedCriticalCorrelation(); dloglogC_dlogr=ψ, ψ=ψ, atol=1e-12) + + # A measured slope from the issue that asked for these, L = 512 over 500 samples, + # stated in units of its own published error rather than a tolerance picked to + # pass: it sits about one standard error from the exact value. + measured, σ = -0.4029, 0.0204 + @test abs(residual(CriticalCorrelationDecay(); dlogC_dlogr=measured, x_m=x_m)) < 1.5σ + @test abs(residual(CriticalCorrelationDecay(); dlogC_dlogr=measured, x_m=x_m)) > 0.5σ + + # The typical probe is worse conditioned and the assertion says so: on the same + # chains an uncorrected slope sits about three of its own errors high, so it is + # NOT consistent with 1/2 the way the average probe is with -2x_m. + @test abs(residual(ActivatedCriticalCorrelation(); dloglogC_dlogr=0.56, ψ=ψ)) > + 1.5 * 0.02 + + # They read different exponents off the same ground state and cannot substitute + # for each other, which is why there are two relations rather than one. + @test variable_types(CriticalCorrelationDecay()) == (ScalingDimension,) + @test variable_types(ActivatedCriticalCorrelation()) == (ActivatedExponent,) + @test isdisjoint( + Set(variables(CriticalCorrelationDecay())), + Set(variables(ActivatedCriticalCorrelation())), + ) + + # `ψ` is the same exponent the size and gap statements carry, reached from a + # third scale rather than being a fourth number. + @test ActivatedExponent in variable_types(ActivatedFiniteSizeScaling()) + @test ActivatedExponent in variable_types(ActivatedDynamicalScaling()) +end From 4d8c72d6e079a564c6dbffb4c1557b358d390352 Mon Sep 17 00:00:00 2001 From: sotashimozono Date: Mon, 14 Sep 2026 14:07:06 +0000 Subject: [PATCH 2/3] Review: two wrong citations, a route this PR made worse, and a check that checked nothing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CITATIONS. Both were copied from the issue and neither survives the source. `C(r) ∼ r^{-2x_m}` is Eq. (A.6), the critical-point scaling law stated generally, which §A.3 carries to an infinite-randomness point rather than states. The typical form is Eq. (6.26), §6.3.3, derived for the random singlet phase where ψ = 1/2, not §A.3 at all. Also checked, and it holds: x_m = (3-√5)/4 follows twice over from that source, from β = (3-√5)/2 with ν = 2, and from the golden mean through φ = (d - x_m)/ψ. THE ROUTE. `derive` fires the first step whose inputs are complete, and registration order is file order. `CFTEntanglementSlope` was already declared first, so appending its domain-safe sibling after it meant a caller holding both slopes got the out-of-domain answer: 0.19992 where the chord route gives 0.51978 and the truth is 0.5. The existing PBC/Infinite pair does not have this problem only because the general form happens to be written first there. Declaring the chord form first does the same here, and `derive` now returns 0.51978. Not grouping the two under one `law_family` is correct and was verified by simulating the alternative: grouped, a 60% wrong `c` is excused, because one member agreeing satisfies the family. Now pinned, since nothing stopped a future reader from grouping them by analogy with the six pairs that should be. THE VACUOUS CHECK. `ncuts = 0` leaves the residual independent of `c`, so both slope relations passed for any central charge, including 999999. Refused now, in both. A region with no cuts has no slope to read. THE MISSING LINK. Neither new scaling relation had an `also_constrains`, so `relations_constraining` could not find them, and the average/typical split that is the entire reason both exist was enforced nowhere. They carry `DisorderAveraged{ConnectedSpinCorrelation}` and `Typical{ConnectedSpinCorrelation}` now, which is the spatial correlator they are about and the reduction split the autocorrelations beside them already use. PROSE. "Halves each time L doubles" is 0.54 then 0.51, so it says that. "Consistent at every L" was not: one of the nine entries is 1.57 of its own error out, past the bar this PR's own test uses. Two claims with nothing behind them are gone, the correction coefficient's size and sign, and a trend in L for the typical probe that one data point cannot show. The QAtlas example passed a type where its `fetch` dispatches on an instance, and called the result the effective central charge while fetching `CentralCharge`, which is true of QAtlas and worth saying rather than smoothing over, since this package keeps those apart. TESTS. The convergence claim ran on one ratio; the third size was already measured and sitting in the docstring, so it is asserted now and the ratio is checked twice. The limit claim was pinned by an equality true for any `c` from the two relations sharing a shape; it now takes both slopes from the same ED data in the ℓ ≪ L window and requires the two relations to return the same charge. The duplicated ED fixture is one copy at file scope. Co-Authored-By: Claude Opus 5 (1M context) --- Project.toml | 2 +- src/relations/entanglement.jl | 79 ++++++++++++-------- src/relations/quantity_links.jl | 11 ++- src/relations/scaling.jl | 29 +++++--- src/structure/scaling_dimensions.jl | 11 +-- test/relations/test_derivation.jl | 8 +++ test/relations/test_entanglement.jl | 107 ++++++++++++++-------------- test/relations/test_scaling.jl | 36 ++++++---- 8 files changed, 167 insertions(+), 116 deletions(-) diff --git a/Project.toml b/Project.toml index db5c6f4..f23a853 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "AbstractQAtlas" uuid = "dcea2817-62f8-4a75-b498-1b50a9ed1e4d" -version = "0.7.13" +version = "0.7.14" authors = ["sota shimozono "] [deps] diff --git a/src/relations/entanglement.jl b/src/relations/entanglement.jl index 4e9045d..ad8ba31 100644 --- a/src/relations/entanglement.jl +++ b/src/relations/entanglement.jl @@ -15,6 +15,18 @@ # relations and the Region-typed layer above them cannot drift apart. _chord(L, ℓ) = (L / π) * sin(π * ℓ / L) +# A slope relation with no cuts constrains nothing: the residual is `slope - 0`, so +# any central charge passes it. `entanglement_cuts` returns 0 for a whole ring, which +# is a correct count and not a slope to take. +function _require_cuts(what::Symbol, ncuts) + ncuts == 0 && error( + "$what: ncuts = 0 leaves the residual independent of the central charge, so " * + "the check would pass for any value of it. A region with no cuts has no " * + "entanglement to grow and no slope to read.", + ) + return nothing +end + # `sin` is periodic, so an ℓ outside the chain does not merely give a wrong number: # ℓ = 250 on L = 100 returns exactly the ℓ = 50 answer. And ℓ = L is not caught by a # blow-up either, since `sin(float(π))` is 1.2e-16 rather than 0, which turns into a @@ -41,33 +53,6 @@ Variables: `S2`, `purity`. """ @relation :entanglement RenyiTwoPurity(S2, purity) = S2 + log(purity) -""" - CFTEntanglementSlope <: AbstractRelation - -Logarithmic growth of a region's entanglement entropy in a 1D CFT, reading off -the central charge (Calabrese & Cardy, [CalabreseCardy2004](@cite)): - -`dS/d(ln ℓ) = ncuts · c/6`. - -`ncuts` counts the cuts bounding the region — set by where it sits, not by the -chain's boundary condition: - -| region | `ncuts` | | -|---|---|---| -| one interval on a ring | 2 | `c/3` | -| block at an open end | 1 | `c/6` | -| block in the bulk of an open chain | 2 | `c/3` | - -The last row is the one "OBC ⇒ `c/6`" gets wrong. - -Variables: `dS_dlogℓ` (caller-computed slope against `ln ℓ`), `c`, `ncuts`. -`c` is the typed subject; [`VonNeumannEntropy`](@ref) arrives via the supplied -derivative, hence [`also_constrains`](@ref). `ncuts` has no default: -[`Region`](@ref) carries no adjacency or boundary, so nothing can compute it. -""" -@relation :entanglement CFTEntanglementSlope(dS_dlogℓ, c::CentralCharge, ncuts) = - dS_dlogℓ - ncuts * c / 6 - """ CFTEntanglementChordSlope <: AbstractRelation @@ -89,8 +74,9 @@ free-fermion ground states of the critical uniform chain, block at an open end, | 128 | 0.03332 | 0.08663 | | 256 | 0.03308 | 0.08502 | -The chord column halves its error each time `L` doubles; the other column does not -move, because it is not a finite-size correction but a different law. Restricted +The chord column's error falls by a factor 0.54 then 0.51 as `L` doubles, toward the +`1/L` an open chain's leading correction carries; the other column moves 5% then +0.7%, because it is not a finite-size correction but a different law. Restricted to `ℓ ≤ L/4` the two agree, which is the regime [`CFTEntanglementSlope`](@ref) is for and does not state. @@ -99,7 +85,40 @@ and that one the limit. Variables: `dS_dlogchord` (caller-computed slope agains `ln[(L/π) sin(πℓ/L)]`), `c`, `ncuts`. """ @relation :entanglement CFTEntanglementChordSlope(dS_dlogchord, c::CentralCharge, ncuts) = - dS_dlogchord - ncuts * c / 6 + begin + _require_cuts(:CFTEntanglementChordSlope, ncuts) + dS_dlogchord - ncuts * c / 6 + end + +""" + CFTEntanglementSlope <: AbstractRelation + +Logarithmic growth of a region's entanglement entropy in a 1D CFT, reading off +the central charge (Calabrese & Cardy, [CalabreseCardy2004](@cite)): + +`dS/d(ln ℓ) = ncuts · c/6`. + +`ncuts` counts the cuts bounding the region — set by where it sits, not by the +chain's boundary condition: + +| region | `ncuts` | | +|---|---|---| +| one interval on a ring | 2 | `c/3` | +| block at an open end | 1 | `c/6` | +| block in the bulk of an open chain | 2 | `c/3` | + +The last row is the one "OBC ⇒ `c/6`" gets wrong. + +Variables: `dS_dlogℓ` (caller-computed slope against `ln ℓ`), `c`, `ncuts`. +`c` is the typed subject; [`VonNeumannEntropy`](@ref) arrives via the supplied +derivative, hence [`also_constrains`](@ref). For `ℓ` comparable to `L` this form +is out of domain and [`CFTEntanglementChordSlope`](@ref) is the one to use. `ncuts` has no default: +[`Region`](@ref) carries no adjacency or boundary, so nothing can compute it. +""" +@relation :entanglement CFTEntanglementSlope(dS_dlogℓ, c::CentralCharge, ncuts) = begin + _require_cuts(:CFTEntanglementSlope, ncuts) + dS_dlogℓ - ncuts * c / 6 +end """ InfiniteRandomnessEntanglementSlope <: AbstractRelation diff --git a/src/relations/quantity_links.jl b/src/relations/quantity_links.jl index ea95642..2108ead 100644 --- a/src/relations/quantity_links.jl +++ b/src/relations/quantity_links.jl @@ -27,8 +27,17 @@ also_constrains(::ParticleNumberResponse) = (GrandPotential,) # grand-canonical also_constrains(::StaticFromDynamicalStructureFactor) = (DynamicalStructureFactor,) # Sq = ∫S(q,ω)dω/2π (supplied) also_constrains(::ChernFromBerryCurvature) = (BerryCurvature,) # topology: C = ∫Ω d²k/2π (supplied integral) also_constrains(::CFTEntanglementInfinite) = (VonNeumannEntropy,) # the L→∞ sibling -also_constrains(::CFTEntanglementSlope) = (VonNeumannEntropy,) +also_constrains(::CFTEntanglementSlope) = (VonNeumannEntropy,) # entanglement: dS/d(ln ℓ), supplied also_constrains(::CFTEntanglementChordSlope) = (VonNeumannEntropy,) # the finite-chain twin +# The spatial correlator's decay, so the subject is the two-point function itself, and +# split by reduction for the same reason the autocorrelations above are: at an +# infinite-randomness point the average is set by rare pairs and the typical is not. +function also_constrains(::CriticalCorrelationDecay) + return (DisorderAveraged{ConnectedSpinCorrelation},) +end +function also_constrains(::ActivatedCriticalCorrelation) + return (Typical{ConnectedSpinCorrelation},) +end also_constrains(::CFTEntanglementPBC) = (VonNeumannEntropy,) # entanglement: S(ℓ) on a ring also_constrains(::CFTEntanglementOBC) = (VonNeumannEntropy,) # entanglement: S(ℓ) at an open end function also_constrains(::OffCriticalEntanglementSaturation) diff --git a/src/relations/scaling.jl b/src/relations/scaling.jl index f30dbe3..5b449f8 100644 --- a/src/relations/scaling.jl +++ b/src/relations/scaling.jl @@ -185,8 +185,10 @@ magnetization, both in §4.1 and both stated for free boundary conditions. """ CriticalCorrelationDecay <: AbstractRelation -The AVERAGE correlation at criticality against DISTANCE -([IgloiMonthus2005](@cite), §A.3): +The AVERAGE correlation at criticality against DISTANCE. The scaling law is +[IgloiMonthus2005](@cite) Eq. (A.6), `C(r) = b^{-2x_m} C(r/b)`, stated for a +critical point generally; §A.3 carries it to an infinite-randomness one, where +the average is dominated by rare pairs and the same conclusion holds: `⟨C(r)⟩ ∼ r^{-2 x_m}`, so `d ln⟨C⟩/d ln r = -2 x_m`. @@ -206,9 +208,12 @@ correction term left open rather than chosen: | 512 | 500 | -0.4029(204) | -0.4123(362) | -0.4101(673) | | 1024 | 200 | -0.4062(304) | -0.3935(523) | -0.3438(1018) | -against `-2 x_m = -(3 - √5)/2 = -0.38197`: consistent at every `L`, with a -correction coefficient small and not keeping its sign, so no correction is needed. -[`ActivatedCriticalCorrelation`](@ref), the typical one, is not that clean. +against `-2 x_m = -(3 - √5)/2 = -0.38197`, the random transverse-field Ising +chain's value, which follows from that source's `β = (3 - √5)/2` and `ν = 2`. +Eight of the nine entries sit within 1.03 of their own quoted error; the ninth, +`L = 256` under `r^{-1/2}`, is 1.57 away and is the noisiest column at the +smallest size. So the uncorrected fit is already consistent and a correction buys +nothing, which is not true of [`ActivatedCriticalCorrelation`](@ref). Variables: `dlogC_dlogr` (caller-computed slope of `ln⟨C⟩` against `ln r`), `x_m`. """ @@ -219,7 +224,9 @@ Variables: `dlogC_dlogr` (caller-computed slope of `ln⟨C⟩` against `ln r`), ActivatedCriticalCorrelation <: AbstractRelation The TYPICAL correlation at criticality against distance, which is a stretched -exponential rather than a power ([IgloiMonthus2005](@cite), §A.3): +exponential rather than a power. [IgloiMonthus2005](@cite) Eq. (6.26) derives it +for the random singlet phase, `-ln C_typ(r) ∼ ln Ω_L ∼ r^{1/2}`, where the +exponent is `ψ = 1/2`; written with `ψ` it is the infinite-randomness statement: `exp⟨ln|C(r)|⟩ ∼ exp(-a r^ψ)`, so `d ln(-⟨ln|C|⟩)/d ln r = ψ`. @@ -228,10 +235,12 @@ The same `ψ` as [`ActivatedFiniteSizeScaling`](@ref) and average correlation are different probes of the fixed point rather than two routes to one number, which is why both relations exist. -Worse conditioned than [`CriticalCorrelationDecay`](@ref), and the docstring says -so because the numbers do: the same chains put `ψ` near `1/2` only once a -correction is allowed, 0.47 to 0.56 across three correction forms, with the -uncorrected slope about 3σ high and not moving toward `1/2` with `L`. +Worse conditioned than [`CriticalCorrelationDecay`](@ref) on the same chains: its +uncorrected slope sits about three of its own errors above `1/2`, where the +average probe's is already within one. The average correlation is +non-self-averaging from the typical one (the source's own conclusion below +Eq. 6.26), so these are two probes of the fixed point and not two routes to one +number. Variables: `dloglogC_dlogr` (caller-computed slope of `ln(-⟨ln|C|⟩)` against `ln r`), `ψ`. diff --git a/src/structure/scaling_dimensions.jl b/src/structure/scaling_dimensions.jl index ed73101..b6087dd 100644 --- a/src/structure/scaling_dimensions.jl +++ b/src/structure/scaling_dimensions.jl @@ -159,11 +159,12 @@ exponent is derived by [`critical_exponents`](@ref). The hazard is not hypothetical and it has an address. For this same class, QAtlas reads the effective central charge as - `fetch(Universality{:IsingSDRG}, CentralCharge(); d = 2)`, and `2` is the only - value that lookup takes, because there `d` is the 1+1D CFT's dimension. A - consumer who carries that `d` straight here is the case above. The two are - different variables wearing one letter, and nothing at either call site says - so, which is tracked as QAtlasHub/AbstractQAtlas.jl#157. + `fetch(Universality(:IsingSDRG), CentralCharge(); d = 2)`, which refuses any + other `d`, because there `d` is the 1+1D CFT's dimension. A consumer who + carries that `d` straight here is the case above. Note also that the value + comes back under `CentralCharge` although it is the effective one, so the + quantity this package keeps separate as [`EffectiveCentralCharge`](@ref) is + not separate on that side either. Tracked as #157. Arguments are promoted to a common type; pass `Rational`s where the values are rational. `ψ ≤ 0` is refused rather than accepted: it names a conventional diff --git a/test/relations/test_derivation.jl b/test/relations/test_derivation.jl index 5b91a11..53d66f8 100644 --- a/test/relations/test_derivation.jl +++ b/test/relations/test_derivation.jl @@ -411,6 +411,14 @@ end filter(t -> t === nothing, last.(variable_slots(ActivatedMomentGrowth()))) ) + # A general law and its own restricted-domain limit must NOT be grouped. Every + # family above is a set of alternatives of which at most one holds; these two are + # both true, on different data, and `_families_satisfied` only needs one member of + # a family to agree. Grouping them would let the in-domain chord route excuse an + # out-of-domain plain-ℓ route rather than reporting the disagreement, which is the + # whole reason the chord relation exists. + @test law_family(CFTEntanglementSlope()) !== law_family(CFTEntanglementChordSlope()) + # And the exemplar stays split. @test OrderParameterExponent in variable_types(Rushbrooke()) @test !(InverseTemperature in variable_types(Rushbrooke())) diff --git a/test/relations/test_entanglement.jl b/test/relations/test_entanglement.jl index 874e399..668f768 100644 --- a/test/relations/test_entanglement.jl +++ b/test/relations/test_entanglement.jl @@ -453,6 +453,25 @@ end @test !any(r -> r isa CFTEntanglementSlope, applicable_relations(b_irfp; slope...)) end +function covariance(N) + A = zeros(2N, 2N) + for j in 1:N + A[2j - 1, 2j] = -2.0 + end + for j in 1:(N - 1) + A[2j, 2j + 1] = -2.0 + end + A = A - transpose(A) + F = svd(A) + Γ = F.U * F.Vt + return (Γ .- transpose(Γ)) ./ 2 +end +function ed_entropy(Γ, sites) + idx = vcat(([2j - 1, 2j] for j in sites)...) + ν = eigvals(Hermitian(im .* Γ[idx, idx])) + return free_fermion_entanglement_entropy([(1 + real(x)) / 2 for x in ν if real(x) > 0]) +end + @testset "the open-chain form against exact diagonalisation and a published constant" begin # The closed forms were otherwise checked only by retyping them in the test, so a # prefactor transcribed wrongly from the source would be transcribed wrongly here @@ -462,27 +481,6 @@ end c = 1 / 2 c₁_source = log(2) * 0.6904133 # Table 1, converted from their bits to nats - function covariance(N) - A = zeros(2N, 2N) - for j in 1:N - A[2j - 1, 2j] = -2.0 - end - for j in 1:(N - 1) - A[2j, 2j + 1] = -2.0 - end - A = A - transpose(A) - F = svd(A) - Γ = F.U * F.Vt - return (Γ .- transpose(Γ)) ./ 2 - end - function ed_entropy(Γ, sites) - idx = vcat(([2j - 1, 2j] for j in sites)...) - ν = eigvals(Hermitian(im .* Γ[idx, idx])) - return free_fermion_entanglement_entropy([ - (1 + real(x)) / 2 for x in ν if real(x) > 0 - ]) - end - N = 256 Γ = covariance(N) # Read c₁ back THROUGH the relation, so the production chord is what is exercised. @@ -507,26 +505,6 @@ end # `CFTEntanglementSlope` carries no `L`, so nothing tells a caller how large `ℓ` # may be, and out of range it returns a number rather than refusing. Exactly on # the ED ground state of the critical uniform chain, block at an open end. - function covariance(N) - A = zeros(2N, 2N) - for j in 1:N - A[2j - 1, 2j] = -2.0 - end - for j in 1:(N - 1) - A[2j, 2j + 1] = -2.0 - end - A = A - transpose(A) - F = svd(A) - Γ = F.U * F.Vt - return (Γ .- transpose(Γ)) ./ 2 - end - function ed_entropy(Γ, sites) - idx = vcat(([2j - 1, 2j] for j in sites)...) - ν = eigvals(Hermitian(im .* Γ[idx, idx])) - return free_fermion_entanglement_entropy([ - (1 + real(x)) / 2 for x in ν if real(x) > 0 - ]) - end function fitslope(xs, ys) x̄, ȳ = sum(xs) / length(xs), sum(ys) / length(ys) return sum((xs .- x̄) .* (ys .- ȳ)) / sum((xs .- x̄) .^ 2) @@ -546,15 +524,17 @@ end ) ys = [ed_entropy(Γ, 1:l) for l in ls] chord = [(N / π) * sin(π * l / N) for l in ls] - near = filter(l -> l <= N ÷ 4, ls) - yn = [ed_entropy(Γ, 1:l) for l in near] + idx = findall(l -> l <= N ÷ 4, ls) + near, yn = ls[idx], ys[idx] + cn = chord[idx] return ( plain_full=fitslope(log.(ls), ys), chord_full=fitslope(log.(chord), ys), plain_near=fitslope(log.(near), yn), + chord_near=fitslope(log.(cn), yn), ) end - s64, s128 = slopes(64), slopes(128) + s64, s128, s256 = slopes(64), slopes(128), slopes(256) # In the regime the present relation is for, it is right. @test isapprox(s128.plain_near, ncuts * c / 6; atol=0.005) @@ -566,13 +546,17 @@ end # The chord form is in domain over the same whole chain. @test isapprox(s128.chord_full, ncuts * c / 6; atol=0.01) - # And the two errors are different KINDS. The chord's halves when L doubles, so it - # is a finite-size correction; the other does not move, so it is a wrong law. A - # threshold alone could not tell those apart. - e64, e128 = abs(s64.chord_full - c / 6), abs(s128.chord_full - c / 6) - @test e128 < 0.6 * e64 - p64, p128 = abs(s64.plain_full - c / 6), abs(s128.plain_full - c / 6) - @test p128 > 0.9 * p64 + # And the two errors are different KINDS, which one ratio cannot show and two can. + # An open chain's leading finite-size correction goes as 1/L, so the chord error + # should fall by about a half per doubling and keep doing it; a wrong law has no + # reason to move at all. A threshold could not separate those. + e = [abs(s.chord_full - c / 6) for s in (s64, s128, s256)] + @test e[2] < 0.6 * e[1] + @test e[3] < 0.6 * e[2] + @test e[3] < e[2] < e[1] + q = [abs(s.plain_full - c / 6) for s in (s64, s128, s256)] + @test q[2] > 0.9 * q[1] + @test q[3] > 0.9 * q[2] # The relation itself: the chord slope reads the central charge back. @test isapprox( @@ -582,10 +566,23 @@ end c; atol=0.06, ) - # It is the general form and `CFTEntanglementSlope` the ℓ ≪ L limit, so on one - # number they agree exactly; what differs is which number the data gives them. - @test residual(CFTEntanglementChordSlope(); dS_dlogchord=c / 6, c=c, ncuts=1) == - residual(CFTEntanglementSlope(); dS_dlogℓ=c / 6, c=c, ncuts=1) + # The limit claim, exercised rather than asserted: restricted to ℓ ≪ L the two + # abscissas coincide, so the same data gives both relations the same slope. + @test isapprox(s128.plain_near, s128.chord_near; rtol=0.05) + @test isapprox( + solve(CFTEntanglementSlope(), Val(:c); dS_dlogℓ=s128.plain_near, ncuts=ncuts), + solve( + CFTEntanglementChordSlope(), Val(:c); dS_dlogchord=s128.chord_near, ncuts=ncuts + ); + rtol=0.05, + ) + + # A region with no cuts has no slope, and the residual would not depend on `c` at + # all, so both refuse rather than passing for every central charge. + @test_throws "ncuts = 0" residual( + CFTEntanglementChordSlope(); dS_dlogchord=0.0, c=9.9, ncuts=0 + ) + @test_throws "ncuts = 0" residual(CFTEntanglementSlope(); dS_dlogℓ=0.0, c=9.9, ncuts=0) end @testset "every route from an entropy to c returns the same c" begin diff --git a/test/relations/test_scaling.jl b/test/relations/test_scaling.jl index 889b8e8..21ae6cf 100644 --- a/test/relations/test_scaling.jl +++ b/test/relations/test_scaling.jl @@ -660,26 +660,34 @@ end # stated in units of its own published error rather than a tolerance picked to # pass: it sits about one standard error from the exact value. measured, σ = -0.4029, 0.0204 - @test abs(residual(CriticalCorrelationDecay(); dlogC_dlogr=measured, x_m=x_m)) < 1.5σ - @test abs(residual(CriticalCorrelationDecay(); dlogC_dlogr=measured, x_m=x_m)) > 0.5σ - - # The typical probe is worse conditioned and the assertion says so: on the same - # chains an uncorrected slope sits about three of its own errors high, so it is - # NOT consistent with 1/2 the way the average probe is with -2x_m. - @test abs(residual(ActivatedCriticalCorrelation(); dloglogC_dlogr=0.56, ψ=ψ)) > - 1.5 * 0.02 + r = abs(residual(CriticalCorrelationDecay(); dlogC_dlogr=measured, x_m=x_m)) + @test r < 1.5σ + # A guard on the literals above, not a floor on accuracy: it catches pasting the + # exact value in place of the measured one. A genuinely better measurement would + # trip it and should arrive with this line removed. + @test r > 0.5σ + + # The typical probe is worse conditioned and the numbers say so: the uncorrected + # slope sits three of its own errors ABOVE 1/2, where the average probe's is + # already within one of -2x_m. Signed and stated as the ratio, so a sign flip in + # the relation cannot satisfy it the way a bare magnitude bound can. + @test residual(ActivatedCriticalCorrelation(); dloglogC_dlogr=0.56, ψ=ψ) / 0.02 ≈ 3.0 atol = + 0.05 # They read different exponents off the same ground state and cannot substitute # for each other, which is why there are two relations rather than one. @test variable_types(CriticalCorrelationDecay()) == (ScalingDimension,) @test variable_types(ActivatedCriticalCorrelation()) == (ActivatedExponent,) @test isdisjoint( - Set(variables(CriticalCorrelationDecay())), - Set(variables(ActivatedCriticalCorrelation())), + variables(CriticalCorrelationDecay()), variables(ActivatedCriticalCorrelation()) ) - # `ψ` is the same exponent the size and gap statements carry, reached from a - # third scale rather than being a fourth number. - @test ActivatedExponent in variable_types(ActivatedFiniteSizeScaling()) - @test ActivatedExponent in variable_types(ActivatedDynamicalScaling()) + # And they are split by reduction the way the autocorrelations beside them are, so + # the average and the typical reading cannot meet at one node. + @test also_constrains(CriticalCorrelationDecay()) == + (DisorderAveraged{ConnectedSpinCorrelation},) + @test also_constrains(ActivatedCriticalCorrelation()) == + (Typical{ConnectedSpinCorrelation},) + @test CriticalCorrelationDecay() in + relations_constraining(DisorderAveraged{ConnectedSpinCorrelation}) end From 946420575ade5304a6828199423e9fa7e970e816 Mon Sep 17 00:00:00 2001 From: sotashimozono Date: Mon, 14 Sep 2026 14:14:52 +0000 Subject: [PATCH 3/3] Cut the prose to what a reader would get wrong without The three docstrings ran 91 lines for three one-line relations. The measured tables were evidence for the change, not for someone calling it: the numbers live in the commit that added them and in the tests that assert their behaviour. What is left is the formula, the equation it comes from, and the one thing a reader would otherwise get wrong, which is the domain limit for the chord slope and the average-versus-typical split for the correlation pair. 42 lines. Also: version back to 0.7.13. The gate wants a single step from the base, and main is 0.7.12; I had given the review-response commit a bump of its own, but the bump is per pull request, not per commit. Co-Authored-By: Claude Opus 5 (1M context) --- Project.toml | 2 +- src/relations/entanglement.jl | 33 ++++---------- src/relations/scaling.jl | 69 ++++++++--------------------- src/structure/scaling_dimensions.jl | 12 ++--- test/relations/test_derivation.jl | 10 ++--- test/relations/test_entanglement.jl | 17 +++---- test/relations/test_scaling.jl | 21 +++------ 7 files changed, 49 insertions(+), 115 deletions(-) diff --git a/Project.toml b/Project.toml index f23a853..db5c6f4 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "AbstractQAtlas" uuid = "dcea2817-62f8-4a75-b498-1b50a9ed1e4d" -version = "0.7.14" +version = "0.7.13" authors = ["sota shimozono "] [deps] diff --git a/src/relations/entanglement.jl b/src/relations/entanglement.jl index ad8ba31..5bed9f2 100644 --- a/src/relations/entanglement.jl +++ b/src/relations/entanglement.jl @@ -56,33 +56,16 @@ Variables: `S2`, `purity`. """ CFTEntanglementChordSlope <: AbstractRelation -The same slope on a FINITE chain, taken against the chord rather than the -distance (Calabrese & Cardy, [CalabreseCardy2004](@cite)): +`dS/d(ln[(L/π) sin(πℓ/L)]) = ncuts · c/6` (Calabrese & Cardy, +[CalabreseCardy2004](@cite)). -`dS/d(ln[(L/π) sin(πℓ/L)]) = ncuts · c/6`. +The finite-chain form. [`CFTEntanglementSlope`](@ref) carries no `L` and so cannot +say how large `ℓ` may be, and out of range it returns a number rather than refusing: +near `ℓ = L` the complement is a few sites and purity alone caps `S`, which no +`ncuts` describes. This form holds over the whole chain and reduces to that one as +`ℓ/L → 0`. -[`CFTEntanglementSlope`](@ref) has no `L` and so no way to say how large `ℓ` may -be, and the answer degrades smoothly rather than refusing: at `ℓ → L` the block's -complement is a few sites and purity alone caps `S`, so the quantity has stopped -being a bulk block entropy and no `ncuts` is right for it. MEASURED on exact -free-fermion ground states of the critical uniform chain, block at an open end, -`ncuts = 1`, so `c/6 = 0.08333`: - -| L | against `ln ℓ`, whole chain | against `ln[chord]`, whole chain | -|---|---|---| -| 64 | 0.03495 | 0.08941 | -| 128 | 0.03332 | 0.08663 | -| 256 | 0.03308 | 0.08502 | - -The chord column's error falls by a factor 0.54 then 0.51 as `L` doubles, toward the -`1/L` an open chain's leading correction carries; the other column moves 5% then -0.7%, because it is not a finite-size correction but a different law. Restricted -to `ℓ ≤ L/4` the two agree, which is the regime [`CFTEntanglementSlope`](@ref) is -for and does not state. - -Reduces to it as `ℓ/L → 0`, the chord tending to `ℓ`, so this is the general form -and that one the limit. Variables: `dS_dlogchord` (caller-computed slope against -`ln[(L/π) sin(πℓ/L)]`), `c`, `ncuts`. +Variables: `dS_dlogchord` (caller-computed), `c`, `ncuts`. """ @relation :entanglement CFTEntanglementChordSlope(dS_dlogchord, c::CentralCharge, ncuts) = begin diff --git a/src/relations/scaling.jl b/src/relations/scaling.jl index 5b449f8..a06f42d 100644 --- a/src/relations/scaling.jl +++ b/src/relations/scaling.jl @@ -185,37 +185,18 @@ magnetization, both in §4.1 and both stated for free boundary conditions. """ CriticalCorrelationDecay <: AbstractRelation -The AVERAGE correlation at criticality against DISTANCE. The scaling law is -[IgloiMonthus2005](@cite) Eq. (A.6), `C(r) = b^{-2x_m} C(r/b)`, stated for a -critical point generally; §A.3 carries it to an infinite-randomness one, where -the average is dominated by rare pairs and the same conclusion holds: - -`⟨C(r)⟩ ∼ r^{-2 x_m}`, so `d ln⟨C⟩/d ln r = -2 x_m`. - -A third scale, and a third statement. [`ActivatedFiniteSizeScaling`](@ref) is -about the system size `L` and [`ActivatedDynamicalScaling`](@ref) about the -correlation length `ξ`, and the argument that keeps those apart, that `ξ` -diverges at criticality and constrains nothing there while `L` is the only scale -left, applies again: the distance `r` inside a large system at `δ = 0` is neither. - -This is the clean probe of the two. MEASURED on exact free-fermion ground states -of the critical random chain, pairs at the centre, `r = 2…128`, fitted with the -correction term left open rather than chosen: - -| L | samples | no correction | with `1/ln r` | with `r^{-1/2}` | -|---|---|---|---|---| -| 256 | 800 | -0.3774(186) | -0.4148(326) | -0.4813(633) | -| 512 | 500 | -0.4029(204) | -0.4123(362) | -0.4101(673) | -| 1024 | 200 | -0.4062(304) | -0.3935(523) | -0.3438(1018) | - -against `-2 x_m = -(3 - √5)/2 = -0.38197`, the random transverse-field Ising -chain's value, which follows from that source's `β = (3 - √5)/2` and `ν = 2`. -Eight of the nine entries sit within 1.03 of their own quoted error; the ninth, -`L = 256` under `r^{-1/2}`, is 1.57 away and is the noisiest column at the -smallest size. So the uncorrected fit is already consistent and a correction buys -nothing, which is not true of [`ActivatedCriticalCorrelation`](@ref). - -Variables: `dlogC_dlogr` (caller-computed slope of `ln⟨C⟩` against `ln r`), `x_m`. +`⟨C(r)⟩ ∼ r^{-2 x_m}`, so `d ln⟨C⟩/d ln r = -2 x_m` ([IgloiMonthus2005](@cite), +Eq. (A.6); §A.3 carries it to an infinite-randomness point, where the average is +dominated by rare pairs). + +The AVERAGE correlation. Not interchangeable with +[`ActivatedCriticalCorrelation`](@ref), the typical one: the correlation function is +non-self-averaging here, so the two read different exponents off one ground state. + +Distance is a third scale, neither the `L` of [`ActivatedFiniteSizeScaling`](@ref) +nor the `ξ` of [`ActivatedDynamicalScaling`](@ref), which diverges at criticality. + +Variables: `dlogC_dlogr` (caller-computed), `x_m`. """ @relation :scaling CriticalCorrelationDecay(dlogC_dlogr, x_m::ScalingDimension) = dlogC_dlogr + 2 * x_m @@ -223,27 +204,15 @@ Variables: `dlogC_dlogr` (caller-computed slope of `ln⟨C⟩` against `ln r`), """ ActivatedCriticalCorrelation <: AbstractRelation -The TYPICAL correlation at criticality against distance, which is a stretched -exponential rather than a power. [IgloiMonthus2005](@cite) Eq. (6.26) derives it -for the random singlet phase, `-ln C_typ(r) ∼ ln Ω_L ∼ r^{1/2}`, where the -exponent is `ψ = 1/2`; written with `ψ` it is the infinite-randomness statement: - -`exp⟨ln|C(r)|⟩ ∼ exp(-a r^ψ)`, so `d ln(-⟨ln|C|⟩)/d ln r = ψ`. - -The same `ψ` as [`ActivatedFiniteSizeScaling`](@ref) and -[`ActivatedDynamicalScaling`](@ref), read off a third scale. The typical and the -average correlation are different probes of the fixed point rather than two routes -to one number, which is why both relations exist. +`exp⟨ln|C(r)|⟩ ∼ exp(-a r^ψ)`, so `d ln(-⟨ln|C|⟩)/d ln r = ψ` +([IgloiMonthus2005](@cite), Eq. (6.26), derived there for the random singlet phase +where `ψ = 1/2`). -Worse conditioned than [`CriticalCorrelationDecay`](@ref) on the same chains: its -uncorrected slope sits about three of its own errors above `1/2`, where the -average probe's is already within one. The average correlation is -non-self-averaging from the typical one (the source's own conclusion below -Eq. 6.26), so these are two probes of the fixed point and not two routes to one -number. +The TYPICAL correlation, and the worse-conditioned of the two probes: on the same +chains its uncorrected slope sits three of its own errors above `1/2` where +[`CriticalCorrelationDecay`](@ref)'s is already within one. -Variables: `dloglogC_dlogr` (caller-computed slope of `ln(-⟨ln|C|⟩)` against -`ln r`), `ψ`. +Variables: `dloglogC_dlogr` (caller-computed), `ψ`. """ @relation :scaling ActivatedCriticalCorrelation(dloglogC_dlogr, ψ::ActivatedExponent) = dloglogC_dlogr - ψ diff --git a/src/structure/scaling_dimensions.jl b/src/structure/scaling_dimensions.jl index b6087dd..a4db051 100644 --- a/src/structure/scaling_dimensions.jl +++ b/src/structure/scaling_dimensions.jl @@ -157,14 +157,10 @@ exponent is derived by [`critical_exponents`](@ref). at construction rather than one per call, and at an infinite-randomness fixed point there is no finite `d + z` to confuse it with anyway. - The hazard is not hypothetical and it has an address. For this same class, - QAtlas reads the effective central charge as - `fetch(Universality(:IsingSDRG), CentralCharge(); d = 2)`, which refuses any - other `d`, because there `d` is the 1+1D CFT's dimension. A consumer who - carries that `d` straight here is the case above. Note also that the value - comes back under `CentralCharge` although it is the effective one, so the - quantity this package keeps separate as [`EffectiveCentralCharge`](@ref) is - not separate on that side either. Tracked as #157. + Concretely: `fetch(Universality(:IsingSDRG), CentralCharge(); d = 2)` in QAtlas + refuses any other `d`, `d` being the CFT's dimension there, and returns the + EFFECTIVE charge under `CentralCharge`. Carrying either number straight here is + the case above. Tracked as #157. Arguments are promoted to a common type; pass `Rational`s where the values are rational. `ψ ≤ 0` is refused rather than accepted: it names a conventional diff --git a/test/relations/test_derivation.jl b/test/relations/test_derivation.jl index 53d66f8..9bb70a5 100644 --- a/test/relations/test_derivation.jl +++ b/test/relations/test_derivation.jl @@ -411,12 +411,10 @@ end filter(t -> t === nothing, last.(variable_slots(ActivatedMomentGrowth()))) ) - # A general law and its own restricted-domain limit must NOT be grouped. Every - # family above is a set of alternatives of which at most one holds; these two are - # both true, on different data, and `_families_satisfied` only needs one member of - # a family to agree. Grouping them would let the in-domain chord route excuse an - # out-of-domain plain-ℓ route rather than reporting the disagreement, which is the - # whole reason the chord relation exists. + # A general law and its own limit must NOT be grouped. A family is alternatives of + # which at most one holds, and one member agreeing satisfies it; these two are both + # true on different data, so grouping would let the chord route excuse an + # out-of-domain plain-ℓ one instead of reporting the disagreement. @test law_family(CFTEntanglementSlope()) !== law_family(CFTEntanglementChordSlope()) # And the exemplar stays split. diff --git a/test/relations/test_entanglement.jl b/test/relations/test_entanglement.jl index 668f768..1e3512b 100644 --- a/test/relations/test_entanglement.jl +++ b/test/relations/test_entanglement.jl @@ -502,9 +502,7 @@ end end @testset "the chord slope is in domain over the whole chain and ln ℓ is not" begin - # `CFTEntanglementSlope` carries no `L`, so nothing tells a caller how large `ℓ` - # may be, and out of range it returns a number rather than refusing. Exactly on - # the ED ground state of the critical uniform chain, block at an open end. + # Exact ED ground state of the critical uniform chain, block at an open end. function fitslope(xs, ys) x̄, ȳ = sum(xs) / length(xs), sum(ys) / length(ys) return sum((xs .- x̄) .* (ys .- ȳ)) / sum((xs .- x̄) .^ 2) @@ -546,10 +544,8 @@ end # The chord form is in domain over the same whole chain. @test isapprox(s128.chord_full, ncuts * c / 6; atol=0.01) - # And the two errors are different KINDS, which one ratio cannot show and two can. - # An open chain's leading finite-size correction goes as 1/L, so the chord error - # should fall by about a half per doubling and keep doing it; a wrong law has no - # reason to move at all. A threshold could not separate those. + # Two ratios, not one: a 1/L correction halves per doubling and keeps doing it, + # a wrong law does not move. A threshold cannot separate those. e = [abs(s.chord_full - c / 6) for s in (s64, s128, s256)] @test e[2] < 0.6 * e[1] @test e[3] < 0.6 * e[2] @@ -566,8 +562,8 @@ end c; atol=0.06, ) - # The limit claim, exercised rather than asserted: restricted to ℓ ≪ L the two - # abscissas coincide, so the same data gives both relations the same slope. + # The limit: for ℓ ≪ L the two abscissas coincide, so one dataset gives both + # relations the same charge. @test isapprox(s128.plain_near, s128.chord_near; rtol=0.05) @test isapprox( solve(CFTEntanglementSlope(), Val(:c); dS_dlogℓ=s128.plain_near, ncuts=ncuts), @@ -577,8 +573,7 @@ end rtol=0.05, ) - # A region with no cuts has no slope, and the residual would not depend on `c` at - # all, so both refuse rather than passing for every central charge. + # With no cuts the residual does not depend on `c`, so both refuse. @test_throws "ncuts = 0" residual( CFTEntanglementChordSlope(); dS_dlogchord=0.0, c=9.9, ncuts=0 ) diff --git a/test/relations/test_scaling.jl b/test/relations/test_scaling.jl index 21ae6cf..e9eb4ce 100644 --- a/test/relations/test_scaling.jl +++ b/test/relations/test_scaling.jl @@ -639,15 +639,12 @@ end end @testset "the two critical correlation probes are separate statements" begin - # `x_m` and `ψ` are what a ground state actually yields, through the correlation - # function at criticality as a function of DISTANCE. That is a third scale: not - # `L`, which `ActivatedFiniteSizeScaling` is about, and not `ξ`, which diverges - # at criticality and constrains nothing there. + # What a ground state yields: the correlation function at criticality against + # distance, which is a third scale, neither `L` nor the divergent `ξ`. x_m = (3 - sqrt(5)) / 4 # the golden-mean IRFP value, IgloiMonthus2005 ψ = 1 / 2 - # Exact on a pure power law: the relation IS the slope, so this pins the - # arithmetic and the sign, and the anchor is the literature value. + # The anchor is the literature value; these pin the sign and the factor 2. @test solve(CriticalCorrelationDecay(), Val(:x_m); dlogC_dlogr=-2 * x_m) ≈ x_m @test solve(CriticalCorrelationDecay(), Val(:dlogC_dlogr); x_m=x_m) ≈ -2 * x_m @test check(CriticalCorrelationDecay(); dlogC_dlogr=-0.38197, x_m=x_m, atol=1e-5) @@ -667,23 +664,19 @@ end # trip it and should arrive with this line removed. @test r > 0.5σ - # The typical probe is worse conditioned and the numbers say so: the uncorrected - # slope sits three of its own errors ABOVE 1/2, where the average probe's is - # already within one of -2x_m. Signed and stated as the ratio, so a sign flip in - # the relation cannot satisfy it the way a bare magnitude bound can. + # Signed and as a ratio, so a sign flip cannot satisfy it the way a magnitude + # bound can: the uncorrected typical slope is three of its own errors ABOVE 1/2. @test residual(ActivatedCriticalCorrelation(); dloglogC_dlogr=0.56, ψ=ψ) / 0.02 ≈ 3.0 atol = 0.05 - # They read different exponents off the same ground state and cannot substitute - # for each other, which is why there are two relations rather than one. + # Different exponents off one ground state, so neither substitutes for the other. @test variable_types(CriticalCorrelationDecay()) == (ScalingDimension,) @test variable_types(ActivatedCriticalCorrelation()) == (ActivatedExponent,) @test isdisjoint( variables(CriticalCorrelationDecay()), variables(ActivatedCriticalCorrelation()) ) - # And they are split by reduction the way the autocorrelations beside them are, so - # the average and the typical reading cannot meet at one node. + # Split by reduction, so the two readings cannot meet at one node. @test also_constrains(CriticalCorrelationDecay()) == (DisorderAveraged{ConnectedSpinCorrelation},) @test also_constrains(ActivatedCriticalCorrelation()) ==