Spin waves from the eigensolver

Magnon modes of a magnetic film can be obtained directly as the eigenpairs of the linearized Landau-Lifshitz-Gilbert (LLG) equation, rather than by exciting the system and transforming a long time trace. This tutorial follows test/tests/spin_waves/sw_eigen_film.i, which computes the magnon bands of a perpendicularly magnetized film using MOOSE's Eigenvalue executioner. It uses only the standard FERRET LLG kernels - no special eigenmode objects are required.

Damon-Eshbach spin waves travelling perpendicular to the magnetization in an in-plane film. Left: the lowest band, a surface mode. Right: the first perpendicular standing spin wave (PSSW). Arrows show the precessing magnetization (tilt exaggerated), coloured by precession phase.

Figure 1: Damon-Eshbach spin waves travelling perpendicular to the magnetization in an in-plane film. Left: the lowest band, a surface mode. Right: the first perpendicular standing spin wave (PSSW). Arrows show the precessing magnetization (tilt exaggerated), coloured by precession phase.

The idea

Linearizing the LLG equation about a static ground state gives

where is the Jacobian of the LLG kernels evaluated at and collects the time-derivative terms. Writing turns this into the generalized eigenproblem

and for zero damping the eigenvalues come in purely imaginary pairs. The magnon frequencies are therefore the imaginary parts of the eigenvalues.

MOOSE builds for free, because it is the same Jacobian the Newton solver already assembles. The only thing the input has to supply is .

Supplying the mass matrix

The transverse components and are the dynamic variables, so is built from one CoefReaction per component, tagged into the eigen vector:

[Kernels]
  [exchange_x]
    type = MasterExchangeCartLLG
    variable = mag_x
    component = 0
    block = film
  []
  [exchange_z]
    type = MasterExchangeCartLLG
    variable = mag_z
    component = 2
    block = film
  []
  [field_x]
    type = MasterInteractionCartLLGHConst
    variable = mag_x
    component = 0
    block = film
  []
  [field_z]
    type = MasterInteractionCartLLGHConst
    variable = mag_z
    component = 2
    block = film
  []
  [magnetostatic_laplace]
    type = Electrostatics
    variable = potential_H_int
    block = 'film vacuum'
  []

  [mag_x_time]
    type = CoefReaction
    variable = mag_x
    coefficient = -1.0
    extra_vector_tags = 'eigen'
    block = film
  []
  [mag_z_time]
    type = CoefReaction
    variable = mag_z
    coefficient = -1.0
    extra_vector_tags = 'eigen'
    block = film
  []
  [magnetostatic_source]
    type = MagHStrongCart
    variable = potential_H_int
    block = film
  []
[]

The extra_vector_tags = 'eigen' entry is what places these terms on the right-hand side of the generalized eigenproblem instead of in the residual. MagHStrongCart supplies the magnetostatic source term, so the mode frequencies include the dipolar field.

The static physics comes from film_common.i, which is shared with the ring-down test and uses only stock objects - MasterExchangeCartLLG for exchange, MasterInteractionCartLLGHConst for the Zeeman term, and Electrostatics for the magnetostatic potential on a graded vacuum region surrounding the film.

The eigensolver

[Executioner]
  type = Eigenvalue
  solve_type = KRYLOVSCHUR
  eigen_problem_type = GEN_NON_HERMITIAN
  which_eigen_pairs = TARGET_MAGNITUDE
  n_eigen_pairs = 8
  n_basis_vectors = 24
  eigen_tol = 1e-12
  initial_eigenvalue = 1e12
  petsc_options_iname = '-st_type -eps_target -st_pc_type -st_pc_factor_mat_solver_type'
  petsc_options_value = 'sinvert 0       lu          mumps'
[]

A shift-and-invert spectral transform (-st_type sinvert) targeted at zero picks out the lowest modes, which are the ones of physical interest. initial_eigenvalue = 1e12 is required: the default initial guess puts SLEPc in the wrong part of the spectrum and the solve fails to converge. MUMPS is used for the inner factorization, and n_eigen_pairs = 8 returns four pairs.

Reading the frequencies out

Eigenvalues arrive as a VectorPostprocessor, and individual bands are pulled out with VectorPostprocessorComponent. Because the eigenvalues come in conjugate pairs, band sits at index :

[VectorPostprocessors]
  [eigenvalues]
    type = Eigenvalues
    execute_on = 'timestep_end'
  []
[]

The last two objects are worth highlighting. rel_kittel compares the frequency against the analytic Kittel result , and a Terminator aborts the run if the two disagree by more than one part in :

[UserObjects]
  [kittel]
    type = Terminator
    expression = '!(abs(rel_kittel) < 1e-10)'
    fail_mode = HARD
    error_level = ERROR
    message = 'k = 0 eigenvalue is not the Kittel frequency g0 (H0 - Ms)'
    execute_on = 'timestep_end'
  []
[]

The input therefore asserts its own correctness. This is the recommended pattern for FERRET inputs that have an analytic limit - it turns a silent numerical error into a hard failure.

Results

For in-plane magnetization and propagation perpendicular to , the modes are Damon-Eshbach waves. The lowest band is the surface mode and the higher bands are perpendicular standing spin waves quantized across the film thickness, as shown in Figure 1.

Damon-Eshbach band structure of the film, compared against the Kalinikos-Slavin analytic dispersion.

Figure 2: Damon-Eshbach band structure of the film, compared against the Kalinikos-Slavin analytic dispersion.

Three further inputs in the same directory build on this one:

  • sw_ringdown_film.i excites the film and extracts the same frequencies from a time trace, as an independent check of the eigensolve.

  • sw_eigen_vs_ringdown.i compares the two and fails if they disagree.

  • afm_skyr_modes.i computes modes about a non-uniform ground state - an antiferromagnetic skyrmion - rather than about a uniformly magnetized state.