diff --git a/examples/05_MC_Ising.jl b/examples/05_MC_Ising.jl index 957a4dc82..cb7fb69a4 100644 --- a/examples/05_MC_Ising.jl +++ b/examples/05_MC_Ising.jl @@ -13,7 +13,7 @@ crystal = Crystal(latvecs, [[0, 0, 0]]) # Create a [`System`](@ref) of spin dipoles. Following the Ising convention, we # will restrict the dipoles to ``±1`` along the global ``\hat{z}``-axis. Select # ``g=-1`` so that the Zeeman coupling between external field ``𝐁`` and spin -# dipole ``𝐬`` is ``-𝐁⋅𝐬``. The system size is 128×128. +# dipole ``𝐒`` is ``-𝐁⋅𝐒``. The system size is 128×128. L = 128 sys = System(crystal, [1 => Moment(s=1, g=-1)], :dipole; dims=(L, L, 1), seed=0) @@ -40,7 +40,7 @@ Tc = 2/log(1+√2) # consists of, on average, one trial update per spin in the system. Each # proposed update is accepted or rejected according to the Metropolis acceptance # probability. As its name suggests, the [`propose_flip`](@ref) function will -# only propose pure spin flips, ``𝐬 \rightarrow -𝐬``. +# only propose pure spin flips, ``𝐒 \rightarrow -𝐒``. nsweeps = 4000 sampler = LocalSampler(kT=Tc, propose=propose_flip) diff --git a/examples/06_CP2_Skyrmions.jl b/examples/06_CP2_Skyrmions.jl index ef998dd17..17331d82c 100644 --- a/examples/06_CP2_Skyrmions.jl +++ b/examples/06_CP2_Skyrmions.jl @@ -24,7 +24,7 @@ positions = [[0, 0, 0]] cryst = Crystal(lat_vecs, positions) # Create a spin [`System`](@ref) containing ``L×L`` cells. Following previous -# worse, select ``g=-1`` so that the Zeeman coupling has the form ``-𝐁⋅𝐬``. +# worse, select ``g=-1`` so that the Zeeman coupling has the form ``-𝐁⋅𝐒``. L = 40 sys = System(cryst, [1 => Moment(s=1, g=-1)], :SUN; dims=(L, L, 1)) diff --git a/examples/spinw_tutorials/SW13_LiNiPO4.jl b/examples/spinw_tutorials/SW13_LiNiPO4.jl index 0c6c5f9d1..0b44f34b8 100644 --- a/examples/spinw_tutorials/SW13_LiNiPO4.jl +++ b/examples/spinw_tutorials/SW13_LiNiPO4.jl @@ -28,7 +28,6 @@ view_crystal(cryst) # "Interaction Renormalization") of anisotropy strengths, as needed for # consistency with the original fits. -S = 3/2 sys = System(cryst, [1 => Moment(s=1, g=2)], :dipole_large_s) Jbc = 1.036 Jb = 0.6701 diff --git a/ext/PlottingExt.jl b/ext/PlottingExt.jl index acb169166..69213b864 100644 --- a/ext/PlottingExt.jl +++ b/ext/PlottingExt.jl @@ -680,7 +680,7 @@ function Sunny.view_crystal(cryst::Crystal; refbonds=10, orthographic=false, gho view_crystal_aux(cryst, nothing; refbonds, orthographic, ghost_radius, ndims, compass, size) end -function Sunny.view_crystal(sys::System; refbonds=8, orthographic=false, ghost_radius=nothing, ndims=3, compass=true, size=(768, 512), dims=nothing) +function Sunny.view_crystal(sys::System; refbonds=10, orthographic=false, ghost_radius=nothing, ndims=3, compass=true, size=(768, 512), dims=nothing) isnothing(dims) || error("Use notation `ndims=$dims` instead of `dims=$dims`") Sunny.is_homogeneous(sys) || error("Cannot plot interactions for inhomogeneous system.") view_crystal_aux(orig_crystal(sys), sys; diff --git a/src/Integrators.jl b/src/Integrators.jl index fe4710ddb..41458890c 100644 --- a/src/Integrators.jl +++ b/src/Integrators.jl @@ -18,10 +18,10 @@ prevents energy drift through exact conservation of the symplectic 2-form. If the [`System`](@ref) has `mode = :dipole`, then the dynamics is the stochastic Landau-Lifshitz equation, ```math - d𝐬/dt = -𝐬 × (ξ - 𝐁 + λ 𝐬 × 𝐁), + d𝐒/dt = -𝐒 × (ξ - 𝐁 + λ 𝐒 × 𝐁), ``` -where ``𝐁 = -dE/d𝐬`` is the effective field felt by the expected spin dipole -``𝐬``. The components of ``ξ`` are Gaussian white noise, with magnitude ``√(2 +where ``𝐁 = -dE/d𝐒`` is the effective field felt by the expected spin dipole +``𝐒``. The components of ``ξ`` are Gaussian white noise, with magnitude ``√(2 k_B T λ)`` set by a fluctuation-dissipation theorem. The parameter `damping` sets the phenomenological coupling ``λ`` to the thermal bath. @@ -38,11 +38,11 @@ parameter `damping` here sets ``λ̃``, which is analogous to ``λ`` above. When applied to SU(2) coherent states, the generalized spin dynamics reduces exactly to the stochastic Landau-Lifshitz equation. The mapping is as follows. -Normalized coherent states ``𝐙`` map to dipole expectation values ``𝐬 = 𝐙^{†} -Ŝ 𝐙``, where spin operators ``Ŝ`` are a spin-``|𝐬|`` representation of +Normalized coherent states ``𝐙`` map to dipole expectation values ``𝐒 = 𝐙^{†} +Ŝ 𝐙``, where spin operators ``Ŝ`` are a spin-``|𝐒|`` representation of SU(2). The local effective Hamiltonian ``ℋ = -𝐁 ⋅ Ŝ`` generates rotation of the dipole in analogy to the vector cross product ``S × 𝐁``. The coupling to -the thermal bath maps as ``λ̃ = |𝐬| λ``. Note, therefore, that the scaling of +the thermal bath maps as ``λ̃ = |𝐒| λ``. Note, therefore, that the scaling of the `damping` parameter varies subtly between `:dipole` and `:SUN` modes. References: @@ -137,7 +137,7 @@ end Suggests a timestep for the numerical integration of spin dynamics according to a given error tolerance `tol`. The `integrator` should be [`Langevin`](@ref) or [`ImplicitMidpoint`](@ref). The suggested ``dt`` will be inversely proportional -to the magnitude of the effective field ``|dE/d𝐬|`` arising from the current +to the magnitude of the effective field ``|dE/d𝐒|`` arising from the current spin configuration in `sys`. The recommended timestep ``dt`` scales like `√tol`, which assumes second-order accuracy of the integrator. @@ -258,14 +258,14 @@ end ################################################################################ -@inline function rhs_dipole!(Δs, s, ξ, ∇E, integrator) +@inline function rhs_dipole!(ΔS, S, ξ, ∇E, integrator) (; dt, damping) = integrator λ = damping if iszero(λ) - @. Δs = - s × (dt*∇E) + @. ΔS = - S × (dt*∇E) else - @. Δs = - s × (ξ + dt*∇E - dt*λ*(s × ∇E)) + @. ΔS = - S × (ξ + dt*∇E - dt*λ*(S × ∇E)) end end @@ -308,20 +308,20 @@ function step! end function step!(sys::System{0}, integrator::Langevin) check_timestep_available(integrator) - (s′, Δs₁, Δs₂, ξ, ∇E) = get_dipole_buffers(sys, 5) - s = sys.dipoles + (S′, ΔS₁, ΔS₂, ξ, ∇E) = get_dipole_buffers(sys, 5) + S = sys.dipoles fill_noise!(sys.rng, ξ, integrator) # Euler prediction step - set_energy_grad_dipoles!(∇E, s, sys) - rhs_dipole!(Δs₁, s, ξ, ∇E, integrator) - @. s′ = normalize_dipole(s + Δs₁, sys.κs) + set_energy_grad_dipoles!(∇E, S, sys) + rhs_dipole!(ΔS₁, S, ξ, ∇E, integrator) + @. S′ = normalize_dipole(S + ΔS₁, sys.κs) # Correction step - set_energy_grad_dipoles!(∇E, s′, sys) - rhs_dipole!(Δs₂, s′, ξ, ∇E, integrator) - @. s = normalize_dipole(s + (Δs₁+Δs₂)/2, sys.κs) + set_energy_grad_dipoles!(∇E, S′, sys) + rhs_dipole!(ΔS₂, S′, ξ, ∇E, integrator) + @. S = normalize_dipole(S + (ΔS₁+ΔS₂)/2, sys.κs) return end @@ -366,42 +366,42 @@ function fast_isapprox(x, y; atol) end # The spherical midpoint method, Phys. Rev. E 89, 061301(R) (2014) -# Integrates ds/dt = s × ∂E/∂s one timestep s → s′ via implicit equations -# s̄ = (s′ + s) / 2 -# ŝ = s̄ / |s̄| -# (s′ - s)/dt = 2(s̄ - s)/dt = - ŝ × B, -# where B = -∂E/∂ŝ. +# Integrates dS/dt = S × ∂E/∂S one timestep S → S′ via implicit equations +# S̄ = (S′ + S) / 2 +# Ŝ = S̄ / |S̄| +# (S′ - S)/dt = 2(S̄ - S)/dt = - Ŝ × B, +# where B = -∂E/∂Ŝ. function step!(sys::System{0}, integrator::ImplicitMidpoint; max_iters=100) check_timestep_available(integrator) - s = sys.dipoles - atol = integrator.atol * √length(s) + S = sys.dipoles + atol = integrator.atol * √length(S) - (Δs, ŝ, s′, s″, ξ, ∇E) = get_dipole_buffers(sys, 6) + (ΔS, Ŝ, S′, S″, ξ, ∇E) = get_dipole_buffers(sys, 6) fill_noise!(sys.rng, ξ, integrator) - @. s′ = s - @. s″ = s + @. S′ = S + @. S″ = S for _ in 1:max_iters # Current guess for midpoint ŝ - @. ŝ = normalize_dipole((s + s′)/2, sys.κs) + @. Ŝ = normalize_dipole((S + S′)/2, sys.κs) - set_energy_grad_dipoles!(∇E, ŝ, sys) - rhs_dipole!(Δs, ŝ, ξ, ∇E, integrator) + set_energy_grad_dipoles!(∇E, Ŝ, sys) + rhs_dipole!(ΔS, Ŝ, ξ, ∇E, integrator) - @. s″ = s + Δs + @. S″ = S + ΔS # If converged, then we can return - if fast_isapprox(s′, s″; atol) + if fast_isapprox(S′, S″; atol) # Normalization here should not be necessary in principle, but it # could be useful in practice for finite `atol`. - @. s = normalize_dipole(s″, sys.κs) + @. S = normalize_dipole(S″, sys.κs) return end - s′, s″ = s″, s′ + S′, S″ = S″, S′ end error("Spherical midpoint method failed to converge to tolerance $atol after $max_iters iterations.") diff --git a/src/KPM/SpinWaveTheoryKPM.jl b/src/KPM/SpinWaveTheoryKPM.jl index bbf5ccb7c..63f83c6be 100644 --- a/src/KPM/SpinWaveTheoryKPM.jl +++ b/src/KPM/SpinWaveTheoryKPM.jl @@ -102,10 +102,10 @@ function intensities!(data, swt_kpm::SpinWaveTheoryKPM, qpts; energies, kernel:: end if sys.mode == :SUN - data_sun = swt.data::SWTDataSUN + (; observables_localized) = swt.data::SWTDataSUN N = sys.Ns[1] for i in 1:Na, μ in 1:Nobs - @views O = data_sun.observables_localized[μ, i] + O = observables_localized[μ, i] for f in 1:Nf u[μ, f + (i-1)*Nf] = Avec_pref[i] * O[f, N] u[μ, f + (i-1)*Nf + L] = Avec_pref[i] * O[N, f] @@ -113,13 +113,12 @@ function intensities!(data, swt_kpm::SpinWaveTheoryKPM, qpts; energies, kernel:: end else @assert sys.mode in (:dipole, :dipole_large_s) - data_dip = swt.data::SWTDataDipole + (; sqrtS, observables_localized) = swt.data::SWTDataDipole for i in 1:Na - sqrt_halfS = data_dip.sqrtS[i]/sqrt(2) for μ in 1:Nobs - O = data_dip.observables_localized[μ, i] - u[μ, i] = Avec_pref[i] * sqrt_halfS * (O[1] + im*O[2]) - u[μ, i+L] = Avec_pref[i] * sqrt_halfS * (O[1] - im*O[2]) + O = observables_localized[μ, i] + u[μ, i] = Avec_pref[i] * (sqrtS[i] / √2) * (O[1] + im*O[2]) + u[μ, i+L] = Avec_pref[i] * (sqrtS[i] / √2) * (O[1] - im*O[2]) end end end diff --git a/src/MonteCarlo/ParallelTempering.jl b/src/MonteCarlo/ParallelTempering.jl index c1ad10524..ec7e5f9ce 100644 --- a/src/MonteCarlo/ParallelTempering.jl +++ b/src/MonteCarlo/ParallelTempering.jl @@ -28,7 +28,7 @@ function ParallelTempering(system::System{N}, sampler::SMP, kT_sched::Vector{Flo if samplers[1] isa LocalSampler for rank in 1:n_replicas samplers[rank].ΔE = energy(systems[rank]) - samplers[rank].Δs = sum(systems[rank].dipoles) + samplers[rank].ΔS = sum(systems[rank].dipoles) samplers[rank].nsweeps = 1.0 end end @@ -63,7 +63,7 @@ function replica_exchange!(PT::ParallelTempering, exch_start::Int64) if PT.samplers[1] isa LocalSampler PT.samplers[id₁].ΔE, PT.samplers[id₂].ΔE = PT.samplers[id₂].ΔE, PT.samplers[id₁].ΔE - PT.samplers[id₁].Δs, PT.samplers[id₂].Δs = PT.samplers[id₂].Δs, PT.samplers[id₁].Δs + PT.samplers[id₁].ΔS, PT.samplers[id₂].ΔS = PT.samplers[id₂].ΔS, PT.samplers[id₁].ΔS end PT.n_accept[id₁] += 1 diff --git a/src/MonteCarlo/Samplers.jl b/src/MonteCarlo/Samplers.jl index da3b4be1e..e4758ceab 100644 --- a/src/MonteCarlo/Samplers.jl +++ b/src/MonteCarlo/Samplers.jl @@ -17,7 +17,7 @@ const propose_uniform = randspin propose_flip Function to propose pure spin flip updates in the context of a -[`LocalSampler`](@ref). Dipoles are flipped as ``𝐬 → -𝐬``. SU(_N_) coherent +[`LocalSampler`](@ref). Dipoles are flipped as ``𝐒 → -𝐒``. SU(_N_) coherent states are flipped using the time-reversal operator. """ propose_flip(sys::System{N}, site) where N = flip(getspin(sys, site)) @@ -27,8 +27,8 @@ propose_flip(sys::System{N}, site) where N = flip(getspin(sys, site)) Generate a proposal function that adds a Gaussian perturbation to the existing spin state. In `:dipole` mode, the procedure is to first introduce a random -three-vector perturbation ``𝐬′ = 𝐬 + |𝐬| ξ`` and then return the properly -normalized spin ``|𝐬| (𝐬′/|𝐬′|)``. Each component of the random vector ``ξ`` +three-vector perturbation ``𝐒′ = 𝐒 + |𝐒| ξ`` and then return the properly +normalized spin ``|𝐒| (𝐒′/|𝐒′|)``. Each component of the random vector ``ξ`` is Gaussian distributed with a standard deviation of `magnitude`; the latter is dimensionless and typically smaller than one. @@ -45,14 +45,14 @@ function propose_delta(magnitude) function ret(sys::System{N}, site) where N κ = sys.κs[site] if N == 0 - s = sys.dipoles[site] + magnitude * κ * randn(sys.rng, Vec3) - s = normalize_dipole(s, κ) - return SpinState(s, CVec{0}()) + S = sys.dipoles[site] + magnitude * κ * randn(sys.rng, Vec3) + S = normalize_dipole(S, κ) + return SpinState(S, CVec{0}()) else Z = sys.coherents[site] + magnitude * sqrt(κ) * randn(sys.rng, CVec{N}) Z = normalize_ket(Z, κ) - s = expected_spin(Z) - return SpinState(s, Z) + S = expected_spin(Z) + return SpinState(S, Z) end end return ret @@ -115,7 +115,7 @@ The trial spin updates are sampled using the `propose` function. Options include [`propose_uniform`](@ref), [`propose_flip`](@ref), and [`propose_delta`](@ref). Multiple proposals can be mixed with the macro [`@mix_proposals`](@ref). -The returned object stores fields `ΔE` and `Δs`, which represent the cumulative +The returned object stores fields `ΔE` and `ΔS`, which represent the cumulative change to the net energy and dipole, respectively. !!! warning "Efficiency considerations @@ -131,7 +131,7 @@ mutable struct LocalSampler{F} nsweeps :: Float64 # Number of MCMC sweeps per `step!` propose :: F # Function: (System, Site) -> SpinState ΔE :: Float64 # Cumulative energy change - Δs :: Vec3 # Cumulative net dipole change + ΔS :: Vec3 # Cumulative net dipole change function LocalSampler(; kT, nsweeps=1.0, propose=propose_uniform) new{typeof(propose)}(kT, nsweeps, propose, 0.0, zero(Vec3)) @@ -158,7 +158,7 @@ function step!(sys::System{N}, sampler::LocalSampler) where N if accept sampler.ΔE += ΔE - sampler.Δs += state.s - sys.dipoles[site] + sampler.ΔS += state.S - sys.dipoles[site] setspin!(sys, state, site) end end diff --git a/src/Optimization.jl b/src/Optimization.jl index 28051022b..1222e4e9b 100644 --- a/src/Optimization.jl +++ b/src/Optimization.jl @@ -75,8 +75,8 @@ end function optim_set_gradient!(G, sys::System{0}, αs, ns) (αs, G) = reinterpret.(reshape, Vec3, (αs, G)) - set_energy_grad_dipoles!(G, sys.dipoles, sys) # G = dE/ds - @. G *= norm(sys.dipoles) # G = dE/ds * ds/du = dE/du + set_energy_grad_dipoles!(G, sys.dipoles, sys) # G = dE/dS + @. G *= norm(sys.dipoles) # G = dE/dS * ds/du = dE/du @. G = adjoint(vjp_stereographic_projection(G, αs, ns)) # G = dE/du du/dα = dE/dα end function optim_set_gradient!(G, sys::System{N}, αs, ns) where N diff --git a/src/SpinWaveTheory/DispersionAndIntensities.jl b/src/SpinWaveTheory/DispersionAndIntensities.jl index 385640f91..9eb4678f5 100644 --- a/src/SpinWaveTheory/DispersionAndIntensities.jl +++ b/src/SpinWaveTheory/DispersionAndIntensities.jl @@ -209,7 +209,7 @@ function intensities_bands(swt::SpinWaveTheory, qpts; kT=0, with_negative=false) # local frame, z is longitudinal, and we are computing # the transverse part only, so the last entry is zero) displacement_local_frame = SA[t[i, 2] + t[i, 1], im * (t[i, 2] - t[i, 1]), 0.0] - Avec[μ] += Avec_pref[μ, i] * (data.sqrtS[i]/sqrt(2)) * (O' * displacement_local_frame)[1] + Avec[μ] += Avec_pref[μ, i] * (data.sqrtS[i]/√2) * (O' * displacement_local_frame)[1] end end diff --git a/src/SpinWaveTheory/HamiltonianDipole.jl b/src/SpinWaveTheory/HamiltonianDipole.jl index 996de31e6..85c5903b3 100644 --- a/src/SpinWaveTheory/HamiltonianDipole.jl +++ b/src/SpinWaveTheory/HamiltonianDipole.jl @@ -24,9 +24,9 @@ function swt_hamiltonian_dipole!(H::Matrix{ComplexF64}, swt::SpinWaveTheory, q_r # Single-ion anisotropy (; c2, c4, c6) = stevens_coefs[i] - S = sqrtS[i]^2 - A1 = -3S*c2[3] - 40*S^3*c4[5] - 168*S^5*c6[7] - A2 = im*(S*c2[5] + 6S^3*c4[7] + 16S^5*c6[9]) + (S*c2[1] + 6S^3*c4[3] + 16S^5*c6[5]) + s = sqrtS[i]^2 + A1 = -3s*c2[3] - 40*s^3*c4[5] - 168*s^5*c6[7] + A2 = im*(s*c2[5] + 6s^3*c4[7] + 16s^5*c6[9]) + (s*c2[1] + 6s^3*c4[3] + 16s^5*c6[5]) H11[i, i] += A1 H22[i, i] += A1 H12[i, i] += A2 @@ -39,38 +39,38 @@ function swt_hamiltonian_dipole!(H::Matrix{ComplexF64}, swt::SpinWaveTheory, q_r (; i, j) = bond phase = exp(2π*im * dot(q_reshaped, bond.n)) # Phase associated with periodic wrapping - Si = sqrtS[i]^2 - Sj = sqrtS[j]^2 - Sij = sqrtS[i] * sqrtS[j] + si = sqrtS[i]^2 + sj = sqrtS[j]^2 + sij = sqrtS[i] * sqrtS[j] # Bilinear exchange if !iszero(coupling.bilin) J = coupling.bilin # Transformed exchange matrix - Q = 0.25 * Sij * (J[1, 1] + J[2, 2] - im*(J[1, 2] - J[2, 1])) + Q = 0.25 * sij * (J[1, 1] + J[2, 2] - im*(J[1, 2] - J[2, 1])) H11[i, j] += Q * phase H11[j, i] += conj(Q) * conj(phase) H22[i, j] += conj(Q) * phase H22[j, i] += Q * conj(phase) - P = 0.25 * Sij * (J[1, 1] - J[2, 2] - im*(J[1, 2] + J[2, 1])) + P = 0.25 * sij * (J[1, 1] - J[2, 2] - im*(J[1, 2] + J[2, 1])) H21[i, j] += P * phase H21[j, i] += P * conj(phase) H12[i, j] += conj(P) * phase H12[j, i] += conj(P) * conj(phase) - H11[i, i] -= 0.5 * Sj * J[3, 3] - H11[j, j] -= 0.5 * Si * J[3, 3] - H22[i, i] -= 0.5 * Sj * J[3, 3] - H22[j, j] -= 0.5 * Si * J[3, 3] + H11[i, i] -= 0.5 * sj * J[3, 3] + H11[j, j] -= 0.5 * si * J[3, 3] + H22[i, i] -= 0.5 * sj * J[3, 3] + H22[j, j] -= 0.5 * si * J[3, 3] end # Biquadratic exchange if !iszero(coupling.biquad) K = coupling.biquad # Transformed quadrupole exchange matrix - Sj2Si = Sj^2 * Si - Si2Sj = Si^2 * Sj + Sj2Si = sj^2 * si + Si2Sj = si^2 * sj H11[i, i] += -6 * Sj2Si * K[3, 3] H22[i, i] += -6 * Sj2Si * K[3, 3] H11[j, j] += -6 * Si2Sj * K[3, 3] @@ -80,13 +80,13 @@ function swt_hamiltonian_dipole!(H::Matrix{ComplexF64}, swt::SpinWaveTheory, q_r H21[j, j] += 2 * Si2Sj * (K[3, 1] - im*K[3, 5]) H12[j, j] += 2 * Si2Sj * (K[3, 1] + im*K[3, 5]) - Q = 0.25 * Sij^3 * ( K[4, 4]+K[2, 2] - im*(-K[4, 2]+K[2, 4])) + Q = 0.25 * sij^3 * ( K[4, 4]+K[2, 2] - im*(-K[4, 2]+K[2, 4])) H11[i, j] += Q * phase H11[j, i] += conj(Q * phase) H22[i, j] += conj(Q) * phase H22[j, i] += Q * conj(phase) - P = 0.25 * Sij^3 * (-K[4, 4]+K[2, 2] - im*( K[4, 2]+K[2, 4])) + P = 0.25 * sij^3 * (-K[4, 4]+K[2, 2] - im*( K[4, 2]+K[2, 4])) H21[i, j] += P * phase H12[j, i] += conj(P * phase) H21[j, i] += P * conj(phase) @@ -181,9 +181,9 @@ function multiply_by_hamiltonian_dipole!(y::AbstractMatrix{ComplexF64}, x::Abstr (; extfield, gs) = sys for i in 1:L (; c2, c4, c6) = stevens_coefs[i] - S = sqrtS[i]^2 - A1 = -3S*c2[3] - 40*S^3*c4[5] - 168*S^5*c6[7] - A2 = im*(S*c2[5] + 6S^3*c4[7] + 16S^5*c6[9]) + (S*c2[1] + 6S^3*c4[3] + 16S^5*c6[5]) + s = sqrtS[i]^2 + A1 = -3s*c2[3] - 40*s^3*c4[5] - 168*s^5*c6[7] + A2 = im*(s*c2[5] + 6s^3*c4[7] + 16s^5*c6[9]) + (s*c2[1] + 6s^3*c4[3] + 16s^5*c6[5]) B = gs[1, 1, 1, i]' * extfield[1, 1, 1, i] B′ = - dot(B, view(local_rotations[i], :, 3)) / 2 @@ -206,9 +206,9 @@ function multiply_by_hamiltonian_dipole!(y::AbstractMatrix{ComplexF64}, x::Abstr isculled && break (; i, j) = bond - Si = sqrtS[i]^2 - Sj = sqrtS[j]^2 - Sij = sqrtS[i] * sqrtS[j] + si = sqrtS[i]^2 + sj = sqrtS[j]^2 + sij = sqrtS[i] * sqrtS[j] map!(phases, qs_reshaped) do q cis(2π*dot(q, bond.n)) @@ -217,28 +217,28 @@ function multiply_by_hamiltonian_dipole!(y::AbstractMatrix{ComplexF64}, x::Abstr if !iszero(coupling.bilin) J = coupling.bilin # This is Rij in previous notation (transformed exchange matrix) - P = 0.25 * Sij * (J[1, 1] - J[2, 2] - im*J[1, 2] - im*J[2, 1]) - Q = 0.25 * Sij * (J[1, 1] + J[2, 2] - im*J[1, 2] + im*J[2, 1]) + P = 0.25 * sij * (J[1, 1] - J[2, 2] - im*J[1, 2] - im*J[2, 1]) + Q = 0.25 * sij * (J[1, 1] + J[2, 2] - im*J[1, 2] + im*J[2, 1]) @inbounds for q in axes(Y, 1) Y[q, i, 1] += Q * phases[q] * X[q, j, 1] Y[q, i, 1] += conj(P) * phases[q] * X[q, j, 2] - Y[q, i, 1] -= 0.5 * Sj * J[3, 3] * X[q, i, 1] + Y[q, i, 1] -= 0.5 * sj * J[3, 3] * X[q, i, 1] end @inbounds for q in axes(Y, 1) Y[q, i, 2] += conj(Q) * phases[q] * X[q, j, 2] Y[q, i, 2] += P * phases[q] * X[q, j, 1] - Y[q, i, 2] -= 0.5 * Sj * J[3, 3] * X[q, i, 2] + Y[q, i, 2] -= 0.5 * sj * J[3, 3] * X[q, i, 2] end @inbounds for q in axes(Y, 1) Y[q, j, 1] += conj(P) * conj(phases[q]) * X[q, i, 2] Y[q, j, 1] += conj(Q) * conj(phases[q]) * X[q, i, 1] - Y[q, j, 1] -= 0.5 * Si * J[3, 3] * X[q, j, 1] + Y[q, j, 1] -= 0.5 * si * J[3, 3] * X[q, j, 1] end @inbounds for q in axes(Y, 1) Y[q, j, 2] += Q * conj(phases[q]) * X[q, i, 2] Y[q, j, 2] += P * conj(phases[q]) * X[q, i, 1] - Y[q, j, 2] -= 0.5 * Si * J[3, 3] * X[q, j, 2] + Y[q, j, 2] -= 0.5 * si * J[3, 3] * X[q, j, 2] end end @@ -246,10 +246,10 @@ function multiply_by_hamiltonian_dipole!(y::AbstractMatrix{ComplexF64}, x::Abstr if !iszero(coupling.biquad) J = coupling.biquad # Transformed quadrupole exchange matrix - Sj2Si = Sj^2 * Si - Si2Sj = Si^2 * Sj - Q = 0.25 * Sij^3 * ( J[4, 4]+J[2, 2] - im*(-J[4, 2]+J[2, 4])) - P = 0.25 * Sij^3 * (-J[4, 4]+J[2, 2] - im*( J[4, 2]+J[2, 4])) + Sj2Si = sj^2 * si + Si2Sj = si^2 * sj + Q = 0.25 * sij^3 * ( J[4, 4]+J[2, 2] - im*(-J[4, 2]+J[2, 4])) + P = 0.25 * sij^3 * (-J[4, 4]+J[2, 2] - im*( J[4, 2]+J[2, 4])) @inbounds for q in 1:Nq Y[q, i, 1] += -6 * Sj2Si * J[3, 3] * X[q, i, 1] diff --git a/src/Spiral/SpinWaveTheorySpiral.jl b/src/Spiral/SpinWaveTheorySpiral.jl index e4d1a5b11..32f0e68e7 100644 --- a/src/Spiral/SpinWaveTheorySpiral.jl +++ b/src/Spiral/SpinWaveTheorySpiral.jl @@ -63,22 +63,22 @@ function swt_hamiltonian_dipole_spiral!(H::Matrix{ComplexF64}, sswt::SpinWaveThe Jij = (J * Rn + Rn * J) ./ 2 phase = exp(2π * im * dot(q_reshaped + (branch-2)*k, n)) - Sj = sqrtS[j]^2 - Sij = sqrtS[i] * sqrtS[j] + sj = sqrtS[j]^2 + sij = sqrtS[i] * sqrtS[j] ui = Ri[:,1] + im*Ri[:,2] uj = Rj[:,1] + im*Rj[:,2] vi = Ri[:,3] vj = Rj[:,3] - H[i,j] += (Sij/2) * (transpose(ui)) * Jij * conj(uj) * phase - H[i+L,j+L] += (Sij/2) * conj((transpose(ui)) * Jij * conj(uj)) * phase + H[i,j] += (sij/2) * (transpose(ui)) * Jij * conj(uj) * phase + H[i+L,j+L] += (sij/2) * conj((transpose(ui)) * Jij * conj(uj)) * phase - H[i,j+L] += (Sij/2) * (transpose(ui) * Jij * uj) * phase - H[j+L,i] += (Sij/2) * conj(transpose(ui) * Jij * uj * phase) + H[i,j+L] += (sij/2) * (transpose(ui) * Jij * uj) * phase + H[j+L,i] += (sij/2) * conj(transpose(ui) * Jij * uj * phase) - H[i,i] -= Sj * transpose(vi) * Jij * vj - H[i+L,i+L] -= Sj * transpose(vi) * Jij * vj + H[i,i] -= sj * transpose(vi) * Jij * vj + H[i+L,i+L] -= sj * transpose(vi) * Jij * vj iszero(c.biquad) || error("Biquadratic interactions not supported") end @@ -96,12 +96,12 @@ function swt_hamiltonian_dipole_spiral!(H::Matrix{ComplexF64}, sswt::SpinWaveThe # Add onsite couplings for i in 1:L - S = sqrtS[i]^2 + s = sqrtS[i]^2 (; c2, c4, c6) = stevens_coefs[i] - H[i, i] += -3S*c2[3] - 40*S^3*c4[5] - 168*S^5*c6[7] - H[i+L, i+L] += -3S*c2[3] - 40*S^3*c4[5] - 168*S^5*c6[7] - H[i, i+L] += +im*(S*c2[5] + 6S^3*c4[7] + 16S^5*c6[9]) + (S*c2[1] + 6S^3*c4[3] + 16S^5*c6[5]) - H[i+L, i] += -im*(S*c2[5] + 6S^3*c4[7] + 16S^5*c6[9]) + (S*c2[1] + 6S^3*c4[3] + 16S^5*c6[5]) + H[i, i] += -3s*c2[3] - 40*s^3*c4[5] - 168*s^5*c6[7] + H[i+L, i+L] += -3s*c2[3] - 40*s^3*c4[5] - 168*s^5*c6[7] + H[i, i+L] += +im*(s*c2[5] + 6s^3*c4[7] + 16s^5*c6[9]) + (s*c2[1] + 6s^3*c4[3] + 16s^5*c6[5]) + H[i+L, i] += -im*(s*c2[5] + 6s^3*c4[7] + 16s^5*c6[9]) + (s*c2[1] + 6s^3*c4[3] + 16s^5*c6[5]) end isnothing(sys.ewald) || error("Ewald interactions not yet supported") diff --git a/src/Spiral/SpiralEnergy.jl b/src/Spiral/SpiralEnergy.jl index 4e8a89dc1..e746194fd 100644 --- a/src/Spiral/SpiralEnergy.jl +++ b/src/Spiral/SpiralEnergy.jl @@ -154,8 +154,8 @@ dreg(x) = 2x * reg(x)^2 function spiral_f(sys::System{0}, axis, params, λ) k = unpack_spiral_params!(sys, axis, params) E, _dEdk = spiral_energy_and_gradient_aux!(nothing, sys; k, axis) - for s in sys.dipoles - u = normalize(s) + for S in sys.dipoles + u = normalize(S) E += λ * reg(u⋅axis) end return E @@ -167,14 +167,14 @@ function spiral_g!(G, sys::System{0}, axis, params, λ) G = reinterpret(Vec3, G) L = length(sys.dipoles) - dEds = view(G, 1:L) - _E, dEdk = spiral_energy_and_gradient_aux!(dEds, sys; k, axis) + dEdS = view(G, 1:L) + _E, dEdk = spiral_energy_and_gradient_aux!(dEdS, sys; k, axis) for i in 1:L - s = sys.dipoles[i] - u = normalize(s) - # dE/du' = dE/ds' * ds/du, where s = |s|*u. - dEdu = dEds[i] * norm(s) + λ * dreg(u⋅axis) * axis + S = sys.dipoles[i] + u = normalize(S) + # dE/du' = dE/dS' * dS/du, where S = |s|*u. + dEdu = dEdS[i] * norm(S) + λ * dreg(u⋅axis) * axis # dE/dv' = dE/du' * du/dv G[i] = vjp_stereographic_projection(dEdu, v[i], axis) end @@ -200,7 +200,7 @@ function minimize_spiral_energy!(sys, axis; maxiters=10_000, k_guess=randn(sys.r sys.mode in (:dipole, :dipole_large_s) || error("SU(N) mode not supported") sys.dims == (1, 1, 1) || error("System must have only a single cell") - norm([s × axis for s in sys.dipoles]) > 1e-12 || error("Spins cannot be exactly aligned with polarization axis") + norm([S × axis for S in sys.dipoles]) > 1e-12 || error("Spins cannot be exactly aligned with polarization axis") # Note: if k were fixed, we could check θ = 2πkᵅ for each component α, which # is a weaker constraint. diff --git a/src/System/Ewald.jl b/src/System/Ewald.jl index 082ee934d..1af12c793 100644 --- a/src/System/Ewald.jl +++ b/src/System/Ewald.jl @@ -179,7 +179,7 @@ function ewald_energy(sys::System{N}) where N return E / prod(dims) end -# Use FFT to accumulate the entire field dE/ds for long-range dipole-dipole +# Use FFT to accumulate the entire field dE/dS for long-range dipole-dipole # interactions function accum_ewald_grad!(∇E, dipoles, sys::System{N}) where N (; ewald, gs) = sys @@ -209,7 +209,7 @@ function accum_ewald_grad!(∇E, dipoles, sys::System{N}) where N end end -# Calculate the field dE/ds at site1 generated by a dipole at site2. +# Calculate the field dE/dS at site1 generated by a dipole at site2. function ewald_pairwise_grad_at(sys::System{N}, site1, site2) where N (; gs, ewald) = sys dims = size(ewald.ϕ)[1:3] @@ -221,7 +221,7 @@ function ewald_pairwise_grad_at(sys::System{N}, site1, site2) where N return gs[site1]' * ewald.A[cell, to_atom(site1), to_atom(site2)] * gs[site2] * sys.dipoles[site2] end -# Calculate the field dE/ds at `site` generated by all `dipoles`. +# Calculate the field dE/dS at `site` generated by all `dipoles`. function ewald_grad_at(sys::System{N}, site) where N acc = zero(Vec3) for site2 in eachsite(sys) @@ -231,14 +231,14 @@ function ewald_grad_at(sys::System{N}, site) where N end # Calculate the change in dipole-dipole energy when the spin at `site` is -# updated to `s` -function ewald_energy_delta(sys::System{N}, site, s::Vec3) where N +# updated to `S` +function ewald_energy_delta(sys::System{N}, site, S::Vec3) where N (; dipoles, ewald) = sys - Δs = s - dipoles[site] - Δμ = sys.gs[site] * Δs + ΔS = S - dipoles[site] + Δμ = sys.gs[site] * ΔS i = to_atom(site) ∇E = ewald_grad_at(sys, site) - return Δs⋅∇E + dot(Δμ, ewald.A[1, 1, 1, i, i], Δμ) / 2 + return ΔS⋅∇E + dot(Δμ, ewald.A[1, 1, 1, i, i], Δμ) / 2 end """ diff --git a/src/System/Interactions.jl b/src/System/Interactions.jl index 100d8b266..50066d44c 100644 --- a/src/System/Interactions.jl +++ b/src/System/Interactions.jl @@ -72,13 +72,12 @@ Enables long-range interactions between magnetic dipole moments, -(μ_0/4π) ∑_{⟨ij⟩} [3 (μ_i⋅𝐫̂_{ij})(μ_j⋅𝐫̂_{ij}) - μ_i⋅μ_j] / r_{ij}^3 ``` -where the sum is over all pairs of spins (singly counted), including periodic -images, regularized using the Ewald summation convention. The -[`magnetic_moment`](@ref) is defined as ``μ = -g μ_B 𝐒``, where ``𝐒`` is the -spin angular momentum dipole. The parameter `μ0_μB²` specifies the physical -constant ``μ_0 μ_B^2``, which has dimensions of length³-energy. Obtain this -constant for a given system of [`Units`](@ref) via its `vacuum_permeability` -property. +where the sum is over all pairs of sites (singly counted), including periodic +images, regularized using the Ewald summation convention. Each magnetic moment +is ``μ = -g μ_B 𝐒``, where ``𝐒`` is the spin angular momentum dipole. The +parameter `μ0_μB²` specifies the physical constant ``μ_0 μ_B^2``, which has +dimensions of length³-energy. Obtain this constant for a given system of +[`Units`](@ref) via its `vacuum_permeability` property. # Example @@ -156,7 +155,7 @@ end function local_energy_change(sys::System{N}, site, state::SpinState) where N - (; s, Z) = state + (; S, Z) = state (; dims, extfield, dipoles, coherents, ewald) = sys if is_homogeneous(sys) @@ -165,19 +164,19 @@ function local_energy_change(sys::System{N}, site, state::SpinState) where N (; onsite, pair) = interactions_inhomog(sys)[site] end - s₀ = dipoles[site] + S₀ = dipoles[site] Z₀ = coherents[site] - Δs = s - s₀ + ΔS = S - S₀ ΔE = 0.0 # Zeeman coupling to external field - ΔE += dot(extfield[site], sys.gs[site], Δs) + ΔE += dot(extfield[site], sys.gs[site], ΔS) # Single-ion anisotropy, dipole or SUN mode if N == 0 stvexp = onsite :: StevensExpansion - E_new, _ = energy_and_gradient_for_classical_anisotropy(s, stvexp) - E_old, _ = energy_and_gradient_for_classical_anisotropy(s₀, stvexp) + E_new, _ = energy_and_gradient_for_classical_anisotropy(S, stvexp) + E_old, _ = energy_and_gradient_for_classical_anisotropy(S₀, stvexp) ΔE += E_new - E_old else Λ = onsite :: HermitianC64 @@ -187,18 +186,18 @@ function local_energy_change(sys::System{N}, site, state::SpinState) where N # Pair coupling for pc in pair cellⱼ = offsetc(to_cell(site), pc.bond.n, dims) - sⱼ = dipoles[cellⱼ, pc.bond.j] + Sⱼ = dipoles[cellⱼ, pc.bond.j] Zⱼ = coherents[cellⱼ, pc.bond.j] # Bilinear J = pc.bilin - ΔE += dot(Δs, J, sⱼ) + ΔE += dot(ΔS, J, Sⱼ) # Biquadratic if !iszero(pc.biquad) if sys.mode in (:dipole, :dipole_large_s) - ΔQ = quadrupole(s) - quadrupole(s₀) - Qⱼ = quadrupole(sⱼ) + ΔQ = quadrupole(S) - quadrupole(S₀) + Qⱼ = quadrupole(Sⱼ) else ΔQ = expected_quadrupole(Z) - expected_quadrupole(Z₀) Qⱼ = expected_quadrupole(Zⱼ) @@ -222,7 +221,7 @@ function local_energy_change(sys::System{N}, site, state::SpinState) where N # Long-range dipole-dipole if !isnothing(ewald) - ΔE += ewald_energy_delta(sys, site, s) + ΔE += ewald_energy_delta(sys, site, S) end return ΔE @@ -281,8 +280,8 @@ function energy_aux(ints::Interactions, sys::System{N}, i::Int, cells) where N if N == 0 # Dipole mode stvexp = ints.onsite :: StevensExpansion for cell in cells - s = sys.dipoles[cell, i] - E += energy_and_gradient_for_classical_anisotropy(s, stvexp)[1] + S = sys.dipoles[cell, i] + E += energy_and_gradient_for_classical_anisotropy(S, stvexp)[1] end else # SU(N) mode Λ = ints.onsite :: HermitianC64 @@ -298,8 +297,8 @@ function energy_aux(ints::Interactions, sys::System{N}, i::Int, cells) where N for cellᵢ in cells cellⱼ = offsetc(cellᵢ, bond.n, sys.dims) - sᵢ = sys.dipoles[cellᵢ, bond.i] - sⱼ = sys.dipoles[cellⱼ, bond.j] + Sᵢ = sys.dipoles[cellᵢ, bond.i] + Sⱼ = sys.dipoles[cellⱼ, bond.j] # Scalar if sys.mode == :SUN @@ -312,13 +311,13 @@ function energy_aux(ints::Interactions, sys::System{N}, i::Int, cells) where N # Bilinear J = pc.bilin :: Union{Float64, Mat3} - E += dot(sᵢ, J, sⱼ) + E += dot(Sᵢ, J, Sⱼ) # Biquadratic if !iszero(pc.biquad) if sys.mode in (:dipole, :dipole_large_s) - Qᵢ = quadrupole(sᵢ) - Qⱼ = quadrupole(sⱼ) + Qᵢ = quadrupole(Sᵢ) + Qⱼ = quadrupole(Sⱼ) else Zᵢ = sys.coherents[cellᵢ, bond.i] Zⱼ = sys.coherents[cellⱼ, bond.j] @@ -349,8 +348,8 @@ function energy_aux(ints::Interactions, sys::System{N}, i::Int, cells) where N end -# Updates ∇E in-place to hold energy gradient, dE/ds, for each spin. In the case -# of :SUN mode, s is interpreted as expected spin, and dE/ds only includes +# Updates ∇E in-place to hold energy gradient, dE/dS, for each spin. In the case +# of :SUN mode, S is interpreted as expected spin, and dE/dS only includes # contributions from Zeeman coupling, bilinear exchange, and long-range # dipole-dipole. Excluded terms include onsite coupling, and general pair # coupling (biquadratic and beyond). @@ -390,8 +389,8 @@ function set_energy_grad_dipoles_aux!(∇E, dipoles::Array{Vec3, 4}, ints::Inter if sys.mode in (:dipole, :dipole_large_s) stvexp = ints.onsite :: StevensExpansion for cell in cells - s = dipoles[cell, i] - ∇E[cell, i] += energy_and_gradient_for_classical_anisotropy(s, stvexp)[2] + S = dipoles[cell, i] + ∇E[cell, i] += energy_and_gradient_for_classical_anisotropy(S, stvexp)[2] end end @@ -401,21 +400,21 @@ function set_energy_grad_dipoles_aux!(∇E, dipoles::Array{Vec3, 4}, ints::Inter for cellᵢ in cells cellⱼ = offsetc(cellᵢ, bond.n, sys.dims) - sᵢ = dipoles[cellᵢ, bond.i] - sⱼ = dipoles[cellⱼ, bond.j] + Sᵢ = dipoles[cellᵢ, bond.i] + Sⱼ = dipoles[cellⱼ, bond.j] # Bilinear J = pc.bilin - ∇E[cellᵢ, bond.i] += J * sⱼ - ∇E[cellⱼ, bond.j] += J' * sᵢ + ∇E[cellᵢ, bond.i] += J * Sⱼ + ∇E[cellⱼ, bond.j] += J' * Sᵢ # Biquadratic for dipole mode only (SU(N) handled differently) if sys.mode in (:dipole, :dipole_large_s) if !iszero(pc.biquad) - Qᵢ = quadrupole(sᵢ) - Qⱼ = quadrupole(sⱼ) - ∇Qᵢ = grad_quadrupole(sᵢ) - ∇Qⱼ = grad_quadrupole(sⱼ) + Qᵢ = quadrupole(Sᵢ) + Qⱼ = quadrupole(Sⱼ) + ∇Qᵢ = grad_quadrupole(Sᵢ) + ∇Qⱼ = grad_quadrupole(Sⱼ) # In matrix case, energy is `Qᵢ' * biquad * Qⱼ`, and we are # taking gradient with respect to either sᵢ or sⱼ. @@ -435,44 +434,44 @@ function set_energy_grad_dipoles_aux!(∇E, dipoles::Array{Vec3, 4}, ints::Inter end # Updates `HZ` in-place to hold `dE/dZ̄`, which is the Schrödinger analog to the -# quantity `dE/ds`. **Overwrites the first two dipole buffers in `sys`.** +# quantity `dE/dS`. **Overwrites the first two dipole buffers in `sys`.** function set_energy_grad_coherents!(HZ, Z::Array{CVec{N}, 4}, sys::System{N}) where N @assert N > 0 fill!(HZ, zero(CVec{N})) # Accumulate Zeeman, Ewald interactions, and spin-bilinear exchange - # interactions into dE/ds, where s is the expected spin associated with Z. - # Note that dE_ds does _not_ include the onsite, biquadratic, or general + # interactions into dE/dS, where S is the expected spin associated with Z. + # Note that dE_dS does _not_ include the onsite, biquadratic, or general # pair couplings, which must be handled differently. - dE_ds, dipoles = get_dipole_buffers(sys, 2) + dE_dS, dipoles = get_dipole_buffers(sys, 2) @. dipoles = expected_spin(Z) - set_energy_grad_dipoles!(dE_ds, dipoles, sys) + set_energy_grad_dipoles!(dE_dS, dipoles, sys) # Accumulate onsite and pair couplings for i in 1:natoms(sys.crystal) if is_homogeneous(sys) # Interactions for sublattice i (same for every cell) interactions = sys.interactions_union[i] - set_energy_grad_coherents_aux!(HZ, Z, dE_ds, interactions, sys, i, eachcell(sys)) + set_energy_grad_coherents_aux!(HZ, Z, dE_dS, interactions, sys, i, eachcell(sys)) else for cell in eachcell(sys) # Interactions for sublattice i and a specific cell interactions = sys.interactions_union[cell, i] - set_energy_grad_coherents_aux!(HZ, Z, dE_ds, interactions, sys, i, (cell,)) + set_energy_grad_coherents_aux!(HZ, Z, dE_dS, interactions, sys, i, (cell,)) end end end - fill!(dE_ds, zero(Vec3)) + fill!(dE_dS, zero(Vec3)) fill!(dipoles, zero(Vec3)) end -function set_energy_grad_coherents_aux!(HZ, Z::Array{CVec{N}, 4}, dE_ds::Array{Vec3, 4}, ints::Interactions, sys::System{N}, i, cells) where N +function set_energy_grad_coherents_aux!(HZ, Z::Array{CVec{N}, 4}, dE_dS::Array{Vec3, 4}, ints::Interactions, sys::System{N}, i, cells) where N for cell in cells - # HZ += (Λ + dE/ds S) Z + # HZ += (Λ + dE/dS S) Z Λ = ints.onsite :: HermitianC64 - HZ[cell, i] += mul_spin_matrices(Λ, dE_ds[cell, i], Z[cell, i]) + HZ[cell, i] += mul_spin_matrices(Λ, dE_dS[cell, i], Z[cell, i]) end for pc in ints.pair diff --git a/src/System/OnsiteCoupling.jl b/src/System/OnsiteCoupling.jl index 3f7c9d132..ede5626a7 100644 --- a/src/System/OnsiteCoupling.jl +++ b/src/System/OnsiteCoupling.jl @@ -11,9 +11,9 @@ function onsite_coupling(sys, site, matrep::AbstractMatrix) if sys.mode == :SUN return Hermitian(matrep) elseif sys.mode == :dipole - S = spin_label(sys, to_atom(site)) + s = spin_label(sys, to_atom(site)) c = matrix_to_stevens_coefficients(hermitianpart(matrep)) - return StevensExpansion(rcs_factors(S) .* c) + return StevensExpansion(rcs_factors(s) .* c) elseif sys.mode == :dipole_large_s error("System with mode `:dipole_large_s` requires a symbolic operator.") end @@ -24,8 +24,8 @@ function onsite_coupling(sys, site, p::DP.AbstractPolynomialLike) error("Symbolic operator only valid for system with mode `:dipole_large_s`.") end - S = sys.κs[site] - c = operator_to_stevens_coefficients(p, S) + s = sys.κs[site] + c = operator_to_stevens_coefficients(p, s) # No renormalization here because symbolic polynomials `p` are associated # with the large-s limit. @@ -35,14 +35,14 @@ end # k-dependent renormalization of Stevens operators O[k,q] as derived in # https://arxiv.org/abs/2304.03874. -function rcs_factors(S) +function rcs_factors(s) λ = [1, # k=0 1, # k=1 - 1 - (1/2)/S, # k=2 - 1 - (3/2)/S + (1/2)/S^2, # k=3 - 1 - 3/S + (11/4)/S^2 - (3/4)/S^3, # k=4 - 1 - 5/S + (35/4)/S^2 - (25/4)/S^3 + (3/2)/S^4, # k=5 - 1 - (15/2)/S + (85/4)/S^2 - (225/8)/S^3 + (137/8)/S^4 - (15/4)/S^5] # k=6 + 1 - (1/2)/s, # k=2 + 1 - (3/2)/s + (1/2)/s^2, # k=3 + 1 - 3/s + (11/4)/s^2 - (3/4)/s^3, # k=4 + 1 - 5/s + (35/4)/s^2 - (25/4)/s^3 + (3/2)/s^4, # k=5 + 1 - (15/2)/s + (85/4)/s^2 - (225/8)/s^3 + (137/8)/s^4 - (15/4)/s^5] # k=6 return OffsetArray(λ, 0:6) end @@ -188,12 +188,12 @@ end # Evaluate a given linear combination of Stevens operators in the large-s limit, -# where each spin operator is replaced by its dipole expectation value 𝐬. In this -# limit, each Stevens operator O[ℓ,m](𝐬) becomes a homogeneous polynomial in the -# spin components sᵅ, and is equal to the spherical Harmonic Yₗᵐ(𝐬) up to an +# where each spin operator is replaced by its dipole expectation value 𝐒. In this +# limit, each Stevens operator O[ℓ,m](𝐒) becomes a homogeneous polynomial in the +# spin components Sᵅ, and is equal to the spherical Harmonic Yₗᵐ(𝐒) up to an # overall (l- and m-dependent) scaling factor. Also return the gradient of the # scalar output. -function energy_and_gradient_for_classical_anisotropy(s::Vec3, stvexp::StevensExpansion) +function energy_and_gradient_for_classical_anisotropy(S::Vec3, stvexp::StevensExpansion) (; kmax, c0, c2, c4, c6) = stvexp E = only(c0) @@ -204,9 +204,9 @@ function energy_and_gradient_for_classical_anisotropy(s::Vec3, stvexp::StevensEx # Quadratic contributions - X = s⋅s - Jp¹ = s[1] + im*s[2] - Jz¹ = s[3] + X = S⋅S + Jp¹ = S[1] + im*S[2] + Jz¹ = S[3] Jp² = Jp¹*Jp¹ Jz² = Jz¹*Jz¹ diff --git a/src/System/PairExchange.jl b/src/System/PairExchange.jl index 69bf1da6f..24355b52c 100644 --- a/src/System/PairExchange.jl +++ b/src/System/PairExchange.jl @@ -238,15 +238,15 @@ function set_pair_coupling_aux!(sys::System, scalar::Float64, bilin::Union{Float if any(x -> x.bond == bond, ints[bond.i].pair) warn_coupling_override("Overriding coupling for $bond.") end - + # General interactions require SU(N) mode check_allowable_dipole_coupling(tensordec, sys.mode) - # Renormalize biquadratic interactions (from rcs_factors with k=2) + # Renormalize biquadratic interactions if sys.mode == :dipole - s1 = spin_label(sys, bond.i) - s2 = spin_label(sys, bond.j) - biquad *= (1 - 1/2s1) * (1 - 1/2s2) + si = spin_label(sys, bond.i) + sj = spin_label(sys, bond.j) + biquad *= rcs_factors(si)[2] * rcs_factors(sj)[2] end # Propagate all couplings by symmetry @@ -294,9 +294,9 @@ function set_pair_coupling!(sys::System{N}, op::AbstractMatrix, bond; extract_pa error("Symbolic operators required for mode `:dipole_large_s`.") end - N1 = Int(2spin_label(sys, bond.i)+1) - N2 = Int(2spin_label(sys, bond.j)+1) - scalar, bilin, biquad, tensordec = decompose_general_coupling(op, N1, N2; extract_parts) + Ni = Int(2spin_label(sys, bond.i)+1) + Nj = Int(2spin_label(sys, bond.j)+1) + scalar, bilin, biquad, tensordec = decompose_general_coupling(op, Ni, Nj; extract_parts) set_pair_coupling_aux!(sys, scalar, bilin, biquad, tensordec, bond) return @@ -307,9 +307,9 @@ function set_pair_coupling!(sys::System{N}, fn::Function, bond; extract_parts=tr error("General couplings not yet supported for mode `:dipole_large_s`.") end - S1 = spin_label(sys, bond.i) - S2 = spin_label(sys, bond.j) - Si, Sj = to_product_space(spin_matrices.([S1, S2])...) + si = spin_label(sys, bond.i) + sj = spin_label(sys, bond.j) + Si, Sj = to_product_space(spin_matrices.([si, sj])...) set_pair_coupling!(sys, fn(Si, Sj), bond; extract_parts) return end @@ -436,9 +436,9 @@ function set_pair_coupling_at_aux!(sys::System, scalar::Float64, bilin::Union{Fl # Renormalize biquadratic interactions if sys.mode == :dipole - S1 = spin_label(sys, to_atom(site1)) - S2 = spin_label(sys, to_atom(site2)) - biquad *= (1 - 1/2S1) * (1 - 1/2S2) + s1 = spin_label(sys, to_atom(site1)) + s2 = spin_label(sys, to_atom(site2)) + biquad *= rcs_factors(s1)[2] * rcs_factors(s2)[2] end site1 = to_cartesian(site1) @@ -458,9 +458,9 @@ will be overwritten. The system must support inhomogeneous interactions via [`to_inhomogeneous`](@ref). Use [`symmetry_equivalent_bonds`](@ref) to find `(site1, site2, offset)` values -that are symmetry equivalent to a given [`Bond`](@ref) in the original system. -For systems that are relatively small, the `offset` vector (in multiples of unit -cells) will resolve ambiguities in the periodic wrapping. +that would be symmetry equivalent to a given [`Bond`](@ref) in a homogeneous +system. For smaller systems, the `offset` vector (in multiples of unit cells) +will resolve ambiguities in the periodic wrapping. See also [`set_exchange!`](@ref) for more details on specifying `J` and `biquad`. For more general couplings, use [`set_pair_coupling_at!`](@ref) @@ -473,7 +473,7 @@ function set_exchange_at!(sys::System{N}, J, site1::Site, site2::Site; biquad::N end """ - set_pair_coupling_at!(sys::System, op, bond) + set_pair_coupling_at!(sys::System, op, site1::Site, site2::Site; offset=nothing) Sets an arbitrary coupling along the single bond connecting two [`Site`](@ref)s, ignoring crystal symmetry. Any previous coupling on this bond will be @@ -481,9 +481,9 @@ overwritten. The system must support inhomogeneous interactions via [`to_inhomogeneous`](@ref). Use [`symmetry_equivalent_bonds`](@ref) to find `(site1, site2, offset)` values -that are symmetry equivalent to a given [`Bond`](@ref) in the original system. -For systems that are relatively small, the `offset` vector (in multiples of unit -cells) will resolve ambiguities in the periodic wrapping. +that would be symmetry equivalent to a given [`Bond`](@ref) in a homogeneous +system. For smaller systems, the `offset` vector (in multiples of unit cells) +will resolve ambiguities in the periodic wrapping. The operator `op` may be provided as an anonymous function that accepts two spin dipole operators, or as a matrix that acts in the tensor product space of the @@ -508,10 +508,10 @@ function set_pair_coupling_at!(sys::System{N}, fn::Function, site1::Site, site2: error("General couplings not yet supported for mode `:dipole_large_s`.") end - S1 = spin_label(sys, to_atom(site1)) - S2 = spin_label(sys, to_atom(site2)) - Si, Sj = to_product_space(spin_matrices.([S1, S2])...) - set_pair_coupling_at!(sys, fn(Si, Sj), site1, site2; offset) + s1 = spin_label(sys, to_atom(site1)) + s2 = spin_label(sys, to_atom(site2)) + S1, S2 = to_product_space(spin_matrices.([s1, s2])...) + set_pair_coupling_at!(sys, fn(S1, S2), site1, site2; offset) return end diff --git a/src/System/System.jl b/src/System/System.jl index 5c42c23af..b3973abb2 100644 --- a/src/System/System.jl +++ b/src/System/System.jl @@ -354,9 +354,9 @@ end Given a [`Bond`](@ref) for the original (unreshaped) crystal, return all symmetry equivalent bonds in the [`System`](@ref). Each returned bond is -represented as a pair of [`Site`](@ref)s, which may be used as input to -[`set_exchange_at!`](@ref) or [`set_pair_coupling_at!`](@ref). Reverse bonds are -not included in the iterator (no double counting). +represented as a pair of [`Site`](@ref)s and an `offset`, which may be used as +input to [`set_exchange_at!`](@ref) or [`set_pair_coupling_at!`](@ref). Reverse +bonds are not included in the iterator (no double counting). # Example ```julia @@ -428,7 +428,7 @@ end struct SpinState{N} - s::Vec3 + S::Vec3 Z::CVec{N} end @@ -444,31 +444,31 @@ end @inline function coherent_state(sys::System{N}, site, Z) where N Z = normalize_ket(CVec{N}(Z), sys.κs[site]) - s = expected_spin(Z) - return SpinState(s, Z) + S = expected_spin(Z) + return SpinState(S, Z) end @inline function dipolar_state(sys::System{0}, site, dir) - s = normalize_dipole(Vec3(dir), sys.κs[site]) + S = normalize_dipole(Vec3(dir), sys.κs[site]) Z = CVec{0}() - return SpinState(s, Z) + return SpinState(S, Z) end @inline function dipolar_state(sys::System{N}, site, dir) where N return coherent_state(sys, site, ket_from_dipole(Vec3(dir), Val(N))) end @inline function flip(spin::SpinState{N}) where N - return SpinState(-spin.s, flip_ket(spin.Z)) + return SpinState(-spin.S, flip_ket(spin.Z)) end @inline function randspin(sys::System{0}, site) - s = normalize_dipole(randn(sys.rng, Vec3), sys.κs[site]) - return SpinState(s, CVec{0}()) + S = normalize_dipole(randn(sys.rng, Vec3), sys.κs[site]) + return SpinState(S, CVec{0}()) end @inline function randspin(sys::System{N}, site) where N Z = normalize_ket(randn(sys.rng, CVec{N}), sys.κs[site]) - s = expected_spin(Z) - return SpinState(s, Z) + S = expected_spin(Z) + return SpinState(S, Z) end @inline function getspin(sys::System{N}, site) where N @@ -476,14 +476,14 @@ end end @inline function setspin!(sys::System{N}, spin::SpinState{N}, site) where N - sys.dipoles[site] = spin.s + sys.dipoles[site] = spin.S sys.coherents[site] = spin.Z return end function is_valid_normalization(sys::System{0}) - all(zip(sys.dipoles, sys.κs)) do (s, κ) - norm2(s) ≈ κ^2 + all(zip(sys.dipoles, sys.κs)) do (S, κ) + norm2(S) ≈ κ^2 end end function is_valid_normalization(sys::System{N}) where N diff --git a/test/shared.jl b/test/shared.jl index ea3c6b854..ecc77d766 100644 --- a/test/shared.jl +++ b/test/shared.jl @@ -36,8 +36,8 @@ function add_quadratic_interactions!(sys, mode) #= # This is a bit slower, but must also work - S = spin_label(sys, 1) - O = stevens_matrices(S) + s = spin_label(sys, 1) + O = stevens_matrices(s) Q = [O[2,q] for q in 2:-1:-2] Qi, Qj = to_product_space(Q, Q) biquad = [1.2 0 0 0 0 diff --git a/test/test_energy_consistency.jl b/test/test_energy_consistency.jl index 10376e572..030da2626 100644 --- a/test/test_energy_consistency.jl +++ b/test/test_energy_consistency.jl @@ -69,7 +69,7 @@ ΔE = Sunny.local_energy_change(sys, site, spin) E0 = energy(sys) - sys.dipoles[site] = spin.s + sys.dipoles[site] = spin.S sys.coherents[site] = spin.Z E1 = energy(sys) ΔE_ref = E1 - E0 diff --git a/test/test_operators.jl b/test/test_operators.jl index 5ad733052..d9f161b10 100644 --- a/test/test_operators.jl +++ b/test/test_operators.jl @@ -62,14 +62,14 @@ end # Spherical tensors satisfying `norm(T) = √tr T† T = 1` (currently unused). function spherical_tensors_normalized(k; N) - S = (N-1)/2 + s = (N-1)/2 ret = Matrix{Float64}[] for q in k:-1:-k T = zeros(Float64, N, N) for i = 1:N, i′ = 1:N - m = S - i + 1 - m′ = S - i′+ 1 - T[i, i′] = clebschgordan(S, m′, k, q, S, m) * sqrt((2k+1)/N) + m = s - i + 1 + m′ = s - i′+ 1 + T[i, i′] = clebschgordan(s, m′, k, q, s, m) * sqrt((2k+1)/N) end push!(ret, T) end