Adding DMI via Lifshitz Invariants#

This notebook shows how to implement Dzyaloshinskii–Moriya Interaction (DMI) energy terms using Lifshitz invariants.

What are Lifshitz Invariants?#

The Dzyaloshinskii–Moriya interaction (DMI) can be written compactly using Lifshitz invariants (LI).

A Lifshitz invariant is an antisymmetric combination of magnetization components and their derivatives:

\[\mathcal{L}_{ij}^{k} = m_i \partial_k m_j - m_j \partial_k m_i, \quad i,j,k \in \{x,y,z\}.\]

Adding LI as an energy contribution#

As an example lets take a system with exchange and a single LI of type \(\mathcal{L}_{xz}^x\).

Each LI corresponds to a possible DMI contribution. For example:

\[w = D_{xzx} \, \mathcal{L}_{xz}^{x} = D_{xzx} \,( m_x \,\partial_x m_z - m_z \,\partial_x m_x ).\]

In this system we would expect the ground state to be a cycloid propagating in the x direction. ### Setup First we import neuralmag and create a 2D mesh.

[1]:
import matplotlib.pyplot as plt
import numpy as np

import neuralmag as nm

nm.config.dtype = "float64"

mesh = nm.Mesh((20, 20), (1e-9, 1e-9, 1e-9))
state = nm.State(mesh)

state.m = nm.VectorFunction(state).fill((0, 0, 1))
2025-09-22 13:03:34 NeuralMag:INFO [NeuralMag] Version 0.9.3
2025-09-22 13:03:35 NeuralMag:INFO [NeuralMag] Backend set to 'jax'.
2025-09-22 13:03:35 NeuralMag:INFO [NeuralMag] Set default dtype to 'float64'.
2025-09-22 13:03:35 NeuralMag:INFO [Mesh] 2D, 20 x 20 (size = 1e-09 x 1e-09 x 1e-09)
WARNING:2025-09-22 13:03:35,261:jax._src.xla_bridge:794: An NVIDIA GPU may be present on this machine, but a CUDA-enabled jaxlib is not installed. Falling back to cpu.
2025-09-22 13:03:35 NeuralMag:INFO [NeuralMag] Set default device to 'TFRT_CPU_0'.
2025-09-22 13:03:35 NeuralMag:INFO [State] Running on device: TFRT_CPU_0 (dtype = float64, backend = jax)

Defining Energy Terms#

We set the material parameters as usual.

To add a Lifshitz invariant, specify the indices \(i,j,k\) as a string (e.g., “xzx”) to nm.LIField, and set the corresponding DMI constant Dijk in the material.

[2]:
state.material.Ms = 0.86e6
state.material.alpha = 1
state.material.A = 1.3e-11
nm.ExchangeField().register(state, "exchange")


state.material.Dxzx = 15e-3
nm.LIField("xzx").register(state, "li_xzx")

nm.TotalField("exchange", "li_xzx").register(state)
2025-09-22 13:03:35 NeuralMag:INFO [ExchangeField] Register state methods (field: 'h_exchange', energy: 'E_exchange')
2025-09-22 13:03:35 NeuralMag:INFO [LIField] Register state methods (field: 'h_li_xzx', energy: 'E_li_xzx')
2025-09-22 13:03:35 NeuralMag:INFO [TotalField] Register state methods (field: 'h', energy: 'E')
[3]:
# relax to energetic minimum
llg = nm.LLGSolver(state)
llg.relax(1e9)
state.write_vti(["m"], "LI/m.vti")
2025-09-22 13:03:35 NeuralMag:INFO [LLGSolverJAX] Initialize RHS function
2025-09-22 13:03:35 NeuralMag:INFO [LLGSolverJAX] Relaxation started, initial energy E = -2.13607e-32 J
2025-09-22 13:03:36 NeuralMag:INFO [LLGSolverJAX] Relaxation step (max dm/dt = 4.34343e+12) 1/s
2025-09-22 13:03:36 NeuralMag:INFO [LLGSolverJAX] Relaxation step (max dm/dt = 1.51136e+11) 1/s
2025-09-22 13:03:36 NeuralMag:INFO [LLGSolverJAX] Relaxation step (max dm/dt = 2.68473e+10) 1/s
2025-09-22 13:03:36 NeuralMag:INFO [LLGSolverJAX] Relaxation step (max dm/dt = 5.00783e+09) 1/s
2025-09-22 13:03:36 NeuralMag:INFO [LLGSolverJAX] Relaxation finished, final energy E = -1.60663e-18 J

Visualization#

[4]:
import pyvista as pv

pv.set_jupyter_backend("static")

grid = pv.read("LI/m.vti")
grid["m_z"] = grid["m"][:, 2]  # z component

glyphs = grid.glyph(orient="m", scale="m", factor=2e-9)

# Plot
plotter = pv.Plotter()
plotter.add_mesh(glyphs, scalars="m_z", lighting=True, smooth_shading=True)
plotter.show_axes()
plotter.show()
../_images/examples_LI_7_0.png

Another Example: Different Invariant#

Let’s try a different Lifshitz invariant, “xzy”. This should give us a helix propagating the the \(y\) direction.

[5]:
state.m = nm.VectorFunction(state).fill((0, 0, 1))

state.material.Ms = 0.86e6
state.material.alpha = 1
state.material.A = 1.3e-11
nm.ExchangeField().register(state, "exchange")


state.material.Dxzy = 15e-3
nm.LIField("xzy").register(state, "li_xzy")

nm.TotalField("exchange", "li_xzy").register(state)
2025-09-22 13:03:38 NeuralMag:INFO [ExchangeField] Register state methods (field: 'h_exchange', energy: 'E_exchange')
2025-09-22 13:03:38 NeuralMag:INFO [LIField] Register state methods (field: 'h_li_xzy', energy: 'E_li_xzy')
2025-09-22 13:03:38 NeuralMag:INFO [TotalField] Register state methods (field: 'h', energy: 'E')
[6]:
# relax to energetic minimum
llg = nm.LLGSolver(state)
llg.relax(1e9)
state.write_vti(["m"], "LI/m.vti")
2025-09-22 13:03:38 NeuralMag:INFO [LLGSolverJAX] Initialize RHS function
2025-09-22 13:03:38 NeuralMag:INFO [LLGSolverJAX] Relaxation started, initial energy E = -2.13607e-32 J
2025-09-22 13:03:38 NeuralMag:INFO [LLGSolverJAX] Relaxation step (max dm/dt = 4.34343e+12) 1/s
2025-09-22 13:03:39 NeuralMag:INFO [LLGSolverJAX] Relaxation step (max dm/dt = 1.51136e+11) 1/s
2025-09-22 13:03:39 NeuralMag:INFO [LLGSolverJAX] Relaxation step (max dm/dt = 2.68473e+10) 1/s
2025-09-22 13:03:39 NeuralMag:INFO [LLGSolverJAX] Relaxation step (max dm/dt = 5.00783e+09) 1/s
2025-09-22 13:03:39 NeuralMag:INFO [LLGSolverJAX] Relaxation finished, final energy E = -1.60663e-18 J
[7]:
import pyvista as pv

pv.set_jupyter_backend("static")

grid = pv.read("LI/m.vti")
grid["m_z"] = grid["m"][:, 2]  # z component

glyphs = grid.glyph(orient="m", scale="m", factor=2e-9)

# Plot
plotter = pv.Plotter()
plotter.add_mesh(glyphs, scalars="m_z", lighting=True, smooth_shading=True)
plotter.show_axes()
plotter.show()
../_images/examples_LI_11_0.png

Using this for particular crystallographic groups#

In NeuralMag we have already created energy terms for the two most common DMI classes:

  • Bulk (T symmetry):

    • \(w_{\text{bulk}} = D \, \mathbf{m} \cdot (\nabla \times \mathbf{m})\)

    • Equivalent to a sum of Lifshitz invariants: \(D \, (\mathcal{L}_{yz}^{(x)} + \mathcal{L}_{zx}^{(y)} + \mathcal{L}_{xy}^{(z)})\).

  • Interfacial (:math:`C_{infty v}`, “Néel” type, thin film):

    • \(w_{\text{int}} = D \, [m_z \, \nabla \cdot \mathbf{m} - (\mathbf{m} \cdot \nabla) m_z]\)

    • Equivalent to a sum of Lifshitz invariants: \(D \, (\mathcal{L}_{zx}^{(x)} + \mathcal{L}_{zy}^{(y)})\).

The table below lists the DMI energy densities for various crystallographic point groups:

Point Group

DMI Energy Density

O, T, \(D_n \, (n \geq 3)\)

\(D \, (\mathcal{L}^{x}_{yz} + \mathcal{L}^{y}_{zx})\)

\(C_{nv} \, (n \geq 3)\)

\(D \, (\mathcal{L}^{x}_{zx} - \mathcal{L}^{y}_{yz})\)

\(D_{2d}\)

\(D \, (\mathcal{L}^{x}_{yz} - \mathcal{L}^{y}_{zx})\)

\(S_{4}\)

\(D_{0} \, (\mathcal{L}^{x}_{zx} + \mathcal{L}^{y}_{yz}) + D_{1} \, (\mathcal{L}^{x}_{yz} - \mathcal{L}^{y}_{zx})\)

\(C_{n} \, (n \geq 3)\)

\(D_{0} \, (\mathcal{L}^{x}_{zx} - \mathcal{L}^{y}_{yz}) + D_{1} \, (\mathcal{L}^{x}_{yz} + \mathcal{L}^{y}_{zx})\)

\(D_{2}\), \(C_{2v}\)

\(D_{0} \, \mathcal{L}^{x}_{yz} + D_{1} \, \mathcal{L}^{y}_{zx}\)

\(C_{2}\)

\(D_{0} \, \mathcal{L}^{x}_{zx} + D_{1} \, \mathcal{L}^{y}_{yz} + D_{2} \, \mathcal{L}^{x}_{yz} + D_{3} \, \mathcal{L}^{y}_{zx}\)

\(C_{1h}\)

\(D_{0} \, \mathcal{L}^{x}_{xy} + D_{1} \, \mathcal{L}^{y}_{xy}\)

\(C_{1v}\)

\(D_{0} \, \mathcal{L}^{x}_{zx} + D_{1} \, \mathcal{L}^{y}_{xy} + D_{2} \, \mathcal{L}^{y}_{yz}\)

\(C_{1}\)

\(D_{0} \, \mathcal{L}^{x}_{xy} + D_{1} \, \mathcal{L}^{x}_{yz} + D_{2} \, \mathcal{L}^{x}_{zx} + D_{3} \, \mathcal{L}^{y}_{xy} + D_{4} \, \mathcal{L}^{y}_{yz} + D_{5} \, \mathcal{L}^{y}_{zx}\)

Using Predefined DMI Fields#

NeuralMag provides predefined fields for common DMI types, such as bulk DMI.

[8]:
mesh = nm.Mesh((10, 10, 10), (1e-9, 1e-9, 1e-9))
state = nm.State(mesh)

state.m = nm.VectorFunction(state).fill((0, 0, 1))

state.material.Ms = 0.86e6
state.material.alpha = 1
state.material.A = 1.3e-11
nm.ExchangeField().register(state, "exchange")


state.material.Db = 15e-3
nm.BulkDMIField().register(state, "bulk")

nm.TotalField("exchange", "bulk").register(state)
2025-09-22 13:03:39 NeuralMag:INFO [Mesh] 3D, 10 x 10 x 10 (size = 1e-09 x 1e-09 x 1e-09)
2025-09-22 13:03:39 NeuralMag:INFO [NeuralMag] Set default device to 'TFRT_CPU_0'.
2025-09-22 13:03:39 NeuralMag:INFO [State] Running on device: TFRT_CPU_0 (dtype = float64, backend = jax)
2025-09-22 13:03:39 NeuralMag:INFO [ExchangeField] Register state methods (field: 'h_exchange', energy: 'E_exchange')
2025-09-22 13:03:39 NeuralMag:INFO [BulkDMIField] Register state methods (field: 'h_bulk', energy: 'E_bulk')
2025-09-22 13:03:39 NeuralMag:INFO [TotalField] Register state methods (field: 'h', energy: 'E')

Equivalent Using Multiple Lifshitz Invariants#

[9]:
mesh = nm.Mesh((10, 10, 10), (1e-9, 1e-9, 1e-9))
state = nm.State(mesh)

state.m = nm.VectorFunction(state).fill((0, 0, 1))

state.material.Ms = 0.86e6
state.material.alpha = 1
state.material.A = 1.3e-11
nm.ExchangeField().register(state, "exchange")


state.material.Dxyz = 15e-3
state.material.Dzxy = state.material.Dxyz
state.material.Dyzx = state.material.Dxyz
nm.LIField("xyz").register(state, "li_xyz")
nm.LIField("zxy").register(state, "li_zxy")
nm.LIField("yzx").register(state, "li_yzx")

nm.TotalField("exchange", "li_xyz", "li_zxy", "li_yzx").register(state)
2025-09-22 13:03:39 NeuralMag:INFO [Mesh] 3D, 10 x 10 x 10 (size = 1e-09 x 1e-09 x 1e-09)
2025-09-22 13:03:39 NeuralMag:INFO [NeuralMag] Set default device to 'TFRT_CPU_0'.
2025-09-22 13:03:39 NeuralMag:INFO [State] Running on device: TFRT_CPU_0 (dtype = float64, backend = jax)
2025-09-22 13:03:39 NeuralMag:INFO [ExchangeField] Register state methods (field: 'h_exchange', energy: 'E_exchange')
2025-09-22 13:03:39 NeuralMag:INFO [LIField] Register state methods (field: 'h_li_xyz', energy: 'E_li_xyz')
2025-09-22 13:03:39 NeuralMag:INFO [LIField] Register state methods (field: 'h_li_zxy', energy: 'E_li_zxy')
2025-09-22 13:03:39 NeuralMag:INFO [LIField] Register state methods (field: 'h_li_yzx', energy: 'E_li_yzx')
2025-09-22 13:03:39 NeuralMag:INFO [TotalField] Register state methods (field: 'h', energy: 'E')

Flexibility with Independent Constants#

By setting different values for the DMI constants, we can reduce the symmetry, equivalent to changing from high-symmetry classes (like T or O) to lower-symmetry ones (like D2).

[10]:
mesh = nm.Mesh((10, 10, 10), (1e-9, 1e-9, 1e-9))
state = nm.State(mesh)

state.m = nm.VectorFunction(state).fill((0, 0, 1))

state.material.Ms = 0.86e6
state.material.alpha = 1
state.material.A = 1.3e-11
nm.ExchangeField().register(state, "exchange")


state.material.Dxyz = 15e-3
state.material.Dzxy = 20e-3
state.material.Dyzx = 10e-3
nm.LIField("xyz").register(state, "li_xyz")
nm.LIField("zxy").register(state, "li_zxy")
nm.LIField("yzx").register(state, "li_yzx")

nm.TotalField("exchange", "li_xyz", "li_zxy", "li_yzx").register(state)
2025-09-22 13:03:39 NeuralMag:INFO [Mesh] 3D, 10 x 10 x 10 (size = 1e-09 x 1e-09 x 1e-09)
2025-09-22 13:03:39 NeuralMag:INFO [NeuralMag] Set default device to 'TFRT_CPU_0'.
2025-09-22 13:03:39 NeuralMag:INFO [State] Running on device: TFRT_CPU_0 (dtype = float64, backend = jax)
2025-09-22 13:03:39 NeuralMag:INFO [ExchangeField] Register state methods (field: 'h_exchange', energy: 'E_exchange')
2025-09-22 13:03:39 NeuralMag:INFO [LIField] Register state methods (field: 'h_li_xyz', energy: 'E_li_xyz')
2025-09-22 13:03:39 NeuralMag:INFO [LIField] Register state methods (field: 'h_li_zxy', energy: 'E_li_zxy')
2025-09-22 13:03:39 NeuralMag:INFO [LIField] Register state methods (field: 'h_li_yzx', energy: 'E_li_yzx')
2025-09-22 13:03:39 NeuralMag:INFO [TotalField] Register state methods (field: 'h', energy: 'E')

Summary#

  • Lifshitz invariants are the building blocks of DMI.

  • By adding the right set of invariants, we represent the DMI for a given crystal symmetry.