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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "AbstractQAtlas"
uuid = "dcea2817-62f8-4a75-b498-1b50a9ed1e4d"
version = "0.7.10"
version = "0.7.11"
authors = ["sota shimozono <shimozono-sota631@g.ecc.u-tokyo.ac.jp>"]

[deps]
Expand Down
12 changes: 8 additions & 4 deletions src/relations/entanglement.jl
Original file line number Diff line number Diff line change
Expand Up @@ -308,10 +308,14 @@ depends on it.
end

@experimental """
Eq. (24)'s scaling function is not settled here: `f` is caller-supplied, no
independent oracle checks the form, and the only case pinned is its conformal
one-harmonic reduction. No bound on `f` follows from the geometry either, so a
positive but small value returns a negative entropy rather than a refusal
Eq. (24)'s scaling function is not settled here. Reflection symmetry and the
normalisation are checked where `f` is still a callable, in
[`finite_size_entropy_report`](@ref); its FORM is not, and cannot be from here,
since separating the higher harmonics needs a disorder average this package does
not compute. The only form pinned is the one-harmonic reduction, which is
[`CFTEntanglementPBC`](@ref). Reaching the relation directly bypasses both checks,
`f` arriving as a number that can only be asked to be positive, so a small one
returns a negative entropy
""" InfiniteRandomnessEntanglementPBC

"""
Expand Down
48 changes: 47 additions & 1 deletion src/relations/region_entropy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,48 @@ function _finite_size_row!(out, rel, A::Region, vars, atol)
return _finite_size_row!(out, rel, (A,), vars, atol)
end

# Eq. (24) states `f` through `f(v) = Σₖ Aₖ sin((2k-1)πv)` under `Σₖ Aₖ(2k-1)π = 1`,
# and two consequences hold whatever the coefficients are: every basis function is
# symmetric about `v = 1/2`, and the normalisation is `f'(0) = 1`. The FORM is not
# checked, and a future check of it must still admit harmonics past `k = 1`, those
# being what Eq. (24) says over the conformal reduction rather than an error in it.
#
# Asked on the grid `f` is read at, `ℓ/N`, so a component the check cannot see cannot
# reach a row either. `f'(0)` is estimated from `f(1/N)` and `f(2/N)` by Richardson,
# error falling as `N^-4`, rather than from a point near zero that an `f` fitted on a
# finite chain has no reason to be defined at.
function _check_symmetry_at(f, v)
a, m = f(v), f(1 - v)
(a isa Real && m isa Real && isfinite(a) && isfinite(m)) || error(
"finite_size_entropy_report: f($v) = $a and f($(1 - v)) = $m, but the scaling " *
"function must be a finite real on the grid it is read at.",
)
isapprox(a, m; rtol=1e-8, atol=1e-12) || error(
"finite_size_entropy_report: f($v) = $a but f($(1 - v)) = $m, so the scaling " *
"function is not symmetric about v = 1/2 and is not in Eq. (24)'s family.",
)
return nothing
end

# Loose on purpose: it separates an order-unity factor, which is what a missing `1/π`
# or a chord passed whole looks like, from the estimate's own truncation. Tightening
# it would start refusing a strongly non-conformal `f` on a ring too short to resolve
# its harmonics, which is a statement about the grid and not about `f`.
function _check_normalisation(f, N)
s1, s2 = f(1 / N), f(2 / N)
(s1 isa Real && s2 isa Real && isfinite(s1) && isfinite(s2)) || error(
"finite_size_entropy_report: f(1/$N) = $s1 and f(2/$N) = $s2, but the scaling " *
"function must be a finite real on the grid it is read at.",
)
slope = (4 * s1 * N - s2 * N / 2) / 3
isapprox(slope, 1; atol=0.1) || error(
"finite_size_entropy_report: f'(0) is about $slope, not 1, so the scaling " *
"function breaks Eq. (24)'s normalisation. A constant factor shifts ln[L f] " *
"into c₁′ and passes; f is the dimensionless sin(πv)/π, not the chord.",
)
return nothing
end

"""
finite_size_entropy_report(b::Bag, bc::BoundaryCondition; c₁, kwargs...)
-> Vector{RegionFiniteSizeRow}
Expand Down Expand Up @@ -466,6 +508,8 @@ function finite_size_entropy_report(
c̃ = get(b, VariableKey(EffectiveCentralCharge), nothing)
ξ = get(b, VariableKey(CorrelationLength), nothing)
(c === nothing && c̃ === nothing) && return out
uses_f = c̃ !== nothing && f !== nothing && bc isa PBC
f_normalised = false
for pair in sort!(collect(ents); by=p -> (length(p.first), repr(p.first)))
A, S = pair.first, pair.second
(isempty(A) || !(eltype(A.sites) <: Integer) || eltype(A.sites) === Bool) &&
Expand All @@ -484,7 +528,9 @@ function finite_size_entropy_report(
)
end
end
if c̃ !== nothing && f !== nothing && bc isa PBC && n == 2
if uses_f && n == 2
f_normalised || (_check_normalisation(f, bc.N); f_normalised=true)
_check_symmetry_at(f, ℓ / bc.N)
_finite_size_row!(
out,
InfiniteRandomnessEntanglementPBC(),
Expand Down
64 changes: 64 additions & 0 deletions test/relations/test_region_entropy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,70 @@ end
@test Set(kinds) == Set([:CFTEntanglementPBC, :InfiniteRandomnessEntanglementPBC])
end

@testset "the scaling function is checked against what Eq. (24) says it is" begin
c̃, c₁′, N = log(2) / 2, 0.31, 1024
conf(v) = sin(π * v) / π
S̄(ℓ) = (c̃ / 3) * log(N * conf(ℓ / N)) + c₁′
b = bag(entanglement_entropy(Region(1:300...)) => S̄(300), EffectiveCentralCharge => c̃)
call(g) = finite_size_entropy_report(b, PBC(N); c₁=0.0, f=g, c₁′=c₁′, atol=1e-12)

# Eq. (24) is about the random ring NOT being the conformal one, and the higher
# harmonics are the whole difference, so a two-term `f` must be accepted. A check
# that took only `sin(πv)/π` would pass everything below and refuse the physics.
two(v) = (1 / π - 0.06) * sin(π * v) + 0.02 * sin(3π * v)
@test !isapprox(two(0.3), conf(0.3); rtol=1e-3)
rows = call(two)
@test length(rows) == 1
@test !only(rows).pass # entropy built on `conf`, so it fails

@test_throws "not symmetric" call(v -> sin(π * v) / π + 0.05 * v)

# A constant factor lands in `c₁′` rather than in the residual, so the normalisation
# is the only thing that can see it.
@test_throws "f'(0)" call(v -> sin(π * v)) # the chord, missing the 1/π
@test_throws "f'(0)" call(v -> 2 * sin(π * v) / π)

# Both are asked on the grid `f` is read at. A component invisible there is
# invisible to the row too, so there is no sample set to slip between: this `f` is
# symmetric at three round points and wildly asymmetric at the one the bag uses.
sneak(v) = sin(π * v) / π + 0.08 * (sin(20π * v) - 0.5 * sin(40π * v))
@test isapprox(sneak(0.25), sneak(0.75); atol=1e-12)
@test_throws "not symmetric" call(sneak)

# And the normalisation is estimated from `1/N` and `2/N`, points on that same
# grid, so an `f` fitted on a finite chain and undefined off it is still accepted.
function fitted(v)
(1 / N <= v <= 1 - 1 / N) || error("outside the fitted range")
return two(v)
end
@test length(call(fitted)) == 1

# An `f` that is not a finite real says so, rather than being reported as an
# asymmetry between two NaNs or reaching the kernel and failing on `isless`.
@test_throws "finite real" call(v -> NaN)
@test_throws "finite real" call(v -> complex(sin(π * v) / π, 0.0))

# Checked only at the points that feed a row. A whole-ring region has no cuts, so
# `f` is never read and must not be refused: the report is empty, not an error.
whole = bag(entanglement_entropy(Region(1:N...)) => 1.0, EffectiveCentralCharge => c̃)
@test isempty(
finite_size_entropy_report(whole, PBC(N); c₁=0.0, f=v -> -1.0, c₁′=c₁′, atol=1e-12)
)

# Same for the other two ways of not being read.
ent = entanglement_entropy(Region(1:300...))
open_chain = bag(ent => 1.0, EffectiveCentralCharge => c̃)
@test isempty(
finite_size_entropy_report(
open_chain, OBC(N); c₁=0.0, f=v -> -1.0, c₁′=c₁′, atol=1e-12
),
)
clean = bag(ent => 1.0, CentralCharge => 0.5)
@test length(
finite_size_entropy_report(clean, PBC(N); c₁=0.0, f=v -> -1.0, atol=1e-12)
) == 1
end

@testset "an infinite chain gives the slope relations their derivative exactly" begin
c, c₁ = 0.5, 0.4785
S(ℓ) = (c / 3) * log(ℓ) + c₁
Expand Down
Loading