Tutorial 02: Exchange energy term#

Exchange energy density is computed as

\[w_\text{e} = A(\nabla\mathbf{m})^{2}\]

where \(\mathbf{m}\) is the normalised (\(|\mathbf{m}|=1\)) magnetisation, and \(A\) is the exchange energy constant. Exchange energy term tends to align all magnetic moments parallel to each other. Direction in which they are going to point is not defined via exchange energy.

In oommfc, \(\mathbf{m}\) is a part of the magnetisation field system.m. Therefore, only exchange energy constant \(A\) should be provided as an input parameter to uniquely define the Exchange energy term. \(A\) can be constant in space or spatially varying.

Spatially constant \(A\)#

Let us start by assembling a simple simple simulation where \(A\) does not vary in space. The sample is a “one-dimensional” chain of magnetic moments.

[1]:
import discretisedfield as df
import micromagneticmodel as mm

import oommfc as oc

p1 = (-10e-9, 0, 0)
p2 = (10e-9, 1e-9, 1e-9)
cell = (1e-9, 1e-9, 1e-9)

region = df.Region(p1=p1, p2=p2)
mesh = df.Mesh(region=region, cell=cell)

The system has a Hamiltonian, which consists of only exchange energy term.

[2]:
A = 1e-12  # exchange energy constant (J/m)
system = mm.System(name="exchange_constant_A")
system.energy = mm.Exchange(A=A)

We are going to minimise the system’s energy using oommfc.MinDriver later. Therefore, we do not have to define the system’s dynamics equation. Finally, we need to define the system’s magnetisation (system.m). We are going to make it random with \(M_\text{s}=8\times10^{5} \,\text{Am}^{-1}\)

[3]:
import random

import discretisedfield as df

Ms = 8e5  # saturation magnetisation (A/m)


def m_fun(pos):
    return [2 * random.random() - 1 for i in range(3)]


system.m = df.Field(mesh, nvdim=3, value=m_fun, norm=Ms)

The magnetisation, we set is

[4]:
system.m.k3d.vector(color_field=system.m.z)

Now, we can minimise the system’s energy by using oommfc.MinDriver.

[5]:
md = oc.MinDriver()
md.drive(system)
Running OOMMF (ExeOOMMFRunner)[2023/10/18 12:36]... (0.4 s)

We expect that now all magnetic moments are aligned parallel to each other.

[6]:
system.m.k3d.vector(color_field=system.m.z)

Finally, we can delete the files created by oommfc.

Spatially varying \(A\)#

There are two different ways how a parameter can be made spatially varying, by using: 1. Dictionary 2. discretisedfield.Field

Dictionary#

In order to define a parameter using a dictionary, regions must be defined in the mesh. Regions are defined as a dictionary, whose keys are the strings and values are discretisedfield.Region objects, which take two corner points of the region as input parameters.

[7]:
p1 = (-10e-9, 0, 0)
p2 = (10e-9, 1e-9, 1e-9)
cell = (1e-9, 1e-9, 1e-9)
subregions = {
    "region1": df.Region(p1=(-10e-9, 0, 0), p2=(0, 1e-9, 1e-9)),
    "region2": df.Region(p1=(0, 0, 0), p2=(10e-9, 1e-9, 1e-9)),
}
region = df.Region(p1=p1, p2=p2)
mesh = df.Mesh(region=region, cell=cell, subregions=subregions)

The regions we defined are:

[8]:
mesh.k3d.subregions()

Let us say there is no exchange energy (\(A=0\)) in region 1, whereas in region 2 \(A=10^{-12} \,\text{Jm}^{-1}\). Unlike Zeeman and anisotropy energy terms, exchange energy constant is defined between cells. Therefore, it is necessary also to define the value of \(A\) between the two regions. This is achieved by adding another item to the dictionary with key 'region1:region2'. A is now defined as a dictionary:

[9]:
A = {"region1": 0, "region2": 1e-12, "region1:region2": 0.5e-12}

The system object is

[10]:
system = mm.System(name="exchange_dict_A")
system.energy = mm.Exchange(A=A)
system.m = df.Field(mesh, nvdim=3, value=m_fun, norm=Ms)

Its magnetisation is

[11]:
system.m.k3d.vector(color_field=system.m.z)

After we minimise the energy

[12]:
md.drive(system)
Running OOMMF (ExeOOMMFRunner)[2023/10/18 12:36]... (0.2 s)

The magnetisation is as we expected. The magnetisation remains random in region 1, and it is alligned in region 2.

[13]:
system.m.k3d.vector(color_field=system.m.z)

discretisedfield.Field#

Let us now define the exchange energy in the similar way as in the previous example, but this time using discretisedfield.Field:

$:nbsphinx-math:mathbf{A}`(x, y, z) = :nbsphinx-math:left`{

\right. $

This time, it is not possible to define the exchange energy constant between cells, but only in cells. Therefore, the exchange energy constant is then computed as the average between two discretisation cells.

[14]:
def A_fun(pos):
    x, y, z = pos
    if x <= 0:
        return 1e-12
    else:
        return 1e-20

The exchange energy constant is

[15]:
A = df.Field(mesh, nvdim=1, value=A_fun)

The system is

[16]:
system = mm.System(name="exchange_field_A")
system.energy = mm.Exchange(A=A)
system.m = df.Field(mesh, nvdim=3, value=m_fun, norm=Ms)

and its magnetisation is

[17]:
system.m.k3d.vector(color_field=system.m.z)

After the energy minimisation, the magnetisation is:

[18]:
md.drive(system)
system.m.k3d.vector(color_field=system.m.z)
Running OOMMF (ExeOOMMFRunner)[2023/10/18 12:36]... (0.2 s)