From 3410dec2b4eef714361d7aa0959309aaa03a2110 Mon Sep 17 00:00:00 2001 From: Kipton Barros Date: Fri, 4 Aug 2023 09:46:59 -0600 Subject: [PATCH 01/12] Wavevectors should be specified in absolute units. --- examples/fei2_tutorial.jl | 78 +++---- src/Intensities/Binning.jl | 5 +- src/Intensities/Interpolation.jl | 23 +- src/Intensities/PowderAveraging.jl | 11 +- .../SampledCorrelations.jl | 2 +- src/SpinWaveTheory/SWTCalculations.jl | 163 ++++++-------- src/SpinWaveTheory/SpinWaveTheory.jl | 63 ++---- src/SpinWaveTheory/kpm.jl | 3 +- src/Sunny.jl | 2 +- src/Symmetry/Crystal.jl | 20 +- src/Symmetry/SymmetryAnalysis.jl | 5 +- src/System/Ewald.jl | 18 +- test/test_lswt.jl | 209 ++++++++---------- 13 files changed, 250 insertions(+), 352 deletions(-) diff --git a/examples/fei2_tutorial.jl b/examples/fei2_tutorial.jl index a8a83bbf7..0b2fe92d1 100644 --- a/examples/fei2_tutorial.jl +++ b/examples/fei2_tutorial.jl @@ -246,19 +246,21 @@ plot_spins(sys_min; arrowlength=2.5, linewidth=0.75, arrowsize=1.5) swt = SpinWaveTheory(sys_min); # Next, select a sequence of wavevectors that will define a piecewise linear -# path in reciprocal space. The coefficients of each $q$-vector are given in -# reciprocal lattice units (RLU), i.e., are multiples of the recripocal lattice -# vectors. The function [`connected_path`](@ref) will linearly sample between -# the provided $q$-points with some `density`. -points = [[0, 0, 0], - [1, 0, 0], - [0, 1, 0], - [1/2, 0, 0], - [0, 1, 0], - [0, 0, 0]] -labels = string.(points) -density = 600 -path, markers = connected_path(swt, points, density); +# interpolation in reciprocal lattice units (RLU). Sunny expects wavevectors in +# absolute units, i.e., inverse angstroms. To convert from RLU, multiply by the +# $3×3$ matrix `cryst.recipvecs`, which provides the reciprocal lattice vectors +# for the conventional unit cell. + +points_rlu = [[0,0,0], [1,0,0], [0,1,0], [1/2,0,0], [0,1,0], [0,0,0]] +points = [cryst.recipvecs*p for p in points_rlu] + +# The function [`connected_path`](@ref) will linearly sample between the +# provided $q$-points with given `density`. Also keep track of location and +# names of the special $𝐪$-points for plotting purposes. + +density = 50 +path, numbers = connected_path(points, density); +xticks = (numbers, ["[0,0,0]", "[1,0,0]", "[0,1,0]", "[1/2,0,0]", "[0,1,0]", "[0,0,0]"]) # The dispersion relation can be calculated by providing `dispersion` with a # `SpinWaveTheory` and a list of wave vectors. For each wave vector, @@ -288,8 +290,7 @@ disp, intensity = intensities_bands(swt, path; formula); # These can be plotted in GLMakie. fig = Figure() -ax = Axis(fig[1,1]; xlabel="𝐪", ylabel="Energy (meV)", - xticks=(markers, labels), xticklabelrotation=π/6) +ax = Axis(fig[1,1]; xlabel="𝐪", ylabel="Energy (meV)", xticks, xticklabelrotation=π/6) ylims!(ax, 0.0, 7.5) xlims!(ax, 1, size(disp, 1)) for i in axes(disp)[2] @@ -297,47 +298,36 @@ for i in axes(disp)[2] end fig -# To make comparisons with inelastic neutron scattering (INS) data, it is helpful to employ -# an empirical broadening kernel. +# To make comparisons with inelastic neutron scattering (INS) data, it is +# helpful to employ an empirical broadening kernel, e.g., a +# [`lorentzian`](@ref). γ = 0.15 # width in meV broadened_formula = intensity_formula(swt; kernel=lorentzian(γ)) -# The broadened intensity can be calculated for using -# [`intensities_broadened`](@ref). One must space a path in $𝐪$-space and an -# energy range. +# The [`intensities_broadened`](@ref) function requires an energy range in +# addition to the $𝐪$-space path. -energies = collect(0:0.01:7.5) # 0 < ω < 7.5 (meV). +energies = collect(0:0.01:10) # 0 < ω < 10 (meV). is1 = intensities_broadened(swt, path, energies, broadened_formula); -# In real FeI$_2$, there will be competing magnetic domains associated with the -# three possible orientations of the symmetry-broken ground state, which derive -# from the three-fold rotational symmetry of a triangular lattice. -# -# The matrix $A$ defined below implements a 120° clockwise rotation on -# wavevectors $𝐪$ defined in RLU. +# A real FeI$_2$ sample will exhibit competing magnetic domains associated with +# spontaneous symmetry breaking of the 6-fold rotational symmetry of the +# triangular lattice. Note that the wavevectors $𝐪$ and $-𝐪$ are equivalent in +# the structure factor, which leaves three distinct domain orientations, which +# are related by 120° rotations. Rather than rotating the spin configuration +# directly, on can rotate the $𝐪$-space path. Below, we collect and plot +# intensity data that is averaged over all three possible orientations. -A = [-1 -1 0; - 1 0 0; - 0 0 1] -@assert A^3 ≈ [1 0 0; 0 1 0; 0 0 1] # Rotation by 360° is the identity - -# We will average the intensity data over the three symmetry-equivalent -# rotations of the $𝐪$-space path. - -function rotated_intensity(R) - rotated_path = [R * q for q in path] - intensities_broadened(swt, rotated_path, energies, broadened_formula) -end +sθ, cθ = sincos(2π/3) +R = [cθ -sθ 0; sθ cθ 0; 0 0 1] # 120° rotation about the ẑ axis -@assert is1 == rotated_intensity(A^0) -is2 = rotated_intensity(A) -is3 = rotated_intensity(A^2) +is2 = intensities_broadened(swt, [R*q for q in path], energies, broadened_formula) +is3 = intensities_broadened(swt, [R*R*q for q in path], energies, broadened_formula) is_averaged = (is1 + is2 + is3) / 3 fig = Figure() -ax = Axis(fig[1,1], xlabel="(H,0,0)", ylabel="Energy (meV)", xticks=(markers, labels), - ) +ax = Axis(fig[1,1]; xlabel="(H,0,0)", ylabel="Energy (meV)", xticks, xticklabelrotation=π/6) heatmap!(ax, 1:size(is_averaged, 1), energies, is_averaged) fig diff --git a/src/Intensities/Binning.jl b/src/Intensities/Binning.jl index 3259f7c93..48e919cdb 100644 --- a/src/Intensities/Binning.jl +++ b/src/Intensities/Binning.jl @@ -131,6 +131,7 @@ function binning_parameters_aabb(params) return lower_aabb_q, upper_aabb_q end +# FIXME: various conversions go away. Always absolute units. """ If `params` expects to bin values `(k,ω)` in absolute units, then calling @@ -247,6 +248,7 @@ function unit_resolution_binning_parameters(ωvals::AbstractVector{Float64}) return ωstart, ωend, ωbinwidth end +# FIXME: remove `units` arg -- always absolute """ slice_2D_binning_parameter(sc::SampledCorrelations, cut_from_q, cut_to_q, cut_bins::Int64, cut_width::Float64; plane_normal = [0,0,1],cut_height = cutwidth, units = :absolute) @@ -408,7 +410,6 @@ function intensities_binned(sc::SampledCorrelations, params::BinningParameters; output_intensities = zeros(Float64,numbins...) output_counts = zeros(Float64,numbins...) ωvals = ωs(sc) - recip_vecs = 2π*inv(sc.crystal.latvecs)' # Find an axis-aligned bounding box containing the histogram. # The AABB needs to be in q-space because that's where we index @@ -452,7 +453,7 @@ function intensities_binned(sc::SampledCorrelations, params::BinningParameters; # Which is the analog of this scattering mode in the first BZ? base_cell = CartesianIndex(mod1.(cell.I,Ls)...) q .= ((cell.I .- 1) ./ Ls) # q is in R.L.U. - k .= recip_vecs * q # But binning is done in absolute units + k .= sc.crystal.recipvecs * q # FIXME -- no scaling factor needed, q will already in absolute units. for (iω,ω) in enumerate(ωvals) if isnothing(integrated_kernel) # `Delta-function energy' logic # Figure out which bin this goes in diff --git a/src/Intensities/Interpolation.jl b/src/Intensities/Interpolation.jl index 6b85414da..f5cc74573 100644 --- a/src/Intensities/Interpolation.jl +++ b/src/Intensities/Interpolation.jl @@ -106,13 +106,12 @@ function pruned_stencil_info(sc::SampledCorrelations, qs, interp::InterpolationS @assert sum(counts) == length(m_info) # Calculate corresponding q (RLU) and k (global) vectors - recip_vecs = 2π*inv(sc.crystal.latvecs)' # Note, qs will be in terms of sc.crystal by this point, not origin_crystal qs_all = map(ms_all) do ms map(m -> m ./ sc.latsize, ms) end ks_all = map(qs_all) do qs - map(q -> recip_vecs * q, qs) + map(q -> sc.crystal.recipvecs * q, qs) # FIXME: should q still by in sc.crystal, or in absolute units? end return (; qs_all, ks_all, idcs_all, counts) @@ -219,17 +218,16 @@ end """ - connected_path(recip_vecs, qs::Vector, density) + connected_path(qs, density) -Takes a list of wave vectors, `qs`, and builds an expanded list of wave vectors -that traces a path through the provided points. Also returned is a list of -marker indices corresponding to the input points. The `density` parameter is -given in samples per inverse Å. +Returns two things: First, a list of wavevectors that linearly interpolates +between the points `qs` and second, a list of markers that index the locations +of each original interpolation point. -Instead of `recip_vecs`, the first argument may be either a `SampledCorrelations` or -a `SpinWaveTheory`. +More sample-points will be returned between `q`-values that are further apart, +and this is controlled by the `density` parameter. """ -function connected_path(recip_vecs, qs::Vector, density) +function connected_path(qs::Vector, density) @assert length(qs) >= 2 "The list `qs` should include at least two wavevectors." qs = Vec3.(qs) @@ -238,7 +236,7 @@ function connected_path(recip_vecs, qs::Vector, density) for i in 1:length(qs)-1 push!(markers, length(path)+1) q1, q2 = qs[i], qs[i+1] - dist = norm(recip_vecs*(q1 - q2)) + dist = norm(q1 - q2) npoints = round(Int, dist*density) for n in 1:npoints push!(path, (1 - (n-1)/npoints)*q1 + (n-1)*q2/npoints) @@ -249,6 +247,3 @@ function connected_path(recip_vecs, qs::Vector, density) return (path, markers) end -connected_path(sc::SampledCorrelations, qs::Vector, density) = connected_path(2π*inv(sc.crystal.latvecs)', qs, density) -connected_path(sw::SpinWaveTheory, qs::Vector, density) = connected_path(sw.recipvecs_chem, qs, density) - diff --git a/src/Intensities/PowderAveraging.jl b/src/Intensities/PowderAveraging.jl index 12601ff2c..04b3764a9 100644 --- a/src/Intensities/PowderAveraging.jl +++ b/src/Intensities/PowderAveraging.jl @@ -77,12 +77,11 @@ function powder_average_binned(sc::SampledCorrelations, radial_binning_parameter output_intensities = zeros(Float64,r_bin_count,ω_bin_count) output_counts = zeros(Float64,r_bin_count,ω_bin_count) ωvals = ωs(sc) - recip_vecs = 2π*inv(sc.crystal.latvecs)' # Loop over every scattering vector Ls = sc.latsize if isnothing(bzsize) - bzsize = (1,1,1) .* ceil(Int64,rend/eigmin(recip_vecs)) + bzsize = (1,1,1) .* ceil(Int64,rend/eigmin(sc.crystal.recipvecs)) # TODO: ceil(Int64, a/b) -> div(a, b, RoundUp) end for cell in CartesianIndices(Ls .* bzsize) base_cell = CartesianIndex(mod1.(cell.I,Ls)...) @@ -93,18 +92,18 @@ function powder_average_binned(sc::SampledCorrelations, radial_binning_parameter # Figure out which radial bin this scattering vector goes in # The spheres are surfaces of fixed |k|, with k in absolute units - k = recip_vecs * q + k = sc.crystal.recipvecs * q # FIXME: q should already be in absolute units r_coordinate = norm(k) # Check if the radius falls within the histogram - rbin = 1 .+ floor.(Int64,(r_coordinate .- rstart) ./ rbinwidth) - if rbin <= r_bin_count && rbin >= 1 + rbin = @. 1 + floor(Int64, (r_coordinate - rstart) / rbinwidth) # TODO: @. 1 + div(r_coordinate-rstart, rbinwidth, RoundDown) + if r1 <= bin <= r_bin_count # If we are energy-broadening, then scattering vectors outside the histogram # in the energy direction need to be considered if isnothing(integrated_kernel) # `Delta-function energy' logic # Check if the ω falls within the histogram ωbin = 1 .+ floor.(Int64,(ω .- ωstart) ./ ωbinwidth) - if ωbin <= ω_bin_count && ωbin >= 1 + if 1 <= ωbin <= ω_bin_count intensity = formula.calc_intensity(sc,k,base_cell,iω) output_intensities[rbin,ωbin] += intensity output_counts[rbin,ωbin] += 1 diff --git a/src/SampledCorrelations/SampledCorrelations.jl b/src/SampledCorrelations/SampledCorrelations.jl index adc20bc81..0553669cc 100644 --- a/src/SampledCorrelations/SampledCorrelations.jl +++ b/src/SampledCorrelations/SampledCorrelations.jl @@ -9,7 +9,7 @@ struct SampledCorrelations{N} # 𝒮^{αβ}(q,ω) data and metadata data :: Array{ComplexF64, 7} # Raw SF data for 1st BZ (numcorrelations × natoms × natoms × latsize × energy) crystal :: Crystal # Crystal for interpretation of q indices in `data` - origin_crystal :: Union{Nothing,Crystal} # Original user-specified crystal (if different from above) + origin_crystal :: Union{Nothing,Crystal} # Original user-specified crystal (if different from above) # FIXME: Eliminate Δω :: Float64 # Energy step size (could make this a virtual property) # Correlation info (αβ indices of 𝒮^{αβ}(q,ω)) diff --git a/src/SpinWaveTheory/SWTCalculations.jl b/src/SpinWaveTheory/SWTCalculations.jl index 6d7ab52a8..9abaed6eb 100644 --- a/src/SpinWaveTheory/SWTCalculations.jl +++ b/src/SpinWaveTheory/SWTCalculations.jl @@ -8,24 +8,16 @@ # where the definition of Oᵢᵃ is given in Appendix B of *Phys. Rev. B 104, 104409* const biquad_metric = 1/2 * diagm([-1, -1, -1, 1, 1, 1, 1, 1]) -""" - swt_hamiltonian_SUN! -Update the linear spin-wave Hamiltonian from the exchange interactions for the SU(N) mode. -Note that `k̃` is a 3-vector, the units of k̃ᵢ is 2π/|ãᵢ|, where |ãᵢ| is the lattice constant of the **magnetic** lattice. -""" -function swt_hamiltonian_SUN!(swt::SpinWaveTheory, k̃ :: Vector{Float64}, Hmat::Matrix{ComplexF64}) +# Set the dynamical quadratic Hamiltonian matrix in SU(N) mode. +function swt_hamiltonian_SUN!(swt::SpinWaveTheory, q, Hmat::Matrix{ComplexF64}) (; sys, s̃_mat, T̃_mat, Q̃_mat) = swt Hmat .= 0 # DD: must be zeroed out! Nm, Ns = length(sys.dipoles), sys.Ns[1] # number of magnetic atoms and dimension of Hilbert space Nf = Ns-1 N = Nf + 1 L = Nf * Nm - @assert size(Hmat) == (2*L, 2*L) - - for k̃ᵢ in k̃ - (k̃ᵢ < 0.0 || k̃ᵢ ≥ 1.0) && throw("k̃ outside [0, 1) range") - end + @assert size(Hmat) == (2L, 2L) # block matrices of `Hmat` Hmat11 = zeros(ComplexF64, L, L) @@ -59,12 +51,12 @@ function swt_hamiltonian_SUN!(swt::SpinWaveTheory, k̃ :: Vector{Float64}, Hmat: ### Bilinear exchange J = Mat3(coupling.bilin*I) - sub_i, sub_j, ΔRδ = bond.i, bond.j, bond.n + sub_i, sub_j = bond.i, bond.j + ΔR = sys.crystal.latvecs * bond.n # Displacement associated with periodic wrapping + phase = exp(im * dot(q, ΔR)) tTi_μ = s̃_mat[:, :, :, sub_i] tTj_ν = s̃_mat[:, :, :, sub_j] - phase = exp(2im * π * dot(k̃, ΔRδ)) - cphase = conj(phase) sub_i_M1, sub_j_M1 = sub_i - 1, sub_j - 1 for m = 2:N @@ -94,15 +86,15 @@ function swt_hamiltonian_SUN!(swt::SpinWaveTheory, k̃ :: Vector{Float64}, Hmat: Hmat22[sub_j_M1*Nf+nM1, sub_j_M1*Nf+mM1] += 0.5 * c2 Hmat11[sub_i_M1*Nf+mM1, sub_j_M1*Nf+nM1] += 0.5 * c3 * phase - Hmat22[sub_j_M1*Nf+nM1, sub_i_M1*Nf+mM1] += 0.5 * c3 * cphase + Hmat22[sub_j_M1*Nf+nM1, sub_i_M1*Nf+mM1] += 0.5 * c3 * conj(phase) Hmat22[sub_i_M1*Nf+mM1, sub_j_M1*Nf+nM1] += 0.5 * c4 * phase - Hmat11[sub_j_M1*Nf+nM1, sub_i_M1*Nf+mM1] += 0.5 * c4 * cphase + Hmat11[sub_j_M1*Nf+nM1, sub_i_M1*Nf+mM1] += 0.5 * c4 * conj(phase) Hmat12[sub_i_M1*Nf+mM1, sub_j_M1*Nf+nM1] += 0.5 * c5 * phase - Hmat12[sub_j_M1*Nf+nM1, sub_i_M1*Nf+mM1] += 0.5 * c5 * cphase + Hmat12[sub_j_M1*Nf+nM1, sub_i_M1*Nf+mM1] += 0.5 * c5 * conj(phase) Hmat21[sub_i_M1*Nf+mM1, sub_j_M1*Nf+nM1] += 0.5 * c6 * phase - Hmat21[sub_j_M1*Nf+nM1, sub_i_M1*Nf+mM1] += 0.5 * c6 * cphase + Hmat21[sub_j_M1*Nf+nM1, sub_i_M1*Nf+mM1] += 0.5 * c6 * conj(phase) end end @@ -147,14 +139,14 @@ function swt_hamiltonian_SUN!(swt::SpinWaveTheory, k̃ :: Vector{Float64}, Hmat: Hmat22[sub_j_M1*Nf+nM1, sub_j_M1*Nf+mM1] += 0.5 * c2 Hmat11[sub_i_M1*Nf+mM1, sub_j_M1*Nf+nM1] += 0.5 * c3 * phase - Hmat22[sub_j_M1*Nf+nM1, sub_i_M1*Nf+mM1] += 0.5 * c3 * cphase + Hmat22[sub_j_M1*Nf+nM1, sub_i_M1*Nf+mM1] += 0.5 * c3 * conj(phase) Hmat22[sub_i_M1*Nf+mM1, sub_j_M1*Nf+nM1] += 0.5 * c4 * phase - Hmat11[sub_j_M1*Nf+nM1, sub_i_M1*Nf+mM1] += 0.5 * c4 * cphase + Hmat11[sub_j_M1*Nf+nM1, sub_i_M1*Nf+mM1] += 0.5 * c4 * conj(phase) Hmat12[sub_i_M1*Nf+mM1, sub_j_M1*Nf+nM1] += 0.5 * c5 * phase - Hmat12[sub_j_M1*Nf+nM1, sub_i_M1*Nf+mM1] += 0.5 * c5 * cphase + Hmat12[sub_j_M1*Nf+nM1, sub_i_M1*Nf+mM1] += 0.5 * c5 * conj(phase) Hmat21[sub_i_M1*Nf+mM1, sub_j_M1*Nf+nM1] += 0.5 * c6 * phase - Hmat21[sub_j_M1*Nf+nM1, sub_i_M1*Nf+mM1] += 0.5 * c6 * cphase + Hmat21[sub_j_M1*Nf+nM1, sub_i_M1*Nf+mM1] += 0.5 * c6 * conj(phase) end end end @@ -192,24 +184,15 @@ function swt_hamiltonian_SUN!(swt::SpinWaveTheory, k̃ :: Vector{Float64}, Hmat: end end -""" - swt_hamiltonian_dipole! - -Update the linear spin-wave Hamiltonian from the exchange interactions for the dipole mode. -Note that `k̃` is a 3-vector, the units of k̃ᵢ is 2π/|ãᵢ|, where |ãᵢ| is the lattice constant of the **magnetic** lattice. -""" -function swt_hamiltonian_dipole!(swt::SpinWaveTheory, k̃ :: Vector{Float64}, Hmat::Matrix{ComplexF64}) +# Set the dynamical quadratic Hamiltonian matrix in dipole mode. +function swt_hamiltonian_dipole!(swt::SpinWaveTheory, q, Hmat::Matrix{ComplexF64}) (; sys, R_mat, c′_coef) = swt Hmat .= 0.0 L, Ns = length(sys.dipoles), sys.Ns[1] S = (Ns-1) / 2 biquad_res_factor = 1 - 1/S + 1/(4S^2) # rescaling factor for biquadratic interaction - @assert size(Hmat) == (2*L, 2*L) - - for k̃ᵢ in k̃ - (k̃ᵢ < 0.0 || k̃ᵢ ≥ 1.0) && throw("k̃ outside [0, 1) range") - end + @assert size(Hmat) == (2L, 2L) # Zeeman contributions (; extfield, gs, units) = sys @@ -230,9 +213,9 @@ function swt_hamiltonian_dipole!(swt::SpinWaveTheory, k̃ :: Vector{Float64}, Hm isculled && break J = Mat3(coupling.bilin*I) - sub_i, sub_j, ΔRδ = bond.i, bond.j, bond.n - phase = exp(2im * π * dot(k̃, ΔRδ)) - cphase = conj(phase) + sub_i, sub_j = bond.i, bond.j + ΔR = sys.crystal.latvecs * bond.n # Displacement associated with periodic wrapping + phase = exp(im * dot(q, ΔR)) R_mat_i = R_mat[sub_i] R_mat_j = R_mat[sub_j] @@ -240,17 +223,16 @@ function swt_hamiltonian_dipole!(swt::SpinWaveTheory, k̃ :: Vector{Float64}, Hm P = 0.25 * (Rij[1, 1] - Rij[2, 2] + 1im * (-Rij[1, 2] - Rij[2, 1])) Q = 0.25 * (Rij[1, 1] + Rij[2, 2] + 1im * (-Rij[1, 2] + Rij[2, 1])) - cP, cQ = conj(P), conj(Q) Hmat[sub_i, sub_j] += Q * phase - Hmat[sub_j, sub_i] += cQ * cphase - Hmat[sub_i+L, sub_j+L] += cQ * phase - Hmat[sub_j+L, sub_i+L] += Q * cphase + Hmat[sub_j, sub_i] += conj(Q) * conj(phase) + Hmat[sub_i+L, sub_j+L] += conj(Q) * phase + Hmat[sub_j+L, sub_i+L] += Q * conj(phase) Hmat[sub_i+L, sub_j] += P * phase - Hmat[sub_j+L, sub_i] += P * cphase - Hmat[sub_i, sub_j+L] += cP * phase - Hmat[sub_j, sub_i+L] += cP * cphase + Hmat[sub_j+L, sub_i] += P * conj(phase) + Hmat[sub_i, sub_j+L] += conj(P) * phase + Hmat[sub_j, sub_i+L] += conj(P) * conj(phase) Hmat[sub_i, sub_i] -= 0.5 * Rij[3, 3] Hmat[sub_j, sub_j] -= 0.5 * Rij[3, 3] @@ -275,41 +257,40 @@ function swt_hamiltonian_dipole!(swt::SpinWaveTheory, k̃ :: Vector{Float64}, Hm B21 = S/4*(Rʳ[1, 1] + 1im*Rʳ[1, 2] + 1im*Rʳ[2, 1] - Rʳ[2, 2]) B12 = B21 - Hmat[sub_i, sub_i] += J*biquad_res_factor * (C0*A11 + C1 * conj(C1)) - Hmat[sub_j, sub_j] += J*biquad_res_factor * (C0*A22 + C2 * conj(C2)) - Hmat[sub_i, sub_j] += J*biquad_res_factor * ((C0*A12 + C1 * conj(C2)) * phase) - Hmat[sub_j, sub_i] += J*biquad_res_factor * ((C0*A21 + C2 * conj(C1)) * cphase) - Hmat[sub_i+L, sub_i+L] += J*biquad_res_factor * (C0*A11 + C1 * conj(C1)) - Hmat[sub_j+L, sub_j+L] += J*biquad_res_factor * (C0*A22 + C2 * conj(C2)) - Hmat[sub_j+L, sub_i+L] += J*biquad_res_factor * ((C0*A12 + C1 * conj(C2)) * cphase) - Hmat[sub_i+L, sub_j+L] += J*biquad_res_factor * ((C0*A21 + C2 * conj(C1)) * phase) - - Hmat[sub_i, sub_i+L] += J*biquad_res_factor * (C1 * conj(C1)) - Hmat[sub_j, sub_j+L] += J*biquad_res_factor * (C2 * conj(C2)) - Hmat[sub_i+L, sub_i] += J*biquad_res_factor * (C1 * conj(C1)) - Hmat[sub_j+L, sub_j] += J*biquad_res_factor * (C2 * conj(C2)) - - Hmat[sub_i, sub_j+L] += J*biquad_res_factor * ((2C0*B12 + C1 * C2) * phase) - Hmat[sub_j, sub_i+L] += J*biquad_res_factor * ((2C0*B21 + C2 * C1) * cphase) - Hmat[sub_i+L, sub_j] += J*biquad_res_factor * (conj(2C0*B12 + C1 * C2) * phase) - Hmat[sub_j+L, sub_i] += J*biquad_res_factor * (conj(2C0*B21 + C2 * C1) * cphase) + Hmat[sub_i, sub_i] += J*biquad_res_factor * (C0*A11 + C1*conj(C1)) + Hmat[sub_j, sub_j] += J*biquad_res_factor * (C0*A22 + C2*conj(C2)) + Hmat[sub_i, sub_j] += J*biquad_res_factor * ((C0*A12 + C1*conj(C2)) * phase) + Hmat[sub_j, sub_i] += J*biquad_res_factor * ((C0*A21 + C2*conj(C1)) * conj(phase)) + Hmat[sub_i+L, sub_i+L] += J*biquad_res_factor * (C0*A11 + C1*conj(C1)) + Hmat[sub_j+L, sub_j+L] += J*biquad_res_factor * (C0*A22 + C2*conj(C2)) + Hmat[sub_j+L, sub_i+L] += J*biquad_res_factor * ((C0*A12 + C1*conj(C2)) * conj(phase)) + Hmat[sub_i+L, sub_j+L] += J*biquad_res_factor * ((C0*A21 + C2*conj(C1)) * phase) + + Hmat[sub_i, sub_i+L] += J*biquad_res_factor * (C1*conj(C1)) + Hmat[sub_j, sub_j+L] += J*biquad_res_factor * (C2*conj(C2)) + Hmat[sub_i+L, sub_i] += J*biquad_res_factor * (C1*conj(C1)) + Hmat[sub_j+L, sub_j] += J*biquad_res_factor * (C2*conj(C2)) + + Hmat[sub_i, sub_j+L] += J*biquad_res_factor * ((2C0*B12 + C1*C2) * phase) + Hmat[sub_j, sub_i+L] += J*biquad_res_factor * ((2C0*B21 + C2*C1) * conj(phase)) + Hmat[sub_i+L, sub_j] += J*biquad_res_factor * (conj(2C0*B12 + C1*C2) * phase) + Hmat[sub_j+L, sub_i] += J*biquad_res_factor * (conj(2C0*B21 + C2*C1) * conj(phase)) # The additional bilinear interactions Rij = -J * S * (Ri' * Rj) / 2 P = 0.25 * (Rij[1, 1] - Rij[2, 2] + 1im * (-Rij[1, 2] - Rij[2, 1])) Q = 0.25 * (Rij[1, 1] + Rij[2, 2] + 1im * (-Rij[1, 2] + Rij[2, 1])) - cP, cQ = conj(P), conj(Q) Hmat[sub_i, sub_j] += Q * phase - Hmat[sub_j, sub_i] += cQ * cphase - Hmat[sub_i+L, sub_j+L] += cQ * phase - Hmat[sub_j+L, sub_i+L] += Q * cphase + Hmat[sub_j, sub_i] += conj(Q) * conj(phase) + Hmat[sub_i+L, sub_j+L] += conj(Q) * phase + Hmat[sub_j+L, sub_i+L] += Q * conj(phase) Hmat[sub_i+L, sub_j] += P * phase - Hmat[sub_j+L, sub_i] += P * cphase - Hmat[sub_i, sub_j+L] += cP * phase - Hmat[sub_j, sub_i+L] += cP * cphase + Hmat[sub_j+L, sub_i] += P * conj(phase) + Hmat[sub_i, sub_j+L] += conj(P) * phase + Hmat[sub_j, sub_i+L] += conj(P) * conj(phase) Hmat[sub_i, sub_i] -= 0.5 * Rij[3, 3] Hmat[sub_j, sub_j] -= 0.5 * Rij[3, 3] @@ -324,8 +305,8 @@ function swt_hamiltonian_dipole!(swt::SpinWaveTheory, k̃ :: Vector{Float64}, Hm (; c2, c4, c6) = c′_coef[matom] Hmat[matom, matom] += -3S*c2[3] - 40*S^3*c4[5] - 168*S^5*c6[7] Hmat[matom+L, matom+L] += -3S*c2[3] - 40*S^3*c4[5] - 168*S^5*c6[7] - Hmat[matom, matom+L] += -1im*(S*c2[5] + 6S^3*c4[7] + 16S^5*c6[9]) + (S*c2[1] + 6S^3*c4[3] + 16S^5*c6[5]) - Hmat[matom+L, matom] += 1im*(S*c2[5] + 6S^3*c4[7] + 16S^5*c6[9]) + (S*c2[1] + 6S^3*c4[3] + 16S^5*c6[5]) + Hmat[matom, matom+L] += -im*(S*c2[5] + 6S^3*c4[7] + 16S^5*c6[9]) + (S*c2[1] + 6S^3*c4[3] + 16S^5*c6[5]) + Hmat[matom+L, matom] += im*(S*c2[5] + 6S^3*c4[7] + 16S^5*c6[9]) + (S*c2[1] + 6S^3*c4[3] + 16S^5*c6[5]) end # Hmat must be hermitian up to round-off errors @@ -335,10 +316,10 @@ function swt_hamiltonian_dipole!(swt::SpinWaveTheory, k̃ :: Vector{Float64}, Hm end # make Hmat exactly hermitian for cholesky decomposition. - Hmat[:, :] = (0.5 + 0.0im) * (Hmat + Hmat') + Hmat[:, :] = (Hmat + Hmat') / 2 # add tiny part to the diagonal elements for cholesky decomposition. - for i = 1:2*L + for i = 1:2L Hmat[i, i] += swt.energy_ϵ end end @@ -458,11 +439,10 @@ function dispersion(swt::SpinWaveTheory, qs) disp = zeros(Float64, nmodes, length(qs)) for (iq, q) in enumerate(qs) - _, qmag = chemical_to_magnetic(swt, q) if sys.mode == :SUN - swt_hamiltonian_SUN!(swt, qmag, ℋ) + swt_hamiltonian_SUN!(swt, q, ℋ) elseif sys.mode == :dipole - swt_hamiltonian_dipole!(swt, qmag, ℋ) + swt_hamiltonian_dipole!(swt, q, ℋ) end bogoliubov!(disp_buf, Vbuf, ℋ, energy_tol) disp[:,iq] .= disp_buf @@ -475,8 +455,8 @@ end """ dssf(swt::SpinWaveTheory, qs) -**Experimental**. Given a [`SpinWaveTheory`](@ref) object, computes the dynamical spin structure -factor, +**Experimental**. Given a [`SpinWaveTheory`](@ref) object, computes the +dynamical spin structure factor, ```math 𝒮^{αβ}(𝐤, ω) = 1/(2πN)∫dt ∑_𝐫 \\exp[i(ωt - 𝐤⋅𝐫)] ⟨S^α(𝐫, t)S^β(0, 0)⟩, @@ -489,8 +469,8 @@ using the result from linear spin-wave theory, ``` `qs` is an array of wave vectors of arbitrary dimension. Each element ``q`` of -`qs` must be a 3-vector in reciprocal lattice units. I.e., ``q_i`` is given in -``2π/|a_i|`` with ``|a_i|`` the lattice constant of the chemical lattice. +`qs` must be a 3-vector in reciprocal lattice units (RLU), i.e., in the basis of +reciprocal lattice vectors. The first indices of the returned array correspond to those of `qs`. A final index, corresponding to mode, is added to these. Each entry of this array is a @@ -564,8 +544,8 @@ end delta_function_kernel = nothing -function intensity_formula(f::Function,swt::SpinWaveTheory,corr_ix::AbstractVector{Int64}; kernel::Union{Nothing,Function}, return_type = Float64, string_formula = "f(Q,ω,S{α,β}[ix_q,ix_ω])", mode_fast = false) - (; sys, positions_chem, s̃_mat, R_mat) = swt +function intensity_formula(f::Function, swt::SpinWaveTheory, corr_ix; kernel::Union{Nothing,Function}, return_type=Float64, string_formula="f(Q,ω,S{α,β}[ix_q,ix_ω])", mode_fast=false) + (; sys, s̃_mat, R_mat) = swt Nm, Ns = length(sys.dipoles), sys.Ns[1] # number of magnetic atoms and dimension of Hilbert space S = (Ns-1) / 2 nmodes = num_bands(swt) @@ -595,17 +575,17 @@ function intensity_formula(f::Function,swt::SpinWaveTheory,corr_ix::AbstractVect _, qmag = chemical_to_magnetic(swt, q) if sys.mode == :SUN - swt_hamiltonian_SUN!(swt, qmag, Hmat) + swt_hamiltonian_SUN!(swt, q, Hmat) elseif sys.mode == :dipole - swt_hamiltonian_dipole!(swt, qmag, Hmat) + swt_hamiltonian_dipole!(swt, q, Hmat) end bogoliubov!(disp, Vmat, Hmat, swt.energy_tol, mode_fast) - for site = 1:Nm - # note that d is the chemical coordinates - chemical_coor = positions_chem[site] - phase = exp(-2im * π * dot(q, chemical_coor)) - Avec_pref[site] = sqrt_Nm_inv * phase + for i = 1:Nm + @assert Nm == natoms(sys.crystal) + r = sys.crystal.latvecs * sys.crystal.positions[i] + phase = exp(-im * dot(q, r)) + Avec_pref[i] = sqrt_Nm_inv * phase end # Fill `intensity` array @@ -623,7 +603,7 @@ function intensity_formula(f::Function,swt::SpinWaveTheory,corr_ix::AbstractVect end elseif sys.mode == :dipole for site = 1:Nm - Vtmp = [v[site+nmodes] + v[site], 1im * (v[site+nmodes] - v[site]), 0.0] + Vtmp = [v[site+nmodes] + v[site], im * (v[site+nmodes] - v[site]), 0.0] Avec += Avec_pref[site] * sqrt_halfS * (R_mat[site] * Vtmp) end end @@ -641,8 +621,7 @@ function intensity_formula(f::Function,swt::SpinWaveTheory,corr_ix::AbstractVect Sαβ[3,1] = conj(Sαβ[1,3]) Sαβ[3,2] = conj(Sαβ[2,3]) - k = swt.recipvecs_chem * q - intensity[band] = f(k,disp[band],Sαβ[corr_ix]) + intensity[band] = f(q, disp[band], Sαβ[corr_ix]) end # Return the result of the diagonalization in an appropriate diff --git a/src/SpinWaveTheory/SpinWaveTheory.jl b/src/SpinWaveTheory/SpinWaveTheory.jl index a53ed77fd..b5f55dfed 100644 --- a/src/SpinWaveTheory/SpinWaveTheory.jl +++ b/src/SpinWaveTheory/SpinWaveTheory.jl @@ -15,16 +15,15 @@ the imaginary part of the eigenvalues. """ struct SpinWaveTheory sys :: System - s̃_mat :: Array{ComplexF64, 4} # dipole operators - T̃_mat :: Array{ComplexF64, 3} # single-ion anisos - Q̃_mat :: Array{ComplexF64, 4} # quarupolar operators - c′_coef :: Vector{StevensExpansion} # c′_coefficents (for dipole mode) + s̃_mat :: Array{ComplexF64, 4} # Dipole operators + T̃_mat :: Array{ComplexF64, 3} # Single-ion anisos + Q̃_mat :: Array{ComplexF64, 4} # Quadrupolar operators + c′_coef :: Vector{StevensExpansion} # Stevens operator coefficents (for dipole mode) R_mat :: Vector{Mat3} # SO(3) rotation to align the quantization axis (for dipole mode) - positions_chem :: Vector{Vec3} # positions of magnetic atoms in units of (a₁, a₂, a₃) of the chemical lattice. (useful when computing the dynamical spin structure factor) - recipvecs_chem :: Mat3 # maybe not useful if we have David's interface for S(q, ω) - recipvecs_mag :: Mat3 # reciprocal lattice basis vectors for the magnetic supercell - energy_ϵ :: Float64 # energy epsilon in the diagonalization. Set to add to diagonal elements of the spin-wave Hamiltonian for cholesky decompostion - energy_tol :: Float64 # energy tolerance for maximal imaginary part of spin-wave energies + positions :: Vector{Vec3} # Positions of sites in global coordinates (Å) + recipvecs :: Mat3 # Reciprocal vectors in global coordinates (1/Å) + energy_ϵ :: Float64 # Energy shift applied to dynamical matrix prior to Bogoliubov transformation + energy_tol :: Float64 # Energy tolerance for maximal imaginary part of quasiparticle energies # Correlation info (αβ indices of 𝒮^{αβ}(q,ω)) # dipole_corrs :: Bool # Whether using all correlations from dipoles @@ -36,14 +35,13 @@ function Base.show(io::IO, ::MIME"text/plain", swt::SpinWaveTheory) # modename = swt.dipole_corrs ? "Dipole correlations" : "Custom correlations" modename = "Dipole correlations" printstyled(io, "SpinWaveTheory [$modename]\n"; bold=true, color=:underline) - println(io, "Atoms in magnetic supercell $(length(swt.positions_chem))") + println(io, "Atoms in magnetic supercell: $(natoms(swt.sys.crystal))") end function num_bands(swt::SpinWaveTheory) (; sys) = swt - Nm, Ns = length(sys.dipoles), sys.Ns[1] # number of magnetic atoms and dimension of Hilbert space - Nf = sys.mode == :SUN ? Ns-1 : 1 - nbands = Nf * Nm + nbosons = sys.mode == :SUN ? sys.Ns[1]-1 : 1 + return nbosons * natoms(sys.crystal) end @@ -59,7 +57,7 @@ function dipole_to_angles(dipoles :: AbstractVector{Float64}) @assert isfinite(θ) ϕ = atan(dipoles[2], dipoles[1]) @assert isfinite(ϕ) - (ϕ < 0.0) && (ϕ += 2.0 * π) + (ϕ < 0.0) && (ϕ += 2π) return θ, ϕ end @@ -157,39 +155,6 @@ function SpinWaveTheory(sys::System{N}; energy_ϵ::Float64=1e-8, energy_tol::Flo Q̃_mat = zeros(ComplexF64, 0, 0, 0, 0) end - latvecs_mag = isnothing(sys.origin) ? diagm(ones(3)) : sys.origin.crystal.latvecs \ sys.crystal.latvecs # DD: correct/necessary? - positions_chem = Vec3.([latvecs_mag * position for position in sys.crystal.positions]) # Positions of atoms in chemical coordinates - recipvecs_mag = inv(latvecs_mag)' - latvecs_chem = isnothing(sys.origin) ? diagm(ones(3)) : sys.origin.crystal.latvecs # DD: correct/necessary? - recipvecs_chem = inv(latvecs_chem)' - - return SpinWaveTheory(sys, s̃_mat, T̃_mat, Q̃_mat, c′_coef, R_mat, positions_chem, recipvecs_chem, recipvecs_mag, energy_ϵ, energy_tol) -end - -""" - chemical_to_magnetic - -Convert the components of a wavevector from the original Brillouin zone (of the chemical lattice) to the reduced Brillouin zone (BZ) -(of the magnetic lattice). \ -This is necessary because components in the reduced BZ are good quantum numbers. -`K` is the reciprocal lattice vector, and `k̃` is the components of wavevector in the reduced BZ. Note `k = K + k̃` -""" -function chemical_to_magnetic(swt::SpinWaveTheory, k) - k = Vec3(k) - α = swt.recipvecs_mag \ k - k̃ = Vector{Float64}(undef, 3) - K = Vector{Int}(undef, 3) - for i = 1:3 - if abs(α[i]) < eps() - K[i] = k̃[i] = 0.0 - else - K[i] = Int(round(floor(α[i]))) - k̃[i] = α[i] - K[i] - end - @assert k̃[i] ≥ 0.0 && k̃[i] < 1.0 - end - k_check = swt.recipvecs_mag * (K + k̃) - @assert norm(k - k_check) < 1e-12 - - return K, k̃ + positions = [global_position(sys, site) for site in all_sites(sys)][:] + return SpinWaveTheory(sys, s̃_mat, T̃_mat, Q̃_mat, c′_coef, R_mat, positions, sys.crystal.recipvecs, energy_ϵ, energy_tol) end diff --git a/src/SpinWaveTheory/kpm.jl b/src/SpinWaveTheory/kpm.jl index b9297ea0f..8bfa19838 100644 --- a/src/SpinWaveTheory/kpm.jl +++ b/src/SpinWaveTheory/kpm.jl @@ -36,8 +36,7 @@ function intensity_formula_kpm(f::Function,swt::SpinWaveTheory,corr_ix::Abstract stuff = setup_stuff(swt) formula = function(swt::SpinWaveTheory,q::Vec3,ω::Float64) Sαβ = do_KPM(swt,stuff) - k = swt.recipvecs_chem * q - intensity = f(k,ω,Sαβ[corr_ix]) + return f(q,ω,Sαβ[corr_ix]) end KPMIntensityFormula{return_type}(P,kT,σ,broadening,kernel,string_formula,formula) end diff --git a/src/Sunny.jl b/src/Sunny.jl index a1fa67748..afd7ebb11 100644 --- a/src/Sunny.jl +++ b/src/Sunny.jl @@ -49,7 +49,7 @@ include("Symmetry/AllowedCouplings.jl") include("Symmetry/AllowedAnisotropy.jl") include("Symmetry/Parsing.jl") include("Symmetry/Printing.jl") -export Crystal, subcrystal, lattice_vectors, lattice_params, Bond, +export Crystal, subcrystal, lattice_vectors, lattice_params, Bond, reference_bonds, print_site, print_bond, print_symmetry_table, print_suggested_frame diff --git a/src/Symmetry/Crystal.jl b/src/Symmetry/Crystal.jl index c4f9f1615..daa47dc6a 100644 --- a/src/Symmetry/Crystal.jl +++ b/src/Symmetry/Crystal.jl @@ -69,8 +69,9 @@ cryst = Crystal(latvecs, positions, 227; setting="1") See also [`lattice_vectors`](@ref). """ struct Crystal - latvecs :: Mat3 # Lattice vectors as columns + latvecs :: Mat3 # Lattice vectors as columns (conventional) prim_latvecs :: Mat3 # Primitive lattice vectors + recipvecs :: Mat3 # Reciprocal lattice vectors (conventional) positions :: Vector{Vec3} # Positions in fractional coords types :: Vector{String} # Types classes :: Vector{Int} # Class indices @@ -81,14 +82,14 @@ struct Crystal end """ - natoms(crystal::Crystal) + natoms(cryst::Crystal) Number of atoms in the unit cell, i.e., number of Bravais sublattices. """ @inline natoms(cryst::Crystal) = length(cryst.positions) """ - cell_volume(crystal::Crystal) + cell_volume(cryst::Crystal) Volume of the crystal unit cell. """ @@ -192,6 +193,7 @@ function crystal_from_inferred_symmetry(latvecs::Mat3, positions::Vector{Vec3}, end end + recipvecs = 2π*Mat3(inv(latvecs)') positions = wrap_to_unit_cell.(positions; symprec) cell = Spglib.Cell(latvecs, positions, types) @@ -219,7 +221,7 @@ function crystal_from_inferred_symmetry(latvecs::Mat3, positions::Vector{Vec3}, sitesyms = SiteSymmetry.(d.site_symmetry_symbols, multiplicities, d.wyckoffs) - ret = Crystal(latvecs, d.primitive_lattice, positions, types, classes, sitesyms, symops, spacegroup, symprec) + ret = Crystal(latvecs, d.primitive_lattice, recipvecs, positions, types, classes, sitesyms, symops, spacegroup, symprec) validate(ret) return ret end @@ -381,7 +383,8 @@ function crystal_from_symops(latvecs::Mat3, positions::Vector{Vec3}, types::Vect inferred else prim_latvecs = latvecs - Crystal(latvecs, prim_latvecs, all_positions, all_types, classes, nothing, symops, spacegroup, symprec) + recipvecs = 2π*Mat3(inv(latvecs)') + Crystal(latvecs, prim_latvecs, recipvecs, all_positions, all_types, classes, nothing, symops, spacegroup, symprec) end sort_sites!(ret) validate(ret) @@ -399,6 +402,9 @@ function reshape_crystal(cryst::Crystal, new_cell_size::Mat3) # Lattice vectors of the new unit cell in global coordinates new_latvecs = cryst.latvecs * new_cell_size + # Reciprocal lattice vectors of the new unit cell + new_recipvecs = 2π * Mat3(inv(new_latvecs)') + # These don't change because both are in global coordinates prim_latvecs = cryst.prim_latvecs @@ -451,8 +457,8 @@ function reshape_crystal(cryst::Crystal, new_cell_size::Mat3) # lost with the resizing procedure. new_symops = SymOp[] - return Crystal(new_latvecs, prim_latvecs, new_positions, new_types, new_classes, new_sitesyms, - new_symops, cryst.spacegroup, new_symprec) + return Crystal(new_latvecs, prim_latvecs, new_recipvecs, new_positions, new_types, new_classes, + new_sitesyms, new_symops, cryst.spacegroup, new_symprec) end diff --git a/src/Symmetry/SymmetryAnalysis.jl b/src/Symmetry/SymmetryAnalysis.jl index a18ba8db1..7c96d9005 100644 --- a/src/Symmetry/SymmetryAnalysis.jl +++ b/src/Symmetry/SymmetryAnalysis.jl @@ -159,13 +159,10 @@ function all_bonds_for_atom(cryst::Crystal, i::Int, max_dist; min_dist=0.0) max_dist += 4 * cryst.symprec * ℓ min_dist -= 4 * cryst.symprec * ℓ - # columns are the reciprocal vectors - recip_vecs = 2π * inv(cryst.latvecs)' - # box_lengths[i] represents the perpendicular distance between two parallel # boundary planes spanned by lattice vectors a_j and a_k (where indices j # and k differ from i) - box_lengths = [a⋅b/norm(b) for (a,b) = zip(eachcol(cryst.latvecs), eachcol(recip_vecs))] + box_lengths = [a⋅b/norm(b) for (a,b) = zip(eachcol(cryst.latvecs), eachcol(cryst.recipvecs))] n_max = round.(Int, max_dist ./ box_lengths, RoundUp) bonds = Bond[] diff --git a/src/System/Ewald.jl b/src/System/Ewald.jl index 25a74c320..087d965c3 100644 --- a/src/System/Ewald.jl +++ b/src/System/Ewald.jl @@ -41,15 +41,13 @@ function precompute_dipole_ewald(cryst::Crystal, latsize::NTuple{3,Int}) :: Arra A = zeros(Mat3, latsize..., na, na) # Superlattice vectors that describe periodicity of system and their inverse - supervecs = cryst.latvecs .* Vec3(latsize)' - recipvecs = 2π * inv(supervecs) - # Split into individual vectors - supervecs = collect(eachcol(supervecs)) - recipvecs = collect(eachrow(recipvecs)) + latscale = diagm(Vec3(latsize)) + slatvecs = collect(eachcol(cryst.latvecs * latscale)) + srecipvecs = collect(eachcol(latscale \ cryst.recipvecs)) # Precalculate constants I₃ = Mat3(I) - V = (supervecs[1] × supervecs[2]) ⋅ supervecs[3] + V = (slatvecs[1] × slatvecs[2]) ⋅ slatvecs[3] L = cbrt(V) # Roughly balances the real and Fourier space costs σ = L/3 @@ -59,10 +57,10 @@ function precompute_dipole_ewald(cryst::Crystal, latsize::NTuple{3,Int}) :: Arra rmax = 6√2 * σ kmax = 6√2 / σ - nmax = map(supervecs, recipvecs) do a, b + nmax = map(slatvecs, srecipvecs) do a, b round(Int, rmax / (a⋅normalize(b)) + 1e-6) + 1 end - mmax = map(supervecs, recipvecs) do a, b + mmax = map(slatvecs, srecipvecs) do a, b round(Int, kmax / (b⋅normalize(a)) + 1e-6) end @@ -78,7 +76,7 @@ function precompute_dipole_ewald(cryst::Crystal, latsize::NTuple{3,Int}) :: Arra ## Real space part for n1 = -nmax[1]:nmax[1], n2 = -nmax[2]:nmax[2], n3 = -nmax[3]:nmax[3] n = Vec3(n1, n2, n3) - rvec = Δr + n' * supervecs + rvec = Δr + n' * slatvecs r² = rvec⋅rvec if 0 < r² <= rmax*rmax r = √r² @@ -93,7 +91,7 @@ function precompute_dipole_ewald(cryst::Crystal, latsize::NTuple{3,Int}) :: Arra ##################################################### ## Fourier space part for m1 = -mmax[1]:mmax[1], m2 = -mmax[2]:mmax[2], m3 = -mmax[3]:mmax[3] - k = Vec3(m1, m2, m3)' * recipvecs + k = Vec3(m1, m2, m3)' * srecipvecs k² = k⋅k if 0 < k² <= kmax*kmax # Replace exp(-ikr) -> cos(kr). It's valid to drop the imaginary diff --git a/test/test_lswt.jl b/test/test_lswt.jl index 349eb6efc..d1eaa62d9 100644 --- a/test/test_lswt.jl +++ b/test/test_lswt.jl @@ -11,45 +11,35 @@ end @testitem "Single Ion" begin - J, J′, D = 1.0, 0.1, 5.0 - - a = b = 1.0 + # Tetragonal crystal + a = 1.0 c = 1.5 - lat_vecs = lattice_vectors(a, b, c, 90, 90, 90) - types = ["A"] - basis_vecs = [[0, 0, 0]] - - cryst = Crystal(lat_vecs, basis_vecs; types) + latvecs = lattice_vectors(a, a, c, 90, 90, 90) + positions = [[0, 0, 0]] + cryst = Crystal(latvecs, positions) - # Spin System - dims = (2, 2, 2) + # System + J, J′, D = 1.0, 0.1, 5.0 infos = [SpinInfo(1, S=1, g=2)] - sys = System(cryst, dims, infos, :SUN) - + sys = System(cryst, (1, 1, 1), infos, :SUN; seed=0) set_exchange!(sys, J, Bond(1, 1, [1, 0, 0])) set_exchange!(sys, J′, Bond(1, 1, [0, 0, 1])) S = spin_operators(sys, 1) set_onsite_coupling!(sys, D * S[3]^2, 1) - Δt = abs(0.05 / D) - λ = 0.1 - langevin = Langevin(Δt; kT=0, λ) - - randomize_spins!(sys) + # Reshape to sheared supercell and minimize energy A = [1 1 1; -1 1 0; 0 0 1] - sys_swt = reshape_supercell(sys, A) - - langevin.kT = 0 - for i in 1:50_000 - step!(sys_swt, langevin) - end + sys = reshape_supercell(sys, A) + randomize_spins!(sys) + @test minimize_energy!(sys) > 0 - swt = SpinWaveTheory(sys_swt) + k = cryst.recipvecs * rand(Float64, 3) + swt = SpinWaveTheory(sys) + ωk_num = dispersion(swt, [k])[1, :] - function sion_analytical_disp(k :: Vector{Float64}) - # analytical solutions - γkxy = cos(2*π*k[1]) + cos(2*π*k[2]) - γkz = cos(2*π*k[3]) + function single_ion_analytical_disp(k) + γkxy = cos(a*k[1]) + cos(a*k[2]) + γkz = cos(c*k[3]) x = 1/2 - D/(8*(2*J+J′)) Ak₊ = -8 * (x-1) * x * (2*J+J′) - (x-1) * D + 2 * (2*x-1) * (J *γkxy + J′*γkz) Bk₊ = -2 * (J * γkxy + J′ * γkz) @@ -59,41 +49,36 @@ end ωk₋ = √(Ak₋^2-Bk₋^2) return ωk₊, ωk₋ end + ωk1, ωk2 = single_ion_analytical_disp(k) + ωk3, ωk4 = single_ion_analytical_disp(k + [π/a, π/a, π/c]) + ωk_ref = sort([ωk1, ωk2, ωk3, ωk4]; rev=true) - k = rand(Float64, 3) - ωk1, ωk2 = sion_analytical_disp(k) - ωk3, ωk4 = sion_analytical_disp(k .+= 0.5) - ωk_ana = [ωk1, ωk2, ωk3, ωk4] - index = sortperm(ωk_ana, rev=true) - ωk_ana = ωk_ana[index] - - ωk_num = dispersion(swt, [k])' - - @test isapprox(ωk_ana, ωk_num) + @test ωk_num ≈ ωk_ref end @testitem "Intensities" begin using LinearAlgebra + # Crystal a = 8.289 - lat_vecs = lattice_vectors(a, a, a, 90, 90, 90) + latvecs = lattice_vectors(a, a, a, 90, 90, 90) types = ["MzR1"] - basis_vecs = [[0, 0, 0]] - fcc = Crystal(lat_vecs, basis_vecs, 225; types) - S = 5/2 + positions = [[0, 0, 0]] + fcc = Crystal(latvecs, positions, 225; types) - # According to a renormalized classical theory for spins (the details will be presented in a manuscript in preparation), the large-S expansion and the :dipole mode should produce the same results when apply the proper renormalization factor for the single-ion interaction strength. - cov_factor = (1 - 3/S + 11/(4*S^2)- 3/(4*S^3)) - J = 22.06 * Sunny.meV_per_K - K = 0.15 * Sunny.meV_per_K + S = 5/2 + J = 22.06 * meV_per_K + K = 0.15 * meV_per_K C = J + K J₁ = diagm([J, J, C]) D_ST = 0.2 - D = D_ST / cov_factor + # Undo Sunny-applied renormalization of quartic anisotropy + D = D_ST / Sunny.anisotropy_renormalization(2S+1)[4] + dims = (1, 1, 1) - infos = [SpinInfo(1, S=S, g=2)] + infos = [SpinInfo(1; S, g=2)] - function compute_trace(mode::Symbol) + function compute(mode) sys = System(fcc, dims, infos, mode) set_exchange!(sys, J₁, Bond(1, 2, [0, 0, 0])) S = spin_operators(sys, 1) @@ -104,7 +89,7 @@ end set_dipole!(sys, (-1, -1, 1), position_to_site(sys, (1/2, 0, 1/2))) set_dipole!(sys, (-1, 1, -1), position_to_site(sys, (0, 1/2, 1/2))) swt = SpinWaveTheory(sys) - k = [0.8, 0.6, 0.1] + k = fcc.recipvecs * [0.8, 0.6, 0.1] _, Sαβs = Sunny.dssf(swt, [k]) sunny_trace = [real(tr(Sαβs[1,a])) for a in axes(Sαβs)[2]] @@ -113,104 +98,88 @@ end return sunny_trace end - spintools_trace = [1.1743243223274487, 1.229979802236658, 1.048056653379038] - SUN_trace = compute_trace(:SUN) - dipole_trace = compute_trace(:dipole) - @test SUN_trace ≈ dipole_trace ≈ spintools_trace + reference = [1.1743243223274487, 1.229979802236658, 1.048056653379038] + @test compute(:SUN) ≈ compute(:dipole) ≈ reference end @testitem "Biquadratic interactions" begin - function test_biquad(k :: Vector{Float64}, S) - - a = 1.0 - lat_vecs = lattice_vectors(a, a, a, 90, 90, 90) - types = ["A"] - basis_vecs = [[0, 0, 0]] - - cryst = Crystal(lat_vecs, basis_vecs; types) - - # Spin System + # Cubic crystal + a = 2.0 + lat_vecs = lattice_vectors(a, a, a, 90, 90, 90) + basis_vecs = [[0, 0, 0]] + cryst = Crystal(lat_vecs, basis_vecs) + + function test_biquad(mode, k, S) + # System dims = (2, 2, 2) - infos = [SpinInfo(1, S=S, g=2)] - sys_SUN = System(cryst, dims, infos, :SUN) - sys_dip = System(cryst, dims, infos, :dipole) - - α = -0.4 * π + infos = [SpinInfo(1; S, g=2)] + sys = System(cryst, dims, infos, mode) + α = -0.4π J = 1.0 JL, JQ = J * cos(α), J * sin(α) / S^2 - set_exchange!(sys_SUN, JL, Bond(1, 1, [1, 0, 0]); biquad=JQ) - set_exchange!(sys_dip, JL, Bond(1, 1, [1, 0, 0]); biquad=JQ) - - sys_swt_SUN = reshape_supercell(sys_SUN, [1 1 1; -1 1 0; 0 0 1]) - set_dipole!(sys_swt_SUN, ( 1, 0, 0), position_to_site(sys_swt_SUN, (0, 0, 0))) - set_dipole!(sys_swt_SUN, (-1, 0, 0), position_to_site(sys_swt_SUN, (0, 1, 0))) - - sys_swt_dip = reshape_supercell(sys_dip, [1 1 1; -1 1 0; 0 0 1]) - set_dipole!(sys_swt_dip, ( 1, 0, 0), position_to_site(sys_swt_dip, (0, 0, 0))) - set_dipole!(sys_swt_dip, (-1, 0, 0), position_to_site(sys_swt_dip, (0, 1, 0))) + set_exchange!(sys, JL, Bond(1, 1, [1, 0, 0]); biquad=JQ) - swt_SUN = SpinWaveTheory(sys_swt_SUN) - swt_dip = SpinWaveTheory(sys_swt_dip) - - γk(k :: Vector{Float64}) = 2 * (cos(2π*k[1]) + cos(2π*k[2]) + cos(2π*k[3])) - ϵk₁(k :: Vector{Float64}) = J * (S*cos(α) - (2*S-2+1/S) * sin(α)) * √(36 - γk(k)^2) + # Initialize Néel order + sys = reshape_supercell(sys, [1 1 1; -1 1 0; 0 0 1]) + set_dipole!(sys, ( 1, 0, 0), position_to_site(sys, (0, 0, 0))) + set_dipole!(sys, (-1, 0, 0), position_to_site(sys, (0, 1, 0))) - ϵk_num_SUN = dispersion(swt_SUN, [k]) - ϵk_num_dip = dispersion(swt_dip, [k]) - ϵk_ana = ϵk₁(k) + # Numerical result + swt = SpinWaveTheory(sys) + disp = dispersion(swt, [k]) - ϵk_num_SUN[end-1] ≈ ϵk_num_SUN[end] ≈ ϵk_num_dip[end] ≈ ϵk_ana + # Analytical result + γk = 2 * (cos(k[1]*a) + cos(k[2]*a) + cos(k[3]*a)) + disp_ref = J * (S*cos(α) - (2*S-2+1/S) * sin(α)) * √(36 - γk^2) + + @test disp[end-1] ≈ disp[end] ≈ disp_ref end k = [0.12, 0.23, 0.34] - @test test_biquad(k, 1) - @test test_biquad(k, 3/2) + for mode in (:SUN, :dipole), S in (1, 3/2) + test_biquad(mode, k, S) + end end -@testitem "Canted afm" begin +@testitem "Canted AFM" begin - function disp_analytical_canted_afm(J, D, h, S, qs) - c₂ = 1 - 1/(2S) - θ = acos(h / (2S*(4J+c₂*D))) - disp = zeros(Float64, 2, length(qs)) - for (iq, q) in enumerate(qs) - Jq = 2J*(cos(2π*q[1])+cos(2π*q[2])) - ωq₊ = real(√Complex(4J*S*(4J*S+2D*S*c₂*sin(θ)^2) + cos(2θ)*(Jq*S)^2 + 2S*Jq*(4J*S*cos(θ)^2 + c₂*D*S*sin(θ)^2))) - ωq₋ = real(√Complex(4J*S*(4J*S+2D*S*c₂*sin(θ)^2) + cos(2θ)*(Jq*S)^2 - 2S*Jq*(4J*S*cos(θ)^2 + c₂*D*S*sin(θ)^2))) - disp[:, iq] = [ωq₊, ωq₋] - end - return Sunny.reshape_dispersions(disp) - end - - function test_canted_afm(q :: Vector{Float64}, S) + function test_canted_afm(S) J, D, h = 1.0, 0.54, 0.76 a = 1 latvecs = lattice_vectors(a, a, 10a, 90, 90, 90) positions = [[0, 0, 0]] cryst = Crystal(latvecs, positions) - + q = cryst.recipvecs * [0.12, 0.23, 0.34] + dims = (2, 2, 1) - sys_dip = System(cryst, dims, [SpinInfo(1, S=S, g=1)], :dipole; units=Units.theory) - - set_exchange!(sys_dip, J, Bond(1, 1, [1, 0, 0])) - - Sz = spin_operators(sys_dip,1)[3] - set_onsite_coupling!(sys_dip, D*Sz^2, 1) - - set_external_field!(sys_dip, [0, 0, h]) - sys_swt_dip = reshape_supercell(sys_dip, [1 -1 0; 1 1 0; 0 0 1]) + sys = System(cryst, dims, [SpinInfo(1; S, g=1)], :dipole; units=Units.theory) + set_exchange!(sys, J, Bond(1, 1, [1, 0, 0])) + Sz = spin_operators(sys,1)[3] + set_onsite_coupling!(sys, D*Sz^2, 1) + set_external_field!(sys, [0, 0, h]) + + # Numerical + sys_swt_dip = reshape_supercell(sys, [1 -1 0; 1 1 0; 0 0 1]) c₂ = 1 - 1/(2S) θ = acos(h / (2S*(4J+D*c₂))) set_dipole!(sys_swt_dip, ( sin(θ), 0, cos(θ)), position_to_site(sys_swt_dip, (0,0,0))) set_dipole!(sys_swt_dip, (-sin(θ), 0, cos(θ)), position_to_site(sys_swt_dip, (1,0,0))) swt_dip = SpinWaveTheory(sys_swt_dip) - ϵq_num = dispersion(swt_dip, [q]) - ϵq_ana = disp_analytical_canted_afm(J, D, h, S, [q]) + ϵq_num = dispersion(swt_dip, [q])[1,:] + + # Analytical + c₂ = 1 - 1/(2S) + θ = acos(h / (2S*(4J+c₂*D))) + Jq = 2J*(cos(q[1])+cos(q[2])) + ωq₊ = real(√Complex(4J*S*(4J*S+2D*S*c₂*sin(θ)^2) + cos(2θ)*(Jq*S)^2 + 2S*Jq*(4J*S*cos(θ)^2 + c₂*D*S*sin(θ)^2))) + ωq₋ = real(√Complex(4J*S*(4J*S+2D*S*c₂*sin(θ)^2) + cos(2θ)*(Jq*S)^2 - 2S*Jq*(4J*S*cos(θ)^2 + c₂*D*S*sin(θ)^2))) + ϵq_ana = [ωq₊, ωq₋] + ϵq_num ≈ ϵq_ana end - q = [0.12, 0.23, 0.34] - @test test_canted_afm(q, 1) - @test test_canted_afm(q, 2) + + @test test_canted_afm(1) + @test test_canted_afm(2) end @testitem "Local stevens expansion" begin From 4962c5ba98f6a3eab89906b556a4fe21d7f3168f Mon Sep 17 00:00:00 2001 From: Kipton Barros Date: Fri, 4 Aug 2023 14:53:24 -0600 Subject: [PATCH 02/12] connected_path_from_rlu --- docs/src/structure-factor.md | 6 +-- examples/fei2_tutorial.jl | 32 +++++---------- examples/longer_examples/CoRh2O4-tutorial.jl | 8 ++-- examples/longer_examples/MgCr2O4-tutorial.jl | 4 +- examples/powder_averaging.jl | 2 +- src/Intensities/Interpolation.jl | 35 ++++++++++------ src/SpinWaveTheory/SWTCalculations.jl | 4 +- src/Sunny.jl | 7 ++-- src/Symmetry/Crystal.jl | 42 ++++++++++++-------- test/test_correlation_sampling.jl | 4 +- 10 files changed, 76 insertions(+), 68 deletions(-) diff --git a/docs/src/structure-factor.md b/docs/src/structure-factor.md index cccd7ae9d..5c1b6387b 100644 --- a/docs/src/structure-factor.md +++ b/docs/src/structure-factor.md @@ -170,9 +170,9 @@ e.g., `bzsize=(2,2,2)`. The resulting list of wave vectors may then be passed to Alternatively, [`intensities_binned`](@ref) can be used to place the exact data into histogram bins for comparison with experiment. -The convenience function [`connected_path`](@ref) returns a list of wavevectors -sampled along a path that connects specified $𝐪$ points. This list can be used -as an input to `intensities`. Another convenience method, +The convenience function [`connected_path_from_rlu`](@ref) returns a list of +wavevectors sampled along a path that connects specified $𝐪$ points. This list +can be used as an input to `intensities`. Another convenience method, [`spherical_shell`](@ref) will provide a list of wave vectors on a sphere of a specified radius. This is useful for powder averaging. diff --git a/examples/fei2_tutorial.jl b/examples/fei2_tutorial.jl index 0b2fe92d1..1ae7600eb 100644 --- a/examples/fei2_tutorial.jl +++ b/examples/fei2_tutorial.jl @@ -245,29 +245,19 @@ plot_spins(sys_min; arrowlength=2.5, linewidth=0.75, arrowsize=1.5) swt = SpinWaveTheory(sys_min); -# Next, select a sequence of wavevectors that will define a piecewise linear -# interpolation in reciprocal lattice units (RLU). Sunny expects wavevectors in -# absolute units, i.e., inverse angstroms. To convert from RLU, multiply by the -# $3×3$ matrix `cryst.recipvecs`, which provides the reciprocal lattice vectors -# for the conventional unit cell. +# Select a sequence of wavevectors that will define a piecewise linear +# interpolation in reciprocal lattice units (RLU). points_rlu = [[0,0,0], [1,0,0], [0,1,0], [1/2,0,0], [0,1,0], [0,0,0]] -points = [cryst.recipvecs*p for p in points_rlu] -# The function [`connected_path`](@ref) will linearly sample between the -# provided $q$-points with given `density`. Also keep track of location and -# names of the special $𝐪$-points for plotting purposes. +# The function [`connected_path_from_rlu`](@ref) will linearly sample between +# the provided $q$-points with a given `density`. The `path` return value is a +# list of wavevectors in absolute units (inverse Å). The `xticks` return value +# keeps track of the locations of the special $𝐪$-points, and provides +# human-readable labels for use in plotting. density = 50 -path, numbers = connected_path(points, density); -xticks = (numbers, ["[0,0,0]", "[1,0,0]", "[0,1,0]", "[1/2,0,0]", "[0,1,0]", "[0,0,0]"]) - -# The dispersion relation can be calculated by providing `dispersion` with a -# `SpinWaveTheory` and a list of wave vectors. For each wave vector, -# `dispersion` will return a list of energies, one for each band. Frequently one -# wants dispersion information along lines that connect special wave vectors. -# The function [`connected_path`](@ref) linearly samples between provided -# $𝐪$-points, with a given sample density. +path, xticks = connected_path_from_rlu(cryst, points_rlu, density); # The [`dispersion`](@ref) function defines the quasiparticle excitation # energies $ω_i(𝐪)$ for each point $𝐪$ along the reciprocal space path. @@ -332,13 +322,13 @@ heatmap!(ax, 1:size(is_averaged, 1), energies, is_averaged) fig # This result can be directly compared to experimental neutron scattering data -# [Bai et al.](https://doi.org/10.1038/s41567-020-01110-1) +# from [Bai et al.](https://doi.org/10.1038/s41567-020-01110-1) # ```@raw html # # ``` # -# (Note that the publication figure uses a different coordinate system to label -# the wave vectors.) +# (The publication figure accidentally used a non-standard coordinate system to +# label the wave vectors.) # # To get this agreement, the use of SU(3) coherent states is essential (in other # words, we needed a theory of multi-flavored bosons). The lower band has large diff --git a/examples/longer_examples/CoRh2O4-tutorial.jl b/examples/longer_examples/CoRh2O4-tutorial.jl index 5766b1c4e..d7ea30f20 100644 --- a/examples/longer_examples/CoRh2O4-tutorial.jl +++ b/examples/longer_examples/CoRh2O4-tutorial.jl @@ -108,7 +108,7 @@ end # ### System Definition for CoRh2O4 # Define the crystal structure of CoRh$_2$O$_4$ in the conventional cell -xtal = Crystal(cif_path ;symprec=1e-4) +xtal = Crystal(cif_path; symprec=1e-4) magxtal = subcrystal(xtal,"Co1") view_crystal(magxtal,6.0) print_symmetry_table(magxtal, 4.0) @@ -214,7 +214,7 @@ symQpts = [[0.75, 0.75, 0.00], # List of wave vectors that define a path [0.25, 1.00, 0.25], [0.00, 1.00, 0.00], [0.00,-4.00, 0.00]]; -(Qpts, symQind) = connected_path(sc, symQpts,densQpts); +(Qpts, xticks) = connected_path_from_rlu(magxtal, symQpts, densQpts); @time iqw = intensities(sc, Qpts, :perp; interpolation = :none, kT=integrator.kT, formfactors=ffs); # If desired, broaden the sc in energy @@ -227,10 +227,10 @@ iqwc = broaden_energy(sc, iqw, (ω, ω₀) -> lorentzian(ω-ω₀, η)); # Plot the resulting I(Q,W) heatmap(1:size(iqwc, 1), ωs(sc), iqwc; colorrange = (0, maximum(iqwc)/20000.0), - axis = ( + axis = (; xlabel="Momentum Transfer (r.l.u)", ylabel="Energy Transfer (meV)", - xticks = (symQind, string.(symQpts)), + xticks, xticklabelrotation=π/5, aspect = 1.4, ) diff --git a/examples/longer_examples/MgCr2O4-tutorial.jl b/examples/longer_examples/MgCr2O4-tutorial.jl index a3c37473a..3b4cc0ff1 100644 --- a/examples/longer_examples/MgCr2O4-tutorial.jl +++ b/examples/longer_examples/MgCr2O4-tutorial.jl @@ -288,7 +288,7 @@ axsqw = (xticks=-4:4, yticks=0:2:10, ylabel="E (meV)", ylabelsize=18, xlabelsize qbs = 0.0:0.5:1.5 # Determine q_b for each slice for (i, qb) in enumerate(qbs) - path, labels = connected_path(sc_pyro, [[-4.0, qb, 0.0],[4.0, qb, 0.0]], 40) # Generate a path of wave + path, _ = connected_path_from_rlu(xtal_pyro, [[-4.0, qb, 0.0],[4.0, qb, 0.0]], 40) # Generate a path of wave ## vectors through the BZ Sqω_pyro = intensities(sc_pyro, path, :perp; kT) # Temperature keyword enables intensity rescaling @@ -303,7 +303,7 @@ fig = Figure(; resolution=(1200,900)) qbs = 0.0:0.5:1.5 for (i, qb) in enumerate(qbs) - path, labels = connected_path(sc_mgcro, [[-4.0, qb, 0.0],[4.0, qb, 0.0]], 40) # Generate a path of wave + path, _ = connected_path_from_rlu(xtal_mgcro, [[-4.0, qb, 0.0],[4.0, qb, 0.0]], 40) # Generate a path of wave ## vectors through the BZ Sqω_mgcro = intensities(sc_mgcro, path, :perp; kT) # Temperature keyword enables intensity rescaling diff --git a/examples/powder_averaging.jl b/examples/powder_averaging.jl index 9575996ee..d0dfdfc45 100644 --- a/examples/powder_averaging.jl +++ b/examples/powder_averaging.jl @@ -49,7 +49,7 @@ add_sample!(sc, sys) # lorentzian(ω-ω₀, 0.1)`. qpoints = [[0.0, 0.0, 0.0], [0.5, 0.0, 0.0], [0.5, 0.5, 0.0], [0.0, 0.0, 0.0]] -qs, markers = connected_path(sc, qpoints, 50) +qs, markers = connected_path_from_rlu(crystal, qpoints, 50) is = intensities_interpolated(sc, qs; interpolation=:round, formula = intensity_formula(sc,:trace)) is_broad = broaden_energy(sc, is, (ω, ω₀) -> lorentzian(ω-ω₀, 0.1)) diff --git a/src/Intensities/Interpolation.jl b/src/Intensities/Interpolation.jl index f5cc74573..9ed6049c7 100644 --- a/src/Intensities/Interpolation.jl +++ b/src/Intensities/Interpolation.jl @@ -218,18 +218,24 @@ end """ - connected_path(qs, density) - -Returns two things: First, a list of wavevectors that linearly interpolates -between the points `qs` and second, a list of markers that index the locations -of each original interpolation point. - -More sample-points will be returned between `q`-values that are further apart, -and this is controlled by the `density` parameter. + connected_path_from_rlu(cryst, qs_rlu, density) + +Returns a pair `(path, xticks)`. The first return value is a path in reciprocal +space that samples linearly between the wavevectors in `qs`. The elements in +`qs` are defined in reciprocal lattice units (RLU) associated with the +[`reciprocal_lattice_vectors``](@ref) for `cryst`. The `density` parameter has +units of inverse length, and controls the number of samples between elements of +`qs`. + +The second return value `xticks` can be used for plotting. The `xticks` object +is itself a pair `(numbers, labels)`, which give the locations of the +interpolating ``q``-points and a human-readable string. """ -function connected_path(qs::Vector, density) - @assert length(qs) >= 2 "The list `qs` should include at least two wavevectors." - qs = Vec3.(qs) +function connected_path_from_rlu(cryst::Crystal, qs_rlu::Vector, density) + @assert length(qs_rlu) >= 2 "The list `qs` should include at least two wavevectors." + qs_rlu = Vec3.(qs_rlu) + + qs = Ref(cryst.recipvecs) .* qs_rlu path = Vec3[] markers = Int[] @@ -245,5 +251,10 @@ function connected_path(qs::Vector, density) push!(markers, length(path)+1) push!(path, qs[end]) - return (path, markers) + labels = map(qs_rlu) do q_rlu + "[" * join(number_to_math_string.(q_rlu), ",") * "]" + end + xticks = (markers, labels) + + return (path, xticks) end diff --git a/src/SpinWaveTheory/SWTCalculations.jl b/src/SpinWaveTheory/SWTCalculations.jl index 9abaed6eb..4e33408dc 100644 --- a/src/SpinWaveTheory/SWTCalculations.jl +++ b/src/SpinWaveTheory/SWTCalculations.jl @@ -571,9 +571,7 @@ function intensity_formula(f::Function, swt::SpinWaveTheory, corr_ix; kernel::Un # # Smooth kernel --> I_of_ω = Intensity as a function of ω # - calc_intensity = function(swt::SpinWaveTheory,q::Vec3) - _, qmag = chemical_to_magnetic(swt, q) - + calc_intensity = function(swt::SpinWaveTheory, q::Vec3) if sys.mode == :SUN swt_hamiltonian_SUN!(swt, q, Hmat) elseif sys.mode == :dipole diff --git a/src/Sunny.jl b/src/Sunny.jl index afd7ebb11..1983953c9 100644 --- a/src/Sunny.jl +++ b/src/Sunny.jl @@ -49,9 +49,8 @@ include("Symmetry/AllowedCouplings.jl") include("Symmetry/AllowedAnisotropy.jl") include("Symmetry/Parsing.jl") include("Symmetry/Printing.jl") -export Crystal, subcrystal, lattice_vectors, lattice_params, Bond, - reference_bonds, print_site, print_bond, print_symmetry_table, - print_suggested_frame +export Crystal, subcrystal, lattice_vectors, lattice_params, reciprocal_lattice_vectors, Bond, + reference_bonds, print_site, print_bond, print_symmetry_table, print_suggested_frame include("Units.jl") export meV_per_K, Units @@ -98,7 +97,7 @@ export SampledCorrelations, dynamical_correlations, instant_correlations, FormFa include("Intensities/ElementContraction.jl") include("Intensities/Interpolation.jl") -export intensities_interpolated, instant_intensities_interpolated, connected_path +export intensities_interpolated, instant_intensities_interpolated, connected_path_from_rlu include("Intensities/Binning.jl") export intensities_binned, BinningParameters, count_bins, integrate_axes!, diff --git a/src/Symmetry/Crystal.jl b/src/Symmetry/Crystal.jl index daa47dc6a..3bdab7345 100644 --- a/src/Symmetry/Crystal.jl +++ b/src/Symmetry/Crystal.jl @@ -81,20 +81,6 @@ struct Crystal symprec :: Float64 # Tolerance to imperfections in symmetry end -""" - natoms(cryst::Crystal) - -Number of atoms in the unit cell, i.e., number of Bravais sublattices. -""" -@inline natoms(cryst::Crystal) = length(cryst.positions) - -""" - cell_volume(cryst::Crystal) - -Volume of the crystal unit cell. -""" -cell_volume(cryst::Crystal) = abs(det(cryst.latvecs)) - # Constructs a crystal from the complete list of atom positions `positions`, # representing fractions (between 0 and 1) of the lattice vectors `latvecs`. # All symmetry information is automatically inferred. @@ -145,6 +131,30 @@ function print_crystal_warnings(latvecs, positions) end end +""" + natoms(cryst::Crystal) + +Number of atoms in the unit cell, i.e., number of Bravais sublattices. +""" +@inline natoms(cryst::Crystal) = length(cryst.positions) + +""" + cell_volume(cryst::Crystal) + +Volume of the crystal unit cell. +""" +cell_volume(cryst::Crystal) = abs(det(cryst.latvecs)) + +""" + reciprocal_lattice_vectors(cryst::Crystal) + +Returns a ``3×3`` matrix, with columns that define the reciprocal lattice +vectors ``(𝐛₁,𝐛₂,𝐛₃)``. These are defined to satisfy ``𝐛ᵢ⋅𝐚ⱼ = 2πδᵢⱼ``, +where ``(𝐚₁,𝐚₂,𝐚₃)`` are the lattice vectors for `cryst` in real-space. +""" +reciprocal_lattice_vectors(cryst::Crystal) = cryst.recipvecs + + function spacegroup_name(hall_number::Int) # String representation of space group sgt = Spglib.get_spacegroup_type(hall_number) @@ -501,8 +511,8 @@ function subcrystal(cryst::Crystal, classes::Vararg{Int, N}) where N @info "Atoms have been renumbered in subcrystal." end - ret = Crystal(cryst.latvecs, cryst.prim_latvecs, new_positions, new_types, new_classes, new_sitesyms, - cryst.symops, cryst.spacegroup, cryst.symprec) + ret = Crystal(cryst.latvecs, cryst.prim_latvecs, cryst.recipvecs, new_positions, new_types, + new_classes, new_sitesyms, cryst.symops, cryst.spacegroup, cryst.symprec) return ret end diff --git a/test/test_correlation_sampling.jl b/test/test_correlation_sampling.jl index 0913ffd54..97d84e146 100644 --- a/test/test_correlation_sampling.jl +++ b/test/test_correlation_sampling.jl @@ -70,7 +70,7 @@ # Test path function and interpolation working (no correctness implied here) - qs, _ = connected_path(sc, [(0, 0, 0), (0, 1, 0), (1, 1, 0)], 20) + qs, _ = connected_path_from_rlu(sc.crystal, [(0, 0, 0), (0, 1, 0), (1, 1, 0)], 20) vals = intensities_interpolated(sc, qs; formula, interpolation=:linear) @test length(size(vals)) == 2 @@ -118,7 +118,7 @@ end sc = dynamical_correlations(sys; nω=25, ωmax=5.5, Δt=2Δt_langevin) add_sample!(sc, sys) - qs, _ = connected_path(sc, [(0.0, 0.0, 0.0), (0.5, 0.0, 0.0), (0.5, 0.5, 0.0), (0.0, 0.0, 0.0)], 10) + qs, _ = connected_path_from_rlu(sc.crystal, [(0.0, 0.0, 0.0), (0.5, 0.0, 0.0), (0.5, 0.5, 0.0), (0.0, 0.0, 0.0)], 10) data = intensities_interpolated(sc, qs; formula = intensity_formula(sc,:trace;kT = kT), interpolation=:linear) refdata = [4.254958223663417 -6.420612698722547e-15 9.517855303472442e-15 2.254439019145773e-15 -7.411655927494534e-15 -3.415475412410896e-15 7.193291213800562e-16 9.03543153937406e-16 -9.219208521726188e-16 3.3188559986441705e-16 -4.05636043338745e-15 0.0 3.844317340337979e-15 -2.6066681841577996e-15 -2.839447505217288e-15 2.056986106201179e-15 -1.475037653587726e-15 9.403365037570114e-16 -5.807960757836408e-16 -8.320134259982439e-16 0.0 4.839967298001723e-16 3.1943784166793436e-15 -1.6962933006317804e-15 -9.403365036110586e-16; 4.050322690710251 0.001503739876456909 0.0011921434008736649 0.0017936849615682003 0.0008070642362539781 0.0019841446952351166 0.003333290236633009 0.01578638047521035 0.2065064395838012 0.2139565139050153 0.008695481317449428 0.0032619849411028076 0.002433260632334384 0.0022639058709873396 0.0014892407581326237 0.0013016014987539154 0.0016154825324611825 0.0012544930922307295 0.0011056948071601774 0.002783872916314107 0.0009854416043813062 0.0017992116724471591 0.000683853705951668 0.002129274756212289 0.0011434575660303405; 3.8456871577570855 0.0030074797529202387 0.002384286801737812 0.003587369923134146 0.001614128472515368 0.003968289390473648 0.0066665804732652986 0.0315727609504198 0.4130128791676033 0.42791302781003027 0.01739096263490291 0.006523969882205615 0.004866521264664924 0.0045278117419772865 0.0029784815162680867 0.002603202997505774 0.00323096506492384 0.0025089861844605184 0.0022113896143209355 0.005567745832629046 0.0019708832087626123 0.0035984233448938343 0.0013677074119001417 0.0042585495124262745 0.0022869151320616217; 3.6410516248039193 0.004511219629383568 0.0035764302026019597 0.005381054884700092 0.002421192708776758 0.005952434085712181 0.009999870709897588 0.047359141425629234 0.6195193187514055 0.6418695417150454 0.0260864439523564 0.009785954823308423 0.007299781896995466 0.0067917176129672335 0.004467722274403551 0.003904804496257633 0.004846447597386498 0.003763479276690309 0.0033170844214816943 0.008351618748943985 0.0029563248131439187 0.00539763501734051 0.0020515611178486158 0.00638782426864026 0.003430372698092903; 3.4364160918507536 0.006014959505846898 0.004768573603466106 0.007174739846266038 0.0032282569450381477 0.007936578780950712 0.013333160946529877 0.06314552190083869 0.8260257583352075 0.8558260556200602 0.03478192526980988 0.01304793976441123 0.009733042529326004 0.009055623483957179 0.005956963032539013 0.005206405995009491 0.006461930129849155 0.005017972368920097 0.004422779228642452 0.011135491665258923 0.003941766417525225 0.007196846689787185 0.002735414823797089 0.008517099024854244 0.0045738302641241835; 3.231780558897588 0.007518699382310227 0.005960717004330253 0.008968424807831984 0.004035321181299538 0.009920723476189244 0.016666451183162168 0.07893190237604812 1.0325321979190099 1.069782569525075 0.043477406587263366 0.01630992470551404 0.012166303161656546 0.011319529354947126 0.007446203790674477 0.00650800749376135 0.008077412662311812 0.006272465461149887 0.0055284740358032105 0.013919364581573862 0.004927208021906531 0.00899605836223386 0.003419268529745563 0.010646373781068232 0.005717287830155465; 3.0271450259444226 0.009022439258773557 0.007152860405194402 0.01076210976939793 0.004842385417560928 0.011904868171427777 0.019999741419794458 0.09471828285125757 1.239038637502812 1.2837390834300904 0.05217288790471685 0.019571909646616845 0.014599563793987087 0.013583435225937074 0.00893544454880994 0.007809608992513209 0.009692895194774473 0.0075269585533796765 0.00663416884296397 0.016703237497888802 0.005912649626287837 0.010795270034680535 0.004103122235694037 0.012775648537282216 0.006860745396186747; 2.822509492991256 0.010526179135236886 0.008345003806058548 0.012555794730963877 0.005649449653822317 0.013889012866666309 0.023333031656426747 0.11050466332646702 1.445545077086614 1.4976955973351054 0.060868369222170336 0.022833894587719655 0.01703282442631763 0.01584734109692702 0.010424685306945404 0.009111210491265068 0.01130837772723713 0.008781451645609466 0.007739863650124727 0.019487110414203743 0.006898091230669145 0.012594481707127212 0.00478697594164251 0.014904923293496202 0.00800420296221803; 2.6178739600380907 0.012029919011700217 0.009537147206922695 0.014349479692529822 0.006456513890083707 0.01587315756190484 0.026666321893059036 0.12629104380167647 1.652051516670416 1.7116521112401202 0.06956385053962381 0.02609587952882246 0.019466085058648164 0.018111246967916966 0.011913926065080866 0.010412811990016925 0.012923860259699785 0.010035944737839253 0.008845558457285485 0.02227098333051868 0.00788353283505045 0.014393693379573885 0.005470829647590983 0.017034198049710184 0.009147660528249307; 2.4132384270849245 0.013533658888163548 0.010729290607786842 0.01614316465409577 0.007263578126345098 0.017857302257143375 0.02999961212969133 0.1420774242768859 1.8585579562542185 1.9256086251451354 0.07825933185707731 0.02935786446992527 0.021899345690978712 0.020375152838906913 0.013403166823216331 0.011714413488768786 0.014539342792162445 0.011290437830069045 0.009951253264446245 0.025054856246833623 0.008868974439431757 0.016192905052020565 0.006154683353539458 0.019163472805924175 0.01029111809428059; 2.2086028941317593 0.015037398764626876 0.01192143400865099 0.017936849615661714 0.008070642362606488 0.019841446952381905 0.03333290236632362 0.15786380475209535 2.0650643958380206 2.1395651390501498 0.08695481317453078 0.03261984941102808 0.024332606323309247 0.022639058709896857 0.014892407581351793 0.013016014987520643 0.016154825324625102 0.012544930922298834 0.011056948071607002 0.027838729163148557 0.009854416043813062 0.017992116724467235 0.0068385370594879315 0.02129274756213816 0.01143457566031187; 2.003967361178593 0.016541138641090206 0.013113577409515138 0.019730534577227662 0.008877706598867878 0.021825591647620442 0.03666619260295591 0.1736501852273048 2.271570835421823 2.3535216529551652 0.09565029449198428 0.03588183435213089 0.02676586695563979 0.02490296458088681 0.016381648339487256 0.014317616486272505 0.01777030785708776 0.013799424014528626 0.012162642878767761 0.0306226020794635 0.010839857648194371 0.019791328396913912 0.007522390765436406 0.023422022318352144 0.012578033226343153; 1.7993318282254276 0.018044878517553535 0.014305720810379285 0.021524219538793604 0.009684770835129267 0.02380973634285897 0.039999482839588194 0.18943656570251424 2.478077275005625 2.5674781668601803 0.10434577580943775 0.03914381929323369 0.02919912758797033 0.027166870451876754 0.01787088909762272 0.01561921798502436 0.01938579038955042 0.015053917106758413 0.01326833768592852 0.03340647499577844 0.011825299252575675 0.02159054006936059 0.008206244471384879 0.025551297074566128 0.013721490792374435; 1.5946962952722614 0.019548618394016865 0.015497864211243433 0.023317904500359556 0.010491835071390659 0.025793881038097505 0.043332773076220486 0.20522294617772371 2.684583714589427 2.7814346807651953 0.11304125712689125 0.04240580423433651 0.03163238822030088 0.0294307763228667 0.019360129855758183 0.01692081948377622 0.021001272922013075 0.016308410198988207 0.01437403249308928 0.036190347912093385 0.012810740856956984 0.023389751741807265 0.008890098177333354 0.02768057183078012 0.014864948358405716; 1.3900607623190957 0.021052358270480194 0.01669000761210758 0.0251115894619255 0.011298899307652047 0.027778025733336032 0.04666606331285277 0.22100932665293316 2.891090154173229 2.9953911946702103 0.12173673844434474 0.04566778917543931 0.034065648852631414 0.03169468219385665 0.020849370613893647 0.01822242098252808 0.022616755454475734 0.017562903291217992 0.015479727300250035 0.03897422082840832 0.01379618246133829 0.02518896341425394 0.009573951883281827 0.0298098465869941 0.016008405924437; 1.18542522936593 0.022556098146943523 0.017882151012971727 0.026905274423491443 0.012105963543913438 0.029762170428574562 0.04999935354948506 0.23679570712814257 3.097596593757031 3.209347708575225 0.1304322197617982 0.04892977411654211 0.03649890948496195 0.03395858806484659 0.02233861137202911 0.019524022481279938 0.02423223798693839 0.01881739638344778 0.016585422107410795 0.04175809374472325 0.014781624065719595 0.02698817508670061 0.0102578055892303 0.03193912134320808 0.017151863490468277; 0.9807896964127646 0.024059838023406853 0.01907429441383587 0.02869895938505739 0.012913027780174825 0.031746315123813096 0.05333264378611735 0.25258208760335205 3.304103033340833 3.42330422248024 0.13912770107925168 0.05219175905764492 0.03893217011729248 0.036222493935836535 0.02382785213016457 0.020825623980031793 0.025847720519401044 0.020071889475677566 0.01769111691457155 0.04454196666103819 0.0157670656701009 0.02878738675914729 0.010941659295178773 0.03406839609942207 0.018295321056499555; 0.776154163459599 0.025563577899870182 0.020266437814700018 0.03049264434662333 0.013720092016436215 0.03373045981905163 0.05666593402274964 0.26836846807856146 3.510609472924635 3.6372607363852545 0.14782318239670517 0.05545374399874772 0.041365430749623025 0.03848639980682648 0.025317092888300034 0.022127225478783652 0.027463203051863703 0.021326382567907354 0.018796811721732306 0.047325839577353125 0.016752507274482204 0.03058659843159396 0.011625513001127246 0.036197670855636056 0.019438778622530836; 0.5715186305064324 0.027067317776333515 0.021458581215564168 0.03228632930818928 0.014527156252697609 0.03571460451429016 0.059999224259381936 0.28415484855377093 3.717115912508438 3.8512172502902704 0.15651866371415868 0.05871572893985054 0.043798691381953574 0.040750305677816435 0.0268063336464355 0.023428826977535518 0.029078685584326365 0.02258087566013715 0.01990250652889307 0.05010971249366808 0.017737948878863513 0.032385810104040645 0.012309366707075723 0.038326945611850044 0.02058223618856212; 0.36688309755326676 0.028571057652796845 0.022650724616428315 0.03408001426975523 0.015334220488958996 0.03769874920952869 0.06333251449601422 0.29994122902898035 3.9236223520922398 4.065173764195285 0.16521414503161214 0.06197771388095335 0.046231952014284115 0.04301421154880638 0.028295574404570964 0.024730428476287373 0.030694168116789024 0.02383536875236694 0.021008201336053824 0.05289358540998301 0.01872339048324482 0.034185021776487315 0.012993220413024196 0.040456220368064025 0.021725693754593402; 0.16224756460010117 0.03007479752926017 0.02384286801729246 0.03587369923132117 0.016141284725220386 0.03968289390476722 0.06666580473264652 0.3157276095041898 4.130128791676042 4.2791302781002996 0.17390962634906562 0.06523969882205616 0.04866521264661465 0.04527811741979632 0.029784815162706427 0.026032029975039228 0.032309650649251676 0.025089861844596727 0.022113896143214587 0.055677458326297946 0.019708832087626124 0.035984233448933985 0.013677074118972668 0.04258549512427801 0.02286915132062468; 0.02547366951406332 0.030608915692135863 0.024271147530223282 0.03662608658488363 0.01676090815809476 0.04064434266479797 0.06849494958147812 0.32145111202501353 4.199360924209134 4.3506321884138615 0.17752645496153693 0.06722822459047063 0.051291481078470945 0.05132773303506229 0.3088833337771953 0.061862859955905555 0.03518424091438777 0.026375405633927505 0.024374059691704853 0.05735620206994079 0.02029650166875649 0.03713960926899464 0.014297916726271169 0.043563776598251065 0.02400154960357742; 0.024423050162280906 0.02920379042783628 0.023171699267287443 0.03529587872243911 0.017005649984195115 0.03956039955441315 0.06731580365470832 0.30704885863706577 3.9940444426408024 4.137224891544513 0.17098597816404387 0.0666698320135085 0.054303765109378746 0.06494876813888033 1.1432004081043936 0.1667521469010012 0.040577046644870746 0.027723050817460265 0.028943160722854154 0.05682468746823945 0.020088627203384956 0.03700731338428324 0.014792737136269728 0.04224007150774224 0.02511182932037308; 0.023372430810498498 0.0277986651635367 0.02207225100435161 0.03396567085999459 0.017250391810295473 0.03847645644402834 0.06613665772793854 0.29264660524911806 3.7887279610724724 3.9238175946751657 0.1644455013665509 0.06611143943654638 0.05731604914028654 0.07856980324269833 1.9775174824315893 0.27164143384609646 0.04596985237535371 0.02907069600099302 0.033512261754003445 0.056293172866538115 0.01988075273801343 0.03687501749957184 0.015287557546268288 0.040916366417233424 0.02622210903716874; 0.022321811458716086 0.026393539899237126 0.020972802741415775 0.03263546299755008 0.017495133636395824 0.03739251333364352 0.06495751180116877 0.27824435186117036 3.583411479504143 3.7104102978058187 0.15790502456905786 0.06555304685958423 0.060328333171194334 0.09219083834651633 2.811834556758785 0.3765307207911917 0.05136265810583667 0.030418341184525778 0.03808136278515273 0.055761658264836776 0.019672878272641903 0.03674272161486043 0.015782377956266847 0.039592661326724614 0.027332388753964397; 0.02127119210693368 0.024988414634937547 0.019873354478479946 0.03130525513510556 0.01773987546249618 0.0363085702232587 0.06377836587439897 0.26384209847322265 3.3780949979358135 3.497003000936471 0.15136454777156488 0.0649946542826221 0.06334061720210213 0.10581187345033433 3.646151631085981 0.481420007736287 0.05675546383631963 0.03176598636805854 0.04265046381630202 0.055230143663135445 0.019465003807270377 0.036610425730149034 0.016277198366265407 0.0382689562362158 0.028442668470760057; 0.02022057275515127 0.023583289370637965 0.01877390621554411 0.029975047272661048 0.017984617288596533 0.03522462711287389 0.06259921994762921 0.24943984508527486 3.172778516367483 3.283595704067124 0.14482407097407182 0.06443626170565997 0.06635290123300994 0.11943290855415237 4.4804687054131795 0.5863092946813826 0.06214826956680262 0.033113631551591305 0.04721956484745132 0.054698629061434106 0.019257129341898847 0.036478129845437635 0.01677201877626397 0.03694525114570699 0.02955294818755572; 0.019169953403368863 0.022178164106338386 0.017674457952608275 0.028644839410216533 0.018229359114696888 0.034140684002489076 0.06142007402085942 0.23503759169732716 2.9674620347991536 3.0701884071977767 0.13828359417657882 0.06387786912869783 0.06936518526391774 0.13305394365797038 5.314785779740375 0.691198581626478 0.06754107529728559 0.03446127673512406 0.051788665878600613 0.05416711445973277 0.01904925487652732 0.036345833960726236 0.017266839186262525 0.03562154605519817 0.030663227904351376; 0.01811933405158645 0.020773038842038807 0.016575009689672442 0.02731463154777202 0.018474100940797242 0.03305674089210426 0.06024092809408964 0.22063533830937945 2.7621455532308237 2.8567811103284297 0.1317431173790858 0.0633194765517357 0.07237746929482552 0.14667497876178837 6.1491028540675705 0.7960878685715732 0.07293388102776854 0.03580892191865682 0.0563577669097499 0.05363559985803143 0.018841380411155795 0.03621353807601484 0.01776165959626108 0.03429784096468936 0.031773507621147036; 0.01706871469980404 0.019367913577739232 0.015475561426736608 0.025984423685327506 0.018718842766897593 0.03197279778171945 0.05906178216731986 0.20623308492143172 2.556829071662494 2.6433738134590823 0.1252026405815928 0.06276108397477356 0.07538975332573333 0.16029601386560638 6.983419928394766 0.9009771555166685 0.0783266867582515 0.03715656710218958 0.06092686794089919 0.05310408525633009 0.018633505945784265 0.03608124219130343 0.018256480006259644 0.03297413587418054 0.03288378733794269; 0.016018095348021632 0.01796278831343965 0.014376113163800774 0.024654215822882988 0.01896358459299795 0.03088885467133463 0.05788263624055008 0.191830831533484 2.351512590094164 2.4299665165897353 0.11866216378409977 0.062202691397811434 0.07840203735664114 0.17391704896942442 7.817737002721964 1.005866442461764 0.08371949248873449 0.038504212285722345 0.0654959689720485 0.05257257065462877 0.018425631480412742 0.03594894630659203 0.018751300416258204 0.031650430783671725 0.033994067054738356; 0.014967475996239217 0.01655766304914007 0.013276664900864937 0.02332400796043847 0.019208326419098302 0.029804911560949814 0.05670349031378029 0.17742857814553625 2.146196108525834 2.2165592197203874 0.11212168698660675 0.0616442988208493 0.08141432138754894 0.18753808407324243 8.652054077049161 1.1107557294068595 0.08911229821921746 0.0398518574692551 0.07006507000319778 0.052041056052927415 0.018217757015041212 0.03581665042188063 0.019246120826256763 0.030326725693162908 0.03510434677153401; 0.014436171477102593 0.01682110165656617 0.01384019039687941 0.02274678870906014 0.018926108021738303 0.029215191636604843 0.05459648496127452 0.16980955833438244 2.043332872550263 2.111326979767518 0.11430303417450743 0.12084061140332855 0.08845069039822534 0.1909162010813912 8.428096226730217 1.4943423937097053 0.11891818265150902 0.05149989182766467 0.07213667095360889 0.053481956722232236 0.02042586397586009 0.036505637914788676 0.019789529645368776 0.03159154617870763 0.03754997948150723; 0.013904866957965967 0.017084540263992268 0.014403715892893881 0.022169569457681816 0.018643889624378306 0.028625471712259876 0.052489479608768735 0.16219053852322862 1.9404696365746914 2.006094739814648 0.1164843813624081 0.18003692398580778 0.09548705940890173 0.19429431808953995 8.204138376411272 1.877929058012551 0.14872406708380057 0.06314792618607425 0.07420827190401999 0.05492285739153705 0.022633970936678967 0.03719462540769671 0.020332938464480785 0.032856366664252346 0.03999561219148044; 0.013373562438829341 0.017347978871418368 0.014967241388908354 0.021592350206303488 0.01836167122701831 0.028035751787914905 0.050382474256262966 0.1545715187120748 1.8376064005991202 1.9008624998617778 0.1186657285503088 0.23923323656828704 0.10252342841957814 0.19767243509768873 7.980180526092329 2.261515722315397 0.17852995151609213 0.07479596054448381 0.07627987285443111 0.05636375806084187 0.024842077897497844 0.03788361290060475 0.0208763472835928 0.03412118714979707 0.04244124490145365; 0.012842257919692714 0.01761141747884447 0.015530766884922827 0.021015130954925163 0.01807945282965831 0.027446031863569937 0.04827546890375718 0.146952498900921 1.7347431646235492 1.7956302599089078 0.12084707573820946 0.29842954915076625 0.10955979743025453 0.20105055210583747 7.756222675773384 2.6451023866182424 0.20833583594838367 0.0864439949028934 0.0783514738048422 0.05780465873014669 0.02705018485831672 0.03857260039351278 0.021419756102704805 0.03538600763534179 0.04488687761142686; 0.01231095340055609 0.01787485608627057 0.016094292380937302 0.020437911703546835 0.017797234432298314 0.02685631193922497 0.04616846355125141 0.13933347908976718 1.631879928647978 1.690398019956038 0.12302842292611015 0.3576258617332455 0.11659616644093093 0.20442866911398627 7.53226482545444 3.028689050921088 0.23814172038067527 0.09809202926130296 0.08042307475525332 0.05924555939945152 0.0292582918191356 0.039261587886420826 0.021963164921816818 0.036650828120886506 0.04733251032140008; 0.011779648881419465 0.01813829469369667 0.016657817876951773 0.01986069245216851 0.017515016034938317 0.026266592014880002 0.04406145819874564 0.1317144592786134 1.529016692672407 1.5851657800031684 0.12520977011401083 0.41682217431572477 0.12363253545160735 0.20780678612213505 7.308306975135497 3.4122757152239345 0.2679476048129668 0.10974006361971254 0.0824946757056644 0.06068646006875633 0.03146639877995448 0.03995057537932886 0.02250657374092883 0.037915648606431235 0.04977814303137329; 0.01124834436228284 0.018401733301122766 0.017221343372966248 0.019283473200790182 0.017232797637578318 0.025676872090535027 0.041954452846239866 0.12409543946745955 1.4261534566968355 1.4799335400502984 0.12739111730191152 0.47601848689820403 0.13066890446228374 0.21118490313028376 7.084349124816551 3.79586237952678 0.2977534892452584 0.12138809797812213 0.08456627665607552 0.06212736073806115 0.03367450574077335 0.0406395628722369 0.02304998256004084 0.03918046909197595 0.052223775741346505; 0.010717039843146214 0.018665171908548862 0.01778486886898072 0.018706253949411854 0.01695057924021832 0.02508715216619006 0.03984744749373409 0.11647641965630574 1.3232902207212645 1.3747013000974286 0.1295724644898122 0.5352147994806832 0.13770527347296013 0.21456302013843254 6.860391274497608 4.179449043829625 0.3275593736775499 0.1330361323365317 0.08663787760648661 0.06356826140736596 0.03588261270159223 0.041328550365144934 0.02359339137915285 0.040445289577520666 0.05466940845131972; 0.010185735324009588 0.018928610515974963 0.01834839436499519 0.01812903469803353 0.01666836084285832 0.024497432241845092 0.03774044214122831 0.10885739984515191 1.220426984745693 1.2694690601445584 0.13175381167771288 0.5944111120631626 0.14474164248363652 0.2179411371465813 6.636433424178663 4.563035708132472 0.35736525810984154 0.14468416669494127 0.08870947855689773 0.06500916207667079 0.03809071966241111 0.04201753785805298 0.024136800198264863 0.04171011006306539 0.057115041161292936; 0.009654430804872964 0.019192049123401063 0.018911919861009666 0.017551815446655204 0.016386142445498325 0.02390771231750012 0.035633436788722536 0.1012383800339981 1.117563748770122 1.1642368201916888 0.13393515886561355 0.6536074246456418 0.15177801149431291 0.22131925415473008 6.4124755738597194 4.946622372435317 0.3871711425421331 0.15633220105335083 0.09078107950730883 0.0664500627459756 0.040298826623229984 0.04270652535096102 0.024680209017376872 0.04297493054861011 0.059560673871266144; 0.009123126285736338 0.019455487730827163 0.019475445357024137 0.016974596195276873 0.016103924048138325 0.02331799239315515 0.03352643143621676 0.09361936022284428 1.0147005127945508 1.0590045802388188 0.13611650605351422 0.7128037372281211 0.15881438050498933 0.22469737116287886 6.188517723540775 5.330209036738163 0.4169770269744246 0.16798023541176044 0.09285268045771994 0.06789096341528042 0.04250693358404887 0.043395512843869055 0.025223617836488885 0.04423975103415483 0.06200630658123936; 0.008591821766599712 0.019718926338253263 0.020038970853038608 0.016397376943898548 0.01582170565077833 0.022728272468810182 0.03141942608371098 0.08600034041169047 0.9118372768189795 0.953772340285949 0.13829785324141491 0.7720000498106003 0.16585074951566575 0.22807548817102763 5.96455987322183 5.7137957010410085 0.44678291140671617 0.17962826977017 0.09492428140813104 0.06933186408458525 0.044715040544867746 0.04408450033677709 0.0257670266556009 0.045504571519699555 0.06445193929121257; 0.008060517247463086 0.019982364945679364 0.020602496349053083 0.015820157692520223 0.015539487253418333 0.022138552544465215 0.029312420731205213 0.07838132060053665 0.8089740408434083 0.848540100333079 0.1404792004293156 0.8311963623930796 0.17288711852634214 0.23145360517917637 5.740602022902886 6.097382365343855 0.47658879583900776 0.19127630412857957 0.09699588235854216 0.07077276475389008 0.04692314750568662 0.04477348782968513 0.026310435474712908 0.04676939200524427 0.06689757200118579; 0.007529212728326461 0.02024580355310546 0.021166021845067554 0.015242938441141895 0.015257268856058335 0.021548832620120244 0.027205415378699437 0.07076230078938284 0.7061108048678372 0.7433078603802092 0.14266054761721625 0.8903926749755587 0.1799234875370185 0.23483172218732512 5.516644172583941 6.4809690296467 0.5063946802712993 0.20292433848698915 0.09906748330895325 0.07221366542319488 0.0491312544665055 0.04546247532259317 0.026853844293824917 0.048034212490788986 0.069343204711159; 0.006997908209189836 0.02050924216053156 0.021729547341082026 0.014665719189763568 0.014975050458698336 0.020959112695775276 0.025098410026193667 0.06314328097822904 0.603247568892266 0.6380756204273396 0.14484189480511694 0.9495889875580379 0.18695985654769492 0.23820983919547392 5.292686322264998 6.864555693949546 0.5362005647035908 0.2145723728453987 0.10113908425936437 0.07365456609249971 0.05133936142732438 0.046151462815501205 0.02739725311293693 0.04929903297633371 0.0717888374211322; 0.006466603690053211 0.020772680767957657 0.0222930728370965 0.014088499938385244 0.01469283206133834 0.02036939277143031 0.02299140467368789 0.05552426116707522 0.5003843329166949 0.5328433804744697 0.14702324199301764 1.0087853001405171 0.19399622555837132 0.24158795620362267 5.068728471946054 7.248142358252391 0.5660064491358824 0.2262204072038083 0.10321068520977546 0.07509546676180452 0.05354746838814325 0.04684045030840924 0.027940661932048937 0.05056385346187843 0.07423447013110542; 0.005935299170916586 0.02103611937538376 0.022856598333110975 0.013511280687006917 0.014410613663978342 0.019779672847085338 0.020884399321182117 0.04790524135592142 0.3975210969411238 0.4276111405216 0.1492045891809183 1.0679816127229964 0.2010325945690477 0.24496607321177144 4.844770621627109 7.631729022555237 0.5958123335681739 0.23786844156221781 0.10528228616018658 0.07653636743110934 0.055755575348962125 0.04752943780131728 0.02848407075116095 0.051828673947423146 0.07668010284107864; 0.005403994651779959 0.021299557982809858 0.023420123829125446 0.012934061435628587 0.014128395266618342 0.019189952922740366 0.018777393968676337 0.040286221544767575 0.29465786096555224 0.3223789005687297 0.15138593636881897 1.127177925305476 0.20806896357972413 0.2483441902199202 4.620812771308164 8.015315686858084 0.6256182180004656 0.24951647592062745 0.10735388711059768 0.07797726810041417 0.05796368230978101 0.04821842529422532 0.029027479570272963 0.05309349443296787 0.07912573555105186; 0.0048726901326433345 0.02156299659023596 0.023983649325139918 0.012356842184250262 0.013846176869258346 0.0186002329983954 0.016670388616170564 0.03266720173361377 0.19179462498998115 0.21714666061585997 0.15356728355671967 1.186374237887955 0.21510533259040052 0.251722307228069 4.39685492098922 8.39890235116093 0.6554241024327572 0.26116451027903703 0.1094254880610088 0.07941816876971898 0.060171789270599886 0.048907412787133356 0.029570888389384972 0.05435831491851259 0.08157136826102507; 0.004341385613506709 0.021826435197662055 0.024547174821154392 0.011779622932871936 0.01356395847189835 0.01801051307405043 0.014563383263664789 0.02504818192245996 0.08893138901441007 0.11191442066299019 0.15574863074462034 1.2455705504704342 0.22214170160107694 0.25510042423621776 4.172897070670277 8.782489015463774 0.6852299868650487 0.2728125446374466 0.11149708901141989 0.08085906943902381 0.062379896231418756 0.0495964002800414 0.030114297208496982 0.055623135404057306 0.08401700097099828; 0.0040615115184178055 0.0218995017937112 0.0248178406041569 0.012447603611329593 0.014006174086251027 0.018088916017701247 0.013457842968559222 0.020585755770862534 0.020443597700697137 0.04191655754350475 0.15538767829138575 1.265703925957692 0.2256843047879328 0.25777441201324325 3.982241209196207 9.015864926829838 0.9209236273137634 0.31780578284792793 0.11806620553186306 0.08373787105232085 0.06626139719751045 0.05077077240674423 0.03136773374212183 0.057533342926944606 0.08670532348850002; 0.004284498271424343 0.021591824367006424 0.02450278696113547 0.015605984149459226 0.01589725772402906 0.019503564697343635 0.014355232788254065 0.022436516938377876 0.02070669571070052 0.04238744809078793 0.14994212655588057 1.2077114272545073 0.22223937632714771 0.25904014132802244 3.8581893254118884 8.948819332322339 1.568392779795327 0.42948942876255314 0.13363035319237038 0.0894924745536024 0.07348968617414778 0.052915913801036635 0.03404122570477236 0.060734324524517075 0.08987902562105884; 0.004507485024430881 0.02128414694030165 0.02418773331811404 0.018764364687588853 0.01778834136180709 0.020918213376986017 0.015252622607948907 0.024287278105893213 0.020969793720703907 0.042858338638071106 0.14449657482037542 1.1497189285513227 0.2187944478663626 0.26030587064280164 3.7341374416275697 8.881773737814838 2.2158619322768884 0.541173074677178 0.14919450085287767 0.09524707805488393 0.08071797515078508 0.05506105519532904 0.03671471766742288 0.06393530612208953 0.09305272775361766; 0.004730471777437419 0.020976469513596874 0.023872679675092615 0.021922745225718476 0.019679424999585117 0.022332862056628402 0.016150012427643752 0.02613803927340855 0.02123289173070729 0.043329229185354284 0.13905102308487027 1.0917264298481382 0.21534951940557753 0.26157159995758084 3.6100855578432514 8.814728143307338 2.8633310847584497 0.6528567205918029 0.16475864851338495 0.10100168155616544 0.08794626412742237 0.05720619658962145 0.039388209630073404 0.067136287719662 0.09622642988617648; 0.004953458530443957 0.020668792086892103 0.023557626032071188 0.0250811257638481 0.02157050863736315 0.023747510736270787 0.017047402247338594 0.02798880044092389 0.021495989740710673 0.04380011973263747 0.13360547134936512 1.0337339311449538 0.21190459094479241 0.26283732927236 3.486033674058933 8.747682548799839 3.510800237240011 0.7645403665064278 0.18032279617389224 0.10675628505744697 0.09517455310405967 0.05935133798391386 0.042061701592723925 0.07033726931723445 0.0994001320187353; 0.005176445283450496 0.02036111466018733 0.02324257238904976 0.028239506301977732 0.023461592275141184 0.025162159415913175 0.01794479206703344 0.029839561608439234 0.02175908775071406 0.044271010279920656 0.12815991961385997 0.975741432441769 0.20845966248400732 0.2641030585871392 3.3619817902746147 8.68063695429234 4.158269389721575 0.876224012421053 0.19588694383439959 0.11251088855872853 0.10240284208069701 0.06149647937820628 0.04473519355537446 0.07353825091480692 0.10257383415129413; 0.0053994320364570345 0.020053437233482553 0.022927518746028334 0.03139788684010736 0.025352675912919214 0.02657680809555556 0.01884218188672828 0.03169032277595457 0.02202218576071744 0.044741900827203834 0.12271436787835481 0.9177489337385843 0.20501473402322226 0.26536878790191837 3.237929906490296 8.61359135978484 4.805738542203136 0.9879076583356778 0.21145109149490687 0.11826549206001005 0.10963113105733432 0.06364162077249869 0.047408685518024976 0.07673923251237938 0.10574753628385294; 0.005622418789463572 0.01974575980677778 0.022612465103006907 0.03455626737823698 0.027243759550697244 0.027991456775197945 0.019739571706423122 0.03354108394346991 0.022285283770720827 0.04521279137448701 0.11726881614284966 0.8597564350353999 0.20156980556243714 0.2666345172166975 3.1138780227059772 8.54654576527734 5.453207694684697 1.0995913042503027 0.22701523915541416 0.1240200955612916 0.11685942003397164 0.06578676216679108 0.050082177480675505 0.07994021410995185 0.10892123841641177; 0.00584540554247011 0.01943808238007301 0.02229741145998548 0.037714647916366605 0.02913484318847527 0.029406105454840327 0.020636961526117967 0.03539184511098525 0.02254838178072421 0.0456836819217702 0.11182326440734452 0.8017639363322153 0.19812487710165205 0.2679002465314767 2.989826138921659 8.479500170769839 6.100676847166259 1.2112749501649276 0.24257938681592148 0.1297746990625731 0.12408770901060892 0.0679319035610835 0.05275566944332602 0.0831411957075243 0.11209494054897058; 0.006068392295476649 0.019130404953368235 0.021982357816964053 0.04087302845449624 0.03102592682625331 0.030820754134482722 0.021534351345812813 0.037242606278500595 0.022811479790727597 0.046154572469053384 0.10637771267183936 0.7437714376290308 0.19467994864086696 0.2691659758462559 2.86577425513734 8.412454576262341 6.7481459996478215 1.322958596079553 0.25814353447642885 0.13552930256385465 0.13131599798724627 0.07007704495537591 0.05542916140597656 0.08634217730509679 0.11526864268152942; 0.006291379048483187 0.01882272752666346 0.021667304173942623 0.044031408992625864 0.03291701046403134 0.032235402814125104 0.022431741165507654 0.039093367446015936 0.02307457780073098 0.04662546301633656 0.1009321609363342 0.6857789389258461 0.19123502018008187 0.2704317051610351 2.7417223713530214 8.34540898175484 7.395615152129384 1.4346422419941778 0.27370768213693614 0.1412839060651362 0.13854428696388357 0.07222218634966832 0.05810265336862708 0.08954315890266924 0.11844234481408822; 0.005945081012206664 0.018728614232401696 0.021312636474666684 0.03969675887247996 0.03013447620092704 0.029931408080412503 0.021513683629800093 0.03686056769195569 0.022496886914371107 0.04497445040176341 0.10844225562728177 0.7683861149030353 0.19523977143692084 0.26229337853762197 2.7027644540659646 7.932870273176352 6.494573878963147 1.271925705507353 0.2515791870342675 0.13091647293808206 0.12877665842528058 0.06741193787578825 0.055905307232000365 0.08358857548193607 0.11235173262125246; 0.005616970312609012 0.018674835132401393 0.020956193159252665 0.03564332430908099 0.02746709515115821 0.027702435010922732 0.020654269060515474 0.03476257544674996 0.021886661394123187 0.04340914016455194 0.11658653432646833 0.8566088291489652 0.19993838789299778 0.2528813932325319 2.6424060977794643 7.497630336384534 5.651780983017238 1.1188362209713925 0.23014691851890634 0.1208606516865352 0.11921456336553957 0.06267549094089367 0.053623068977771984 0.07767744495037049 0.10650157434603617; 0.0053070469496902285 0.018661390226662543 0.02059797422770055 0.03187110530242895 0.024914867314724837 0.025548483605655772 0.019853497457653788 0.032799390710398724 0.021243901239987192 0.041929532304702075 0.12536499703389378 0.9504470816636346 0.20533086954831262 0.24219574924576462 2.560647302493519 7.039689171379381 4.867236464291655 0.975373788386296 0.20941087659085253 0.11111644231049544 0.10985800178466058 0.05801284554498452 0.0512559386059419 0.07180976730797248 0.1008918699884393; 0.005015310923450316 0.018688279515185154 0.020237979680010357 0.028380101852523824 0.02247779269162692 0.023469553864611623 0.019111368821215037 0.030971013482902 0.02056860645196315 0.040535626822213865 0.1347776437495583 1.049900872447045 0.21141721640286545 0.23023644657732034 2.4574880682081295 6.55904677816089 4.140940322786398 0.8415384077520633 0.189371061250106 0.10168384480996284 0.10070697368264354 0.053424001688060836 0.04880391611651015 0.06598554255474204 0.09552261954846188; 0.004741762233889273 0.01875550299796922 0.01987620951618208 0.025170313959365642 0.020155871281864468 0.0214656457877903 0.018427883151199226 0.029277443764259788 0.019860777030051048 0.0392274237170873 0.1448244744734617 1.1549702014991954 0.2181974284566562 0.21700348522719903 2.3329283949232966 6.055703156729068 3.472892558501471 0.7173300790686956 0.170027472496667 0.09256285918493745 0.09176147905948855 0.04890895937012262 0.046267001509476734 0.0602047706906792 0.0903938230261039; 0.0044864008810071 0.018863060675014743 0.019512663736215713 0.022241741622954392 0.01794910308543748 0.019536759375191795 0.017803040447606354 0.02771868155447208 0.019120412974250885 0.03800492298932238 0.15550548920560409 1.2656550688200858 0.22567150570968486 0.2024968651954006 2.1869682826390195 5.529658307083912 2.863093171436871 0.6027488023361921 0.15138011033053536 0.08375348543541924 0.08302151791519556 0.04446771859116987 0.04364519478484164 0.054467451715783946 0.08550548042136535; 0.004249226864803798 0.01901095254632172 0.019147342340111263 0.019594384843290073 0.015857488102345954 0.01768289462681611 0.017236840710436414 0.026294726853538883 0.018347514284562667 0.036868124638919086 0.16682068794598542 1.381955474409716 0.23383944816195149 0.18671658648192518 2.0196077313552983 4.980912229225421 2.311542161592598 0.4977945775545527 0.13342897475171112 0.0752557235614082 0.07448709024976458 0.04010027935120258 0.04093849594260487 0.048773585630056265 0.08085759173424628; 0.004030240185279365 0.019199178611890154 0.018780245327868726 0.017228243620372674 0.01388102633258988 0.015904051542663236 0.016729283939689414 0.025005579661460185 0.017542080960986386 0.03581702866587744 0.17877007069460574 1.5038714182680875 0.24270125581345603 0.16966264908677256 1.8308467410721319 4.4094649231535925 1.818239528968651 0.402467404723777 0.11617406576019418 0.0670695735629043 0.06615819606319553 0.035806641650220725 0.0381469049827664 0.04312317243349614 0.07645015696474661; 0.003829440842433803 0.019427738871720054 0.018411372699488112 0.015143317954202217 0.012019717776169279 0.014200230122733193 0.016280370135365357 0.02385123997823601 0.01670411300352205 0.034851635070197444 0.19135363745146505 1.6314029003951989 0.2522569286641986 0.151335053009943 1.6206853117895224 3.8153163888684336 1.383185273565033 0.3167672838438662 0.09961538335598473 0.0591950354399076 0.058034835355488555 0.03158680548822436 0.035270421905326275 0.03751621212610364 0.07228317611286642; 0.00364682883626711 0.0196966333258114 0.018040724454969403 0.013339607844778686 0.010273562433084136 0.012571430367025963 0.015890099297464228 0.022831707803866324 0.015833610412169654 0.03397194385187907 0.2045713882165632 1.7645499207910493 0.26250646671417893 0.13173379825143633 1.3891234435074682 3.19846662636994 1.0063793953817421 0.24069421491481943 0.08375292753908264 0.05163210919241808 0.05011700812664356 0.027440770865213446 0.032309046710284464 0.03195270470787871 0.06835664917860562; 0.003482404166779289 0.020005861974164207 0.017668300594312618 0.011817113292102093 0.008642560303334457 0.011017652275541556 0.015558471425986046 0.021946983138351164 0.014930573186929204 0.03317795501092236 0.21842332298990041 1.903312479455641 0.2734498699633974 0.11085888481125263 1.1361611362259707 2.5589156356581126 0.6878218944187784 0.17424819793663693 0.06858669830948796 0.04438079482043574 0.04240471437666059 0.023368537781188 0.029262779397640992 0.026432650178821376 0.06467057616196431; 0.003336166833970336 0.02035542481677846 0.017294101117517738 0.010575834296172424 0.007126711386920239 0.00953889584827996 0.015285486520930792 0.0211970659816905 0.01399500132780069 0.032469668547327274 0.2329094417714765 2.047690576388972 0.28508713841185357 0.08871031268939183 0.8617983899450286 1.8966634167329506 0.42751277067614196 0.11742923290931864 0.05411669566720066 0.03744109232396056 0.03489795410553959 0.019370106236148012 0.02613161996739583 0.020956048538931606 0.06122495706294241; 0.0032081168378402546 0.02074532185365419 0.01691812602458478 0.009615770856989688 0.005726015683841483 0.008135161085241191 0.015071144582298482 0.02058195633388435 0.013026894834784123 0.03184708446109385 0.2480297445612916 2.1976842115910444 0.2974182720595479 0.065288081885854 0.5660352046646426 1.2117099695944544 0.22545202415383264 0.07023731983286455 0.04034291961222077 0.03081300170299257 0.027596727313280618 0.015445476230093489 0.022915568419549 0.015522899788209431 0.05801979188153998; 0.0030982541783890418 0.02117555308479136 0.016540375315513736 0.008936922974553878 0.004440473194098185 0.006806447986425233 0.014915445610089104 0.020101654194932702 0.012026253707879492 0.03131020275222205 0.2637842313593456 2.353293385061856 0.31044327090647994 0.0405921924006391 0.24887158038481239 0.5040552942426242 0.08163965485185048 0.03267245870727467 0.027265370144548252 0.024496522957531738 0.020501033999883626 0.011594647763024421 0.019614624754100488 0.01013320392665483 0.05505508061775698; 0.003986060199204506 0.021667187117796857 0.01636513474953283 0.008961312317088707 0.003984787990792592 0.006593319207341276 0.01568255954966047 0.026585774437496455 0.09323269334405329 0.09894405893660566 0.2720159537942247 2.405810542249199 0.3132574140559028 0.023802033162204958 0.025510611235400365 0.019857044300259814 0.018342042545192346 0.013150324561259558 0.018986928009989176 0.02074879622283559 0.016003939766600226 0.009483012447212029 0.01737059247264471 0.007269691016504726 0.05240627180170216; 0.013372182982866916 0.022155568535481448 0.01656164409942635 0.00986570251646603 0.004944644709510053 0.008389942979407048 0.01805866197172783 0.04581474041317424 0.3275943486229148 0.29335635548569283 0.2640765795721831 2.2464238510404266 0.29485531428654443 0.024856788637303444 0.02538637723863029 0.02035090249159238 0.018934962928222495 0.013624389939802934 0.019109317598796076 0.02154713402505953 0.016320151162677467 0.010655363193894584 0.017354130581092475 0.009278526594886734 0.05001626806215685; 0.032558243211696786 0.022555698110233387 0.01670380885351584 0.010683421218062494 0.0058261254852875945 0.0100419967795584 0.020226415798254266 0.06360472661611265 0.544986248491847 0.4736383548454688 0.25603431347488276 2.0924962817597463 0.2769960802296381 0.025777578738165316 0.025208183080951168 0.020760576121720744 0.019440354410629333 0.014032203543619663 0.019176199241579848 0.022237342289137325 0.016574357459609775 0.01172061742801308 0.017296059828413073 0.01112596827477222 0.047667899388239905; 0.06154424088569435 0.02286757584205268 0.0167916290118013 0.011414468421878102 0.006629230318125225 0.011549480607795348 0.022185821029239796 0.0799557330463119 0.7454083929508517 0.639790057015935 0.24788915550232368 1.9440278344071564 0.25967971188518363 0.02656440346479059 0.02497602876236301 0.02108606519064492 0.019858216992412844 0.014373765372709759 0.019187572938340496 0.022819421015068994 0.01676655865739716 0.012678775149567532 0.017196380214606517 0.012812016056161203 0.045361165779951286; 0.10033017600485925 0.023091201730939315 0.016825104574282733 0.012058844127912846 0.0073539592080229315 0.012912394464117873 0.023936877664684393 0.09486775970377176 0.9288607819999266 0.7918114619970896 0.23964110565450605 1.8010185089826598 0.2429062092531812 0.02721726281717924 0.024689914282865807 0.021327369698364906 0.020188550673573034 0.014649075427073206 0.019143438689078005 0.023293370202854526 0.01689675475603962 0.013529836358557923 0.017055091739672804 0.014336669939053664 0.04309606723729104; 0.1489160485691916 0.023226575776893302 0.01680423554096014 0.012616548336166732 0.008000312154980722 0.014130738348525986 0.025479585704588074 0.10834080658849235 1.095343415639073 0.9297025697889336 0.23129016393142968 1.663468305486254 0.22667557233363075 0.027736156795331285 0.024349839642459563 0.021484489644880696 0.020431355454109904 0.014858133706710012 0.019043796493792398 0.02365918985249392 0.01696494575553715 0.014273801054984263 0.01687219440361193 0.01569992992344961 0.04087260376025915; 0.20730185857869146 0.023273697979914635 0.016729021911833515 0.013087581046639757 0.008568289158998594 0.015204512261019681 0.026813945148950833 0.12037487370047362 1.2448562938682906 1.053463380391467 0.2228363303330947 1.5313772239179404 0.21098780112653223 0.028121085399246716 0.023955804841144273 0.021557425030192295 0.020586631334023452 0.015000940211620177 0.018888646352483662 0.023916879963987184 0.016971131655889753 0.014910669238846548 0.016647688206423896 0.01690179600934904 0.0386907753488556; 0.27548760603335876 0.02323256834000332 0.016599463686902863 0.013471942259331925 0.00905789022007655 0.016133716201598966 0.02793995599777268 0.13096996103971564 1.3773994166875794 1.1630938938046897 0.21427960485950098 1.404745264277718 0.1958428956318856 0.02837204862892554 0.023507809878919945 0.0215461758542997 0.02065437831331368 0.015077494941803701 0.018677988265151796 0.024066440537334314 0.01691531245709743 0.015440440910144782 0.016381573148108702 0.017942268196751963 0.03655058200308041; 0.3534732909331936 0.023103186857159355 0.016415560866168185 0.013769631974243236 0.00946911533821459 0.016918350170263836 0.028857618251053603 0.14012606860621835 1.4929727840969398 1.258594110028602 0.20561998751064864 1.2835724265655875 0.18124085584969096 0.028489046484367758 0.023005854755786582 0.02145074211720292 0.02063459639198059 0.015087797897260585 0.0184118222317968 0.024107871572535307 0.016797488159160184 0.01586311606887896 0.01607384922866635 0.01882134648565837 0.03445202372293357; 0.4412589132781957 0.022885553531382732 0.016177313449629478 0.013980650191373683 0.009801964513412713 0.017558414167014293 0.029566931908793604 0.14784319639998178 1.5915763960963716 1.3399640290632033 0.19685747828653763 1.1678587107815483 0.16718168177994827 0.028472078965573355 0.022449939471744174 0.021271123818901942 0.020527285570024176 0.015031849077990826 0.018090148252418677 0.024041173069590166 0.016617658762078012 0.01617869471504909 0.015724516448096842 0.019539030876068265 0.03239510050841508; 0.5388444730683659 0.02257966836267346 0.015884721437286742 0.014104996910723276 0.010056437745670919 0.01805390819185034 0.030067896970992693 0.15412134442100595 1.6732102526858752 1.4072036509084944 0.1879920771871679 1.0576041169256003 0.1536653734226575 0.028321146072542356 0.021840064026792713 0.021007320959396773 0.020332445847444442 0.014909648483994422 0.017712966327017427 0.02386634502849889 0.016375824265850908 0.016387176848655163 0.015333574806400168 0.020095321367981644 0.030379812359524937; 0.6462299703037032 0.022185531351031542 0.015537784829139983 0.014142672132292006 0.010232535034989207 0.01840483224477197 0.03036051343765086 0.15896051266929084 1.7378743538654502 1.4603129755644748 0.1790237842125395 0.9528086449977444 0.14069193077781872 0.028036247805274743 0.021176228420932218 0.020659333538687413 0.020050077224241385 0.014721196115271385 0.01728027645559305 0.023583387449261486 0.01607198467047888 0.016488562469697182 0.014901024303576338 0.020490217961398507 0.028406159276263145; 0.7634154049842079 0.02170314249645697 0.015136503625189195 0.014093675856079875 0.010330256381367578 0.018611186325779183 0.030444781308768105 0.16236070114483642 1.7855686996350961 1.4992920030311443 0.1699525993626525 0.8534722949979804 0.12826135384543189 0.02761738416377052 0.02045843265416268 0.02022716155677386 0.01968017970041501 0.014466491971821701 0.016792078638145545 0.02319230033187794 0.01570613997596193 0.016482851578175153 0.01442686493962535 0.02072372065631886 0.026474141258629708; 0.8904007771098802 0.021132501798949745 0.014680877825434376 0.01395800808208689 0.010349601784806034 0.01867297043487199 0.03032070058434444 0.16432190984764272 1.8162932899948134 1.5241407333085033 0.16077852263750675 0.7595950669263075 0.11637364262549699 0.027064555148029683 0.019686676726484102 0.019710805013656112 0.019222753275965315 0.014145536053645377 0.016248372874674916 0.02269308367634826 0.01527829018230005 0.016370044174089063 0.013911096714547204 0.020795829452742706 0.024583758306624626; 1.0271860866807196 0.02047360925850987 0.014170907429875533 0.013735668810313045 0.01029057124530457 0.01859018457205038 0.029988271264379848 0.1648441387777098 1.8300481249446021 1.5348591663965518 0.1515015540371024 0.6711769607827264 0.10502879711801405 0.02637776075805224 0.018860960637896485 0.019110263909334176 0.0186777979508923 0.01375832836074241 0.015649159165181153 0.022085737482672452 0.014788435289493248 0.016150140257438925 0.013353719628341896 0.020706544350670028 0.022735010420247895; 1.1737713336967275 0.019726464875137347 0.013606592438512661 0.01342665804075834 0.010153164762863192 0.018362828737314357 0.02944749334887434 0.16392738793503753 1.8268332044844626 1.5314473022952897 0.14212169356143933 0.5882179765672367 0.09422681732298305 0.02555700099383819 0.017981284388399823 0.01842553824380805 0.01804531372519596 0.013304868893112803 0.014994437509664268 0.02137026175085051 0.014236575297541517 0.01582313982822474 0.012754733681009432 0.020455865350100848 0.020927897599499513; 1.3301565181579027 0.018891068648832166 0.012987932851345755 0.013030975773422774 0.009937382337481895 0.017990902930663917 0.028698366837827907 0.16157165731962597 1.806648528614394 1.5139051410047166 0.13263894121051756 0.5107181142798384 0.08396770324040395 0.024602275855387518 0.017047647977994115 0.017656628017077722 0.017325300598876294 0.012785157650756551 0.01428420790812425 0.02054665648088242 0.013622710206444854 0.015389042886446491 0.012114138872549803 0.020043792451035138 0.019162419844379473; 1.4963416400642449 0.017967420579594336 0.012314928668374827 0.012548622008306351 0.00964322396916068 0.017474407152099067 0.02774089173124056 0.15777694693147512 1.7694940973343969 1.482232682524833 0.12305329698433715 0.43867737392053197 0.07425145487027687 0.023513585342700247 0.01606005140667937 0.016803533229143207 0.016517758571933314 0.01219919463367366 0.013518470360561105 0.01961492167276821 0.012946840016203269 0.014847849432104196 0.011431935202963019 0.019470325653472925 0.017438577154887794; 1.6723266994157542 0.016955520667423854 0.011587579889599872 0.011979596745409066 0.00927068965789955 0.0168133414016198 0.026575068029112296 0.152543256770585 1.7153699106444709 1.4364299268556389 0.11336476088289807 0.3720957554893173 0.06507807221260173 0.022290929455776362 0.015018494674455582 0.015866253880004504 0.015622687644367016 0.01154697984186413 0.012697224866974834 0.01857505732650786 0.012208964726816759 0.014199559465197843 0.010708122672249078 0.018735464957414197 0.015756369531024467; 1.8581116962124316 0.015855368912320728 0.010805886515020888 0.011323899984730925 0.008819779403698503 0.016007705679226124 0.025200895731443115 0.14587058683695564 1.6442759685446169 1.3764968739971342 0.10357333290620035 0.3109732589861941 0.05644755526737853 0.02093430819461587 0.013922977781322754 0.014844789969661605 0.014640087816177393 0.010828513275327956 0.011820471427365434 0.017427063442101383 0.011409084338285322 0.013444172985727441 0.009942701280407975 0.01783921036285896 0.014115796972789495; 2.0536966304542763 0.01466696531428494 0.009969848544637876 0.010581531726271926 0.008290493206557538 0.015057499984918031 0.023618374838233012 0.13775893713058696 1.556212271034834 1.3024335239493188 0.09367901305424393 0.2553098844111626 0.048359904034607305 0.019443721559218766 0.012773500727280883 0.013739141498114517 0.013569959087364454 0.010043794934065143 0.01088821004173291 0.016170940019548767 0.010547198850608959 0.012581689993692986 0.009135671027439715 0.016781561869807206 0.012516859480182869; 2.259081502141289 0.0133903098733165 0.009079465978450833 0.00975249197003206 0.007682831066476654 0.01396272431869552 0.021827505349481982 0.12820830765147892 1.451178818115122 1.2142398767121922 0.08368180132702879 0.20510563176422236 0.04081511851428798 0.017819169549585047 0.011570063512329965 0.01254930846536323 0.012412301457928184 0.009192824818075682 0.00990044071007725 0.014806687058850007 0.009623308263787666 0.011612110489094472 0.00828703191334429 0.015562519478258931 0.010959557053204588; 2.4742663112734693 0.012025402589415417 0.008134738816459767 0.008836780716011343 0.006996792983455857 0.012723378680558603 0.019828287265190044 0.11721869839963167 1.3291756097854819 1.1119159322857555 0.07358169772455506 0.16036050104537403 0.03381319870642065 0.01606065216571473 0.010312666136470014 0.011275290871407757 0.011167114927868602 0.008275602927359585 0.008857163432398469 0.013334304560005126 0.008637412577821451 0.01053543447193191 0.007396783938121711 0.014182083188214152 0.009443889691854668; 2.6992510578508155 0.01057224346258168 0.007135667058664673 0.007834397964209766 0.006232378957495141 0.011339463070507271 0.01762072058535718 0.10479010937504513 1.190202646045913 0.9954616906700081 0.06337870224682263 0.12107449225461722 0.02735414461100525 0.014168169407607796 0.009001308599701018 0.00991708871624809 0.009834399497185694 0.007292129261916846 0.00775837820869656 0.011753792523014103 0.007589511792710309 0.009351661942205294 0.006464927101771972 0.012640252999672856 0.007969857396133099; 2.934035741873331 0.009030832492815293 0.006082250705065552 0.00674534371462733 0.005389588988594509 0.009810977488541525 0.015204805309983405 0.09092254057771933 1.0342599268964159 0.8648771518649502 0.05307281489383153 0.08724760539195207 0.021437956228041816 0.012141721275264254 0.007635990902022981 0.008474701999884234 0.008414155165879471 0.0062424038217474675 0.006604085038971525 0.010065150947876954 0.006479605908454241 0.008060792899914626 0.005491461404295074 0.010937028912635052 0.006537460166039883; 3.178620363341012 0.007401169680116254 0.004974489755662401 0.005569617967264034 0.00446842307675396 0.008137921934661365 0.012580541439068706 0.07561599200765423 0.8613474523369898 0.7201623158705817 0.042664035665581766 0.05887984045737848 0.016064633557530322 0.009981307768684104 0.006216713043435901 0.006948130722316183 0.006906381933949924 0.005126426606851446 0.005394283923223359 0.008268379834593666 0.005307694925053246 0.006662827345059906 0.004476386845691018 0.009072410927100729 0.005146698001575016; 3.4330049222538626 0.005683255024484565 0.003812384210455224 0.00430722072211988 0.0034688812219734943 0.006320296408866792 0.009747928972613094 0.058870463664849854 0.6714652223676354 0.5613171826869026 0.032152364562073335 0.03597119745089648 0.011234176599470784 0.007686928887867345 0.004743475023939781 0.005337374883543943 0.005311079801397059 0.003944197617228784 0.004128974861452068 0.006363479183164246 0.004073778842507326 0.005157765277641132 0.003419703425959802 0.007046399043069897 0.0037975709027385013; 3.69718941861188 0.0038770885259202153 0.0025959340694440124 0.0029581519791948593 0.002390963424253105 0.004358100911157798 0.006706967910616544 0.040685955549306096 0.4646132369883512 0.3883417523139119 0.02153780158330617 0.018521676372506 0.006946585353863173 0.005258584632813963 0.0032162768435346108 0.0036424344835675002 0.0036282487682208625 0.0026957168528794727 0.0028081578536576415 0.00435044899358868 0.0027778576608164726 0.003545606697658297 0.0023214111451014216 0.004858993260542538 0.0024900788695303316; 3.9711738524150646 0.001982670184423223 0.0013251393326287788 0.001522411738488986 0.0012346696835928055 0.0022513354415343977 0.0034576582530790904 0.02106246766102314 0.24079149619913948 0.20123602475161143 0.010820346729280395 0.006531277222207204 0.003201859820707534 0.002696275003523983 0.0016351185022204064 0.001863309522386875 0.0018578888344213539 0.001380984313803527 0.0014318328998400942 0.0022292892658669914 0.0014199313799806994 0.0018263516051114168 0.0011815100031158875 0.002510193579518678 0.00122422190195052; 4.254958223663417 -6.420612698722547e-15 9.517855303472442e-15 2.254439019145773e-15 -7.411655927494534e-15 -3.415475412410896e-15 7.193291213800562e-16 9.03543153937406e-16 -9.219208521726188e-16 3.3188559986441705e-16 -4.05636043338745e-15 0.0 3.844317340337979e-15 -2.6066681841577996e-15 -2.839447505217288e-15 2.056986106201179e-15 -1.475037653587726e-15 9.403365037570114e-16 -5.807960757836408e-16 -8.320134259982439e-16 0.0 4.839967298001723e-16 3.1943784166793436e-15 -1.6962933006317804e-15 -9.403365036110586e-16] From 6db0c4748e7550f75985234df26e65242f94ba9a Mon Sep 17 00:00:00 2001 From: Kipton Barros Date: Sat, 5 Aug 2023 08:14:40 -0600 Subject: [PATCH 03/12] Cleanup of LSWT --- src/Intensities/Interpolation.jl | 11 +- src/SpinWaveTheory/SWTCalculations.jl | 98 +++++++++--------- src/SpinWaveTheory/SpinWaveTheory.jl | 138 +++++++++++--------------- 3 files changed, 112 insertions(+), 135 deletions(-) diff --git a/src/Intensities/Interpolation.jl b/src/Intensities/Interpolation.jl index 9ed6049c7..3335cbcd6 100644 --- a/src/Intensities/Interpolation.jl +++ b/src/Intensities/Interpolation.jl @@ -221,15 +221,14 @@ end connected_path_from_rlu(cryst, qs_rlu, density) Returns a pair `(path, xticks)`. The first return value is a path in reciprocal -space that samples linearly between the wavevectors in `qs`. The elements in -`qs` are defined in reciprocal lattice units (RLU) associated with the -[`reciprocal_lattice_vectors``](@ref) for `cryst`. The `density` parameter has -units of inverse length, and controls the number of samples between elements of -`qs`. +space that samples linearly between the wavevectors in `qs_rlu`. The elements in +`qs_rlu` are defined in reciprocal lattice units (RLU) associated with the +[`reciprocal_lattice_vectors`](@ref) for `cryst`. The sampling `density` between +elements of `qs` has units of inverse length. The second return value `xticks` can be used for plotting. The `xticks` object is itself a pair `(numbers, labels)`, which give the locations of the -interpolating ``q``-points and a human-readable string. +interpolating ``q``-points and labels as pretty-printed strings. """ function connected_path_from_rlu(cryst::Crystal, qs_rlu::Vector, density) @assert length(qs_rlu) >= 2 "The list `qs` should include at least two wavevectors." diff --git a/src/SpinWaveTheory/SWTCalculations.jl b/src/SpinWaveTheory/SWTCalculations.jl index 4e33408dc..395e6a572 100644 --- a/src/SpinWaveTheory/SWTCalculations.jl +++ b/src/SpinWaveTheory/SWTCalculations.jl @@ -2,7 +2,7 @@ # Below are the implementations of the SU(N) linear spin-wave calculations # ########################################################################### -@inline δ(x, y) = ==(x, y) # my delta function +@inline δ(x, y) = (x==y) # The "metric" of scalar biquad interaction. Here we are using the following identity: # (𝐒ᵢ⋅𝐒ⱼ)² = -(𝐒ᵢ⋅𝐒ⱼ)/2 + ∑ₐ (OᵢᵃOⱼᵃ)/2, a=4,…,8, # where the definition of Oᵢᵃ is given in Appendix B of *Phys. Rev. B 104, 104409* @@ -11,12 +11,14 @@ const biquad_metric = 1/2 * diagm([-1, -1, -1, 1, 1, 1, 1, 1]) # Set the dynamical quadratic Hamiltonian matrix in SU(N) mode. function swt_hamiltonian_SUN!(swt::SpinWaveTheory, q, Hmat::Matrix{ComplexF64}) - (; sys, s̃_mat, T̃_mat, Q̃_mat) = swt - Hmat .= 0 # DD: must be zeroed out! - Nm, Ns = length(sys.dipoles), sys.Ns[1] # number of magnetic atoms and dimension of Hilbert space - Nf = Ns-1 - N = Nf + 1 - L = Nf * Nm + (; sys, data) = swt + (; s̃_mat, T̃_mat, Q̃_mat) = data + + Hmat .= 0 + Nm = natoms(sys.crystal) + N = sys.Ns[1] # Dimension of SU(N) coherent states + Nf = N - 1 # Number of local boson flavors + L = Nf * Nm # Number of quasiparticle bands @assert size(Hmat) == (2L, 2L) # block matrices of `Hmat` @@ -186,10 +188,14 @@ end # Set the dynamical quadratic Hamiltonian matrix in dipole mode. function swt_hamiltonian_dipole!(swt::SpinWaveTheory, q, Hmat::Matrix{ComplexF64}) - (; sys, R_mat, c′_coef) = swt + (; sys, data) = swt + (; R_mat, c_coef) = data Hmat .= 0.0 - L, Ns = length(sys.dipoles), sys.Ns[1] - S = (Ns-1) / 2 + + N = sys.Ns[1] # Dimension of SU(N) coherent states + S = (N-1)/2 # Spin magnitude + L = natoms(sys.crystal) # Number of quasiparticle bands + biquad_res_factor = 1 - 1/S + 1/(4S^2) # rescaling factor for biquadratic interaction @assert size(Hmat) == (2L, 2L) @@ -198,7 +204,7 @@ function swt_hamiltonian_dipole!(swt::SpinWaveTheory, q, Hmat::Matrix{ComplexF64 (; extfield, gs, units) = sys for matom = 1:L effB = units.μB * (gs[1, 1, 1, matom]' * extfield[1, 1, 1, matom]) - res = dot(effB, (R_mat[matom])[:, 3]) / 2 + res = dot(effB, R_mat[matom][:, 3]) / 2 Hmat[matom, matom] += res Hmat[matom+L, matom+L] += res end @@ -221,8 +227,8 @@ function swt_hamiltonian_dipole!(swt::SpinWaveTheory, q, Hmat::Matrix{ComplexF64 R_mat_j = R_mat[sub_j] Rij = S * (R_mat_i' * J * R_mat_j) - P = 0.25 * (Rij[1, 1] - Rij[2, 2] + 1im * (-Rij[1, 2] - Rij[2, 1])) - Q = 0.25 * (Rij[1, 1] + Rij[2, 2] + 1im * (-Rij[1, 2] + Rij[2, 1])) + P = 0.25 * (Rij[1, 1] - Rij[2, 2] - im*Rij[1, 2] - im*Rij[2, 1]) + Q = 0.25 * (Rij[1, 1] + Rij[2, 2] - im*Rij[1, 2] + im*Rij[2, 1]) Hmat[sub_i, sub_j] += Q * phase Hmat[sub_j, sub_i] += conj(Q) * conj(phase) @@ -244,17 +250,17 @@ function swt_hamiltonian_dipole!(swt::SpinWaveTheory, q, Hmat::Matrix{ComplexF64 J = coupling.biquad # ⟨Ω₂, Ω₁|(𝐒₁⋅𝐒₂)^2|Ω₁, Ω₂⟩ = (1-1/S+1/(4S^2)) (Ω₁⋅Ω₂)^2 - 1/2 Ω₁⋅Ω₂ + const. # The biquadratic part including the biquadratic scaling factor. - Ri = swt.R_mat[sub_i] - Rj = swt.R_mat[sub_j] + Ri = R_mat[sub_i] + Rj = R_mat[sub_j] Rʳ = Ri' * Rj C0 = Rʳ[3, 3]*S^2 - C1 = S*√S/2*(Rʳ[1, 3] + 1im * Rʳ[2, 3]) - C2 = S*√S/2*(Rʳ[3, 1] + 1im * Rʳ[3, 2]) + C1 = S*√S/2*(Rʳ[1, 3] + im*Rʳ[2, 3]) + C2 = S*√S/2*(Rʳ[3, 1] + im*Rʳ[3, 2]) A11 = -Rʳ[3, 3]*S A22 = -Rʳ[3, 3]*S - A21 = S/2*(Rʳ[1, 1] - 1im*Rʳ[1, 2] - 1im*Rʳ[2, 1] + Rʳ[2, 2]) - A12 = S/2*(Rʳ[1, 1] + 1im*Rʳ[1, 2] + 1im*Rʳ[2, 1] + Rʳ[2, 2]) - B21 = S/4*(Rʳ[1, 1] + 1im*Rʳ[1, 2] + 1im*Rʳ[2, 1] - Rʳ[2, 2]) + A21 = S/2*(Rʳ[1, 1] - im*Rʳ[1, 2] - im*Rʳ[2, 1] + Rʳ[2, 2]) + A12 = S/2*(Rʳ[1, 1] + im*Rʳ[1, 2] + im*Rʳ[2, 1] + Rʳ[2, 2]) + B21 = S/4*(Rʳ[1, 1] + im*Rʳ[1, 2] + im*Rʳ[2, 1] - Rʳ[2, 2]) B12 = B21 Hmat[sub_i, sub_i] += J*biquad_res_factor * (C0*A11 + C1*conj(C1)) @@ -279,8 +285,8 @@ function swt_hamiltonian_dipole!(swt::SpinWaveTheory, q, Hmat::Matrix{ComplexF64 # The additional bilinear interactions Rij = -J * S * (Ri' * Rj) / 2 - P = 0.25 * (Rij[1, 1] - Rij[2, 2] + 1im * (-Rij[1, 2] - Rij[2, 1])) - Q = 0.25 * (Rij[1, 1] + Rij[2, 2] + 1im * (-Rij[1, 2] + Rij[2, 1])) + P = 0.25 * (Rij[1, 1] - Rij[2, 2] - im*Rij[1, 2] - im*Rij[2, 1]) + Q = 0.25 * (Rij[1, 1] + Rij[2, 2] - im*Rij[1, 2] + im*Rij[2, 1]) Hmat[sub_i, sub_j] += Q * phase Hmat[sub_j, sub_i] += conj(Q) * conj(phase) @@ -297,12 +303,11 @@ function swt_hamiltonian_dipole!(swt::SpinWaveTheory, q, Hmat::Matrix{ComplexF64 Hmat[sub_i+L, sub_i+L] -= 0.5 * Rij[3, 3] Hmat[sub_j+L, sub_j+L] -= 0.5 * Rij[3, 3] end - end # single-ion anisotropy for matom = 1:L - (; c2, c4, c6) = c′_coef[matom] + (; c2, c4, c6) = c_coef[matom] Hmat[matom, matom] += -3S*c2[3] - 40*S^3*c4[5] - 168*S^5*c6[7] Hmat[matom+L, matom+L] += -3S*c2[3] - 40*S^3*c4[5] - 168*S^5*c6[7] Hmat[matom, matom+L] += -im*(S*c2[5] + 6S^3*c4[7] + 16S^5*c6[9]) + (S*c2[1] + 6S^3*c4[3] + 16S^5*c6[5]) @@ -324,13 +329,10 @@ function swt_hamiltonian_dipole!(swt::SpinWaveTheory, q, Hmat::Matrix{ComplexF64 end end -""" - bogoliubov! -Bogoliubov transformation that diagonalizes a bosonic Hamiltonian. -See Colpa JH. *Diagonalization of the quadratic boson hamiltonian* -Physica A: Statistical Mechanics and its Applications, 1978 Sep 1;93(3-4):327-53. -""" +# Bogoliubov transformation that diagonalizes a bosonic Hamiltonian. See Colpa +# JH. *Diagonalization of the quadratic boson hamiltonian* Physica A: +# Statistical Mechanics and its Applications, 1978 Sep 1;93(3-4):327-53. function bogoliubov!(disp, V, Hmat, energy_tol, mode_fast::Bool = false) @assert size(Hmat, 1) == size(Hmat, 2) "Hmat is not a square matrix" @assert size(Hmat, 1) % 2 == 0 "dimension of Hmat is not even" @@ -384,9 +386,11 @@ function bogoliubov!(disp, V, Hmat, energy_tol, mode_fast::Bool = false) @assert all(<(1e-6), abs.(V' * Σ * V - Σ)) "Para-renormalization check fails (Boson commutatition relations not preserved after the Bogoliubov transformation!)" end - # The linear spin-wave dispersion also in descending order. - return [disp[i] = 2.0 * eigval[i] for i = 1:L] - + # The linear spin-wave dispersion in descending order. + for i in 1:L + disp[i] = 2eigval[i] + end + return end @@ -455,8 +459,8 @@ end """ dssf(swt::SpinWaveTheory, qs) -**Experimental**. Given a [`SpinWaveTheory`](@ref) object, computes the -dynamical spin structure factor, +Given a [`SpinWaveTheory`](@ref) object, computes the dynamical spin structure +factor, ```math 𝒮^{αβ}(𝐤, ω) = 1/(2πN)∫dt ∑_𝐫 \\exp[i(ωt - 𝐤⋅𝐫)] ⟨S^α(𝐫, t)S^β(0, 0)⟩, @@ -485,7 +489,7 @@ function dssf(swt::SpinWaveTheory, qs) # dssf(...) doesn't do any contraction, temperature correction, etc. # It simply returns the full Sαβ correlation matrix - formula = intensity_formula(swt,:full; kernel = delta_function_kernel) + formula = intensity_formula(swt, :full; kernel = delta_function_kernel) # Calculate DSSF for qidx in CartesianIndices(qs) @@ -512,28 +516,24 @@ struct SpinWaveIntensityFormula{T} calc_intensity :: Function end -function Base.show(io::IO, formula::SpinWaveIntensityFormula{T}) where T +function Base.show(io::IO, ::SpinWaveIntensityFormula{T}) where T print(io,"SpinWaveIntensityFormula{$T}") end function Base.show(io::IO, ::MIME"text/plain", formula::SpinWaveIntensityFormula{T}) where T - printstyled(io, "Quantum Scattering Intensity Formula\n";bold=true, color=:underline) + printstyled(io, "Quantum Scattering Intensity Formula\n"; bold=true, color=:underline) - formula_lines = split(formula.string_formula,'\n') + formula_lines = split(formula.string_formula, '\n') if isnothing(formula.kernel) + println(io, "At any Q and for each band ωᵢ = εᵢ(Q), with S = S(Q,ωᵢ):\n") intensity_equals = " Intensity(Q,ω) = ∑ᵢ δ(ω-ωᵢ) " - println(io,"At any Q and for each band ωᵢ = εᵢ(Q), with S = S(Q,ωᵢ):") else + println(io, "At any (Q,ω), with S = S(Q,ωᵢ):\n") intensity_equals = " Intensity(Q,ω) = ∑ᵢ Kernel(ω-ωᵢ) " - println(io,"At any (Q,ω), with S = S(Q,ωᵢ):") - end - println(io) - println(io,intensity_equals,formula_lines[1]) - for i = 2:length(formula_lines) - precursor = repeat(' ', textwidth(intensity_equals)) - println(io,precursor,formula_lines[i]) end + separator = '\n' * repeat(' ', textwidth(intensity_equals)) + println(io, intensity_equals, join(formula_lines, separator)) println(io) if isnothing(formula.kernel) println(io,"BandStructure information (ωᵢ and intensity) reported for each band") @@ -545,7 +545,7 @@ end delta_function_kernel = nothing function intensity_formula(f::Function, swt::SpinWaveTheory, corr_ix; kernel::Union{Nothing,Function}, return_type=Float64, string_formula="f(Q,ω,S{α,β}[ix_q,ix_ω])", mode_fast=false) - (; sys, s̃_mat, R_mat) = swt + (; sys, data) = swt Nm, Ns = length(sys.dipoles), sys.Ns[1] # number of magnetic atoms and dimension of Hilbert space S = (Ns-1) / 2 nmodes = num_bands(swt) @@ -591,6 +591,7 @@ function intensity_formula(f::Function, swt::SpinWaveTheory, corr_ix; kernel::Un v = Vmat[:, band] Avec = zeros(ComplexF64, 3) if sys.mode == :SUN + (; s̃_mat) = data for site = 1:Nm @views tS_μ = s̃_mat[:, :, :, site] for μ = 1:3 @@ -600,6 +601,7 @@ function intensity_formula(f::Function, swt::SpinWaveTheory, corr_ix; kernel::Un end end elseif sys.mode == :dipole + (; R_mat) = data for site = 1:Nm Vtmp = [v[site+nmodes] + v[site], im * (v[site+nmodes] - v[site]), 0.0] Avec += Avec_pref[site] * sqrt_halfS * (R_mat[site] * Vtmp) diff --git a/src/SpinWaveTheory/SpinWaveTheory.jl b/src/SpinWaveTheory/SpinWaveTheory.jl index b5f55dfed..f53c76ef8 100644 --- a/src/SpinWaveTheory/SpinWaveTheory.jl +++ b/src/SpinWaveTheory/SpinWaveTheory.jl @@ -2,11 +2,22 @@ # Below takes Sunny to construct `SpinWave` for LSWT calculations. # ########################################################################### +struct SWTDataDipole + R_mat :: Vector{Mat3} # SO(3) rotation to align the quantization axis + c_coef :: Vector{StevensExpansion} # Stevens operator coefficents +end + +struct SWTDataSUN + s̃_mat :: Array{ComplexF64, 4} # Dipole operators + T̃_mat :: Array{ComplexF64, 3} # Single-ion anisos + Q̃_mat :: Array{ComplexF64, 4} # Quadrupolar operators +end + """ SpinWaveTheory(sys, energy_ϵ::Float64=1e-8, energy_tol=1e-6) -**Experimental**. Constructs an object to perform linear spin wave theory. Use -it with [`dispersion`](@ref) and [`dssf`](@ref) functions. +Constructs an object to perform linear spin wave theory. Use it with +[`dispersion`](@ref) and [`dssf`](@ref) functions. The optional parameter `energy_ϵ` adds a small positive shift to the diagonal of the dynamical matrix ``D`` to avoid numerical issues with zero-energy @@ -14,14 +25,8 @@ quasi-particle modes. The optional parameter `energy_tol` relaxes the check on the imaginary part of the eigenvalues. """ struct SpinWaveTheory - sys :: System - s̃_mat :: Array{ComplexF64, 4} # Dipole operators - T̃_mat :: Array{ComplexF64, 3} # Single-ion anisos - Q̃_mat :: Array{ComplexF64, 4} # Quadrupolar operators - c′_coef :: Vector{StevensExpansion} # Stevens operator coefficents (for dipole mode) - R_mat :: Vector{Mat3} # SO(3) rotation to align the quantization axis (for dipole mode) - positions :: Vector{Vec3} # Positions of sites in global coordinates (Å) - recipvecs :: Mat3 # Reciprocal vectors in global coordinates (1/Å) + sys :: System + data :: Union{SWTDataDipole, SWTDataSUN} energy_ϵ :: Float64 # Energy shift applied to dynamical matrix prior to Bogoliubov transformation energy_tol :: Float64 # Energy tolerance for maximal imaginary part of quasiparticle energies @@ -31,6 +36,18 @@ struct SpinWaveTheory # idxinfo :: SortedDict{CartesianIndex{2}, Int64} # (α, β) to save from 𝒮^{αβ}(q, ω) end +function SpinWaveTheory(sys::System{N}; energy_ϵ::Float64=1e-8, energy_tol::Float64=1e-6) where N + # Reshape into single unit cell + cellsize_mag = cell_dimensions(sys) * diagm(collect(sys.latsize)) + sys = reshape_supercell_aux(sys, (1,1,1), cellsize_mag) + + # Rotate local operators to quantization axis + data = sys.mode == :SUN ? swt_data_sun(sys) : swt_data_dipole(sys) + + return SpinWaveTheory(sys, data, energy_ϵ, energy_tol) +end + + function Base.show(io::IO, ::MIME"text/plain", swt::SpinWaveTheory) # modename = swt.dipole_corrs ? "Dipole correlations" : "Custom correlations" modename = "Dipole correlations" @@ -40,36 +57,26 @@ end function num_bands(swt::SpinWaveTheory) (; sys) = swt - nbosons = sys.mode == :SUN ? sys.Ns[1]-1 : 1 - return nbosons * natoms(sys.crystal) + nflavors = sys.mode == :SUN ? sys.Ns[1]-1 : 1 + return nflavors * natoms(sys.crystal) end -""" - dipole_to_angles - -convert the dipole expectation values from the Cartesian frame to the spherical frame -""" -function dipole_to_angles(dipoles :: AbstractVector{Float64}) +# Convert 3-vector from the Cartesian frame to the spherical frame +function dipole_to_angles(dipoles) r = norm(dipoles) @assert r > 1e-7 θ = acos(dipoles[3] / r) - @assert isfinite(θ) ϕ = atan(dipoles[2], dipoles[1]) - @assert isfinite(ϕ) - (ϕ < 0.0) && (ϕ += 2π) return θ, ϕ end -""" - generate_local_sun_gens -Compute SU(N) generators in the local reference frame (for :SUN mode). -""" -# DD: Redo this using existing operator rotation facilities. -function generate_local_sun_gens(sys :: System) - Nₘ, N = length(sys.dipoles), sys.Ns[1] # number of magnetic atoms and dimension of Hilbert +# Compute SU(N) generators in the local reference frame (for :SUN mode). DD: +# Redo this using existing operator rotation facilities. +function swt_data_sun(sys::System{N}) where N S = (N-1)/2 + Nₘ = natoms(sys.crystal) s_mat_N = spin_matrices(; N) @@ -100,61 +107,30 @@ function generate_local_sun_gens(sys :: System) T̃_mat[:, :, atom] = Hermitian(U_mat' * sys.interactions_union[atom].onsite.matrep * U_mat) end - return s̃_mat, T̃_mat, Q̃_mat -end - -""" - generate_local_stevens_coefs - -Compute the stevens coefficients in the local reference frame (for :dipole mode). -""" -function generate_local_stevens_coefs(sys :: System) - c′_coef = Vector{StevensExpansion}() - R_mat = Vector{Mat3}() - Nₘ = length(sys.dipoles) # number of magnetic atoms and dimension of Hilbert - R = zeros(Float64, 3, 3) - for atom = 1:Nₘ - θ, ϕ = dipole_to_angles(sys.dipoles[1, 1, 1, atom]) - # U_mat = exp(-1im*ϕ*s_mat_N[3]) * exp(-1im*θ*s_mat_N[2]) - # SO(3) rotation that aligns the quantization axis. Important: since we will project out bosons that correspond to multipolar fluctuations, - # therefore we use the explicit matrix to get rid of any ambiguity - # Note that R * (0, 0, 1) = normalize(sys.dipoles[1,1,1,atom])) - R[:] = [-sin(ϕ) -cos(ϕ)*cos(θ) cos(ϕ)*sin(θ); - cos(ϕ) -sin(ϕ)*cos(θ) sin(ϕ)*sin(θ); - 0.0 sin(θ) cos(θ)] - - (; c2, c4, c6) = sys.interactions_union[atom].onsite.stvexp - - SR = Mat3(R) - push!(R_mat, SR) - c2′ = rotate_stevens_coefficients(c2, SR) - c4′ = rotate_stevens_coefficients(c4, SR) - c6′ = rotate_stevens_coefficients(c6, SR) - c′ = StevensExpansion(c2′, c4′, c6′) - push!(c′_coef, c′) - end - return R_mat, c′_coef + return SWTDataSUN(s̃_mat, T̃_mat, Q̃_mat) end - -function SpinWaveTheory(sys::System{N}; energy_ϵ::Float64=1e-8, energy_tol::Float64=1e-6) where N - # Reshape into single unit cell - cellsize_mag = cell_dimensions(sys) * diagm(collect(sys.latsize)) - sys = reshape_supercell_aux(sys, (1,1,1), cellsize_mag) - - # Computes the Stevens operator in the local reference frame and the SO(3) rotation matrix from global to local frame - # (:dipole mode only) - if sys.mode == :SUN - s̃_mat, T̃_mat, Q̃_mat = generate_local_sun_gens(sys) - c′_coef = Vector{StevensExpansion}() - R_mat = Vector{Mat3}() - elseif sys.mode == :dipole - R_mat, c′_coef = generate_local_stevens_coefs(sys) - s̃_mat = zeros(ComplexF64, 0, 0, 0, 0) - T̃_mat = zeros(ComplexF64, 0, 0, 0) - Q̃_mat = zeros(ComplexF64, 0, 0, 0, 0) +# Compute Stevens coefficients in the local reference frame +function swt_data_dipole(sys::System{0}) + cs = StevensExpansion[] + Rs = Mat3[] + + for atom in 1:natoms(sys.crystal) + # SO(3) rotation that aligns the quantization axis. Important: since we + # will project out bosons that correspond to multipolar fluctuations, + # therefore we use the explicit matrix to get rid of any ambiguity. + # + # As a unitary, U = exp(-i ϕ Sz) exp(-i θ Sy) + θ, ϕ = dipole_to_angles(sys.dipoles[1,1,1,atom]) + R = SA[-sin(ϕ) -cos(ϕ)*cos(θ) cos(ϕ)*sin(θ); + cos(ϕ) -sin(ϕ)*cos(θ) sin(ϕ)*sin(θ); + 0.0 sin(θ) cos(θ)] + # Rotated Stevens expansion + c = rotate_operator(sys.interactions_union[atom].onsite.stvexp, R) + + push!(Rs, R) + push!(cs, c) end - positions = [global_position(sys, site) for site in all_sites(sys)][:] - return SpinWaveTheory(sys, s̃_mat, T̃_mat, Q̃_mat, c′_coef, R_mat, positions, sys.crystal.recipvecs, energy_ϵ, energy_tol) + return SWTDataDipole(Rs, cs) end From 8ee53b2a22654f8d5c827b9d383ab14958383d15 Mon Sep 17 00:00:00 2001 From: Kipton Barros Date: Sat, 5 Aug 2023 08:55:48 -0600 Subject: [PATCH 04/12] Simplify spherical_shell, update versions.md --- docs/src/versions.md | 6 ++++++ examples/longer_examples/CoRh2O4-tutorial.jl | 2 +- examples/powder_averaging.jl | 7 +++---- src/Intensities/PowderAveraging.jl | 17 +++++++---------- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/docs/src/versions.md b/docs/src/versions.md index ca69f2e86..1c707c822 100644 --- a/docs/src/versions.md +++ b/docs/src/versions.md @@ -38,6 +38,12 @@ Remove `intensities` function. Instead, use one of [`intensities_interpolated`](@ref) or [`intensities_binned`](@ref). These will require an [`intensity_formula`](@ref), which defines a calculator (e.g., LSWT). +Sunny now expects all wavevectors in units of inverse Angstrom (1/Å). This +facilitates orientational averaging. Replace `connected_path` with +[`connected_path_from_rlu`](@ref), which returns wavevectors in 1/Å. Now +[`spherical_shell`](@ref) needs one fewer argument, and returns wavevectors in +1/Å. + Rename `polarize_spin!` to [`set_dipole!`](@ref) for consistency with [`set_coherent!`](@ref). The behavior of the former function is unchanged: the spin at a given site will still be polarized along the provided direction. diff --git a/examples/longer_examples/CoRh2O4-tutorial.jl b/examples/longer_examples/CoRh2O4-tutorial.jl index d7ea30f20..2b9e3abb9 100644 --- a/examples/longer_examples/CoRh2O4-tutorial.jl +++ b/examples/longer_examples/CoRh2O4-tutorial.jl @@ -88,7 +88,7 @@ function powder_average(sc, rs, density; η=0.1, mode=:perp, kwargs...) prog = Progress(length(rs); dt=10., desc="Powder Averaging: ", color=:blue); output = zeros(Float64, length(rs), length(ωs(sc))) for (i, r) in enumerate(rs) - qs = spherical_shell(sc, r, density) + qs = spherical_shell(r, density) if length(qs) == 0 qs = [[0., 0., 0.]] ## If radius is too small, just look at 0 vector end diff --git a/examples/powder_averaging.jl b/examples/powder_averaging.jl index d0dfdfc45..897e1b90d 100644 --- a/examples/powder_averaging.jl +++ b/examples/powder_averaging.jl @@ -49,19 +49,18 @@ add_sample!(sc, sys) # lorentzian(ω-ω₀, 0.1)`. qpoints = [[0.0, 0.0, 0.0], [0.5, 0.0, 0.0], [0.5, 0.5, 0.0], [0.0, 0.0, 0.0]] -qs, markers = connected_path_from_rlu(crystal, qpoints, 50) +qs, xticks = connected_path_from_rlu(crystal, qpoints, 50) is = intensities_interpolated(sc, qs; interpolation=:round, formula = intensity_formula(sc,:trace)) is_broad = broaden_energy(sc, is, (ω, ω₀) -> lorentzian(ω-ω₀, 0.1)) ## Plot results fig = Figure(; resolution=(1000,400)) -xticklabels = [string(tuple(qs[i]...)) for i in markers] plotparams = (; aspect=1.4, ylabel = "ω (meV)", xlabel = "𝐪 (RLU)", - xticks=(markers, xticklabels), + xticks, xticklabelrotation=π/10, xticklabelsize=14, ) @@ -86,7 +85,7 @@ function powder_average(sc, rs, density; η=0.1, kwargs...) output = zeros(Float64, length(rs), nω) for (i, r) in enumerate(rs) - qs = spherical_shell(sc, r, density) # Get points on a sphere of radius r + qs = spherical_shell(r, density) # Get points on a sphere of radius r if length(qs) == 0 qs = [[0., 0., 0.]] # If no points (r is too small), just look at 0 vector end diff --git a/src/Intensities/PowderAveraging.jl b/src/Intensities/PowderAveraging.jl index 04b3764a9..0bb786a4b 100644 --- a/src/Intensities/PowderAveraging.jl +++ b/src/Intensities/PowderAveraging.jl @@ -9,26 +9,23 @@ function spherical_points_fibonacci(N) end """ - spherical_shell(sc::SampledCorrelations, radius, density) + spherical_shell(radius, density) -Returns a set of wave vectors lying on a sphere of specified radius, where -`radius` is given in ``Å^{-1}``. `density` controls how many points to select -per ``Å^{-2}``. +Sample points on a sphere of given `radius`, with given sample `density`. The points are generated by mapping a Fibonacci lattice onto a sphere. """ -function spherical_shell(sc::SampledCorrelations, radius, density) +function spherical_shell(radius, density) numpoints = round(Int, 4π*radius^2 * density) - crystal = orig_crystal(sc) - C = inv(2π*inv(crystal.latvecs)') # Transformation for inverse angstroms to RLU - return if numpoints == 0 - [Vec3(0,0,0)] # If radius too small, just return the 0 vector + if numpoints == 0 + return [Vec3(0,0,0)] # If radius too small, just return the 0 vector else - map(v->C*v, radius * spherical_points_fibonacci(numpoints)) + return radius * spherical_points_fibonacci(numpoints) end end function powder_average_interpolated(sc::SampledCorrelations, q_ias, density; kwargs...) + # FIXME A = inv(inv(sc.crystal.latvecs)') # Transformation to convert from inverse angstroms to RLUs nω = length(ωs(sc)) output = zeros(Float64, length(q_ias), nω) # generalize this so matches contract From 1288ad59009296881c693e4d95558334066d50e0 Mon Sep 17 00:00:00 2001 From: Kipton Barros Date: Sat, 5 Aug 2023 10:03:56 -0600 Subject: [PATCH 05/12] Tweak doc string --- src/Symmetry/Crystal.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Symmetry/Crystal.jl b/src/Symmetry/Crystal.jl index 3bdab7345..0ab7b2762 100644 --- a/src/Symmetry/Crystal.jl +++ b/src/Symmetry/Crystal.jl @@ -148,9 +148,9 @@ cell_volume(cryst::Crystal) = abs(det(cryst.latvecs)) """ reciprocal_lattice_vectors(cryst::Crystal) -Returns a ``3×3`` matrix, with columns that define the reciprocal lattice -vectors ``(𝐛₁,𝐛₂,𝐛₃)``. These are defined to satisfy ``𝐛ᵢ⋅𝐚ⱼ = 2πδᵢⱼ``, -where ``(𝐚₁,𝐚₂,𝐚₃)`` are the lattice vectors for `cryst` in real-space. +Returns the ``3×3`` matrix ``(𝐛₁,𝐛₂,𝐛₃)`` with columns ``𝐛ᵢ`` as reciprocal +lattice vectors. These are defined to satisfy ``𝐛ᵢ⋅𝐚ⱼ = 2πδᵢⱼ``, where +``(𝐚₁,𝐚₂,𝐚₃)`` are the lattice vectors used to construct `cryst`. """ reciprocal_lattice_vectors(cryst::Crystal) = cryst.recipvecs From 78436d99b438fa740d5d91f7d04b916e27e8b7bd Mon Sep 17 00:00:00 2001 From: Kipton Barros Date: Sat, 5 Aug 2023 11:17:22 -0600 Subject: [PATCH 06/12] Tweak Ewald for clarity --- src/System/Ewald.jl | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/System/Ewald.jl b/src/System/Ewald.jl index 087d965c3..a75f72a9e 100644 --- a/src/System/Ewald.jl +++ b/src/System/Ewald.jl @@ -40,14 +40,14 @@ function precompute_dipole_ewald(cryst::Crystal, latsize::NTuple{3,Int}) :: Arra na = natoms(cryst) A = zeros(Mat3, latsize..., na, na) - # Superlattice vectors that describe periodicity of system and their inverse - latscale = diagm(Vec3(latsize)) - slatvecs = collect(eachcol(cryst.latvecs * latscale)) - srecipvecs = collect(eachcol(latscale \ cryst.recipvecs)) + # Superlattice vectors and reciprocals for the full system volume + sys_size = diagm(Vec3(latsize)) + latvecs = cryst.latvecs * sys_size + recipvecs = sys_size \ cryst.recipvecs # Precalculate constants I₃ = Mat3(I) - V = (slatvecs[1] × slatvecs[2]) ⋅ slatvecs[3] + V = det(latvecs) L = cbrt(V) # Roughly balances the real and Fourier space costs σ = L/3 @@ -57,10 +57,10 @@ function precompute_dipole_ewald(cryst::Crystal, latsize::NTuple{3,Int}) :: Arra rmax = 6√2 * σ kmax = 6√2 / σ - nmax = map(slatvecs, srecipvecs) do a, b + nmax = map(eachcol(latvecs), eachcol(recipvecs)) do a, b round(Int, rmax / (a⋅normalize(b)) + 1e-6) + 1 end - mmax = map(slatvecs, srecipvecs) do a, b + mmax = map(eachcol(latvecs), eachcol(recipvecs)) do a, b round(Int, kmax / (b⋅normalize(a)) + 1e-6) end @@ -75,8 +75,7 @@ function precompute_dipole_ewald(cryst::Crystal, latsize::NTuple{3,Int}) :: Arra ##################################################### ## Real space part for n1 = -nmax[1]:nmax[1], n2 = -nmax[2]:nmax[2], n3 = -nmax[3]:nmax[3] - n = Vec3(n1, n2, n3) - rvec = Δr + n' * slatvecs + rvec = Δr + latvecs * Vec3(n1, n2, n3) r² = rvec⋅rvec if 0 < r² <= rmax*rmax r = √r² @@ -91,7 +90,7 @@ function precompute_dipole_ewald(cryst::Crystal, latsize::NTuple{3,Int}) :: Arra ##################################################### ## Fourier space part for m1 = -mmax[1]:mmax[1], m2 = -mmax[2]:mmax[2], m3 = -mmax[3]:mmax[3] - k = Vec3(m1, m2, m3)' * srecipvecs + k = recipvecs * Vec3(m1, m2, m3) k² = k⋅k if 0 < k² <= kmax*kmax # Replace exp(-ikr) -> cos(kr). It's valid to drop the imaginary From 02aec233f55bc4f0d329175617faa89540f07370 Mon Sep 17 00:00:00 2001 From: ddahlbom Date: Sat, 5 Aug 2023 23:22:28 -0400 Subject: [PATCH 07/12] Fix sampled correlations tests --- src/Intensities/Interpolation.jl | 28 ++------- src/SampledCorrelations/CorrelationUtils.jl | 7 +-- src/SampledCorrelations/DataRetrieval.jl | 63 +++++++++---------- src/SampledCorrelations/FormFactor.jl | 21 +++++++ .../SampledCorrelations.jl | 6 +- 5 files changed, 62 insertions(+), 63 deletions(-) diff --git a/src/Intensities/Interpolation.jl b/src/Intensities/Interpolation.jl index 3335cbcd6..1f2516061 100644 --- a/src/Intensities/Interpolation.jl +++ b/src/Intensities/Interpolation.jl @@ -2,9 +2,6 @@ abstract type InterpolationScheme{NumInterp} end struct NoInterp <: InterpolationScheme{1} end struct LinearInterp <: InterpolationScheme{8} end -#= -ddtodo: Explanation of interpolation "API" -=# function interpolated_intensity(::SampledCorrelations, _, _, stencil_intensities, ::NoInterp) return only(stencil_intensities) @@ -35,17 +32,10 @@ end function stencil_points(sc::SampledCorrelations, q, ::NoInterp) - - # Each of the following lines causes a 32 byte allocation Ls = size(sc.samplebuf)[2:4] m = round.(Int, Ls .* q) im = map(i -> mod(m[i], Ls[i])+1, (1, 2, 3)) |> CartesianIndex{3} - ## The following lines cause no allocations, but don't seem to be any faster. - # _, L1, L2, L3, _, _ = size(sc.samplebuf) - # m = (round(Int, L1*q[1]), round(Int, L2*q[2]), round(Int, L3*q[3])) - # im = CartesianIndex{3}(mod(m[1], L1)+1, mod(m[2], L2)+1, mod(m[3], L3)+1) - return (m,), (im,) end @@ -140,21 +130,13 @@ function intensities_interpolated(sc::SampledCorrelations, qs; formula = intensity_formula(sc,:perp) :: ClassicalIntensityFormula, interpolation = :round, negative_energies = false, - static_warn = true + instantaneous_warning = true ) qs = Vec3.(qs) - - # If working on reshaped system, assume qs given as coordinates in terms of - # reciprocal vectors of original crystal and convert them to qs in terms of - # the reciprocal vectors of the reshaped crystal. - if !isnothing(sc.origin_crystal) - rvecs_reshaped = inv(sc.crystal.latvecs)' # Note, leading 2π will cancel - rvecs_origin = inv(sc.origin_crystal.latvecs)' - qs = map(q -> rvecs_reshaped \ rvecs_origin * q, qs) - end + qs = map!(q -> sc.crystal.recipvecs \ q, qs, qs) # Make sure it's a dynamical structure factor - if static_warn && size(sc.data, 7) == 1 + if instantaneous_warning && size(sc.data, 7) == 1 error("`intensities_interpolated` given a SampledCorrelations with no dynamical information. Call `instant_intensities_interpolated` to retrieve instantaneous (static) structure factor data.") end @@ -174,7 +156,7 @@ function intensities_interpolated(sc::SampledCorrelations, qs; return_type = typeof(formula).parameters[1] intensities = zeros(return_type, size(qs)..., nω) - # Call type stable version of the function + # Call type-stable version of the function intensities_interpolated!(intensities, sc, qs, ωvals, interp, formula, stencil_info, return_type) return intensities @@ -211,7 +193,7 @@ i.e., ``𝒮(𝐪,ω)``, the ``ω`` information is integrated out. function instant_intensities_interpolated(sc::SampledCorrelations, qs; kwargs...) datadims = size(qs) ndims = length(datadims) - vals = intensities_interpolated(sc, qs; static_warn=false, kwargs...) + vals = intensities_interpolated(sc, qs; instantaneous_warning=false, kwargs...) static_vals = sum(vals, dims=(ndims+1,)) return reshape(static_vals, datadims) end diff --git a/src/SampledCorrelations/CorrelationUtils.jl b/src/SampledCorrelations/CorrelationUtils.jl index 6206d5711..c062b9fa8 100644 --- a/src/SampledCorrelations/CorrelationUtils.jl +++ b/src/SampledCorrelations/CorrelationUtils.jl @@ -12,7 +12,7 @@ function all_exact_wave_vectors(sc::SampledCorrelations; bzsize=(1,1,1)) lo = map(L -> 1 - div(L, 2), up) .- offsets qs = zeros(Vec3, up...) for (k, lz) in enumerate(lo[3]:hi[3]), (j, ly) in enumerate(lo[2]:hi[2]), (i, lx) in enumerate(lo[1]:hi[1]) - qs[i,j,k] = Vec3(lx/Ls[1], ly/Ls[2], lz/Ls[3]) + qs[i,j,k] = sc.crystal.recipvecs * Vec3(lx/Ls[1], ly/Ls[2], lz/Ls[3]) end return qs end @@ -36,7 +36,4 @@ function ωs(sc::SampledCorrelations; negative_energies=false) ωvals[i] -= 2ωvals[hω] end return negative_energies ? ωvals : ωvals[1:hω] -end - - -orig_crystal(sc::SampledCorrelations) = isnothing(sc.origin_crystal) ? sc.crystal : sc.origin_crystal +end \ No newline at end of file diff --git a/src/SampledCorrelations/DataRetrieval.jl b/src/SampledCorrelations/DataRetrieval.jl index 735f834b7..f58c179ea 100644 --- a/src/SampledCorrelations/DataRetrieval.jl +++ b/src/SampledCorrelations/DataRetrieval.jl @@ -44,37 +44,47 @@ end formula = intensity_formula(sc::SampledCorrelations; kwargs...) formula.calc_intensity(sc,q,ix_q,ix_ω) -Establish a formula for computing the intensity of the discrete scattering modes `(q,ω)` using the correlation data ``𝒮^{αβ}(q,ω)`` stored in the [`SampledCorrelations`](@ref). -The `formula` returned from `intensity_formula` can be passed to [`intensities_interpolated`](@ref) or [`intensities_binned`](@ref). +Establish a formula for computing the intensity of the discrete scattering modes +`(q,ω)` using the correlation data ``𝒮^{αβ}(q,ω)`` stored in the +[`SampledCorrelations`](@ref). The `formula` returned from `intensity_formula` +can be passed to [`intensities_interpolated`](@ref) or +[`intensities_binned`](@ref). -Sunny has several built-in formulas that can be selected by setting `contraction_mode` to one of these values: +Sunny has several built-in formulas that can be selected by setting +`contraction_mode` to one of these values: -- `:perp` (default), which contracts ``𝒮^{αβ}(q,ω)`` with the dipole factor ``δ_{αβ} - q_{α}q_{β}``, returning the unpolarized intensity. +- `:perp` (default), which contracts ``𝒮^{αβ}(q,ω)`` with the dipole factor + ``δ_{αβ} - q_{α}q_{β}``, returning the unpolarized intensity. - `:trace`, which yields ``\\operatorname{tr} 𝒮(q,ω) = ∑_α 𝒮^{αα}(q,ω)`` - `:full`, which will return all elements ``𝒮^{αβ}(𝐪,ω)`` without contraction. -Additionally, there are keyword arguments providing temperature and form factor corrections: +Additionally, there are keyword arguments providing temperature and form factor +corrections: - `kT`: If a temperature is provided, the intensities will be rescaled by a temperature- and ω-dependent classical-to-quantum factor. `kT` should be specified when making comparisons with spin wave calculations or - experimental data. If `kT` is not specified, infinite temperature (no correction) is assumed. + experimental data. If `kT` is not specified, infinite temperature (no + correction) is assumed. - `formfactors`: To apply form factor corrections, provide this keyword with a - vector of `FormFactor`s, one for each unique site in the unit cell. The form factors - will be symmetry propagated to all equivalent sites. + vector of `FormFactor`s, one for each unique site in the unit cell. The form + factors will be symmetry propagated to all equivalent sites. -Alternatively, a custom formula can be specifed by providing a function `intensity = f(q,ω,correlations)` and specifying which correlations it requires: +Alternatively, a custom formula can be specifed by providing a function +`intensity = f(q,ω,correlations)` and specifying which correlations it requires: intensity_formula(f,sc::SampledCorrelations, required_correlations; kwargs...) -The function is intended to be specified using `do` notation. For example, this custom formula sums the off-diagonal correlations: +The function is intended to be specified using `do` notation. For example, this +custom formula sums the off-diagonal correlations: required = [(:Sx,:Sy),(:Sy,:Sz),(:Sx,:Sz)] intensity_formula(sc,required,return_type = ComplexF64) do k, ω, off_diagonal_correlations sum(off_diagonal_correlations) end -If your custom formula returns a type other than `Float64`, use the `return_type` keyword argument to flag this. +If your custom formula returns a type other than `Float64`, use the +`return_type` keyword argument to flag this. """ function intensity_formula(f::Function,sc::SampledCorrelations,required_correlations; kwargs...) # SQTODO: This corr_ix may contain repeated correlations if the user does a silly @@ -94,7 +104,7 @@ function intensity_formula(f::Function,sc::SampledCorrelations,corr_ix::Abstract NAtoms = Val(size(sc.data)[2]) NCorr = Val(length(corr_ix)) - ωs_sc = ωs(sc;negative_energies=true) + ωs_sc = ωs(sc;negative_energies=true) # I think this default `true` may be a problem # Intensity is calculated at the discrete (ix_q,ix_ω) modes available to the system. # Additionally, for momentum transfers outside of the first BZ, the norm `k` of the @@ -110,29 +120,18 @@ function intensity_formula(f::Function,sc::SampledCorrelations,corr_ix::Abstract end function classical_to_quantum(ω, kT::Float64) - if kT == Inf - return 1.0 - end - if ω > 0 - ω/(kT*(1 - exp(-ω/kT))) - elseif iszero(ω) - 1.0 - else - -ω*exp(ω/kT)/(kT*(1 - exp(ω/kT))) - end -end - -function prepare_form_factors(sc, formfactors) - if isnothing(formfactors) - cryst = isnothing(sc.origin_crystal) ? sc.crystal : sc.origin_crystal - class_indices = [findfirst(==(class_label), cryst.classes) for class_label in unique(cryst.classes)] - formfactors = [FormFactor{Sunny.EMPTY_FF}(; atom) for atom in class_indices] + if kT == Inf + return 1.0 + end + if ω > 0 + ω/(kT*(1 - exp(-ω/kT))) + elseif iszero(ω) + 1.0 + else + -ω*exp(ω/kT)/(kT*(1 - exp(ω/kT))) end - formfactors = upconvert_form_factors(formfactors) # Ensure formfactors have consistent type - return propagate_form_factors(sc, formfactors) end - """ lorentzian(x, η) diff --git a/src/SampledCorrelations/FormFactor.jl b/src/SampledCorrelations/FormFactor.jl index 2a5a02e72..fa0870d82 100644 --- a/src/SampledCorrelations/FormFactor.jl +++ b/src/SampledCorrelations/FormFactor.jl @@ -15,6 +15,8 @@ end Basic type for specifying form factor parameters. Must be provided a site within the unit cell (`atom`) and a string specifying the element name. This used when creating an [`intensity_formula`](@ref), which accepts a list of `FormFactors`s. +Note that these corrections assume S(q,ω) is being calculated in the dipole +approximation; they may not be appropriate for a general SU(N) system. A list of supported element names is available at: @@ -103,6 +105,8 @@ end # If necessary, update the indices of FormFactors from original crystal # to the corresponding indices of the new crystal. +# TODO: Moving ion information into `SpinInfo` will eliminate the need for this as well +# as symmetry propagation. function map_form_factors_to_crystal(sc::SampledCorrelations, ffs::Vector{FormFactor{FFType}}) where FFType (; crystal, origin_crystal) = sc @@ -121,6 +125,8 @@ function map_form_factors_to_crystal(sc::SampledCorrelations, ffs::Vector{FormFa return ffs_new end +# Remap form factors to reshaped crystal (if necessary) and propagate to symmetry +# equivalent sites. function propagate_form_factors(sc::SampledCorrelations, ffs::Vector{FormFactor{FFType}}) where FFType ffs = map_form_factors_to_crystal(sc, ffs) ref_atoms = [ff.atom for ff in ffs] @@ -133,7 +139,20 @@ function propagate_form_factors(sc::SampledCorrelations, ffs::Vector{FormFactor{ end end +# Process form factor information into optimal form for efficient calculation. +# Form factors are provided at the level of an `intensities` call, so there is +# no opportunity to precalculate this in current arrangement. +function prepare_form_factors(sc, formfactors) + if isnothing(formfactors) + cryst = isnothing(sc.origin_crystal) ? sc.crystal : sc.origin_crystal + class_indices = [findfirst(==(class_label), cryst.classes) for class_label in unique(cryst.classes)] + formfactors = [FormFactor{Sunny.EMPTY_FF}(; atom) for atom in class_indices] + end + formfactors = upconvert_form_factors(formfactors) # Ensure formfactors have consistent type + return propagate_form_factors(sc, formfactors) +end +# Second-order form factor calculation. function compute_form(k::Float64, params::FormFactor{DOUBLE_FF}) s = k/4π g = params.g_lande @@ -147,12 +166,14 @@ function compute_form(k::Float64, params::FormFactor{DOUBLE_FF}) return ((2-g)/g) * (form2*s^2) + form1 end +# First-order form factor calculation. function compute_form(k::Float64, params::FormFactor{SINGLE_FF}) s = k/4π (A, a, B, b, C, c, D) = params.J0_params return A*exp(-a*s^2) + B*exp(-b*s^2) + C*exp(-c*s^2) + D end +# No form factor correction. function compute_form(::Float64, ::FormFactor{EMPTY_FF}) return 1.0 end diff --git a/src/SampledCorrelations/SampledCorrelations.jl b/src/SampledCorrelations/SampledCorrelations.jl index 0553669cc..515984231 100644 --- a/src/SampledCorrelations/SampledCorrelations.jl +++ b/src/SampledCorrelations/SampledCorrelations.jl @@ -9,8 +9,8 @@ struct SampledCorrelations{N} # 𝒮^{αβ}(q,ω) data and metadata data :: Array{ComplexF64, 7} # Raw SF data for 1st BZ (numcorrelations × natoms × natoms × latsize × energy) crystal :: Crystal # Crystal for interpretation of q indices in `data` - origin_crystal :: Union{Nothing,Crystal} # Original user-specified crystal (if different from above) # FIXME: Eliminate - Δω :: Float64 # Energy step size (could make this a virtual property) + origin_crystal :: Union{Nothing,Crystal} # Original user-specified crystal (if different from above) -- needed for FormFactor accounting + Δω :: Float64 # Energy step size (could make this a virtual property) # Correlation info (αβ indices of 𝒮^{αβ}(q,ω)) observables :: Vector{LinearMap} # Operators corresponding to observables @@ -110,7 +110,7 @@ function all_observable_names(sc::SampledCorrelations) end """ - dynamic_correlations(sys::System; Δt, nω, ωmax, + dynamical_correlations(sys::System; Δt, nω, ωmax, process_trajectory=:none, observables=nothing, correlations=nothing) Creates a `SampledCorrelations` for calculating and storing ``𝒮(𝐪,ω)`` data. From 3c2fb184f9c5b723d755e6b2ec3340cb6e2db671 Mon Sep 17 00:00:00 2001 From: Kipton Barros Date: Sun, 6 Aug 2023 08:28:46 -0600 Subject: [PATCH 08/12] Tweaks --- src/Intensities/Interpolation.jl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Intensities/Interpolation.jl b/src/Intensities/Interpolation.jl index 1f2516061..fa885a1cd 100644 --- a/src/Intensities/Interpolation.jl +++ b/src/Intensities/Interpolation.jl @@ -110,7 +110,8 @@ end """ - intensities_interpolated(sc::SampledCorrelations, qs; interpolation = nothing, formula = intensity_formula(sc,:perp), negative_energies = false) + intensities_interpolated(sc::SampledCorrelations, qs; interpolation=nothing, + formula=intensity_formula(sc,:perp), negative_energies=false) The basic function for retrieving ``𝒮(𝐪,ω)`` information from a `SampledCorrelations`. Maps an array of wave vectors `qs` to an array of structure @@ -132,8 +133,9 @@ function intensities_interpolated(sc::SampledCorrelations, qs; negative_energies = false, instantaneous_warning = true ) - qs = Vec3.(qs) - qs = map!(q -> sc.crystal.recipvecs \ q, qs, qs) + # Convert wavevectors from absolute units to reciprocal lattice units (RLU) + # associated with sc.crystal + qs = [sc.crystal.recipvecs \ Vec3(q) for q in qs] # Make sure it's a dynamical structure factor if instantaneous_warning && size(sc.data, 7) == 1 From 787ac4620addfe02f452acd90671582475a4c1f6 Mon Sep 17 00:00:00 2001 From: Kipton Barros Date: Sun, 6 Aug 2023 16:48:08 -0600 Subject: [PATCH 09/12] Update powder averaging example. --- docs/src/assets/CoRh2O4_intensity.jpg | Bin 0 -> 42931 bytes examples/longer_examples/CoRh2O4-tutorial.jl | 2 +- examples/powder_averaging.jl | 259 +++++++------------ src/Intensities/PowderAveraging.jl | 17 +- 4 files changed, 100 insertions(+), 178 deletions(-) create mode 100644 docs/src/assets/CoRh2O4_intensity.jpg diff --git a/docs/src/assets/CoRh2O4_intensity.jpg b/docs/src/assets/CoRh2O4_intensity.jpg new file mode 100644 index 0000000000000000000000000000000000000000..89ca374c769afeeaacd5750a2dfc9383245bcf12 GIT binary patch literal 42931 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%0D+dZ-;>ggKINQwq?~Z_ObxoG?D`^ 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 aK&s#VJ#$(8FJQ%MSM?!c|4YOC`u=YPe_Ad8 literal 0 HcmV?d00001 diff --git a/examples/longer_examples/CoRh2O4-tutorial.jl b/examples/longer_examples/CoRh2O4-tutorial.jl index 2b9e3abb9..1de174a06 100644 --- a/examples/longer_examples/CoRh2O4-tutorial.jl +++ b/examples/longer_examples/CoRh2O4-tutorial.jl @@ -88,7 +88,7 @@ function powder_average(sc, rs, density; η=0.1, mode=:perp, kwargs...) prog = Progress(length(rs); dt=10., desc="Powder Averaging: ", color=:blue); output = zeros(Float64, length(rs), length(ωs(sc))) for (i, r) in enumerate(rs) - qs = spherical_shell(r, density) + qs = spherical_shell(r; density) if length(qs) == 0 qs = [[0., 0., 0.]] ## If radius is too small, just look at 0 vector end diff --git a/examples/powder_averaging.jl b/examples/powder_averaging.jl index 897e1b90d..f50fecbb1 100644 --- a/examples/powder_averaging.jl +++ b/examples/powder_averaging.jl @@ -1,185 +1,106 @@ # # Powder Averaging -# This tutorial gives a brief demonstration of how to calculate polycrystalline -# data using Sunny's structure factor tools. - -# We begin by constructing a simple anti-ferromagnetic model on a diamond -# lattice. +# +# This tutorial illustrates the calculation of the powder-averaged structure +# factor by performing an orientational average. We consider a simple model of +# the diamond-cubic crystal CoRh$_2$O$_4$, with parameters extracted from Ge et +# al., Phys. Rev. B 96, 064413 (https://doi.org/10.1103/PhysRevB.96.064413). using Sunny, GLMakie -using Statistics: mean -dims = (8,8,8) # Lattice dimensions -seed = 1 # RNG seed for repeatable behavior -J = Sunny.meV_per_K*7.5413 # Nearest-neighbor exchange parameter - -crystal = Sunny.diamond_crystal() -sys = System(crystal, dims, [SpinInfo(1, S=3/2, g=2)], :dipole; seed) +# Construct a diamond [`Crystal`](@ref) in the conventional (non-primitive) +# cubic unit cell. Sunny will populate all eight symmetry-equivalent sites when +# given the international spacegroup number 227 ("Fd-3m") and the appropriate +# setting. For this spacegroup, there are two conventional translations of the +# unit cell, and it is necessary to disambiguate through the `setting` keyword +# argument. Try omitting the `setting` argument and see how Sunny responds. + +a = 8.5031 # (Å) +latvecs = lattice_vectors(a, a, a, 90, 90, 90) +crystal = Crystal(latvecs, [[0,0,0]], 227, setting="1") + +# Construct a [`System`](@ref) with an antiferromagnetic nearest neighbor +# interaction `J`. Because the diamond crystal is bipartite, the ground state +# will have unfrustrated Néel order. Selecting `latsize=(1,1,1)` is sufficient +# because the ground state is periodic over each cubic unit cell. By passing an +# explicit `seed`, the system's random number generator will give repeatable +# results. + +latsize = (1,1,1) +seed = 0 +S = 3/2 +J = 7.5413*meV_per_K # (meV) +sys = System(crystal, latsize, [SpinInfo(1; S, g=2)], :dipole; seed=0) set_exchange!(sys, J, Bond(1, 3, [0,0,0])) -# We next set up a [`Langevin`](@ref) integrator and thermalize the system. - -Δt = 0.07 # Step size for Langevin integrator -kT = Sunny.meV_per_K * 2 # Temperature of simulation (2K) -λ = 0.1 # Damping parameter -integrator = Langevin(Δt; kT, λ); - -for _ ∈ 1:3000 - step!(sys, integrator) -end; - -# We can now estimate ``𝒮(𝐪,ω)`` with [`dynamical_correlations`](@ref). We -# will tell Sunny to symmetrize the sample trajectory along the time-axis to -# minimize Fourier artifacts. - -sc = dynamical_correlations(sys; - Δt=2Δt, - nω=100, - ωmax=5.5, - process_trajectory=:symmetrize -) -add_sample!(sc, sys) - -# To get some intuition about the expected results, we first look at the "single -# crystal" results along a high-symmetry path in the first Brillouin zone. While -# doing so, we will add some artificial broadening along the energy axis with -# [`broaden_energy`](@ref). To use this function, it is necessary to define a -# kernel function with the form, `kernel(ω, ω₀)`, where `ω` is energy and `ω₀` -# is the center frequency of the kernel. In this example we apply some -# Lorentzian broadening using an anonymous function: `(ω, ω₀) -> -# lorentzian(ω-ω₀, 0.1)`. +# The ground state is non-frustrated. Each spin should be exactly anti-aligned +# with its 4 nearest-neighbors, such that every bond contributes an energy of +# $-JS^2$. This gives an energy per site of $-2JS^2$. In this calculation, a +# factor of 1/2 is necessary to avoid double-counting the bonds. Given the small +# magnetic supercell (which includes only one unit cell), direct energy +# minimization is successful in finding the ground state. -qpoints = [[0.0, 0.0, 0.0], [0.5, 0.0, 0.0], [0.5, 0.5, 0.0], [0.0, 0.0, 0.0]] -qs, xticks = connected_path_from_rlu(crystal, qpoints, 50) - -is = intensities_interpolated(sc, qs; interpolation=:round, formula = intensity_formula(sc,:trace)) -is_broad = broaden_energy(sc, is, (ω, ω₀) -> lorentzian(ω-ω₀, 0.1)) - -## Plot results -fig = Figure(; resolution=(1000,400)) -plotparams = (; - aspect=1.4, - ylabel = "ω (meV)", - xlabel = "𝐪 (RLU)", - xticks, - xticklabelrotation=π/10, - xticklabelsize=14, -) -ax1 = Axis(fig[1,1]; title="No artificial broadening", plotparams...) -heatmap!(ax1, 1:size(is, 1), ωs(sc), is; colorrange=(0,0.5)) -ax2 = Axis(fig[1,2]; title="Lorentzian broadening (η=0.1)", plotparams...) -heatmap!(ax2, 1:size(is, 1), ωs(sc), is_broad; colorrange=(0,2.0)) -fig +randomize_spins!(sys) +minimize_energy!(sys) -# We next write a simple powder averaging function that takes a structure -# factor, a list of radius values (Å⁻¹), and a density parameter (Å⁻²) that will -# control the number of wave vectors to sample at each radius. For each radius -# `r`, the function will generate wavevectors on a sphere of this radius and -# retrieve their [`intensities_interpolated`](@ref). These intensities will be broadened, as -# just demonstrated above, and then averaged to produce a single vector of -# energy-intensities for each `r`. Note that our `powder_average` function -# passes most of its keywords through to [`intensities_interpolated`](@ref), so it can be -# given an [`intensity_formula`](@ref). - -function powder_average(sc, rs, density; η=0.1, kwargs...) - nω = length(ωs(sc)) - output = zeros(Float64, length(rs), nω) - - for (i, r) in enumerate(rs) - qs = spherical_shell(r, density) # Get points on a sphere of radius r - if length(qs) == 0 - qs = [[0., 0., 0.]] # If no points (r is too small), just look at 0 vector - end - vals = intensities_interpolated(sc, qs; kwargs...) # Retrieve energy intensities - vals[:,1] .*= 0.0 # Remove elastic peaks before broadening - vals = broaden_energy(sc, vals, (ω,ω₀)->lorentzian(ω-ω₀, η)) # Apply Lorentzian broadening - output[i,:] = reshape(mean(vals, dims=1), (nω,)) # Average single radius results and save - end - - return output -end; - -# Finally, we perform the calculation, - -rs = range(0, 6π, length=55) # Set of radius values -η = 0.05 # Lorentzian broadening parameter -density = 0.15 # Number of samples in Å⁻² - -formula = intensity_formula(sc,:perp) -pa = powder_average(sc, rs, density; η, formula); - -# and plot the results. - -fig1= Figure() -ax = Axis(fig1[1,1]; xlabel = "|Q| (Å⁻¹)", ylabel = "ω (meV)") -heatmap!(ax, rs, ωs(sc), pa; colorrange=(0, 25.0)) -fig1 - -# Note that the bandwidth is similar to what we saw above along the high -# symmetry path. -# -# This was a very quick calculation. The structure factor calculation itself and -# the powder averaging will each execute in < 10 s on a typical laptop. Higher -# quality results can be obtained by: -# - Increasing the number of samples used to calculate ``𝒮(𝐪,ω)`` using -# [`add_sample!`](@ref) -# - Increasing the system size to improve momentum space resolution -# - Increasing the energy resolution (`nω` keyword of -# [`dynamical_correlations`](@ref)) -# - Applying instrument-specific energy broadening by giving `broaden_energy` a -# custom kernel function. -# - Including [`FormFactor`](@ref) corrections -# - Setting `interpolation=:linear` when retrieving intensities in the powder -# averaging loop. +energy_per_site = energy(sys) / length(all_sites(sys)) +@assert energy_per_site ≈ -2J*S^2 + +# Plotting the spins confirms the expected Néel order. Note that the overall, +# global rotation of dipoles is arbitrary. +plot_spins(sys; arrowlength=1.0, linewidth=0.4, arrowsize=0.5) -# The intensity data can alternatively be collected into bonafide histogram bins. See [`integrated_lorentzian`](@ref), [`powder_average_binned`](@ref), and [`axes_bincenters`](@ref). -radial_binning_parameters = (0,6π,6π/55) -integrated_kernel = integrated_lorentzian(0.05) # Lorentzian broadening +# We can now estimate ``𝒮(𝐪,ω)`` with [`SpinWaveTheory`](@ref) and +# [`intensity_formula`](@ref). The mode `:perp` contracts with a dipole factor +# to return the unpolarized intensity. We will also apply broadening with the +# [`lorentzian`](@ref) kernel. -pa_intensities, pa_counts = powder_average_binned(sc,radial_binning_parameters;integrated_kernel = integrated_kernel,formula) +swt = SpinWaveTheory(sys) +η = 0.3 # (meV) +formula = intensity_formula(swt, :perp, kernel=lorentzian(η)) # TODO: formfactors=[FormFactor(1, "Co2")] -pa_normalized_intensities = pa_intensities ./ pa_counts +# First, we consider the "single crystal" results. Use +# [`connected_path_from_rlu`](@ref) to construct a path that connects +# high-symmetry points in reciprocal space. The [`intensities_broadened`](@ref) +# function collects intensities along this path for the given set of energy +# ($ħω$) values. + +qpoints = [[0.0, 0.0, 0.0], [0.5, 0.0, 0.0], [0.5, 0.5, 0.0], [0.0, 0.0, 0.0]] +path, xticks = connected_path_from_rlu(crystal, qpoints, 50) +energies = collect(0:0.01:6) +is = intensities_broadened(swt, path, energies, formula) + +# Plot the results fig = Figure() -ax = Axis(fig[1,1]; xlabel = "|k| (Å⁻¹)", ylabel = "ω (meV)") -rs_bincenters = axes_bincenters(radial_binning_parameters...) -heatmap!(ax, rs_bincenters[1], ωs(sc), pa_normalized_intensities; colorrange=(0,3.0)) +ax = Axis(fig[1,1]; aspect=1.4, ylabel="ω (meV)", xlabel="𝐪 (RLU)", + xticks, xticklabelrotation=π/10) +heatmap!(ax, 1:size(is, 1), energies, is, colormap=:gnuplot2) fig -# The striations in the graph tell us that the simulation is under resolved for this bin size. -# We should increase the size of either the periodic lattice, or the bins. -# -# Using the `bzsize` option, we can even resolve the contribution from each brillouin zone: -intensity_firstBZ, counts_firstBZ = powder_average_binned(sc,radial_binning_parameters;integrated_kernel = integrated_kernel, bzsize=(1,1,1),formula) -#md #intensity_secondBZ, counts_secondBZ = powder_average_binned(..., bzsize=(2,2,2)) -intensity_secondBZ, counts_secondBZ = powder_average_binned(sc,radial_binning_parameters;integrated_kernel = integrated_kernel, bzsize=(2,2,2),formula)#hide -#md #intensity_thirdBZ, counts_thirdBZ = powder_average_binned(..., bzsize=(3,3,3)) -intensity_thirdBZ = pa_intensities;#hide -counts_thirdBZ = pa_counts;#hide - -# First BZ: -fig = Figure()#hide -ax = Axis(fig[1,1]; xlabel = "|k| (Å⁻¹)", ylabel = "ω (meV)")#hide -rs_bincenters = axes_bincenters(radial_binning_parameters...)#hide -heatmap!(ax, rs_bincenters[1], ωs(sc), - intensity_firstBZ ./ counts_firstBZ - ; colorrange=(0,3.0)) -fig#hide - -# Second BZ: -fig = Figure()#hide -ax = Axis(fig[1,1]; xlabel = "|k| (Å⁻¹)", ylabel = "ω (meV)")#hide -rs_bincenters = axes_bincenters(radial_binning_parameters...)#hide -heatmap!(ax, rs_bincenters[1], ωs(sc), - (intensity_secondBZ .- intensity_firstBZ) ./ (counts_secondBZ .- counts_firstBZ) - ; colorrange=(0,3.0)) -fig#hide - -# Third BZ: -fig = Figure()#hide -ax = Axis(fig[1,1]; xlabel = "|k| (Å⁻¹)", ylabel = "ω (meV)")#hide -rs_bincenters = axes_bincenters(radial_binning_parameters...)#hide -heatmap!(ax, rs_bincenters[1], ωs(sc), - (intensity_thirdBZ .- intensity_secondBZ) ./ (counts_thirdBZ .- counts_secondBZ) - ; colorrange=(0,3.0)) -fig#hide +# To compare with experimental measurements on a crystal powder, we should +# average over all possible crystal orientations. For this, consider a sequence +# of radii `rs` (units of inverse Å) that define spherical shells in reciprocal +# space. The function [`spherical_shell`](@ref) selects points on these shells +# that are approximately equidistant. For each shell, again call +# `intensities_broadened`, and average over the energy index. The result is a +# powder averaged intensity. + +rs = 0.01:0.02:3 # (1/Å) +output = zeros(Float64, length(rs), length(energies)) +for (i, r) in enumerate(rs) + qs = spherical_shell(r; minpoints=300) + is = intensities_broadened(swt, qs, energies, formula) + output[i, :] = sum(is, dims=1) / size(is, 1) +end + +empty!(fig) +ax = Axis(fig[1,1]; xlabel="|Q| (Å⁻¹)", ylabel="ω (meV)") +heatmap!(ax, rs, energies, output, colormap=:gnuplot2) +fig + +# This result can be compared to experimental neutron scattering data +# from [Ge et al.](https://doi.org/10.1038/s41567-020-01110-1) +# ```@raw html +# +# ``` diff --git a/src/Intensities/PowderAveraging.jl b/src/Intensities/PowderAveraging.jl index 0bb786a4b..2bd9fb0a4 100644 --- a/src/Intensities/PowderAveraging.jl +++ b/src/Intensities/PowderAveraging.jl @@ -9,19 +9,20 @@ function spherical_points_fibonacci(N) end """ - spherical_shell(radius, density) + spherical_shell(radius; density=0.0, minpoints=0, maxpoints=typemax(Int64)) -Sample points on a sphere of given `radius`, with given sample `density`. +Sample points on a sphere of given `radius`, with given sample `density`. Bounds +on the number of sample points may be provided with `minpoints` and `maxpoints`. The points are generated by mapping a Fibonacci lattice onto a sphere. """ -function spherical_shell(radius, density) - numpoints = round(Int, 4π*radius^2 * density) - if numpoints == 0 - return [Vec3(0,0,0)] # If radius too small, just return the 0 vector - else - return radius * spherical_points_fibonacci(numpoints) +function spherical_shell(radius; density=0.0, minpoints=0, maxpoints=typemax(Int64)) + if iszero(max(density, minpoints)) + error("Must specify `density` or `minpoints`.") end + n = round(Int, 4π*radius^2 * density) + n = min(max(n, minpoints), maxpoints) + return n <= 1 ? [Vec3(0,0,0)] : radius * spherical_points_fibonacci(n) end function powder_average_interpolated(sc::SampledCorrelations, q_ias, density; kwargs...) From 1a559ce553fe307ca4d626cc60c3cc90905dc989 Mon Sep 17 00:00:00 2001 From: Kipton Barros Date: Sun, 6 Aug 2023 17:18:37 -0600 Subject: [PATCH 10/12] Allow one sample point on shell It should have correct radius. --- src/Intensities/PowderAveraging.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Intensities/PowderAveraging.jl b/src/Intensities/PowderAveraging.jl index 2bd9fb0a4..7620f8567 100644 --- a/src/Intensities/PowderAveraging.jl +++ b/src/Intensities/PowderAveraging.jl @@ -21,8 +21,8 @@ function spherical_shell(radius; density=0.0, minpoints=0, maxpoints=typemax(Int error("Must specify `density` or `minpoints`.") end n = round(Int, 4π*radius^2 * density) - n = min(max(n, minpoints), maxpoints) - return n <= 1 ? [Vec3(0,0,0)] : radius * spherical_points_fibonacci(n) + n = min(max(n, minpoints, 1), maxpoints) + return radius * spherical_points_fibonacci(n) end function powder_average_interpolated(sc::SampledCorrelations, q_ias, density; kwargs...) From 76a6ed7ce13ca3fef4acd87a184c8e5ead5632f4 Mon Sep 17 00:00:00 2001 From: Sam Quinn Date: Sun, 6 Aug 2023 23:00:11 -0400 Subject: [PATCH 11/12] Fix typo breaking tests Also removes a FIXME; that q is for sure *not* in absolute units because it's calculated in RLU --- src/Intensities/PowderAveraging.jl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Intensities/PowderAveraging.jl b/src/Intensities/PowderAveraging.jl index 7620f8567..a06b809c0 100644 --- a/src/Intensities/PowderAveraging.jl +++ b/src/Intensities/PowderAveraging.jl @@ -84,18 +84,16 @@ function powder_average_binned(sc::SampledCorrelations, radial_binning_parameter for cell in CartesianIndices(Ls .* bzsize) base_cell = CartesianIndex(mod1.(cell.I,Ls)...) for (iω,ω) in enumerate(ωvals) - # Compute intensity - # [c.f. all_exact_wave_vectors, but we need `cell' index as well here] q = SVector((cell.I .- 1) ./ Ls) # q is in R.L.U. # Figure out which radial bin this scattering vector goes in # The spheres are surfaces of fixed |k|, with k in absolute units - k = sc.crystal.recipvecs * q # FIXME: q should already be in absolute units + k = sc.crystal.recipvecs * q r_coordinate = norm(k) # Check if the radius falls within the histogram rbin = @. 1 + floor(Int64, (r_coordinate - rstart) / rbinwidth) # TODO: @. 1 + div(r_coordinate-rstart, rbinwidth, RoundDown) - if r1 <= bin <= r_bin_count + if 1 <= rbin <= r_bin_count # If we are energy-broadening, then scattering vectors outside the histogram # in the energy direction need to be considered if isnothing(integrated_kernel) # `Delta-function energy' logic From 4ec093f3debce89dd4d07aa70e9270b19e096c52 Mon Sep 17 00:00:00 2001 From: Sam Quinn Date: Sun, 6 Aug 2023 23:01:52 -0400 Subject: [PATCH 12/12] Un-export `bin_rlu_as_absolute_units!` and remove references to it from docs. Remove options for selecting units from functions for creating binning parameters. All user-facing parameters should now accept exclusively absolute units. --- src/Intensities/Binning.jl | 40 +++++++++++--------------------------- src/Sunny.jl | 1 - 2 files changed, 11 insertions(+), 30 deletions(-) diff --git a/src/Intensities/Binning.jl b/src/Intensities/Binning.jl index 48e919cdb..c124e7230 100644 --- a/src/Intensities/Binning.jl +++ b/src/Intensities/Binning.jl @@ -10,7 +10,6 @@ The coordinates of the histogram axes are specified by multiplication of `(k,ω)` with each row of the `covectors` matrix, with `k` given in absolute units. Since the default `covectors` matrix is the identity matrix, the default axes are `(kx,ky,kz,ω)` in absolute units. -To bin `(q,ω)` values given in reciprocal lattice units (R.L.U.) instead, see [`bin_rlu_as_absolute_units!`](@ref). The convention for the binning scheme is that: - The left edge of the first bin starts at `binstart` @@ -29,6 +28,8 @@ mutable struct BinningParameters binwidth::MVector{4,Float64} covectors::MMatrix{4,4,Float64} end +# TODO: Use the more efficient three-argument `div(a,b,RoundDown)` instead of `floor(a/b)` +# to implement binning. Both performance and correctness need to be checked. function Base.show(io::IO, ::MIME"text/plain", params::BinningParameters) printstyled(io, "Binning Parameters\n"; bold=true, color=:underline) @@ -131,7 +132,6 @@ function binning_parameters_aabb(params) return lower_aabb_q, upper_aabb_q end -# FIXME: various conversions go away. Always absolute units. """ If `params` expects to bin values `(k,ω)` in absolute units, then calling @@ -178,18 +178,16 @@ bin_rlu_as_absolute_units!(params::BinningParameters,swt::SpinWaveTheory) = bin_ """ - unit_resolution_binning_parameters(sc::SampledCorrelations; units = :absolute) + unit_resolution_binning_parameters(sc::SampledCorrelations) Create [`BinningParameters`](@ref) which place one histogram bin centered at each possible `(q,ω)` scattering vector of the crystal. This is the finest possible binning without creating bins with zero scattering vectors in them. -Setting `units = :rlu` returns [`BinningParameters`](@ref) which accept values in R.L.U. instead of absolute units. - This function can be used without reference to a [`SampledCorrelations`](@ref) using an alternate syntax to manually specify the bin centers for the energy axis and the lattice size: - unit_resolution_binning_parameters(ω_bincenters,latsize,[reciprocal lattice vectors]; [units]) + unit_resolution_binning_parameters(ω_bincenters,latsize,[reciprocal lattice vectors]) -As in [`bin_absolute_units_as_rlu!`](@ref), the last argument may be a 3x3 matrix specifying the reciprocal lattice vectors, or any of these objects: +The last argument may be a 3x3 matrix specifying the reciprocal lattice vectors, or any of these objects: - [`Crystal`](@ref) - [`System`](@ref) - [`SampledCorrelations`](@ref) @@ -199,7 +197,7 @@ Lastly, binning parameters for a single axis may be specifed by their bin center (binstart,binend,binwidth) = unit_resolution_binning_parameters(bincenters::Vector{Float64}) """ -function unit_resolution_binning_parameters(ωvals,latsize,args...;units = :absolute) +function unit_resolution_binning_parameters(ωvals,latsize,args...) numbins = (latsize...,length(ωvals)) # Bin centers should be at Sunny scattering vectors maxQ = 1 .- (1 ./ numbins) @@ -223,13 +221,7 @@ function unit_resolution_binning_parameters(ωvals,latsize,args...;units = :abso end end - if units == :absolute - bin_absolute_units_as_rlu!(params,args...) - elseif units == :rlu - params - else - error("unit_resolution_binning_parameters: Unsupported units $units") - end + bin_absolute_units_as_rlu!(params,args...) end unit_resolution_binning_parameters(sc::SampledCorrelations; kwargs...) = unit_resolution_binning_parameters(ωs(sc),sc.latsize,sc;kwargs...) @@ -248,9 +240,8 @@ function unit_resolution_binning_parameters(ωvals::AbstractVector{Float64}) return ωstart, ωend, ωbinwidth end -# FIXME: remove `units` arg -- always absolute """ - slice_2D_binning_parameter(sc::SampledCorrelations, cut_from_q, cut_to_q, cut_bins::Int64, cut_width::Float64; plane_normal = [0,0,1],cut_height = cutwidth, units = :absolute) + slice_2D_binning_parameter(sc::SampledCorrelations, cut_from_q, cut_to_q, cut_bins::Int64, cut_width::Float64; plane_normal = [0,0,1],cut_height = cutwidth) Creates [`BinningParameters`](@ref) which make a 1D cut in Q-space. @@ -269,8 +260,6 @@ The four axes of the resulting histogram are: 3. Second transverse Q direction 4. Energy -Setting `units = :rlu` returns [`BinningParameters`](@ref) which accept values in R.L.U. instead of absolute units. - This function can be used without reference to a [`SampledCorrelations`](@ref) using this alternate syntax to manually specify the bin centers for the energy axis: slice_2D_binning_parameter(ω_bincenters, cut_from, cut_to,...) @@ -301,16 +290,9 @@ function slice_2D_binning_parameters(ωvals::Vector{Float64},cut_from_q,cut_to_q BinningParameters(binstart,binend;numbins = numbins, covectors = covectors) end -function slice_2D_binning_parameters(sc::SampledCorrelations,cut_from_q,cut_to_q,args...;units = :absolute,kwargs...) +function slice_2D_binning_parameters(sc::SampledCorrelations,cut_from_q,cut_to_q,args...;kwargs...) params = slice_2D_binning_parameters(ωs(sc),cut_from_q,cut_to_q,args...;kwargs...) - - if units == :absolute - bin_absolute_units_as_rlu!(params,sc) - elseif units == :rlu - params - else - error("slice_2D_binning_parameters: Unsupported units $units") - end + bin_absolute_units_as_rlu!(params,sc) end """ @@ -453,7 +435,7 @@ function intensities_binned(sc::SampledCorrelations, params::BinningParameters; # Which is the analog of this scattering mode in the first BZ? base_cell = CartesianIndex(mod1.(cell.I,Ls)...) q .= ((cell.I .- 1) ./ Ls) # q is in R.L.U. - k .= sc.crystal.recipvecs * q # FIXME -- no scaling factor needed, q will already in absolute units. + k .= sc.crystal.recipvecs * q for (iω,ω) in enumerate(ωvals) if isnothing(integrated_kernel) # `Delta-function energy' logic # Figure out which bin this goes in diff --git a/src/Sunny.jl b/src/Sunny.jl index 1983953c9..30484d272 100644 --- a/src/Sunny.jl +++ b/src/Sunny.jl @@ -102,7 +102,6 @@ export intensities_interpolated, instant_intensities_interpolated, connected_pat include("Intensities/Binning.jl") export intensities_binned, BinningParameters, count_bins, integrate_axes!, unit_resolution_binning_parameters, - bin_absolute_units_as_rlu!, bin_rlu_as_absolute_units!, slice_2D_binning_parameters, axes_bincenters, connected_path_bins