Download this example as Jupyter notebook or Julia script.

SW18 - Distorted kagome

This is a Sunny port of SpinW Tutorial 18, originally authored by Goran Nilsen and Sandor Toth. This tutorial illustrates Sunny's experimental for studying incommensurate, single-$Q$ structures. The test system is KCu₃As₂O₇(OD)₃. The Cu ions are arranged in a distorted kagome lattice, and exhibit helical magnetic order, as described in G. J. Nilsen, et al., Phys. Rev. B 89, 140412 (2014).

using Sunny, GLMakie

Build the distorted kagome crystal, with spacegroup 12.

latvecs = lattice_vectors(10.2, 5.94, 7.81, 90, 117.7, 90)
positions = [[0, 0, 0], [1/4, 1/4, 0]]
types = ["Cu1", "Cu2"]
cryst = Crystal(latvecs, positions, 12; types, setting="b1")
view_crystal(cryst)
Example block output

Define the interactions.

spininfos = [SpinInfo(1, S=1/2, g=2), SpinInfo(3, S=1/2, g=2)]
sys = System(cryst, (1,1,1), spininfos, :dipole, seed=0)
J   = -2
Jp  = -1
Jab = 0.75
Ja  = -J/.66 - Jab
Jip = 0.01
set_exchange!(sys, J, Bond(1, 3, [0, 0, 0]))
set_exchange!(sys, Jp, Bond(3, 5, [0, 0, 0]))
set_exchange!(sys, Ja, Bond(3, 4, [0, 0, 0]))
set_exchange!(sys, Jab, Bond(1, 2, [0, 0, 0]))
set_exchange!(sys, Jip, Bond(3, 4, [0, 0, 1]))

Optimize the generalized spiral structure. This will determine the propagation wavevector k, as well as spin values within the unit cell. One must provide a fixed axis perpendicular to the polarization plane. For this system, all interactions are rotationally invariant, and the axis vector is arbitrary. In other cases, a good axis will frequently be determined from symmetry considerations. The qualified notation Sunny.* indicates that these functions are not stabilized, and may change between Sunny releases.

axis = [0, 0, 1]
randomize_spins!(sys)
k = Sunny.minimize_energy_spiral!(sys, axis; k_guess=randn(3))
plot_spins(sys; dims=2)
Example block output

If successful, the optimization process will find one two possible wavevectors, ±k_ref, with opposite chiralities.

k_ref = [0.785902495, 0.0, 0.107048756]
k_ref_alt = [1, 0, 1] - k_ref
@assert isapprox(k, k_ref; atol=1e-6) || isapprox(k, k_ref_alt; atol=1e-6)
@assert Sunny.spiral_energy_per_site(sys; k, axis) ≈ -0.78338383838

Check the energy with a real-space calculation using a large magnetic cell. First, we must determine a lattice size for which k becomes approximately commensurate.

suggest_magnetic_supercell([k_ref]; tol=1e-3)
Possible magnetic supercell in multiples of lattice vectors:

    [14 0 1; 0 1 0; 0 0 2]

for the rationalized wavevectors:

    [[11/14, 0, 3/28]]

Resize the system as suggested, and perform a real-space calculation. Working with a commensurate wavevector increases the energy slightly. The precise value might vary from run-to-run due to trapping in a local energy minimum.

new_shape = [14 0 1; 0 1 0; 0 0 2]
sys2 = reshape_supercell(sys, new_shape)
randomize_spins!(sys2)
minimize_energy!(sys2)
energy_per_site(sys2) # < -0.7834 meV
-0.7791966901275342

Now return to the perfectly incommensurate single-k order. Define a path in reciprocal space.

q_points = [[0,0,0], [1,0,0]]
density = 200
path, xticks = reciprocal_space_path(cryst, q_points, density)
swt = SpinWaveTheory(sys)
formula = Sunny.intensity_formula_spiral(swt, :perp; k, axis, kernel=delta_function_kernel)
disp, _ = intensities_bands(swt, path, formula);
energies = collect(0:0.01:5.5)
broadened_formula = Sunny.intensity_formula_spiral(swt, :perp; k, axis,
                                                   kernel=gaussian(fwhm=0.1))
is = intensities_broadened(swt, path, energies, broadened_formula);

Create the plot

fig = Figure()
ax = Axis(fig[1,1]; xlabel="Momentum (r.l.u.)", ylabel="Energy (meV)",
          title="Spin wave dispersion: ", xticks)
ylims!(ax, 0, 5)
heatmap!(ax, 1:size(is, 1), energies, is; colorrange=(0.2, 100),
         colormap=Reverse(:thermal), lowclip=:white)
for d in eachcol(disp)
    lines!(ax, d; color=:black)
end
fig
Example block output

The powder average can be calculated as shown below. It is commented out for now because the calculation is a bit slow to run and is yet to be optimized.

if false
    radii = 0.01:0.02:3
    output = zeros(Float64, length(radii), length(energies))
    for (i, radius) in enumerate(radii)
        n = 300
        qs = reciprocal_space_shell(cryst, radius, n)
        is1 = intensities_broadened(swt, qs, energies, broadened_formula)
        is2 = dropdims(sum(is1[:,:,:,:], dims=[3,4]), dims=(3,4))
        output[i, :] = sum(is2, dims=1) / size(is2, 1)
    end

    fig = Figure()
    ax = Axis(fig[1,1]; xlabel="Q (Å⁻¹)", ylabel="ω (meV)", title="Broadened powder spectra")
    ylims!(ax, 0.0, 5)
    heatmap!(ax, radii, energies, output, colormap=:gnuplot2, colorrange=(0.0,1))
    fig
end