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:
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:
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()
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()
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.