Skip to content

Commit 0e419a4

Browse files
authored
Update docs and release notes for 0.8.0 (#463)
1 parent 31161db commit 0e419a4

File tree

6 files changed

+73
-65
lines changed

6 files changed

+73
-65
lines changed

docs/src/versions.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Release Notes
22

33
## v0.8.0
4-
(In development)
4+
(Nov 30, 2025)
55

66
**Breaking changes** in this release.
77

@@ -33,7 +33,7 @@
3333
* Add missing 3D support for [`q_space_grid`](@ref) ([#457](@ref)).
3434
* If user-provided lattice vectors do not match crystallographic conventions,
3535
suggest the use of [`standardize`](@ref) ([#461](@ref)).
36-
* Fixes to [`SpinWaveTheoryKPM`](@ref) ([#462](@ref)).
36+
* Improve robustness of [`SpinWaveTheoryKPM`](@ref) ([#462](@ref)).
3737

3838
## v0.7.8
3939
(Jul 1, 2025)

ext/PlottingExt/PlotIntensities.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -338,9 +338,9 @@ is:
338338
339339
To export the figure to a PDF file, use the CairoMakie backend and call
340340
341-
```jl
342-
fig = plot_intensities(..., interpolate=true)
343-
save("myfile.pdf", fig)
341+
```julia
342+
fig = plot_intensities(..., interpolate=true)
343+
save("myfile.pdf", fig)
344344
```
345345
346346
The choice `interpolate=true` significantly reduces file sizes for plots

src/Binning/Binning.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ The convention for the binning scheme is that:
2525
2626
A `value` can be binned by computing its bin index:
2727
28-
```jl
28+
```julia
2929
coords = covectors * value
3030
bin_ix = 1 .+ floor.(Int64, (coords .- binstart) ./ binwidth)
3131
```

src/KPM/SpinWaveTheoryKPM.jl

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,43 @@
11
"""
22
SpinWaveTheoryKPM(sys::System; measure, regularization=1e-8, tol=nothing,
3-
niters=nothing, niters_bounds=10, method=:lanczos)
3+
niters=nothing, method=:lanczos)
44
55
A variant of [`SpinWaveTheory`](@ref) that estimates [`intensities`](@ref) using
66
iterated matrix-vector products. By avoiding direct matrix diagonalization, this
77
method reduces computational cost from cubic to linear-scaling in the system
88
size ``N``. Large system sizes can arise, e.g., for models of quenched disorder
99
or models with nearly incommensurate ordering wavevectors.
1010
11-
Computational cost scales like ``𝒪(N M + M^2)``. The number of iterations ``M``
12-
may be specified directly with the `niters` parameter. Alternatively, one may
13-
specify a dimensionless error tolerance `tol` such that `M ≈ -2 log10(tol) Δϵ /
14-
fwhm` where `Δϵ` is the estimated spectral bandwidth of excitations and `fwhm`
15-
is the full width at half maximum of the user-supplied broadening `kernel`.
16-
Common choices for `tol` are `0.05` (more speed) or `0.01` (more accuracy).
17-
Either `tol` or `niters` is required. The parameter `niters_bounds` selects the
18-
Krylov subspace dimension to be used for estimating spectral bounds.
19-
20-
!!! warning "Accuracy considerations"
21-
Energy-space resolution scales inversely with the number ``M`` of
22-
iterations. Such broadening errors can usually be well controlled via the
23-
tolerance parameter `tol`. A more serious problem is intensity loss at bands
24-
with small excitation energy, e.g., in the viscinity of Goldstone modes. In
25-
certain cases this missing intensity will be due to numerical roundoff error
26-
that cannot be fixed by increasing the number of iterations ``M``.
27-
28-
Two `method` options are available, `:lanczos` and `:kpm`. Lanczos is generally
29-
preferred, as it achieves near-optimal accuracy for a given number of iterations
30-
[1, 2]. The Lanczos implementation builds on earlier research using the Kernel
31-
Polynomial Method [3], which may be of historical interest.
11+
Energy resolution is controlled by the dimensionless `tol` parameter. Common
12+
choices are `tol=0.05` (more speed) or `0.01` (more accuracy). This will
13+
determine the number of iterations as `M ≈ -2 log10(tol) Δϵ / fwhm`, where `Δϵ`
14+
is the estimated spectral bandwidth of excitations and `fwhm` is the full width
15+
at half maximum of the user-supplied broadening `kernel`. Computational cost
16+
scales like ``𝒪(N M + M^2)``. Use `niters` instead of `tol` to directly specify
17+
``M``.
18+
19+
!!! warning "Intensity loss at low-energy excitations"
20+
21+
Not all numerical artifacts can be resolved by reducing `tol`. In particular,
22+
there may be unavoidable intensity loss at low-energy excitations, e.g., near
23+
Goldstone modes. This type of error originates from finite numerical precision
24+
and ill-conditioning of the dynamical matrix.
25+
26+
!!! tip "Consider `SampledCorrelations` when calculating powder averages"
27+
28+
Spin wave theory requires an independent calculation for each ``𝐪`` point of
29+
interest. Consequently, it can be very slow to sample a 3D volume of
30+
``𝐪``-space, e.g., as required for a [`powder_average`](@ref). A compelling
31+
alternative may be [`SampledCorrelations`](@ref). It uses real-time spin
32+
dynamics to calculate structure factor data over the entire 3D grid of
33+
commensurate ``𝐪``-vectors in one shot. This may provide a considerable speedup
34+
at the cost of: limited ``𝐪``-space resolution and stochastic error due to
35+
statistical sampling.
36+
37+
Two choices of `method` are possible. Lanczos is the default because it achieves
38+
appears near-optimal accuracy at fixed iterations ``M`` [1, 2] and can detect
39+
energetic instabilities. The alternative, `method=:kpm`, implements the Kernel
40+
Polynomial Method as described in Ref. [3], which may be of historical interest.
3241
3342
## References
3443

test/test_lswt.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -613,7 +613,7 @@ end
613613
res = intensities(swt, qs; energies, kernel)
614614
# println(round.(res.data; digits=12))
615615
data_ref = [0.042551644188 0.148531187027 0.042551644188; 0.123710785142 0.944407715529 0.123710785142; 1.079575108835 1.261286085876 1.079575108835; 0.492703854423 0.168505531387 0.492703854423; 0.087770506619 0.06066521855 0.087770506619; 0.034461978527 0.030825188879 0.034461978527; 0.018214262744 0.018585357642 0.018214262744; 0.011230027228 0.012410289203 0.011230027228; 0.007607320239 0.008868510563 0.007607320239; 0.005490942587 0.006651305827 0.005490942587; 0.004148615687 0.005172181047 0.004148615687]
616-
isapprox(res.data, data_ref; atol=1e-9)
616+
@test isapprox(res.data, data_ref; atol=1e-9)
617617
end
618618

619619

test/test_scga.jl

Lines changed: 35 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,23 @@
1-
@testitem "diamond_lattice" begin
2-
# test against JuliaSCGA (S. Gao)
3-
res_jscga = [0.7046602277469309, 0.8230846832863896, 0.23309034250417973, 0.40975668535137943, 0.8474163786642979,
4-
0.8230846832694241, 0.723491683211756, 0.5939752161027589, 0.6506966347286152, 0.8012263819500781]
1+
@testitem "Square lattice" begin
2+
latvecs = lattice_vectors(1, 1, 10, 90, 90, 90)
3+
cryst = Crystal(latvecs, [[0, 0, 0]])
4+
s = 1
5+
sys = System(cryst, [1 => Moment(; s, g=1)], :dipole; seed=0)
6+
set_exchange!(sys, -1, Bond(1, 1, [1, 0, 0]))
7+
set_exchange!(sys, 0.5, Bond(1, 1, [1, 1, 0]))
8+
set_exchange!(sys, 0.25, Bond(1, 1, [2, 0, 0]))
9+
measure = ssf_perp(sys)
10+
kT = 27.5*meV_per_K
11+
scga = SCGA(sys; measure, kT, dq=1/8)
12+
path = q_space_path(cryst, [[-1, -1, 0], [-0.04, -1, 0]], 9)
13+
res = Sunny.intensities_static(scga, path)
14+
ref_jscga = [0.3578208506624103, 0.3850668587212228, 0.4211633125595451, 0.3930558742921638, 0.3586715762816389,
15+
0.3775147329089877, 0.4188431376589759, 0.4009835864744404, 0.36119385315852837]
16+
ref_jscga *= s^2 * Sunny.natoms(cryst)
17+
@test isapprox(vec(res.data)/2, ref_jscga; rtol=1e-5)
18+
end
19+
20+
@testitem "Diamond lattice" begin
521
a = 8.5031 # (Å)
622
latvecs = lattice_vectors(a, a, a, 90, 90, 90)
723
cryst = Crystal(latvecs, [[0, 0, 0]], 227; choice="1")
@@ -13,34 +29,18 @@
1329
measure = ssf_perp(sys)
1430
kT = 15*meV_per_K
1531
scga = SCGA(sys; measure, kT, dq=1/8)
16-
γ = s^2 * Sunny.natoms(cryst)
1732
grid = q_space_grid(cryst, [1, 0, 0], range(0, 3.6, 5), [0, 1, 0], range(0, 0.9, 2))
1833
res = intensities_static(scga, grid)
19-
@test isapprox(vec(res.data)/γ, res_jscga; rtol=1e-8)
20-
end
21-
22-
@testitem "square_lattice" begin
23-
# test against JuliaSCGA (S. Gao)
24-
res_jscga = [0.3578208506624103, 0.3850668587212228, 0.4211633125595451, 0.3930558742921638, 0.3586715762816389,
25-
0.3775147329089877, 0.4188431376589759, 0.4009835864744404, 0.36119385315852837]
26-
a = 1 # (Å)
27-
latvecs = lattice_vectors(a, a, 10a, 90, 90, 90)
28-
cryst = Crystal(latvecs, [[0, 0, 0]])
29-
sys = System(cryst, [1 => Moment(; s=1, g=1)], :dipole; seed=0)
30-
set_exchange!(sys, -1, Bond(1, 1, [1, 0, 0]))
31-
set_exchange!(sys, 0.5, Bond(1, 1, [1, 1, 0]))
32-
set_exchange!(sys, 0.25, Bond(1, 1, [2, 0, 0]))
33-
measure = ssf_perp(sys)
34-
kT = 27.5*meV_per_K
35-
scga = SCGA(sys; measure, kT, dq=1/8)
36-
path = q_space_path(cryst, [[-1, -1, 0], [-0.04, -1, 0]], 9)
37-
res = Sunny.intensities_static(scga, path)
38-
@test isapprox(vec(res.data)/2, res_jscga; rtol=1e-5)
34+
ref_jscga = [0.7046602277469309, 0.8230846832863896, 0.23309034250417973, 0.40975668535137943, 0.8474163786642979,
35+
0.8230846832694241, 0.723491683211756, 0.5939752161027589, 0.6506966347286152, 0.8012263819500781]
36+
ref_jscga *= s^2 * Sunny.natoms(cryst)
37+
@test isapprox(vec(res.data), ref_jscga; rtol=1e-8)
3938
end
4039

4140
@testitem "MgCr2O4" begin
4241
using LinearAlgebra
43-
# Reproduce calculation in Conlon and Chalker, PRL 102, 237206 (2009)
42+
# Reproduce calculation in Bai et al., Phys. Rev. Lett. 122, 097201 (2019).
43+
# See also Conlon and Chalker, Phys. Rev. B 81, 224413 (2010).
4444
latvecs = lattice_vectors(8.3342, 8.3342, 8.3342, 90, 90, 90)
4545
positions = [[1/2, 1/2, 1/2]]
4646
cryst = Crystal(latvecs, positions, 227)
@@ -59,18 +59,15 @@ end
5959
scga = SCGA(sys; measure, kT, dq=1/4)
6060
grid = q_space_grid(cryst, [1, 0, 0], range(-1.5, 1.5, 4), [0, 1, 0], range(-1.5, 1.5, 4))
6161
res = Sunny.intensities_static(scga, grid)
62-
res_cc = [2.4168819 1.237733 1.237733 2.4168819;
63-
1.237733 0.16242284 0.16242284 1.237733;
64-
1.237733 0.16242284 0.16242284 1.237733;
65-
2.4168819 1.237733 1.237733 2.4168819]
66-
γ = s*(s+1) / det(primitive_cell(cryst)) # Adapt to normalization convention of C+C
67-
@test isapprox(vec(res.data), γ*vec(res_cc); rtol=1e-3)
62+
ref = [2.4168819 1.237733 1.237733 2.4168819;
63+
1.237733 0.16242284 0.16242284 1.237733;
64+
1.237733 0.16242284 0.16242284 1.237733;
65+
2.4168819 1.237733 1.237733 2.4168819]
66+
ref *= s*(s+1) / det(primitive_cell(cryst)) # Differing normalization convention
67+
@test isapprox(vec(res.data), vec(ref); rtol=1e-3)
6868
end
6969

7070
@testitem "Arbitrary Anisotropy" begin
71-
# test against JuliaSCGA (S. Gao)
72-
res_jscga = [0.700944539713289, 0.6175127864675868, 0.5205893697530085, 0.48172879530096047,
73-
0.5219040226511135, 0.6218544838522482, 0.7110417061581527, 0.738269833048121]
7471
latvecs = lattice_vectors(1, 1, 10, 90, 90, 90)
7572
cryst = Crystal(latvecs, [[0, 0 ,0]], 1)
7673
sys = System(cryst, [1 => Moment(; s=1, g=1)], :dipole; seed=0)
@@ -91,7 +88,9 @@ end
9188
scga = SCGA(sys; measure, kT, dq=1/8)
9289
path = q_space_path(cryst, [[0.125, 0.625, 0], [1, 0.625, 0]], 8)
9390
res = Sunny.intensities_static(scga, path)
94-
@test isapprox(vec(res.data), res_jscga; rtol=1e-6)
91+
ref_jscga = [0.700944539713289, 0.6175127864675868, 0.5205893697530085, 0.48172879530096047,
92+
0.5219040226511135, 0.6218544838522482, 0.7110417061581527, 0.738269833048121]
93+
@test isapprox(vec(res.data), ref_jscga; rtol=1e-6)
9594
end
9695

9796
@testitem "Ferrimagnetic chain sum rule" begin

0 commit comments

Comments
 (0)