Source code for magnumnp.field_terms.dmi

#
# This file is part of the magnum.np distribution
# (https://gitlab.com/magnum.np/magnum.np).
# Copyright (c) 2023 magnum.np team.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

from magnumnp.common import timedmethod, constants
import torch
from .field_terms import LinearFieldTerm
import numpy as np

__all__ = ["DMIField", "InterfaceDMIField", "BulkDMIField", "D2dDMIField"]


[docs] class DMIField(LinearFieldTerm): r""" General Dzyaloshinskii-Moriya interaction The general expression for the DMI field can be expressed as .. math:: \vec{h}^\text{dmi}(\vec{x}) = \frac{2 \, D}{\mu_0 \, M_s} \; \sum_{k=x,y,z} \vec{e}^\text{dmi}_k \times \frac{\partial \vec{m}}{\partial \vec{e}_k}, with the DMI strength :math:`D` and the DMI vectors :math:`\vec{e}^\text{dmi}_k`, which describe which components of the gradient of :math:`\vec{m}` contribute to which component of the corresponding field. It is assumed that :math:`\vec{e}^\text{dmi}_{-k} = -\vec{e}^\text{dmi}_k`. The occuring gradient is discretized using central differences which finally yields .. math:: \vec{h}^\text{dmi}_i = \frac{2 \, D_i}{\mu_0 \, M_{s,i}} \; \sum_{k=\pm x, \pm y,\pm z} \frac{\vec{e}^\text{dmi}_k \times \vec{m}_{i+\vec{e}_k}}{2 \, \Delta_k}. :param Ku: Name of the material parameter for the anisotropy constant :math:`K_\text{u}`, defaults to "Ku" :type Ku: str, optional :param Ku_axis: Name of the material parameter for the anisotropy axis :math:`\vec{e}_\text{u}`, defaults to "Ku_axis" :type Ku_axis: str, optional """ parameters = ["D"] def __init__(self, dmi_vector, **kwargs): self._dmi_vector = dmi_vector super().__init__(**kwargs) @timedmethod @torch.compile def h(self, state): D = state.material[self.D] Ms = state.material["Ms"] m = state.m dx = state.mesh.dx_tensor[0].reshape(-1,1,1,1) dy = state.mesh.dx_tensor[1].reshape(1,-1,1,1) dz = state.mesh.dx_tensor[2].reshape(1,1,-1,1) h = torch.zeros_like(state.m) # x if state.mesh.pbc[0] == 0: v = torch.tensor(self._dmi_vector[0]).expand(m[1:,:,:].shape) # TODO: implement for different signs (according to https://github.com/mumax/3/issues/236) # D_avg = torch.where(D[1:,:,:]*D[:-1,:,:] < 0, ... D_avg = 2.*D[1:,:,:]*D[:-1,:,:] / (D[1:,:,:]*dx[:-1,:,:,:] + D[:-1,:,:]*dx[1:,:,:,:]) h[:-1,:,:] += D_avg * torch.linalg.cross(v, m[ 1:,:,:]) / 2. h[ 1:,:,:] -= D_avg * torch.linalg.cross(v, m[:-1,:,:]) / 2. else: v = torch.tensor(self._dmi_vector[0]).expand(m.shape) D_next = torch.roll(D, +1, dims=0) dx_next = torch.roll(dx, +1, dims=0) # TODO: implement for different signs (according to https://github.com/mumax/3/issues/236) # D_avg = torch.where(D_next*D < 0, D_avg = 2.*D_next*D / (D_next*dx + D*dx_next) h -= D_avg * torch.linalg.cross(v, torch.roll(state.m, +1, dims=0)) / 2. D_avg = torch.roll(D_avg, -1, dims=0) h += D_avg * torch.linalg.cross(v, torch.roll(state.m, -1, dims=0)) / 2. # y if state.mesh.pbc[1] == 0: v = torch.tensor(self._dmi_vector[1]).expand(m[:,1:,:].shape) # TODO: implement for different signs (according to https://github.com/mumax/3/issues/236) # D_avg = torch.where(D[:,1:,:]*D[:,:-1,:] < 0, D_avg = 2.*D[:,1:,:]*D[:,:-1,:] / (D[:,1:,:]*dy[:,:-1,:,:] + D[:,:-1,:]*dy[:,1:,:,:]) h[:,:-1,:] += D_avg * torch.linalg.cross(v, m[:, 1:,:]) / 2. h[:, 1:,:] -= D_avg * torch.linalg.cross(v, m[:,:-1,:]) / 2. else: v = torch.tensor(self._dmi_vector[1]).expand(m.shape) D_next = torch.roll(D, +1, dims=1) dy_next = torch.roll(dx, +1, dims=1) # TODO: implement for different signs (according to https://github.com/mumax/3/issues/236) # D_avg = torch.where(D_next*D < 0, D_avg = 2.*D_next*D / (D_next*dy + D*dy_next) h -= D_avg * torch.linalg.cross(v, torch.roll(state.m, +1, dims=1)) / 2. D_avg = torch.roll(D_avg, -1, dims=1) h += D_avg * torch.linalg.cross(v, torch.roll(state.m, -1, dims=1)) / 2. # z if state.mesh.pbc[2] == 0: v = torch.tensor(self._dmi_vector[2]).expand(m[:,:,1:].shape) # TODO: implement for different signs (according to https://github.com/mumax/3/issues/236) # D_avg = torch.where(D[:,:,1:]*D[:,:,:-1] < 0, D_avg = 2.*D[:,:,1:]*D[:,:,:-1] / (D[:,:,1:]*dz[:,:,:-1,:] + D[:,:,:-1]*dz[:,:,1:,:]) h[:,:,:-1] += D_avg * torch.linalg.cross(v, m[:,:, 1:]) / 2. h[:,:, 1:] -= D_avg * torch.linalg.cross(v, m[:,:,:-1]) / 2. else: v = torch.tensor(self._dmi_vector[2]).expand(m.shape) D_next = torch.roll(D, +1, dims=2) dz_next = torch.roll(dx, +1, dims=2) # TODO: implement for different signs (according to https://github.com/mumax/3/issues/236) # D_avg = torch.where(D_next*D < 0, D_avg = 2.*D_next*D / (D_next*dz + D*dz_next) h -= D_avg * torch.linalg.cross(v, torch.roll(state.m, +1, dims=2)) / 2. D_avg = torch.roll(D_avg, -1, dims=2) h += D_avg * torch.linalg.cross(v, torch.roll(state.m, -1, dims=2)) / 2. h *= 2. / (constants.mu_0 * Ms) return h.nan_to_num(posinf=0, neginf=0)
[docs] class InterfaceDMIField(DMIField): r""" Interface Dzyaloshinskii-Moriya interaction .. math:: \vec{h}^\text{dmii}(\vec{x}) = -\frac{2 \, D_i}{\mu_0 \, M_s} \; \left[ \nabla \left(\vec{e}_z \cdot \vec{m} \right) - \left(\nabla \cdot \vec{m} \right) \, \vec{e}_z\right], with the DMI strength :math:`D_i` and an interface normal :math:`\vec{e}_z` in z-direction. The corresponding DMI vectors are :math:`\vec{e}^\text{dmi}_x = [ 0, 1, 0]`, :math:`\vec{e}^\text{dmi}_y = [-1, 0, 0]`, and :math:`\vec{e}^\text{dmi}_z = [0, 0, 0]`. :param Di: Name of the material parameter for the anisotropy constant :math:`D_i`, defaults to "Di" :type Di: str, optional """ def __init__(self, Di = "Di"): dmi_vector = [[ 0., 1., 0.], # x [-1., 0., 0.], # y [ 0., 0., 0.]] # z super().__init__(dmi_vector, D = Di)
[docs] class BulkDMIField(DMIField): r""" Bulk Dzyaloshinskii-Moriya interaction .. math:: \vec{h}^\text{dmib}(\vec{x}) = -\frac{2 \, D_b}{\mu_0 \, M_s} \; \nabla \times \vec{m}, with the DMI strength :math:`D_b`. The corresponding DMI vectors are :math:`\vec{e}^\text{dmi}_x = [1, 0, 0]`, :math:`\vec{e}^\text{dmi}_y = [0, 1, 0]`, and :math:`\vec{e}^\text{dmi}_z = [0, 0, 1]`. :param Db: Name of the material parameter for the anisotropy constant :math:`D_i`, defaults to "Di" :type Db: str, optional """ def __init__(self, Db = "Db"): dmi_vector = [[1., 0., 0.], # x [0., 1., 0.], # y [0., 0., 1.]] # z super().__init__(dmi_vector, D = Db)
[docs] class D2dDMIField(DMIField): r""" D2d Dzyaloshinskii-Moriya interaction .. math:: \vec{h}^\text{dmib}(\vec{x}) = -\frac{2 \, D_b}{\mu_0 \, M_s} \; \nabla \times \vec{m}, with the DMI strength :math:`D_{D2d}`. The corresponding DMI vectors are :math:`\vec{e}^\text{dmi}_x = [-1, 0, 0]`, :math:`\vec{e}^\text{dmi}_y = [0, 1, 0]`, and :math:`\vec{e}^\text{dmi}_z = [0, 0, 0]`. :param DD2d: Name of the material parameter for the anisotropy constant :math:`D_{D2d}`, defaults to "DD2d" :type DD2d: str, optional """ def __init__(self, DD2d = "DD2d"): dmi_vector = [[-1., 0., 0.], # x [ 0., 1., 0.], # y [ 0., 0., 0.]] # z super().__init__(dmi_vector, D = DD2d)