-
Notifications
You must be signed in to change notification settings - Fork 19
/
Broadening.jl
127 lines (109 loc) · 4.43 KB
/
Broadening.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
abstract type AbstractBroadening end
struct Broadening{F <: Function, G <: Union{Nothing, Function}} <: AbstractBroadening
# ϵ is the intrinsic excitation energy, ω is the nominal energy transfer in
# measured intensities I(q, ω).
kernel :: F # Function mapping x = (ω - ϵ) to an intensity scaling factor
integral :: G # Definite integral of kernel from 0 to x
fwhm :: Float64
name :: String
function Broadening(kernel; integral=nothing, fwhm=NaN, name="Custom")
if !isnan(fwhm)
kernel(fwhm/2) ≈ kernel(-fwhm/2) ≈ kernel(0)/2 || error("Invalid full width at half maximum")
end
if !isnothing(integral)
if !isnan(fwhm)
ϵ = 1e-6 * fwhm
(integral(ϵ) - integral(-ϵ)) / 2ϵ ≈ kernel(0) || error("Invalid integral function at 0")
(integral(fwhm+ϵ) - integral(fwhm-ϵ)) / 2ϵ ≈ kernel(fwhm) || error("Invalid integral function at $fwhm")
end
integral(0) ≈ 0 || error("Definite integral must start from 0")
integral(Inf) ≈ -integral(-Inf) ≈ 1/2 || error("Full integral must be 1")
end
return new{typeof(kernel), typeof(integral)}(kernel, integral, fwhm, name)
end
end
struct NonstationaryBroadening{F <: Function} <: AbstractBroadening
kernel :: F # (ϵ, ω) -> intensity
end
function (b::Broadening)(ϵ, ω)
b.kernel(ω - ϵ)
end
function (b::NonstationaryBroadening)(ϵ, ω)
b.kernel(ϵ, ω)
end
function Base.show(io::IO, kernel::Broadening)
(; name, fwhm) = kernel
print(io, "$name kernel")
if !isnan(fwhm)
print(io, ", fwhm=", number_to_simple_string(fwhm; digits=3))
end
end
function Base.show(io::IO, ::NonstationaryBroadening)
print(io, "NonstationaryBroadening")
end
"""
lorentzian(; fwhm)
Returns the function `(Γ/2) / (π*(x^2+(Γ/2)^2))` where `Γ = fwhm ` is the full
width at half maximum.
"""
function lorentzian(; fwhm)
Γ = fwhm
kernel(x) = (Γ/2) / (π*(x^2+(Γ/2)^2))
integral(x) = atan(2x/Γ)/π
return Broadening(kernel; integral, fwhm, name="Lorentzian")
end
"""
gaussian(; {fwhm, σ})
Returns the function `exp(-x^2/2σ^2) / √(2π*σ^2)`. Either `fwhm` or `σ` must be
specified, where `fwhm = (2.355...) * σ` is the full width at half maximum.
"""
function gaussian(; fwhm=nothing, σ=nothing)
if sum(.!isnothing.((fwhm, σ))) != 1
error("Either fwhm or σ must be specified.")
end
fwhm = @something fwhm 2√(2log(2))*σ
σ = fwhm/2√(2log(2))
kernel(x) = exp(-x^2/2σ^2) / √(2π*σ^2)
integral(x) = erf(x/√2σ)/2
return Broadening(kernel; integral, fwhm, name="Gaussian")
end
function broaden!(data::AbstractArray{Ret}, bands::BandIntensities{Ret}; energies, kernel) where Ret
energies = collect(Float64, energies)
issorted(energies) || error("energies must be sorted")
nω = length(energies)
nq = size(bands.qpts.qs)
(nω, nq...) == size(data) || error("Argument data must have size ($nω×$(sizestr(bands.qpts)))")
cutoff = 1e-12 * Statistics.quantile(norm.(vec(bands.data)), 0.95)
for iq in CartesianIndices(bands.qpts.qs)
for (ib, b) in enumerate(view(bands.disp, :, iq))
norm(bands.data[ib, iq]) < cutoff && continue
for (iω, ω) in enumerate(energies)
data[iω, iq] += kernel(b, ω) * bands.data[ib, iq]
end
# If this broadening is a bottleneck, one can terminate when kernel
# magnitude is small. This may, however, affect reference data used
# in test suite.
#=
iω0 = searchsortedfirst(energies, b)
for iω in iω0:lastindex(energies)
ω = energies[iω]
x = kernel(b, ω) * bands.data[ib, iq]
data[iω, iq] += x
x < cutoff && break
end
for iω in iω0-1:-1:firstindex(energies)
ω = energies[iω]
x = kernel(b, ω) * bands.data[ib, iq]
data[iω, iq] += x
x < cutoff && break
end
=#
end
end
return data
end
function broaden(bands::BandIntensities; energies, kernel)
data = zeros(eltype(bands.data), length(energies), size(bands.qpts.qs)...)
broaden!(data, bands; energies, kernel)
return Intensities(bands.crystal, bands.qpts, collect(Float64, energies), data)
end