-
Notifications
You must be signed in to change notification settings - Fork 19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix wraparound bug #246
Fix wraparound bug #246
Conversation
Here's a few plots in support of this PR. In this one, set in real time, the old behavior is in orange (both new and old are computed from the exact same trajectories, which causes the old one to have half the length). Essentially the bug is that the left and right portions of the blue line (new behavior) were superimposed and added together to obtain the (old) orange line. The green line is the new behavior with an additional cosine squared filter applied, to eliminate the sharp feature. On the level of the spectrum, not much has changed in this example (this PR does not necessarily fix any particular artifact; but it's a correctness fix so it may fix things in situations we don't know about) N.B. the old buggy one is also half energy resolution due to the superimposition I mentioned before. For this example, I've deliberately gone to low temperature so that the thermal averaging doesn't cause the signal to decay on its own. If you're familiar with NMR notations, the thermal averaging with Langevin at finite temperature (and ImplicitMidpoint at zero temperature) is capable of capturing T2-type decay--but I have suppressed this using low temperature. Using the cosine filter fakes a T1-type decay; but a true T1-type decay can also be implemented by using a finite temperature in ImplicitMidpoint. |
This is really nice work. Although Sam's original motivation may have been real-time correlations, it looks like this approach will solve the "energy bleeding" problem in Plots generated using David's test script in the details below: latvecs = lattice_vectors(1, 1, 1.2, 90, 90, 90)
cryst = Crystal(latvecs, [[0,0,0]])
units = Sunny.Units.theory
seed = 101
sys_rcs = System(cryst, (10, 10, 1), [SpinInfo(1, S=1, g=1)], :dipole; units, seed)
## Model parameter
J = 1.0
h = 0.5
D = 0.05
## Set exchange interactions
set_exchange!(sys_rcs, J, Bond(1, 1, [1, 0, 0]))
## Single-ion anisotropy
Ss = spin_operators(sys_rcs, 1)
set_onsite_coupling!(sys_rcs, D*Ss[3]^2, 1)
## External field
set_external_field!(sys_rcs, h*[0,0,3])
##
randomize_spins!(sys_rcs)
minimize_energy!(sys_rcs)
out = minimize_energy!(sys_rcs)
println(out)
Δt = 0.025
kT = 0.2
λ = 0.1
langevin = Langevin(Δt; kT, λ)
for _ in 1:10_000
step!(sys_rcs, langevin)
end
sc = dynamical_correlations(sys_rcs; Δt=2Δt, nω=100, ωmax=5.0)
add_sample!(sc, sys_rcs)
nsamples = 50
for _ in 1:nsamples
for _ in 1:1_000
step!(sys_rcs, langevin)
end
add_sample!(sc, sys_rcs)
end
density = 100
qs, xticks = reciprocal_space_path(sc.crystal, [(0.0, 0.5, 0.0), (0.5, 0.5, 0.0), (1.0, 0.5, 0.0)], density)
data = intensities_interpolated(sc, qs, intensity_formula(sc, :trace; kT); interpolation=:round);
formula = intensity_formula(sc, :trace; kT=Inf)
qs = available_wave_vectors(sc)
is = intensities_interpolated(sc, qs, formula; negative_energies=true)
total_weight = sum(is) / *(size(sys_rcs.coherents)...)
println("Total spectral weight (classical): ", total_weight)
heatmap(1:size(data, 1), available_energies(sc), data; axis=(xticks=xticks,), colorrange=(0.0, 0.5)) The core idea of the new approach is that the end of a finite-length dynamical trajectory should not be treated as periodically wrapping to the beginning of the trajectory. One can still take advantage of FFT from time to frequency space if one adds a zero-buffer to the end of the trajectory. The above-mentioned Details are in the attached note. With some revision, it can evolve into a specification of the Sunny behavior. |
I agree this is very nice work. @Lazersmoke shared a few examples explicitly examining the convergence of LL to LSWT as T->0. The behavior with respect to satisfying the quantum sum rule by applying the C2Q factor was much the same as before. There is a particular temperature where it the procedure works well. Below this temperature, the sum is overestimated. In other words, addressing this low-T "problem" seems to be orthogonal to the question of merging this PR. In addition to having the procedure documented precisely, a few practical questions that should be resolved before merging.
|
Thanks for the list David!
The updated algorithm is a little bit slower, since there's an additional fft/ifft to apply the time-domain rescaling by 1/(T-t). It's not avoidable as far as I know. This is currently out-of-place but could be made in-place as an optimization at a later time without affecting UI. The size of
I think Kip wanted The windowing, and 3 & 4 are up to how Kip would like to handle these. This branch is specifically for this PR and edits are allowed so feel free to adjust as you like! |
Also shorten a bit to reduce file sizes
In a collaborative effort, we have updated this branch in the following ways:
With this PR, David's test script linked above now works very nicely. The intensity scale is the same as on |
Just spent an hour two running this through its paces (trying different models, combinations of options). Everything looks good from my perspective. I think this will be a big improvement. |
Curious users can learn about it from an extensive comment in the source code.
For experts, a keyword argument with the same name still exists, but now accepts a function rather than symbol.
Slightly revised note specifying the new algorithm: |
Here are some more polished results. I've modified David's script from before to run on a 20x20 lattice, and to now collect 1000 samples. Updated scriptusing Sunny, GLMakie
Makie.inline!(true)
latvecs = lattice_vectors(1, 1, 1.2, 90, 90, 90)
cryst = Crystal(latvecs, [[0,0,0]])
units = Sunny.Units.theory
seed = 101
sys_rcs = System(cryst, (20, 20, 1), [SpinInfo(1, S=1, g=1)], :dipole; units, seed)
## Model parameter
J = 1.0
h = 0.5
D = 0.05
## Set exchange interactions
set_exchange!(sys_rcs, J, Bond(1, 1, [1, 0, 0]))
## Single-ion anisotropy
Ss = spin_operators(sys_rcs, 1)
set_onsite_coupling!(sys_rcs, D*Ss[3]^2, 1)
## External field
set_external_field!(sys_rcs, h*[0,0,3])
##
randomize_spins!(sys_rcs)
minimize_energy!(sys_rcs)
out = minimize_energy!(sys_rcs)
println(out)
Δt = 0.025
kT = 0.2
λ = 0.1
langevin = Langevin(Δt; kT, λ)
for _ in 1:10_000
step!(sys_rcs, langevin)
end
# Add `process_trajectory=:symmetrize` here
sc = dynamical_correlations(sys_rcs; Δt=2Δt, nω=100, ωmax=5.0)
add_sample!(sc, sys_rcs)
nsamples = 1000
for _ in 1:nsamples
for _ in 1:1_000
step!(sys_rcs, langevin)
end
add_sample!(sc, sys_rcs)
end
density = 100
qs, xticks = reciprocal_space_path(sc.crystal, [(0.0, 0.5, 0.0), (0.5, 0.5, 0.0), (1.0, 0.5, 0.0)], density)
data = intensities_interpolated(sc, qs, intensity_formula(sc, :trace; kT); interpolation=:round);
formula = intensity_formula(sc, :trace; kT=Inf)
qs = available_wave_vectors(sc)
is = intensities_interpolated(sc, qs, formula; negative_energies=true)
total_weight = sum(is) / *(size(sys_rcs.coherents)...)
println("Total spectral weight (classical): ", total_weight)
# Include a factor of 2 for `process_trajectory=:symmetrize``
heatmap(1:size(data, 1), available_energies(sc), data; axis=(xticks=xticks,), colorrange=(0.0, 0.5)) Some takeaways:
Here are the numerical costs to collect 10 samples, for the three different calculation methods (let's assume
So this PR offers a considerable speedup at fixed |
This PR fixes the wraparoud bug by using the
1/(T-|t|)
algorithm. It also incidentally allows the edge casenomega = 1
, which was not previously an allowed value fordynamical_correlations
.Compared to previously, the trajectory will only be computed up to half the previous length (the rest is the wraparound zero padding).
In the interest of breaking up PRs, this PR fixes the same
1/nomega
normalization bug as #237 (but in the way which is relevant here) and this is the same wraparound bug as fixed in #217 , but this time without computing both (Sx,Sy) and (Sy,Sx).